diff options
Diffstat (limited to 'viewer/src/components/LdTagInput.vue')
-rw-r--r-- | viewer/src/components/LdTagInput.vue | 94 |
1 files changed, 34 insertions, 60 deletions
diff --git a/viewer/src/components/LdTagInput.vue b/viewer/src/components/LdTagInput.vue index 71131e6..6b6e749 100644 --- a/viewer/src/components/LdTagInput.vue +++ b/viewer/src/components/LdTagInput.vue | |||
@@ -19,7 +19,7 @@ | |||
19 | 19 | ||
20 | <template> | 20 | <template> |
21 | <b-taginput | 21 | <b-taginput |
22 | v-model="$uiStore.currentTags" | 22 | v-model="model" |
23 | :placeholder="$t('tagInput.placeholder')" | 23 | :placeholder="$t('tagInput.placeholder')" |
24 | autocomplete | 24 | autocomplete |
25 | ellipsis | 25 | ellipsis |
@@ -27,12 +27,12 @@ | |||
27 | :data="filteredTags" | 27 | :data="filteredTags" |
28 | field="display" | 28 | field="display" |
29 | type="is-black" | 29 | type="is-black" |
30 | icon="tag" | ||
31 | size="is-medium" | 30 | size="is-medium" |
32 | class="panelTagInput" | 31 | class="paneltag-input" |
33 | @typing="searchTags" | 32 | @typing="searchTags" |
34 | @add="onAdd" | 33 | @add="clearCurrentFilter" |
35 | @remove="onRemove" | 34 | @remove="clearCurrentFilter" |
35 | @keydown.enter.native="onKeyEnter" | ||
36 | > | 36 | > |
37 | <template slot-scope="props">{{displayOption(props.option)}}</template> | 37 | <template slot-scope="props">{{displayOption(props.option)}}</template> |
38 | <template slot="empty">{{$t('tagInput.nomatch')}}</template> | 38 | <template slot="empty">{{$t('tagInput.nomatch')}}</template> |
@@ -40,84 +40,58 @@ | |||
40 | </template> | 40 | </template> |
41 | 41 | ||
42 | <script lang="ts"> | 42 | <script lang="ts"> |
43 | import { Component, Vue } from "vue-property-decorator"; | 43 | import { Component, Vue, Prop, PropSync, Emit } from "vue-property-decorator"; |
44 | import { Operation } from "@/@types/tag/Operation"; | 44 | import { Operation } from "@/@types/Operation"; |
45 | import Navigation from "@/services/navigation"; | ||
46 | import IndexFactory from "@/services/indexfactory"; | ||
45 | 47 | ||
46 | @Component | 48 | @Component |
47 | export default class LdTagInput extends Vue { | 49 | export default class LdTagInput extends Vue { |
48 | filteredTags: Tag.Search[] = []; | 50 | @Prop({ required: true }) readonly tagsIndex!: Tag.Index; |
49 | 51 | @PropSync("searchFilters", { type: Array, required: true }) model!: Tag.Search[]; | |
50 | onAdd(e: any) { | ||
51 | this.$uiStore.mode = "search"; | ||
52 | } | ||
53 | 52 | ||
54 | onRemove() { | 53 | currentFilter: string = ""; |
55 | if (this.$uiStore.currentTags.length === 0) this.$uiStore.mode = "navigation"; | 54 | filteredTags: Tag.Search[] = []; |
56 | } | ||
57 | 55 | ||
58 | displayOption(option: Tag.Search): string { | 56 | displayOption(option: Tag.Search): string { |
59 | return `${option.display} (${option.items.length})`; | 57 | return `${option.display} (${option.items.length})`; |
60 | } | 58 | } |
61 | 59 | ||
62 | extractOperation(filter: string): Operation { | 60 | filterAlreadyPresent(newSearch: Tag.Search) { |
63 | const first = filter.slice(0, 1); | 61 | return !this.model.find( |
64 | switch (first) { | 62 | currentSearch => |
65 | case Operation.ADDITION: | 63 | currentSearch.tag === newSearch.tag && (!currentSearch.parent || currentSearch.parent === newSearch.parent) |
66 | case Operation.SUBSTRACTION: | 64 | ); |
67 | return first; | ||
68 | default: | ||
69 | return Operation.INTERSECTION; | ||
70 | } | ||
71 | } | 65 | } |
72 | 66 | ||
73 | searchTags(filter: string) { | 67 | searchTags(filter: string) { |
74 | const tags = this.$galleryStore.tags; | 68 | this.currentFilter = filter; |
75 | let search: Tag.Search[] = []; | 69 | this.filteredTags = IndexFactory.searchTags(this.tagsIndex, filter, false) |
76 | if (tags && filter) { | 70 | .filter(this.filterAlreadyPresent) |
77 | const operation = this.extractOperation(filter); | 71 | .sort((a, b) => b.items.length - a.items.length); |
78 | if (operation !== Operation.INTERSECTION) filter = filter.slice(1); | ||
79 | if (filter.includes(":")) { | ||
80 | const filterParts = filter.split(":"); | ||
81 | search = this.searchTagsFromFilterWithCategory(tags, operation, filterParts[0], filterParts[1]); | ||
82 | } else { | ||
83 | search = this.searchTagsFromFilter(tags, operation, filter); | ||
84 | } | ||
85 | } | ||
86 | this.filteredTags = this.cleanupAndSort(search); | ||
87 | } | 72 | } |
88 | 73 | ||
89 | searchTagsFromFilterWithCategory( | 74 | clearCurrentFilter() { |
90 | tags: Tag.Index, | 75 | // Necessary for @keydown.enter.native, nexttick is too short |
91 | operation: Operation, | 76 | setTimeout(() => { |
92 | category: string, | 77 | this.currentFilter = ""; |
93 | disambiguation: string | 78 | this.filteredTags = []; |
94 | ): Tag.Search[] { | 79 | }); |
95 | return Object.values(tags) | ||
96 | .filter(node => node.tag.includes(category)) | ||
97 | .flatMap(node => | ||
98 | Object.values(node.children) | ||
99 | .filter(child => child.tag.includes(disambiguation)) | ||
100 | .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` })) | ||
101 | ); | ||
102 | } | 80 | } |
103 | 81 | ||
104 | searchTagsFromFilter(tags: Tag.Index, operation: Operation, filter: string): Tag.Search[] { | 82 | onKeyEnter(e: KeyboardEvent) { |
105 | return Object.values(tags) | 83 | if (!this.currentFilter) this.onkeyenterEmpty(e); |
106 | .filter(node => node.tag.includes(filter)) | ||
107 | .map(node => ({ ...node, operation, display: `${operation}${node.tag}` })); | ||
108 | } | 84 | } |
109 | 85 | ||
110 | cleanupAndSort(search: Tag.Search[]): Tag.Search[] { | 86 | @Emit() |
111 | const currentTags = this.$uiStore.currentTags; | 87 | onkeyenterEmpty(e: KeyboardEvent) { |
112 | return search | 88 | return e; |
113 | .filter(node => !currentTags.find(currentTag => currentTag.tag === node.tag)) | ||
114 | .sort((a, b) => b.items.length - a.items.length); | ||
115 | } | 89 | } |
116 | } | 90 | } |
117 | </script> | 91 | </script> |
118 | 92 | ||
119 | <style lang="scss"> | 93 | <style lang="scss"> |
120 | .panelTagInput .autocomplete .dropdown-content { | 94 | .paneltag-input .autocomplete .dropdown-content { |
121 | max-height: 300px; | 95 | max-height: 300px; |
122 | } | 96 | } |
123 | </style> | 97 | </style> |