diff options
Diffstat (limited to 'viewer/src/components/LdProposition.vue')
-rw-r--r-- | viewer/src/components/LdProposition.vue | 179 |
1 files changed, 0 insertions, 179 deletions
diff --git a/viewer/src/components/LdProposition.vue b/viewer/src/components/LdProposition.vue deleted file mode 100644 index aa44943..0000000 --- a/viewer/src/components/LdProposition.vue +++ /dev/null | |||
@@ -1,179 +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="proposition"> | ||
23 | <h2 v-if="showCategory && Object.keys(propositions).length" class="subtitle category">{{ title }}</h2> | ||
24 | <div v-for="proposed in proposedTags" :key="proposed.rawTag"> | ||
25 | <a | ||
26 | class="operation-btns link" | ||
27 | :title="$t('tag-propositions.substraction')" | ||
28 | @click="add(Operation.SUBSTRACTION, proposed.rawTag)" | ||
29 | > | ||
30 | <fa-icon icon="minus" alt="[-]" /> | ||
31 | </a> | ||
32 | |||
33 | <a | ||
34 | class="operation-btns link" | ||
35 | :title="$t('tag-propositions.addition')" | ||
36 | @click="add(Operation.ADDITION, proposed.rawTag)" | ||
37 | > | ||
38 | <fa-icon icon="plus" alt="[+]" /> | ||
39 | </a> | ||
40 | |||
41 | <a | ||
42 | class="operation-tag link" | ||
43 | :title="$t('tag-propositions.intersection')" | ||
44 | @click="add(Operation.INTERSECTION, proposed.rawTag)" | ||
45 | >{{ proposed.rawTag }}</a | ||
46 | > | ||
47 | |||
48 | <div class="disabled" :title="$t('tag-propositions.item-count')">{{ proposed.count }}</div> | ||
49 | </div> | ||
50 | <div v-if="showMoreCount > 0" class="showmore" @click="limit += showMoreCount"> | ||
51 | {{ $t("tag-propositions.showmore", [showMoreCount]) }}<fa-icon icon="angle-double-down" /> | ||
52 | </div> | ||
53 | </div> | ||
54 | </template> | ||
55 | |||
56 | <script lang="ts"> | ||
57 | import { Component, Vue, Prop, PropSync, Watch } from "vue-property-decorator"; | ||
58 | import { Operation } from "@/@types/Operation"; | ||
59 | |||
60 | @Component | ||
61 | export default class LdProposition extends Vue { | ||
62 | @Prop() readonly category?: Tag.Node; | ||
63 | @Prop({ type: Boolean, required: true }) readonly showCategory!: boolean; | ||
64 | @Prop({ type: Array, required: true }) readonly currentTags!: string[]; | ||
65 | @Prop({ required: true }) readonly tagsIndex!: Tag.Index; | ||
66 | @PropSync("searchFilters", { type: Array, required: true }) model!: Tag.Search[]; | ||
67 | |||
68 | readonly INITIAL_TAG_DISPLAY_LIMIT = this.getInitialTagDisplayLimit(); | ||
69 | |||
70 | limit: number = this.INITIAL_TAG_DISPLAY_LIMIT; | ||
71 | |||
72 | getInitialTagDisplayLimit() { | ||
73 | const limit = this.$galleryStore.config?.initialTagDisplayLimit ?? 10; | ||
74 | return limit >= 0 ? limit : 1000; | ||
75 | } | ||
76 | |||
77 | @Watch("$route") | ||
78 | onRouteChange() { | ||
79 | this.limit = this.INITIAL_TAG_DISPLAY_LIMIT; | ||
80 | } | ||
81 | |||
82 | get Operation() { | ||
83 | return Operation; | ||
84 | } | ||
85 | |||
86 | get propositions(): Record<string, number> { | ||
87 | const propositions: Record<string, number> = {}; | ||
88 | if (this.model.length > 0) { | ||
89 | // Tags count from current search | ||
90 | this.extractDistinctItems(this.model) | ||
91 | .flatMap(item => item.tags) | ||
92 | .map(this.rightmost) | ||
93 | .filter(rawTag => this.tagsIndex[rawTag] && !this.model.find(search => search.tag === rawTag)) | ||
94 | .forEach(rawTag => (propositions[rawTag] = (propositions[rawTag] ?? 0) + 1)); | ||
95 | } else { | ||
96 | // Tags count from the current directory | ||
97 | this.currentTags | ||
98 | .flatMap(tag => tag.split(":")) | ||
99 | .map(tag => this.tagsIndex[tag]) | ||
100 | .filter(Boolean) | ||
101 | .forEach(tagindex => (propositions[tagindex.tag] = tagindex.items.length)); | ||
102 | } | ||
103 | return propositions; | ||
104 | } | ||
105 | |||
106 | get proposedTags() { | ||
107 | return Object.entries(this.propositions) | ||
108 | .sort((a, b) => b[1] - a[1]) | ||
109 | .slice(0, this.limit) | ||
110 | .map(entry => ({ rawTag: entry[0], count: entry[1] })); | ||
111 | } | ||
112 | |||
113 | get showMoreCount(): number { | ||
114 | return Object.keys(this.propositions).length - Object.keys(this.proposedTags).length; | ||
115 | } | ||
116 | |||
117 | get title() { | ||
118 | return this.category?.tag ?? this.$t("panelLeft.propositions.other"); | ||
119 | } | ||
120 | |||
121 | extractDistinctItems(currentTags: Tag.Search[]): Gallery.Item[] { | ||
122 | return [...new Set(currentTags.flatMap(tag => tag.items))]; | ||
123 | } | ||
124 | |||
125 | rightmost(tag: Gallery.RawTag): Gallery.RawTag { | ||
126 | const dot = tag.lastIndexOf(":"); | ||
127 | return dot <= 0 ? tag : tag.substr(dot + 1); | ||
128 | } | ||
129 | |||
130 | add(operation: Operation, rawTag: Gallery.RawTag) { | ||
131 | const node = this.tagsIndex[rawTag]; | ||
132 | const display = this.category ? `${operation}${this.category.tag}:${node.tag}` : `${operation}${node.tag}`; | ||
133 | this.model.push({ ...node, parent: this.category, operation, display }); | ||
134 | } | ||
135 | } | ||
136 | </script> | ||
137 | |||
138 | <style lang="scss"> | ||
139 | @import "~@/assets/scss/theme.scss"; | ||
140 | |||
141 | .proposition { | ||
142 | .subtitle { | ||
143 | background-color: $proposed-category-bgcolor; | ||
144 | width: 100%; | ||
145 | padding: 0 0 6px 0; | ||
146 | margin: 0; | ||
147 | text-align: center; | ||
148 | font-variant: small-caps; | ||
149 | } | ||
150 | > div { | ||
151 | display: flex; | ||
152 | align-items: center; | ||
153 | padding-right: 7px; | ||
154 | .operation-tag { | ||
155 | text-overflow: ellipsis; | ||
156 | white-space: nowrap; | ||
157 | overflow: hidden; | ||
158 | flex-grow: 1; | ||
159 | cursor: pointer; | ||
160 | } | ||
161 | .operation-btns { | ||
162 | padding: 2px 7px; | ||
163 | cursor: pointer; | ||
164 | } | ||
165 | } | ||
166 | .showmore { | ||
167 | display: block; | ||
168 | text-align: right; | ||
169 | color: $palette-300; | ||
170 | cursor: pointer; | ||
171 | > svg { | ||
172 | margin-left: 10px; | ||
173 | } | ||
174 | &:hover { | ||
175 | color: $link-hover; | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | </style> | ||