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.vue123
1 files changed, 123 insertions, 0 deletions
diff --git a/viewer/src/components/LdTagInput.vue b/viewer/src/components/LdTagInput.vue
new file mode 100644
index 0000000..71131e6
--- /dev/null
+++ b/viewer/src/components/LdTagInput.vue
@@ -0,0 +1,123 @@
1<!-- ldgallery - A static generator which turns a collection of tagged
2-- pictures into a searchable web gallery.
3--
4-- Copyright (C) 2019-2020 Guillaume FOUET
5--
6-- This program is free software: you can redistribute it and/or modify
7-- it under the terms of the GNU Affero General Public License as
8-- published by the Free Software Foundation, either version 3 of the
9-- License, or (at your option) any later version.
10--
11-- This program is distributed in the hope that it will be useful,
12-- but WITHOUT ANY WARRANTY; without even the implied warranty of
13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14-- GNU Affero General Public License for more details.
15--
16-- You should have received a copy of the GNU Affero General Public License
17-- along with this program. If not, see <https://www.gnu.org/licenses/>.
18-->
19
20<template>
21 <b-taginput
22 v-model="$uiStore.currentTags"
23 :placeholder="$t('tagInput.placeholder')"
24 autocomplete
25 ellipsis
26 attached
27 :data="filteredTags"
28 field="display"
29 type="is-black"
30 icon="tag"
31 size="is-medium"
32 class="panelTagInput"
33 @typing="searchTags"
34 @add="onAdd"
35 @remove="onRemove"
36 >
37 <template slot-scope="props">{{displayOption(props.option)}}</template>
38 <template slot="empty">{{$t('tagInput.nomatch')}}</template>
39 </b-taginput>
40</template>
41
42<script lang="ts">
43import { Component, Vue } from "vue-property-decorator";
44import { Operation } from "@/@types/tag/Operation";
45
46@Component
47export default class LdTagInput extends Vue {
48 filteredTags: Tag.Search[] = [];
49
50 onAdd(e: any) {
51 this.$uiStore.mode = "search";
52 }
53
54 onRemove() {
55 if (this.$uiStore.currentTags.length === 0) this.$uiStore.mode = "navigation";
56 }
57
58 displayOption(option: Tag.Search): string {
59 return `${option.display} (${option.items.length})`;
60 }
61
62 extractOperation(filter: string): Operation {
63 const first = filter.slice(0, 1);
64 switch (first) {
65 case Operation.ADDITION:
66 case Operation.SUBSTRACTION:
67 return first;
68 default:
69 return Operation.INTERSECTION;
70 }
71 }
72
73 searchTags(filter: string) {
74 const tags = this.$galleryStore.tags;
75 let search: Tag.Search[] = [];
76 if (tags && filter) {
77 const operation = this.extractOperation(filter);
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 }
88
89 searchTagsFromFilterWithCategory(
90 tags: Tag.Index,
91 operation: Operation,
92 category: string,
93 disambiguation: string
94 ): Tag.Search[] {
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 }
103
104 searchTagsFromFilter(tags: Tag.Index, operation: Operation, filter: string): Tag.Search[] {
105 return Object.values(tags)
106 .filter(node => node.tag.includes(filter))
107 .map(node => ({ ...node, operation, display: `${operation}${node.tag}` }));
108 }
109
110 cleanupAndSort(search: Tag.Search[]): Tag.Search[] {
111 const currentTags = this.$uiStore.currentTags;
112 return search
113 .filter(node => !currentTags.find(currentTag => currentTag.tag === node.tag))
114 .sort((a, b) => b.items.length - a.items.length);
115 }
116}
117</script>
118
119<style lang="scss">
120.panelTagInput .autocomplete .dropdown-content {
121 max-height: 300px;
122}
123</style>