diff options
Diffstat (limited to 'viewer/src/components/LdProposition.vue')
-rw-r--r-- | viewer/src/components/LdProposition.vue | 186 |
1 files changed, 0 insertions, 186 deletions
diff --git a/viewer/src/components/LdProposition.vue b/viewer/src/components/LdProposition.vue deleted file mode 100644 index 07b27f5..0000000 --- a/viewer/src/components/LdProposition.vue +++ /dev/null | |||
@@ -1,186 +0,0 @@ | |||
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 | -- 2020 Pacien TRAN-GIRARD | ||
6 | -- | ||
7 | -- This program is free software: you can redistribute it and/or modify | ||
8 | -- it under the terms of the GNU Affero General Public License as | ||
9 | -- published by the Free Software Foundation, either version 3 of the | ||
10 | -- License, or (at your option) any later version. | ||
11 | -- | ||
12 | -- This program is distributed in the hope that it will be useful, | ||
13 | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | -- GNU Affero General Public License for more details. | ||
16 | -- | ||
17 | -- You should have received a copy of the GNU Affero General Public License | ||
18 | -- along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
19 | --> | ||
20 | |||
21 | <template> | ||
22 | <div :class="$style.proposition"> | ||
23 | <h2 v-if="showCategory && Object.keys(propositions).length" :class="[$style.subtitle, $style.category]"> | ||
24 | {{ title }} | ||
25 | </h2> | ||
26 | <div v-for="proposed in proposedTags" :key="proposed.rawTag"> | ||
27 | <a | ||
28 | class="link" | ||
29 | :class="$style.operationBtns" | ||
30 | :title="$t('tag-propositions.substraction')" | ||
31 | @click="add(Operation.SUBSTRACTION, proposed.rawTag)" | ||
32 | > | ||
33 | <fa-icon icon="minus" alt="[-]" /> | ||
34 | </a> | ||
35 | |||
36 | <a | ||
37 | class="link" | ||
38 | :class="$style.operationBtns" | ||
39 | :title="$t('tag-propositions.addition')" | ||
40 | @click="add(Operation.ADDITION, proposed.rawTag)" | ||
41 | > | ||
42 | <fa-icon icon="plus" alt="[+]" /> | ||
43 | </a> | ||
44 | |||
45 | <a | ||
46 | class="link" | ||
47 | :class="$style.operationTag" | ||
48 | :title="$t('tag-propositions.intersection')" | ||
49 | @click="add(Operation.INTERSECTION, proposed.rawTag)" | ||
50 | >{{ proposed.rawTag }}</a | ||
51 | > | ||
52 | |||
53 | <div class="disabled" :title="$t('tag-propositions.item-count')">{{ proposed.count }}</div> | ||
54 | </div> | ||
55 | <div v-if="showMoreCount > 0" :class="$style.showmore" @click="limit += showMoreCount"> | ||
56 | {{ $t("tag-propositions.showmore", [showMoreCount]) }}<fa-icon icon="angle-double-down" /> | ||
57 | </div> | ||
58 | </div> | ||
59 | </template> | ||
60 | |||
61 | <script lang="ts"> | ||
62 | import { Item, RawTag } from "@/@types/gallery"; | ||
63 | import { Operation } from "@/@types/Operation"; | ||
64 | import { TagIndex, TagNode, TagSearch } from "@/@types/tag"; | ||
65 | import { Component, Prop, PropSync, Vue, Watch } from "vue-property-decorator"; | ||
66 | |||
67 | @Component | ||
68 | export default class LdProposition extends Vue { | ||
69 | @Prop() readonly category?: TagNode; | ||
70 | @Prop({ type: Boolean, required: true }) readonly showCategory!: boolean; | ||
71 | @Prop({ type: Array, required: true }) readonly currentTags!: string[]; | ||
72 | @Prop({ required: true }) readonly tagsIndex!: TagIndex; | ||
73 | @PropSync("searchFilters", { type: Array, required: true }) model!: TagSearch[]; | ||
74 | |||
75 | readonly INITIAL_TAG_DISPLAY_LIMIT = this.getInitialTagDisplayLimit(); | ||
76 | |||
77 | limit: number = this.INITIAL_TAG_DISPLAY_LIMIT; | ||
78 | |||
79 | getInitialTagDisplayLimit() { | ||
80 | const limit = this.$galleryStore.config?.initialTagDisplayLimit ?? 10; | ||
81 | return limit >= 0 ? limit : 1000; | ||
82 | } | ||
83 | |||
84 | @Watch("$route") | ||
85 | onRouteChange() { | ||
86 | this.limit = this.INITIAL_TAG_DISPLAY_LIMIT; | ||
87 | } | ||
88 | |||
89 | get Operation() { | ||
90 | return Operation; | ||
91 | } | ||
92 | |||
93 | get propositions(): Record<string, number> { | ||
94 | const propositions: Record<string, number> = {}; | ||
95 | if (this.model.length > 0) { | ||
96 | // Tags count from current search | ||
97 | this.extractDistinctItems(this.model) | ||
98 | .flatMap(item => item.tags) | ||
99 | .map(this.rightmost) | ||
100 | .filter(rawTag => this.tagsIndex[rawTag] && !this.model.find(search => search.tag === rawTag)) | ||
101 | .forEach(rawTag => (propositions[rawTag] = (propositions[rawTag] ?? 0) + 1)); | ||
102 | } else { | ||
103 | // Tags count from the current directory | ||
104 | this.currentTags | ||
105 | .flatMap(tag => tag.split(":")) | ||
106 | .map(tag => this.tagsIndex[tag]) | ||
107 | .filter(Boolean) | ||
108 | .forEach(tagindex => (propositions[tagindex.tag] = tagindex.items.length)); | ||
109 | } | ||
110 | return propositions; | ||
111 | } | ||
112 | |||
113 | get proposedTags() { | ||
114 | return Object.entries(this.propositions) | ||
115 | .sort((a, b) => b[1] - a[1]) | ||
116 | .slice(0, this.limit) | ||
117 | .map(entry => ({ rawTag: entry[0], count: entry[1] })); | ||
118 | } | ||
119 | |||
120 | get showMoreCount(): number { | ||
121 | return Object.keys(this.propositions).length - Object.keys(this.proposedTags).length; | ||
122 | } | ||
123 | |||
124 | get title() { | ||
125 | return this.category?.tag ?? this.$t("panelLeft.propositions.other"); | ||
126 | } | ||
127 | |||
128 | extractDistinctItems(currentTags: TagSearch[]): Item[] { | ||
129 | return [...new Set(currentTags.flatMap(tag => tag.items))]; | ||
130 | } | ||
131 | |||
132 | rightmost(tag: RawTag): RawTag { | ||
133 | const dot = tag.lastIndexOf(":"); | ||
134 | return dot <= 0 ? tag : tag.substr(dot + 1); | ||
135 | } | ||
136 | |||
137 | add(operation: Operation, rawTag: RawTag) { | ||
138 | const node = this.tagsIndex[rawTag]; | ||
139 | const display = this.category ? `${operation}${this.category.tag}:${node.tag}` : `${operation}${node.tag}`; | ||
140 | this.model.push({ ...node, parent: this.category, operation, display }); | ||
141 | } | ||
142 | } | ||
143 | </script> | ||
144 | |||
145 | <style lang="scss" module> | ||
146 | @import "~@/assets/scss/theme.scss"; | ||
147 | |||
148 | .proposition { | ||
149 | .subtitle { | ||
150 | background-color: $proposed-category-bgcolor; | ||
151 | width: 100%; | ||
152 | padding: 0 0 6px 0; | ||
153 | margin: 0; | ||
154 | text-align: center; | ||
155 | font-variant: small-caps; | ||
156 | } | ||
157 | > div { | ||
158 | display: flex; | ||
159 | align-items: center; | ||
160 | padding-right: 7px; | ||
161 | .operationTag { | ||
162 | text-overflow: ellipsis; | ||
163 | white-space: nowrap; | ||
164 | overflow: hidden; | ||
165 | flex-grow: 1; | ||
166 | cursor: pointer; | ||
167 | } | ||
168 | .operationBtns { | ||
169 | padding: 2px 7px; | ||
170 | cursor: pointer; | ||
171 | } | ||
172 | } | ||
173 | .showmore { | ||
174 | display: block; | ||
175 | text-align: right; | ||
176 | color: $palette-300; | ||
177 | cursor: pointer; | ||
178 | > svg { | ||
179 | margin-left: 10px; | ||
180 | } | ||
181 | &:hover { | ||
182 | color: $link-hover; | ||
183 | } | ||
184 | } | ||
185 | } | ||
186 | </style> | ||