diff options
Diffstat (limited to 'viewer/src')
-rw-r--r-- | viewer/src/@types/tag/Operation.ts | 5 | ||||
-rw-r--r-- | viewer/src/@types/tag/index.d.ts | 8 | ||||
-rw-r--r-- | viewer/src/components/LdTagInput.vue | 48 | ||||
-rw-r--r-- | viewer/src/components/index.ts | 2 | ||||
-rw-r--r-- | viewer/src/plugins/fontawesome.ts | 14 | ||||
-rw-r--r-- | viewer/src/store/galleryStore.ts | 2 | ||||
-rw-r--r-- | viewer/src/store/uiStore.ts | 2 | ||||
-rw-r--r-- | viewer/src/views/Gallery.vue | 46 |
8 files changed, 106 insertions, 21 deletions
diff --git a/viewer/src/@types/tag/Operation.ts b/viewer/src/@types/tag/Operation.ts new file mode 100644 index 0000000..a0de92b --- /dev/null +++ b/viewer/src/@types/tag/Operation.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export enum Operation { | ||
2 | INTERSECTION = '', | ||
3 | ADDITION = '+', | ||
4 | SUBSTRACTION = '-', | ||
5 | }; \ 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 30bbebb..6a0c605 100644 --- a/viewer/src/@types/tag/index.d.ts +++ b/viewer/src/@types/tag/index.d.ts | |||
@@ -4,9 +4,11 @@ declare namespace Tag { | |||
4 | items: Gallery.Item[]; | 4 | items: Gallery.Item[]; |
5 | children: Index; | 5 | children: Index; |
6 | } | 6 | } |
7 | interface NodeWithParent extends Node { | 7 | interface Search extends Node { |
8 | parent: Node; | 8 | parent?: Node; |
9 | operation: string; // Enum Operation | ||
10 | display: string; | ||
9 | } | 11 | } |
10 | type Search = Node | NodeWithParent; | 12 | type SearchByOperation = { [index: string]: Tag.Search[] }; |
11 | type Index = { [index: string]: Node }; | 13 | type Index = { [index: string]: Node }; |
12 | } \ No newline at end of file | 14 | } \ No newline at end of file |
diff --git a/viewer/src/components/LdTagInput.vue b/viewer/src/components/LdTagInput.vue index 4121cd7..daca62d 100644 --- a/viewer/src/components/LdTagInput.vue +++ b/viewer/src/components/LdTagInput.vue | |||
@@ -6,11 +6,14 @@ | |||
6 | ellipsis | 6 | ellipsis |
7 | attached | 7 | attached |
8 | :data="filteredTags" | 8 | :data="filteredTags" |
9 | field="tag" | 9 | field="display" |
10 | type="is-black" | 10 | type="is-black" |
11 | icon="tag" | 11 | icon="tag" |
12 | size="is-medium" | ||
12 | class="panelTagInput" | 13 | class="panelTagInput" |
13 | @typing="searchTags" | 14 | @typing="searchTags" |
15 | @add="onAdd" | ||
16 | @remove="onRemove" | ||
14 | > | 17 | > |
15 | <template slot-scope="props">{{displayOption(props.option)}}</template> | 18 | <template slot-scope="props">{{displayOption(props.option)}}</template> |
16 | <template slot="empty">{{$t('tagInput.nomatch')}}</template> | 19 | <template slot="empty">{{$t('tagInput.nomatch')}}</template> |
@@ -19,41 +22,70 @@ | |||
19 | 22 | ||
20 | <script lang="ts"> | 23 | <script lang="ts"> |
21 | import { Component, Vue } from "vue-property-decorator"; | 24 | import { Component, Vue } from "vue-property-decorator"; |
25 | import { Operation } from "@/@types/tag/Operation"; | ||
22 | 26 | ||
23 | @Component | 27 | @Component |
24 | export default class LdTagInput extends Vue { | 28 | export default class LdTagInput extends Vue { |
25 | filteredTags: Tag.Search[] = []; | 29 | filteredTags: Tag.Search[] = []; |
26 | 30 | ||
31 | onAdd(e: any) { | ||
32 | this.$uiStore.mode = "search"; | ||
33 | } | ||
34 | |||
35 | onRemove() { | ||
36 | if (this.$uiStore.currentTags.length === 0) this.$uiStore.mode = "navigation"; | ||
37 | } | ||
38 | |||
27 | displayOption(option: Tag.Search): string { | 39 | displayOption(option: Tag.Search): string { |
28 | return `${option.tag} (${option.items.length})`; | 40 | return `${option.display} (${option.items.length})`; |
41 | } | ||
42 | |||
43 | extractOperation(filter: string) { | ||
44 | const first = filter.slice(0, 1); | ||
45 | switch (first) { | ||
46 | case Operation.ADDITION: | ||
47 | case Operation.SUBSTRACTION: | ||
48 | return first; | ||
49 | default: | ||
50 | return Operation.INTERSECTION; | ||
51 | } | ||
29 | } | 52 | } |
30 | 53 | ||
31 | searchTags(filter: string) { | 54 | searchTags(filter: string) { |
32 | const tags = this.$galleryStore.tags; | 55 | const tags = this.$galleryStore.tags; |
33 | let search: Tag.Search[] = []; | 56 | let search: Tag.Search[] = []; |
34 | if (tags && filter) { | 57 | if (tags && filter) { |
58 | const operation = this.extractOperation(filter); | ||
59 | if (operation !== Operation.INTERSECTION) filter = filter.slice(1); | ||
35 | if (filter.includes(":")) { | 60 | if (filter.includes(":")) { |
36 | const filterParts = filter.split(":"); | 61 | const filterParts = filter.split(":"); |
37 | search = this.searchTagsFromFilterWithCategory(tags, filterParts[0], filterParts[1]); | 62 | search = this.searchTagsFromFilterWithCategory(tags, operation, filterParts[0], filterParts[1]); |
38 | } else { | 63 | } else { |
39 | search = this.searchTagsFromFilter(tags, filter); | 64 | search = this.searchTagsFromFilter(tags, operation, filter); |
40 | } | 65 | } |
41 | } | 66 | } |
42 | this.filteredTags = this.cleanupAndSort(search); | 67 | this.filteredTags = this.cleanupAndSort(search); |
43 | } | 68 | } |
44 | 69 | ||
45 | searchTagsFromFilterWithCategory(tags: Tag.Index, category: string, disambiguation: string): Tag.NodeWithParent[] { | 70 | searchTagsFromFilterWithCategory( |
71 | tags: Tag.Index, | ||
72 | operation: Operation, | ||
73 | category: string, | ||
74 | disambiguation: string | ||
75 | ): Tag.Search[] { | ||
46 | return Object.values(tags) | 76 | return Object.values(tags) |
47 | .filter(node => node.tag.includes(category)) | 77 | .filter(node => node.tag.includes(category)) |
48 | .flatMap(node => | 78 | .flatMap(node => |
49 | Object.values(node.children) | 79 | Object.values(node.children) |
50 | .filter(child => child.tag.includes(disambiguation)) | 80 | .filter(child => child.tag.includes(disambiguation)) |
51 | .map(child => ({ ...child, parent: node, tag: `${node.tag}:${child.tag}` })) | 81 | .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` })) |
52 | ); | 82 | ); |
53 | } | 83 | } |
54 | 84 | ||
55 | searchTagsFromFilter(tags: Tag.Index, filter: string): Tag.Node[] { | 85 | searchTagsFromFilter(tags: Tag.Index, operation: Operation, filter: string): Tag.Search[] { |
56 | return Object.values(tags).filter(node => node.tag.includes(filter)); | 86 | return Object.values(tags) |
87 | .filter(node => node.tag.includes(filter)) | ||
88 | .map(node => ({ ...node, operation, display: `${operation}${node.tag}` })); | ||
57 | } | 89 | } |
58 | 90 | ||
59 | cleanupAndSort(search: Tag.Search[]): Tag.Search[] { | 91 | cleanupAndSort(search: Tag.Search[]): Tag.Search[] { |
diff --git a/viewer/src/components/index.ts b/viewer/src/components/index.ts index 1406b34..4586f62 100644 --- a/viewer/src/components/index.ts +++ b/viewer/src/components/index.ts | |||
@@ -17,6 +17,6 @@ requireComponent.keys().forEach(fileName => { | |||
17 | // Look for the component options on `.default`, which will | 17 | // Look for the component options on `.default`, which will |
18 | // exist if the component was exported with `export default`, | 18 | // exist if the component was exported with `export default`, |
19 | // otherwise fall back to module's root. | 19 | // otherwise fall back to module's root. |
20 | componentConfig.default || componentConfig | 20 | componentConfig.default ?? componentConfig |
21 | ) | 21 | ) |
22 | }) \ No newline at end of file | 22 | }) \ No newline at end of file |
diff --git a/viewer/src/plugins/fontawesome.ts b/viewer/src/plugins/fontawesome.ts index e129c57..ba31c9e 100644 --- a/viewer/src/plugins/fontawesome.ts +++ b/viewer/src/plugins/fontawesome.ts | |||
@@ -1,9 +1,19 @@ | |||
1 | import Vue from "vue"; | 1 | import Vue from "vue"; |
2 | 2 | ||
3 | import { library } from "@fortawesome/fontawesome-svg-core"; | 3 | import { library } from "@fortawesome/fontawesome-svg-core"; |
4 | import { faExpandArrowsAlt, faFolder, faSearch, faTag } from "@fortawesome/free-solid-svg-icons"; | ||
5 | import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; | 4 | import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; |
5 | import { | ||
6 | faExpandArrowsAlt, | ||
7 | faFolder, | ||
8 | faSearch, | ||
9 | faTag | ||
10 | } from "@fortawesome/free-solid-svg-icons"; | ||
6 | 11 | ||
7 | library.add(faExpandArrowsAlt, faFolder, faSearch, faTag); | 12 | library.add( |
13 | faExpandArrowsAlt, | ||
14 | faFolder, | ||
15 | faSearch, | ||
16 | faTag, | ||
17 | ); | ||
8 | 18 | ||
9 | Vue.component("fa-icon", FontAwesomeIcon); | 19 | Vue.component("fa-icon", FontAwesomeIcon); |
diff --git a/viewer/src/store/galleryStore.ts b/viewer/src/store/galleryStore.ts index ca36a32..8a611ea 100644 --- a/viewer/src/store/galleryStore.ts +++ b/viewer/src/store/galleryStore.ts | |||
@@ -65,7 +65,7 @@ export default class GalleryStore extends VuexModule { | |||
65 | const itemFound = item.properties.items | 65 | const itemFound = item.properties.items |
66 | .map(item => this.searchCurrentItem(item, path)) | 66 | .map(item => this.searchCurrentItem(item, path)) |
67 | .find(item => Boolean(item)); | 67 | .find(item => Boolean(item)); |
68 | return itemFound || null; | 68 | return itemFound ?? null; |
69 | } | 69 | } |
70 | return null; | 70 | return null; |
71 | } | 71 | } |
diff --git a/viewer/src/store/uiStore.ts b/viewer/src/store/uiStore.ts index 4a6f487..25d2a28 100644 --- a/viewer/src/store/uiStore.ts +++ b/viewer/src/store/uiStore.ts | |||
@@ -9,7 +9,7 @@ export default class UIStore extends VuexModule { | |||
9 | 9 | ||
10 | fullscreen: boolean = false; | 10 | fullscreen: boolean = false; |
11 | mode: "navigation" | "search" = "navigation"; | 11 | mode: "navigation" | "search" = "navigation"; |
12 | currentTags: Tag.Node[] = []; | 12 | currentTags: Tag.Search[] = []; |
13 | 13 | ||
14 | // --- | 14 | // --- |
15 | 15 | ||
diff --git a/viewer/src/views/Gallery.vue b/viewer/src/views/Gallery.vue index 38199b9..f04b276 100644 --- a/viewer/src/views/Gallery.vue +++ b/viewer/src/views/Gallery.vue | |||
@@ -8,6 +8,7 @@ | |||
8 | 8 | ||
9 | <script lang="ts"> | 9 | <script lang="ts"> |
10 | import { Component, Vue, Prop } from "vue-property-decorator"; | 10 | import { Component, Vue, Prop } from "vue-property-decorator"; |
11 | import { Operation } from '@/@types/tag/Operation'; | ||
11 | import GallerySearch from "./GallerySearch.vue"; | 12 | import GallerySearch from "./GallerySearch.vue"; |
12 | import GalleryDirectory from "./GalleryDirectory.vue"; | 13 | import GalleryDirectory from "./GalleryDirectory.vue"; |
13 | import GalleryImage from "./GalleryImage.vue"; | 14 | import GalleryImage from "./GalleryImage.vue"; |
@@ -29,10 +30,10 @@ export default class Gallery extends Vue { | |||
29 | 30 | ||
30 | // Results of the search (by tags) | 31 | // Results of the search (by tags) |
31 | get currentSearch(): Gallery.Item[] { | 32 | get currentSearch(): Gallery.Item[] { |
32 | const currentTags = this.$uiStore.currentTags; |