diff options
-rw-r--r-- | viewer/src/@types/gallery/index.d.ts | 3 | ||||
-rw-r--r-- | viewer/src/@types/tag/index.d.ts | 2 | ||||
-rw-r--r-- | viewer/src/components/LdProposition.vue | 75 | ||||
-rw-r--r-- | viewer/src/locales/en.json | 3 | ||||
-rw-r--r-- | viewer/src/plugins/fontawesome.ts | 6 | ||||
-rw-r--r-- | viewer/src/store/galleryStore.ts | 6 | ||||
-rw-r--r-- | viewer/src/views/PanelLeft.vue | 2 |
7 files changed, 91 insertions, 6 deletions
diff --git a/viewer/src/@types/gallery/index.d.ts b/viewer/src/@types/gallery/index.d.ts index b47c812..310c865 100644 --- a/viewer/src/@types/gallery/index.d.ts +++ b/viewer/src/@types/gallery/index.d.ts | |||
@@ -9,7 +9,7 @@ declare namespace Gallery { | |||
9 | title: string, | 9 | title: string, |
10 | date: string, | 10 | date: string, |
11 | description: string, | 11 | description: string, |
12 | tags: string[], | 12 | tags: RawTag[], |
13 | path: string, | 13 | path: string, |
14 | thumbnail: { | 14 | thumbnail: { |
15 | path: string, | 15 | path: string, |
@@ -28,4 +28,5 @@ declare namespace Gallery { | |||
28 | type: "directory", | 28 | type: "directory", |
29 | items: Item[] | 29 | items: Item[] |
30 | } | 30 | } |
31 | type RawTag = string; | ||
31 | } \ No newline at end of file | 32 | } \ No newline at end of file |
diff --git a/viewer/src/@types/tag/index.d.ts b/viewer/src/@types/tag/index.d.ts index 6a0c605..181f47a 100644 --- a/viewer/src/@types/tag/index.d.ts +++ b/viewer/src/@types/tag/index.d.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | declare namespace Tag { | 1 | declare namespace Tag { |
2 | interface Node { | 2 | interface Node { |
3 | tag: string; | 3 | tag: Gallery.RawTag; |
4 | items: Gallery.Item[]; | 4 | items: Gallery.Item[]; |
5 | children: Index; | 5 | children: Index; |
6 | } | 6 | } |
diff --git a/viewer/src/components/LdProposition.vue b/viewer/src/components/LdProposition.vue new file mode 100644 index 0000000..b23c14a --- /dev/null +++ b/viewer/src/components/LdProposition.vue | |||
@@ -0,0 +1,75 @@ | |||
1 | <template> | ||
2 | <div> | ||
3 | <div v-for="proposed in proposedTags" :key="proposed.rawTag" class="proposition"> | ||
4 | <fa-icon icon="minus" @click="add(Operation.SUBSTRACTION, proposed.rawTag)" /> | ||
5 | <span | ||
6 | @click="add(Operation.INTERSECTION, proposed.rawTag)" | ||
7 | >{{proposed.rawTag}} x{{proposed.count}}</span> | ||
8 | <fa-icon icon="plus" @click="add(Operation.ADDITION, proposed.rawTag)" /> | ||
9 | </div> | ||
10 | </div> | ||
11 | </template> | ||
12 | |||
13 | <script lang="ts"> | ||
14 | import { Component, Vue } from "vue-property-decorator"; | ||
15 | import { Operation } from "@/@types/tag/Operation"; | ||
16 | import Gallery from '../views/Gallery.vue'; | ||
17 | |||
18 | @Component | ||
19 | export default class LdTagInput extends Vue { | ||
20 | get Operation() { | ||
21 | return Operation; | ||
22 | } | ||
23 | |||
24 | get proposedTags() { | ||
25 | const currentTags = this.$uiStore.currentTags; | ||
26 | let propositions: { [index: string]: number } = {}; | ||
27 | if (currentTags.length > 0) { | ||
28 | // Tags count from current search | ||
29 | this.extractDistinctItems(currentTags) | ||
30 | .flatMap(item => item.tags) | ||
31 | .map(this.rightmost) | ||
32 | .filter(rawTag => !currentTags.find(currentTag => currentTag.tag === rawTag)) | ||
33 | .forEach(rawTag => (propositions[rawTag] = (propositions[rawTag] ?? 0) + 1)); | ||
34 | } else { | ||
35 | // Tags count from the whole gallery | ||
36 | Object.entries(this.$galleryStore.tags) | ||
37 | .forEach(entry => (propositions[entry[0]] = entry[1].items.length)); | ||
38 | } | ||
39 | |||
40 | return Object.entries(propositions) | ||
41 | .sort((a,b) => b[1] - a[1]) | ||
42 | .map(entry => ({rawTag: entry[0], count: entry[1]})); | ||
43 | } | ||
44 | |||
45 | extractDistinctItems(currentTags: Tag.Search[]): Gallery.Item[] { | ||
46 | return [...new Set(currentTags.flatMap(tag => tag.items))]; | ||
47 | } | ||
48 | |||
49 | rightmost(tag: Gallery.RawTag): Gallery.RawTag { | ||
50 | const dot = tag.lastIndexOf("."); | ||
51 | return dot <= 0 ? tag : tag.substr(dot + 1); | ||
52 | } | ||
53 | |||
54 | add(operation: Operation, rawTag: Gallery.RawTag) { | ||
55 | const node = this.$galleryStore.tags[rawTag]; | ||
56 | const search: Tag.Search = { ...node, operation, display: `${operation}${node.tag}` }; | ||
57 | this.$uiStore.currentTags.push(search); | ||
58 | this.$uiStore.mode = "search"; | ||
59 | } | ||
60 | } | ||
61 | </script> | ||
62 | |||
63 | <style lang="scss"> | ||
64 | .proposition { | ||
65 | display: flex; | ||
66 | justify-content: space-between; | ||
67 | align-items: center; | ||
68 | margin: 10px; | ||
69 | color: lightcyan; | ||
70 | cursor: pointer; | ||
71 | } | ||
72 | .proposition span { | ||
73 | padding: 0 10px; | ||
74 | } | ||
75 | </style> | ||
diff --git a/viewer/src/locales/en.json b/viewer/src/locales/en.json index d885872..4c9f5d4 100644 --- a/viewer/src/locales/en.json +++ b/viewer/src/locales/en.json | |||
@@ -5,5 +5,6 @@ | |||
5 | "panelLeft.mode": "Mode", | 5 | "panelLeft.mode": "Mode", |
6 | "mode.navigation": "Navigation", | 6 | "mode.navigation": "Navigation", |
7 | "mode.search": "Search", | 7 | "mode.search": "Search", |
8 | "search.no-results": "No results" | 8 | "search.no-results": "No results", |
9 | "panelLeft.propositions": "Proposed tags" | ||
9 | } \ No newline at end of file | 10 | } \ No newline at end of file |
diff --git a/viewer/src/plugins/fontawesome.ts b/viewer/src/plugins/fontawesome.ts index ba31c9e..7308afe 100644 --- a/viewer/src/plugins/fontawesome.ts +++ b/viewer/src/plugins/fontawesome.ts | |||
@@ -6,7 +6,9 @@ import { | |||
6 | faExpandArrowsAlt, | 6 | faExpandArrowsAlt, |
7 | faFolder, | 7 | faFolder, |
8 | faSearch, | 8 | faSearch, |
9 | faTag | 9 | faTag, |
10 | faPlus, | ||
11 | faMinus, | ||
10 | } from "@fortawesome/free-solid-svg-icons"; | 12 | } from "@fortawesome/free-solid-svg-icons"; |
11 | 13 | ||
12 | library.add( | 14 | library.add( |
@@ -14,6 +16,8 @@ library.add( | |||
14 | faFolder, | 16 | faFolder, |
15 | faSearch, | 17 | faSearch, |
16 | faTag, | 18 | faTag, |
19 | faPlus, | ||
20 | faMinus, | ||
17 | ); | 21 | ); |
18 | 22 | ||
19 | Vue.component("fa-icon", FontAwesomeIcon); | 23 | Vue.component("fa-icon", FontAwesomeIcon); |
diff --git a/viewer/src/store/galleryStore.ts b/viewer/src/store/galleryStore.ts index 8a611ea..179fbe2 100644 --- a/viewer/src/store/galleryStore.ts +++ b/viewer/src/store/galleryStore.ts | |||
@@ -44,6 +44,10 @@ export default class GalleryStore extends VuexModule { | |||
44 | // Pushes all tags for a root item (and its children) to the index | 44 | // Pushes all tags for a root item (and its children) to the index |
45 | private static pushTagsForItem(index: Tag.Index, item: Gallery.Item) { | 45 | private static pushTagsForItem(index: Tag.Index, item: Gallery.Item) { |
46 | console.log("IndexingTagsFor: ", item.path); | 46 | console.log("IndexingTagsFor: ", item.path); |
47 | if (item.properties.type === "directory") { | ||
48 | item.properties.items.forEach(item => this.pushTagsForItem(index, item)); | ||
49 | return; // Directories are not indexed | ||
50 | } | ||
47 | for (const tag of item.tags) { | 51 | for (const tag of item.tags) { |
48 | const parts = tag.split('.'); | 52 | const parts = tag.split('.'); |
49 | let lastPart: string | null = null; | 53 | let lastPart: string | null = null; |
@@ -54,8 +58,6 @@ export default class GalleryStore extends VuexModule { | |||
54 | lastPart = part; | 58 | lastPart = part; |
55 | } | 59 | } |
56 | } | 60 | } |
57 | if (item.properties.type === "directory") | ||
58 | item.properties.items.forEach(item => this.pushTagsForItem(index, item)); | ||
59 | } | 61 | } |
60 | 62 | ||
61 | // Searches for an item by path from a root item (navigation) | 63 | // Searches for an item by path from a root item (navigation) |
diff --git a/viewer/src/views/PanelLeft.vue b/viewer/src/views/PanelLeft.vue index c187ce6..df1fe54 100644 --- a/viewer/src/views/PanelLeft.vue +++ b/viewer/src/views/PanelLeft.vue | |||
@@ -4,6 +4,8 @@ | |||
4 | <ld-mode-radio /> | 4 | <ld-mode-radio /> |
5 | <h1>{{$t('panelLeft.filters')}}</h1> | 5 | <h1>{{$t('panelLeft.filters')}}</h1> |
6 | <ld-tag-input /> | 6 | <ld-tag-input /> |
7 | <h1>{{$t('panelLeft.propositions')}}</h1> | ||
8 | <ld-proposition /> | ||
7 | </div> | 9 | </div> |
8 | </template> | 10 | </template> |
9 | 11 | ||