aboutsummaryrefslogtreecommitdiff
path: root/viewer/src
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src')
-rw-r--r--viewer/src/@types/tag/Operation.ts5
-rw-r--r--viewer/src/@types/tag/index.d.ts8
-rw-r--r--viewer/src/components/LdTagInput.vue48
-rw-r--r--viewer/src/components/index.ts2
-rw-r--r--viewer/src/plugins/fontawesome.ts14
-rw-r--r--viewer/src/store/galleryStore.ts2
-rw-r--r--viewer/src/store/uiStore.ts2
-rw-r--r--viewer/src/views/Gallery.vue46
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 @@
1export 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">
21import { Component, Vue } from "vue-property-decorator"; 24import { Component, Vue } from "vue-property-decorator";
25import { Operation } from "@/@types/tag/Operation";
22 26
23@Component 27@Component
24export default class LdTagInput extends Vue { 28export 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 @@
1import Vue from "vue"; 1import Vue from "vue";
2 2
3import { library } from "@fortawesome/fontawesome-svg-core"; 3import { library } from "@fortawesome/fontawesome-svg-core";
4import { faExpandArrowsAlt, faFolder, faSearch, faTag } from "@fortawesome/free-solid-svg-icons";
5import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; 4import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
5import {
6 faExpandArrowsAlt,
7 faFolder,
8 faSearch,
9 faTag
10} from "@fortawesome/free-solid-svg-icons";
6 11
7library.add(faExpandArrowsAlt, faFolder, faSearch, faTag); 12library.add(
13 faExpandArrowsAlt,
14 faFolder,
15 faSearch,
16 faTag,
17);
8 18
9Vue.component("fa-icon", FontAwesomeIcon); 19Vue.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">
10import { Component, Vue, Prop } from "vue-property-decorator"; 10import { Component, Vue, Prop } from "vue-property-decorator";
11import { Operation } from '@/@types/tag/Operation';
11import GallerySearch from "./GallerySearch.vue"; 12import GallerySearch from "./GallerySearch.vue";
12import GalleryDirectory from "./GalleryDirectory.vue"; 13import GalleryDirectory from "./GalleryDirectory.vue";
13import GalleryImage from "./GalleryImage.vue"; 14import GalleryImage from "./GalleryImage.vue";
@@ -29,10 +30,10 @@ export default class Gallery extends Vue {
29