aboutsummaryrefslogtreecommitdiff
path: root/viewer/src/components/LdTagInput.vue
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src/components/LdTagInput.vue')
-rw-r--r--viewer/src/components/LdTagInput.vue94
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">
43import { Component, Vue } from "vue-property-decorator"; 43import { Component, Vue, Prop, PropSync, Emit } from "vue-property-decorator";
44import { Operation } from "@/@types/tag/Operation"; 44import { Operation } from "@/@types/Operation";
45import Navigation from "@/services/navigation";
46import IndexFactory from "@/services/indexfactory";
45 47
46@Component 48@Component
47export default class LdTagInput extends Vue { 49export 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>