diff options
author | Zero~Informatique | 2019-12-22 03:50:40 +0100 |
---|---|---|
committer | Zero~Informatique | 2019-12-22 03:50:40 +0100 |
commit | e34be1261d9219e5b2b92ebe271f609f11d55f63 (patch) | |
tree | f9bb705d0b7ec819b48ddfd5a318642ca239aff3 /viewer/src | |
parent | c2b4c5d144db17ebf2dc9de32ba25cc836831ae2 (diff) | |
download | ldgallery-e34be1261d9219e5b2b92ebe271f609f11d55f63.tar.gz |
vewer: Tags indexing and search input
Diffstat (limited to 'viewer/src')
-rw-r--r-- | viewer/src/@types/tag/index.d.ts | 8 | ||||
-rw-r--r-- | viewer/src/components/LdTagInput.vue | 40 | ||||
-rw-r--r-- | viewer/src/locales/en.json | 4 | ||||
-rw-r--r-- | viewer/src/store/galleryStore.ts | 41 | ||||
-rw-r--r-- | viewer/src/store/uiStore.ts | 5 | ||||
-rw-r--r-- | viewer/src/views/Gallery.vue | 6 | ||||
-rw-r--r-- | viewer/src/views/MainLayout.vue | 7 | ||||
-rw-r--r-- | viewer/src/views/PanelLeft.vue | 20 |
8 files changed, 120 insertions, 11 deletions
diff --git a/viewer/src/@types/tag/index.d.ts b/viewer/src/@types/tag/index.d.ts new file mode 100644 index 0000000..6a027d4 --- /dev/null +++ b/viewer/src/@types/tag/index.d.ts | |||
@@ -0,0 +1,8 @@ | |||
1 | declare namespace Tag { | ||
2 | interface Node { | ||
3 | tag: string; | ||
4 | items: Gallery.Item[]; | ||
5 | children: Index; | ||
6 | } | ||
7 | type Index = { [index: string]: Node }; | ||
8 | } \ No newline at end of file | ||
diff --git a/viewer/src/components/LdTagInput.vue b/viewer/src/components/LdTagInput.vue new file mode 100644 index 0000000..4edc1ce --- /dev/null +++ b/viewer/src/components/LdTagInput.vue | |||
@@ -0,0 +1,40 @@ | |||
1 | <template> | ||
2 | <b-taginput | ||
3 | v-model="$uiStore.currentTags" | ||
4 | :placeholder="$t('tagInput.placeholder')" | ||
5 | autocomplete | ||
6 | :data="filteredTags" | ||
7 | field="tag" | ||
8 | type="is-black" | ||
9 | size="is-large" | ||
10 | class="panelTagInput" | ||
11 | @typing="getFilteredTags" | ||
12 | > | ||
13 | <template slot-scope="props">{{props.option.tag}} ({{props.option.items.length}})</template> | ||
14 | <template slot="empty">{{$t('tagInput.nomatch')}}</template> | ||
15 | </b-taginput> | ||
16 | </template> | ||
17 | |||
18 | <script lang="ts"> | ||
19 | import { Component, Vue } from "vue-property-decorator"; | ||
20 | |||
21 | @Component | ||
22 | export default class LdTagInput extends Vue { | ||
23 | filteredTags: Tag.Node[] = []; | ||
24 | |||
25 | getFilteredTags(filter: string) { | ||
26 | const tags = this.$galleryStore.tags; | ||
27 | if (tags && filter) | ||
28 | this.filteredTags = Object.values(tags) | ||
29 | .filter(node => node.tag.includes(filter)) | ||
30 | .sort((a, b) => b.items.length - a.items.length); | ||
31 | else this.filteredTags = []; | ||
32 | } | ||
33 | } | ||
34 | </script> | ||
35 | |||
36 | <style lang="scss"> | ||
37 | .panelTagInput .autocomplete .dropdown-content { | ||
38 | max-height: 300px; | ||
39 | } | ||
40 | </style> | ||
diff --git a/viewer/src/locales/en.json b/viewer/src/locales/en.json index d966983..f24ac1f 100644 --- a/viewer/src/locales/en.json +++ b/viewer/src/locales/en.json | |||
@@ -1,3 +1,5 @@ | |||
1 | { | 1 | { |
2 | "message": "hello i18n !!" | 2 | "tagInput.placeholder": "Tags", |
3 | "panelLeft.title": "Filters", | ||
4 | "tagInput.nomatch": "No match" | ||
3 | } \ No newline at end of file | 5 | } \ No newline at end of file |
diff --git a/viewer/src/store/galleryStore.ts b/viewer/src/store/galleryStore.ts index 4751561..c875837 100644 --- a/viewer/src/store/galleryStore.ts +++ b/viewer/src/store/galleryStore.ts | |||
@@ -7,16 +7,49 @@ const VuexModule = createModule({ | |||
7 | 7 | ||
8 | export default class GalleryStore extends VuexModule { | 8 | export default class GalleryStore extends VuexModule { |
9 | 9 | ||
10 | galleryItems: Gallery.Item | null = null; | 10 | galleryItemsRoot: Gallery.Item | null = null; |
11 | tags: Tag.Index = {}; | ||
11 | 12 | ||
12 | @mutation setGalleryItems(galleryItems: Gallery.Item) { | 13 | // --- |
13 | this.galleryItems = galleryItems; | 14 | |
15 | @mutation setGalleryItemsRoot(galleryItemsRoot: Gallery.Item) { | ||
16 | this.galleryItemsRoot = galleryItemsRoot; | ||
17 | } | ||
18 | |||
19 | @mutation private setTags(tags: Tag.Index) { | ||
20 | this.tags = tags; | ||
14 | } | 21 | } |
15 | 22 | ||
23 | // --- | ||
24 | |||
16 | @action async fetchGalleryItems(url: string) { | 25 | @action async fetchGalleryItems(url: string) { |
17 | fetch(url) | 26 | fetch(url) |
18 | .then(response => response.json()) | 27 | .then(response => response.json()) |
19 | .then(this.setGalleryItems); | 28 | .then(this.setGalleryItemsRoot) |
29 | .then(this.indexTags); | ||
20 | } | 30 | } |
21 | 31 | ||
32 | @action async indexTags() { | ||
33 | let index = {}; | ||
34 | if (this.galleryItemsRoot) | ||
35 | GalleryStore.pushTagsForItem(index, this.galleryItemsRoot); | ||
36 | console.log(index); | ||
37 | this.setTags(index); | ||
38 | } | ||
39 | |||
40 | private static pushTagsForItem(index: Tag.Index, item: Gallery.Item) { | ||
41 | console.log("IndexingTagsFor: ", item.path); | ||
42 | for (const tag of item.tags) { | ||
43 | const parts = tag.split('.'); | ||
44 | let lastPart: string | null = null; | ||
45 | for (const part of parts) { | ||
46 | if (!index[part]) index[part] = { tag: part, items: [], children: {} }; | ||
47 | index[part].items.push(item); | ||
48 | if (lastPart) index[lastPart].children[part] = index[part]; | ||
49 | lastPart = part; | ||
50 | } | ||
51 | } | ||
52 | if (item.properties.type === "directory") | ||
53 | item.properties.items.forEach(item => this.pushTagsForItem(index, item)); | ||
54 | } | ||
22 | } \ No newline at end of file | 55 | } \ No newline at end of file |
diff --git a/viewer/src/store/uiStore.ts b/viewer/src/store/uiStore.ts index c4143a1..e04b507 100644 --- a/viewer/src/store/uiStore.ts +++ b/viewer/src/store/uiStore.ts | |||
@@ -2,12 +2,15 @@ import { createModule, mutation, action } from "vuex-class-component"; | |||
2 | 2 | ||
3 | const VuexModule = createModule({ | 3 | const VuexModule = createModule({ |
4 | namespaced: "uiStore", | 4 | namespaced: "uiStore", |
5 | strict: true | 5 | strict: false |
6 | }) | 6 | }) |
7 | 7 | ||
8 | export default class UIStore extends VuexModule { | 8 | export default class UIStore extends VuexModule { |
9 | 9 | ||
10 | fullscreen: boolean = false; | 10 | fullscreen: boolean = false; |
11 | currentTags: Tag.Node[] = []; | ||
12 | |||
13 | // --- | ||
11 | 14 | ||
12 | @mutation toggleFullscreen() { | 15 | @mutation toggleFullscreen() { |
13 | this.fullscreen = !this.fullscreen; | 16 | this.fullscreen = !this.fullscreen; |
diff --git a/viewer/src/views/Gallery.vue b/viewer/src/views/Gallery.vue index 954903a..2020280 100644 --- a/viewer/src/views/Gallery.vue +++ b/viewer/src/views/Gallery.vue | |||
@@ -13,7 +13,7 @@ import GalleryImage from "./GalleryImage.vue"; | |||
13 | @Component({ | 13 | @Component({ |
14 | components: { GalleryDirectory, GalleryImage }, | 14 | components: { GalleryDirectory, GalleryImage }, |
15 | }) | 15 | }) |
16 | export default class Root extends Vue { | 16 | export default class Gallery extends Vue { |
17 | @Prop(String) readonly pathMatch!: string; | 17 | @Prop(String) readonly pathMatch!: string; |
18 | 18 | ||
19 | get isDirectory(): boolean { | 19 | get isDirectory(): boolean { |
@@ -25,8 +25,8 @@ export default class Root extends Vue { | |||
25 | } | 25 | } |
26 | 26 | ||
27 | get currentItem(): Gallery.Item | null { | 27 | get currentItem(): Gallery.Item | null { |
28 | const galleryItems = this.$galleryStore.galleryItems; | 28 | const galleryItemsRoot = this.$galleryStore.galleryItemsRoot; |
29 | if (galleryItems) return this.searchCurrentItem(galleryItems, this.pathMatch); | 29 | if (galleryItemsRoot) return this.searchCurrentItem(galleryItemsRoot, this.pathMatch); |
30 | return null; | 30 | return null; |
31 | } | 31 | } |
32 | 32 | ||
diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue index 9f3a17b..2afd4b9 100644 --- a/viewer/src/views/MainLayout.vue +++ b/viewer/src/views/MainLayout.vue | |||
@@ -1,7 +1,7 @@ | |||
1 | <template> | 1 | <template> |
2 | <div :class="{fullscreen: $uiStore.fullscreen}"> | 2 | <div :class="{fullscreen: $uiStore.fullscreen}"> |
3 | <div class="layout layout-top">header</div> | 3 | <div class="layout layout-top">header</div> |
4 | <div class="layout layout-left">panel</div> | 4 | <panel-left class="layout layout-left" /> |
5 | <router-view class="layout layout-content" /> | 5 | <router-view class="layout layout-content" /> |
6 | <ld-button-fullscreen /> | 6 | <ld-button-fullscreen /> |
7 | <b-loading :active="isLoading" is-full-page /> | 7 | <b-loading :active="isLoading" is-full-page /> |
@@ -10,8 +10,11 @@ | |||
10 | 10 | ||
11 | <script lang="ts"> | 11 | <script lang="ts"> |
12 | import { Component, Vue } from "vue-property-decorator"; | 12 | import { Component, Vue } from "vue-property-decorator"; |
13 | import PanelLeft from "./PanelLeft.vue"; | ||
13 | 14 | ||
14 | @Component | 15 | @Component({ |
16 | components: { PanelLeft }, | ||
17 | }) | ||
15 | export default class MainLayout extends Vue { | 18 | export default class MainLayout extends Vue { |
16 | isLoading: boolean = false; | 19 | isLoading: boolean = false; |
17 | 20 | ||
diff --git a/viewer/src/views/PanelLeft.vue b/viewer/src/views/PanelLeft.vue new file mode 100644 index 0000000..4b5bce0 --- /dev/null +++ b/viewer/src/views/PanelLeft.vue | |||
@@ -0,0 +1,20 @@ | |||
1 | <template> | ||
2 | <div> | ||
3 | <b-field :label="$t('panelLeft.title')"> | ||
4 | <ld-tag-input /> | ||
5 | </b-field> | ||
6 | </div> | ||
7 | </template> | ||
8 | |||
9 | <script lang="ts"> | ||
10 | import { Component, Vue, Prop } from "vue-property-decorator"; | ||
11 | |||
12 | @Component | ||
13 | export default class PanelLeft extends Vue {} | ||
14 | </script> | ||
15 | |||
16 | <style lang="scss"> | ||
17 | .label { | ||
18 | color: white; | ||
19 | } | ||
20 | </style> | ||