aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--viewer/src/@types/tag.d.ts5
-rw-r--r--viewer/src/assets/scss/global.scss4
-rw-r--r--viewer/src/assets/scss/theme.scss4
-rw-r--r--viewer/src/components/LdProposition.vue47
-rw-r--r--viewer/src/locales/en.json5
-rw-r--r--viewer/src/services/indexfactory.ts19
-rw-r--r--viewer/src/store/galleryStore.ts15
-rw-r--r--viewer/src/views/PanelLeft.vue24
8 files changed, 96 insertions, 27 deletions
diff --git a/viewer/src/@types/tag.d.ts b/viewer/src/@types/tag.d.ts
index a390c80..425a995 100644
--- a/viewer/src/@types/tag.d.ts
+++ b/viewer/src/@types/tag.d.ts
@@ -31,4 +31,9 @@ declare namespace Tag {
31 } 31 }
32 type SearchByOperation = { [index: string]: Tag.Search[] }; 32 type SearchByOperation = { [index: string]: Tag.Search[] };
33 type Index = { [index: string]: Node }; 33 type Index = { [index: string]: Node };
34
35 interface Category {
36 tag: string;
37 index: Index;
38 }
34} 39}
diff --git a/viewer/src/assets/scss/global.scss b/viewer/src/assets/scss/global.scss
index bd7a7e7..a8a42a1 100644
--- a/viewer/src/assets/scss/global.scss
+++ b/viewer/src/assets/scss/global.scss
@@ -85,8 +85,8 @@ button svg + span {
85 overflow: auto; 85 overflow: auto;
86} 86}
87.scrollbar::-webkit-scrollbar { 87.scrollbar::-webkit-scrollbar {
88 width: 10px; 88 width: $scrollbar_width;
89 height: 10px; 89 height: $scrollbar_width;
90} 90}
91.scrollbar::-webkit-scrollbar-corner { 91.scrollbar::-webkit-scrollbar-corner {
92 background-color: transparent; 92 background-color: transparent;
diff --git a/viewer/src/assets/scss/theme.scss b/viewer/src/assets/scss/theme.scss
index 26cb355..35983a7 100644
--- a/viewer/src/assets/scss/theme.scss
+++ b/viewer/src/assets/scss/theme.scss
@@ -42,6 +42,8 @@ $radius: 0;
42$loading-background: $palette-800; 42$loading-background: $palette-800;
43$title-color: $palette-200; 43$title-color: $palette-200;
44$title-size: $size-5; 44$title-size: $size-5;
45$subtitle-color: $palette-200;
46$subtitle-size: $size-5;
45$tag-background-color: $palette-800; 47$tag-background-color: $palette-800;
46$button-color: $palette-100; 48$button-color: $palette-100;
47$button-active-color: $palette-100; 49$button-active-color: $palette-100;
@@ -61,11 +63,13 @@ $panel-left-txtcolor: $primary;
61$command-buttons-bgcolor: $palette-700; 63$command-buttons-bgcolor: $palette-700;
62$content-bgcolor: $palette-900; 64$content-bgcolor: $palette-900;
63$scrollbar-color: $palette-300; 65$scrollbar-color: $palette-300;
66$scrollbar_width: 10px;
64$loader-color: $palette-800; 67$loader-color: $palette-800;
65$input-tag-delete-background-color: $palette-700; 68$input-tag-delete-background-color: $palette-700;
66$breadcrumb-margins: 12px; 69$breadcrumb-margins: 12px;
67$breadcrumb-overflow-mask-size: $breadcrumb-margins + 60px; 70$breadcrumb-overflow-mask-size: $breadcrumb-margins + 60px;
68$thumbnail-other-size: 120px; 71$thumbnail-other-size: 120px;
72$proposed-category-bgcolor: $palette-700;
69 73
70// Layout 74// Layout
71 75
diff --git a/viewer/src/components/LdProposition.vue b/viewer/src/components/LdProposition.vue
index 3357777..1a1d387 100644
--- a/viewer/src/components/LdProposition.vue
+++ b/viewer/src/components/LdProposition.vue
@@ -19,8 +19,12 @@
19--> 19-->
20 20
21<template> 21<template>
22 <div> 22 <div class="proposition">
23 <div v-for="proposed in proposedTags" :key="proposed.rawTag" class="proposition"> 23 <h2
24 v-if="showTitle && proposedTags.length"
25 class="subtitle category"
26 >{{title || $t('panelLeft.propositions.other')}}</h2>
27 <div v-for="proposed in proposedTags" :key="proposed.rawTag">
24 <a 28 <a
25 class="operation-btns link" 29 class="operation-btns link"
26 :title="$t('tag-propositions.substraction')" 30 :title="$t('tag-propositions.substraction')"
@@ -54,6 +58,8 @@ import { Operation } from "@/@types/Operation";
54 58
55@Component 59@Component
56export default class LdProposition extends Vue { 60export default class LdProposition extends Vue {
61 @Prop({ type: String, required: true }) readonly title!: string;
62 @Prop({ type: Boolean, required: true }) readonly showTitle!: boolean;
57 @Prop({ type: Array, required: true }) readonly currentTags!: string[]; 63 @Prop({ type: Array, required: true }) readonly currentTags!: string[];
58 @Prop({ required: true }) readonly tagsIndex!: Tag.Index; 64 @Prop({ required: true }) readonly tagsIndex!: Tag.Index;
59 @PropSync("searchFilters", { type: Array, required: true }) model!: Tag.Search[]; 65 @PropSync("searchFilters", { type: Array, required: true }) model!: Tag.Search[];
@@ -69,13 +75,14 @@ export default class LdProposition extends Vue {
69 this.extractDistinctItems(this.model) 75 this.extractDistinctItems(this.model)
70 .flatMap(item => item.tags) 76 .flatMap(item => item.tags)
71 .map(this.rightmost) 77 .map(this.rightmost)
72 .filter(rawTag => !this.model.find(search => search.tag === rawTag)) 78 .filter(rawTag => this.tagsIndex[rawTag] && !this.model.find(search => search.tag === rawTag))
73 .forEach(rawTag => (propositions[rawTag] = (propositions[rawTag] ?? 0) + 1)); 79 .forEach(rawTag => (propositions[rawTag] = (propositions[rawTag] ?? 0) + 1));
74 } else { 80 } else {
75 // Tags count from the current directory 81 // Tags count from the current directory
76 this.currentTags 82 this.currentTags
77 .flatMap(tag => tag.split(":")) 83 .flatMap(tag => tag.split(":"))
78 .map(tag => this.tagsIndex[tag]) 84 .map(tag => this.tagsIndex[tag])
85 .filter(Boolean)
79 .forEach(tagindex => (propositions[tagindex.tag] = tagindex.items.length)); 86 .forEach(tagindex => (propositions[tagindex.tag] = tagindex.items.length));
80 } 87 }
81 88
@@ -105,19 +112,29 @@ export default class LdProposition extends Vue {
105@import "@/assets/scss/theme.scss"; 112@import "@/assets/scss/theme.scss";
106 113
107.proposition { 114.proposition {
108 display: flex; 115 .subtitle {
109 align-items: center; 116 background-color: $proposed-category-bgcolor;
110 padding-right: 7px; 117 width: 100%;
111 .operation-tag { 118 padding: 0 0 6px 0;
112 text-overflow: ellipsis; 119 margin: 0;
113 white-space: nowrap; 120 text-align: center;
114 overflow: hidden; 121 font-variant: small-caps;
115 flex-grow: 1;
116 cursor: pointer;
117 } 122 }
118 .operation-btns { 123 > div {
119 padding: 2px 7px; 124 display: flex;
120 cursor: pointer; 125 align-items: center;
126 padding-right: 7px;
127 .operation-tag {
128 text-overflow: ellipsis;
129 white-space: nowrap;
130 overflow: hidden;
131 flex-grow: 1;
132 cursor: pointer;
133 }
134 .operation-btns {
135 padding: 2px 7px;
136 cursor: pointer;
137 }
121 } 138 }
122} 139}
123</style> 140</style>
diff --git a/viewer/src/locales/en.json b/viewer/src/locales/en.json
index 58adcd2..4209a67 100644
--- a/viewer/src/locales/en.json
+++ b/viewer/src/locales/en.json
@@ -14,5 +14,6 @@
14 "command.search.search": "Search", 14 "command.search.search": "Search",
15 "command.back": "Go back", 15 "command.back": "Go back",
16 "command.parent": "Go to parent directory", 16 "command.parent": "Go to parent directory",
17 "directory.no-results": "Empty directory" 17 "directory.no-results": "Empty directory",
18} 18 "panelLeft.propositions.other": "other filters"
19} \ No newline at end of file
diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts
index a31f3ef..466b509 100644
--- a/viewer/src/services/indexfactory.ts
+++ b/viewer/src/services/indexfactory.ts
@@ -112,4 +112,23 @@ export default class IndexFactory {
112 if (strict) return node.tagfiltered === filter; 112 if (strict) return node.tagfiltered === filter;
113 return node.tagfiltered.includes(filter) 113 return node.tagfiltered.includes(filter)
114 } 114 }
115
116 // ---
117
118 public static generateCategories(tagsIndex: Tag.Index, tags?: Gallery.RawTag[]): Tag.Category[] {
119 if (!tags?.length) return [{ tag: "", index: tagsIndex }];
120
121 const tagsCategories: Tag.Category[] = [];
122 const tagsRemaining = new Map(Object.entries(tagsIndex));
123 tags
124 .map(tag => ({ tag, index: tagsIndex[tag]?.children }))
125 .filter(category => category.index && Object.keys(category.index).length)
126 .forEach(category => {
127 tagsCategories.push(category);
128 tagsRemaining.delete(category.tag);
129 Object.values(category.index).map(node => node.tag).forEach(tag => tagsRemaining.delete(tag));
130 });
131 tagsCategories.push({ tag: "", index: Object.fromEntries(tagsRemaining) });
132 return tagsCategories;
133 }
115} 134}
diff --git a/viewer/src/store/galleryStore.ts b/viewer/src/store/galleryStore.ts
index 4a5de0f..352a266 100644
--- a/viewer/src/store/galleryStore.ts
+++ b/viewer/src/store/galleryStore.ts
@@ -31,6 +31,7 @@ export default class GalleryStore extends VuexModule {
31 config: Gallery.Config | null = null; 31 config: Gallery.Config | null = null;
32 galleryIndex: Gallery.Index | null = null; 32 galleryIndex: Gallery.Index | null = null;
33 tagsIndex: Tag.Index = {}; 33 tagsIndex: Tag.Index = {};
34 tagsCategories: Tag.Category[] = [];
34 currentPath: string = "/"; 35 currentPath: string = "/";
35 currentSearch: Tag.Search[] = []; 36 currentSearch: Tag.Search[] = [];
36 37
@@ -48,6 +49,10 @@ export default class GalleryStore extends VuexModule {
48 this.tagsIndex = Object.freeze(tagsIndex); 49 this.tagsIndex = Object.freeze(tagsIndex);
49 } 50 }
50 51
52 @mutation private setTagsCategories(tagsCategories: Tag.Category[]) {
53 this.tagsCategories = tagsCategories;
54 }
55
51 @mutation setCurrentPath(currentPath: string) { 56 @mutation setCurrentPath(currentPath: string) {
52 this.currentPath = currentPath; 57 this.currentPath = currentPath;
53 } 58 }
@@ -89,7 +94,8 @@ export default class GalleryStore extends VuexModule {
89 return fetch(`${process.env.VUE_APP_DATA_URL}${root}index.json`, { cache: "no-cache" }) 94 return fetch(`${process.env.VUE_APP_DATA_URL}${root}index.json`, { cache: "no-cache" })
90 .then(response => response.json()) 95 .then(response => response.json())
91 .then(this.setGalleryIndex) 96 .then(this.setGalleryIndex)