diff options
Diffstat (limited to 'viewer/src/components/LdProposition.vue')
-rw-r--r-- | viewer/src/components/LdProposition.vue | 114 |
1 files changed, 81 insertions, 33 deletions
diff --git a/viewer/src/components/LdProposition.vue b/viewer/src/components/LdProposition.vue index 9a32e0a..fe3af07 100644 --- a/viewer/src/components/LdProposition.vue +++ b/viewer/src/components/LdProposition.vue | |||
@@ -2,6 +2,7 @@ | |||
2 | -- pictures into a searchable web gallery. | 2 | -- pictures into a searchable web gallery. |
3 | -- | 3 | -- |
4 | -- Copyright (C) 2019-2020 Guillaume FOUET | 4 | -- Copyright (C) 2019-2020 Guillaume FOUET |
5 | -- 2020 Pacien TRAN-GIRARD | ||
5 | -- | 6 | -- |
6 | -- This program is free software: you can redistribute it and/or modify | 7 | -- 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 | -- it under the terms of the GNU Affero General Public License as |
@@ -18,46 +19,77 @@ | |||
18 | --> | 19 | --> |
19 | 20 | ||
20 | <template> | 21 | <template> |
21 | <div> | 22 | <div class="proposition"> |
22 | <div v-for="proposed in proposedTags" :key="proposed.rawTag" class="proposition"> | 23 | <h2 v-if="showCategory && proposedTags.length" class="subtitle category">{{title}}</h2> |
23 | <fa-icon icon="minus" @click="add(Operation.SUBSTRACTION, proposed.rawTag)" /> | 24 | <div v-for="proposed in proposedTags" :key="proposed.rawTag"> |
24 | <span | 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')" | ||
25 | @click="add(Operation.INTERSECTION, proposed.rawTag)" | 44 | @click="add(Operation.INTERSECTION, proposed.rawTag)" |
26 | >{{proposed.rawTag}} x{{proposed.count}}</span> | 45 | >{{proposed.rawTag}}</a> |
27 | <fa-icon icon="plus" @click="add(Operation.ADDITION, proposed.rawTag)" /> | 46 | |
47 | <div class="disabled" :title="$t('tag-propositions.item-count')">{{proposed.count}}</div> | ||
28 | </div> | 48 | </div> |
29 | </div> | 49 | </div> |
30 | </template> | 50 | </template> |
31 | 51 | ||
32 | <script lang="ts"> | 52 | <script lang="ts"> |
33 | import { Component, Vue } from "vue-property-decorator"; | 53 | import { Component, Vue, Prop, PropSync } from "vue-property-decorator"; |
34 | import { Operation } from "@/@types/tag/Operation"; | 54 | import { Operation } from "@/@types/Operation"; |
35 | 55 | ||
36 | @Component | 56 | @Component |
37 | export default class LdTagInput extends Vue { | 57 | export default class LdProposition extends Vue { |
58 | @Prop() readonly category?: Tag.Node; | ||
59 | @Prop({ type: Boolean, required: true }) readonly showCategory!: boolean; | ||
60 | @Prop({ type: Array, required: true }) readonly currentTags!: string[]; | ||
61 | @Prop({ required: true }) readonly tagsIndex!: Tag.Index; | ||
62 | @PropSync("searchFilters", { type: Array, required: true }) model!: Tag.Search[]; | ||
63 | |||
38 | get Operation() { | 64 | get Operation() { |
39 | return Operation; | 65 | return Operation; |
40 | } | 66 | } |
41 | 67 | ||
42 | get proposedTags() { | 68 | get proposedTags() { |
43 | const currentTags = this.$uiStore.currentTags; | ||
44 | let propositions: { [index: string]: number } = {}; | 69 | let propositions: { [index: string]: number } = {}; |
45 | if (currentTags.length > 0) { | 70 | if (this.model.length > 0) { |
46 | // Tags count from current search | 71 | // Tags count from current search |
47 | this.extractDistinctItems(currentTags) | 72 | this.extractDistinctItems(this.model) |
48 | .flatMap(item => item.tags) | 73 | .flatMap(item => item.tags) |
49 | .map(this.rightmost) | 74 | .map(this.rightmost) |
50 | .filter(rawTag => !currentTags.find(currentTag => currentTag.tag === rawTag)) | 75 | .filter(rawTag => this.tagsIndex[rawTag] && !this.model.find(search => search.tag === rawTag)) |
51 | .forEach(rawTag => (propositions[rawTag] = (propositions[rawTag] ?? 0) + 1)); | 76 | .forEach(rawTag => (propositions[rawTag] = (propositions[rawTag] ?? 0) + 1)); |
52 | } else { | 77 | } else { |
53 | // Tags count from the whole gallery | 78 | // Tags count from the current directory |
54 | Object.entries(this.$galleryStore.tags) | 79 | this.currentTags |
55 | .forEach(entry => (propositions[entry[0]] = entry[1].items.length)); | 80 | .flatMap(tag => tag.split(":")) |
81 | .map(tag => this.tagsIndex[tag]) | ||
82 | .filter(Boolean) | ||
83 | .forEach(tagindex => (propositions[tagindex.tag] = tagindex.items.length)); | ||
56 | } | 84 | } |
57 | 85 | ||
58 | return Object.entries(propositions) | 86 | return Object.entries(propositions) |
59 | .sort((a,b) => b[1] - a[1]) | 87 | .sort((a, b) => b[1] - a[1]) |
60 | .map(entry => ({rawTag: entry[0], count: entry[1]})); | 88 | .map(entry => ({ rawTag: entry[0], count: entry[1] })); |
89 | } | ||
90 | |||
91 | get title() { | ||
92 | return this.category?.tag ?? this.$t("panelLeft.propositions.other"); | ||
61 | } | 93 | } |
62 | 94 | ||
63 | extractDistinctItems(currentTags: Tag.Search[]): Gallery.Item[] { | 95 | extractDistinctItems(currentTags: Tag.Search[]): Gallery.Item[] { |
@@ -65,29 +97,45 @@ export default class LdTagInput extends Vue { | |||
65 | } | 97 | } |
66 | 98 | ||
67 | rightmost(tag: Gallery.RawTag): Gallery.RawTag { | 99 | rightmost(tag: Gallery.RawTag): Gallery.RawTag { |
68 | const dot = tag.lastIndexOf("."); | 100 | const dot = tag.lastIndexOf(":"); |
69 | return dot <= 0 ? tag : tag.substr(dot + 1); | 101 | return dot <= 0 ? tag : tag.substr(dot + 1); |
70 | } | 102 | } |
71 | 103 | ||
72 | add(operation: Operation, rawTag: Gallery.RawTag) { | 104 | add(operation: Operation, rawTag: Gallery.RawTag) { |
73 | const node = this.$galleryStore.tags[rawTag]; | 105 | const node = this.tagsIndex[rawTag]; |
74 | const search: Tag.Search = { ...node, operation, display: `${operation}${node.tag}` }; | 106 | const display = this.category ? `${operation}${this.category.tag}:${node.tag}` : `${operation}${node.tag}`; |
75 | this.$uiStore.currentTags.push(search); | 107 | this.model.push({ ...node, parent: this.category, operation, display }); |
76 | this.$uiStore.mode = "search"; | ||
77 | } | 108 | } |
78 | } | 109 | } |
79 | </script> | 110 | </script> |
80 | 111 | ||
81 | <style lang="scss"> | 112 | <style lang="scss"> |
113 | @import "~@/assets/scss/theme.scss"; | ||
114 | |||
82 | .proposition { | 115 | .proposition { |
83 | display: flex; | 116 | .subtitle { |
84 | justify-content: space-between; | 117 | background-color: $proposed-category-bgcolor; |
85 | align-items: center; | 118 | width: 100%; |
86 | margin: 10px; | 119 | padding: 0 0 6px 0; |
87 | color: lightcyan; | 120 | margin: 0; |
88 | cursor: pointer; | 121 | text-align: center; |
89 | } | 122 | font-variant: small-caps; |
90 | .proposition span { | 123 | } |
91 | padding: 0 10px; | 124 | > div { |
125 | display: flex; | ||
126 | align-items: center; | ||
127 | padding-right: 7px; | ||
128 | .operation-tag { | ||
129 | text-overflow: ellipsis; | ||
130 | white-space: nowrap; | ||
131 | overflow: hidden; | ||
132 | flex-grow: 1; | ||
133 | cursor: pointer; | ||
134 | } | ||
135 | .operation-btns { | ||
136 | padding: 2px 7px; | ||
137 | cursor: pointer; | ||
138 | } | ||
139 | } | ||
92 | } | 140 | } |
93 | </style> | 141 | </style> |