diff options
Diffstat (limited to 'viewer/src')
-rw-r--r-- | viewer/src/@types/gallery.d.ts | 2 | ||||
-rw-r--r-- | viewer/src/@types/splashscreen.d.ts | 25 | ||||
-rw-r--r-- | viewer/src/locales/en.json | 2 | ||||
-rw-r--r-- | viewer/src/store/uiStore.ts | 29 | ||||
-rw-r--r-- | viewer/src/views/MainLayout.vue | 46 | ||||
-rw-r--r-- | viewer/src/views/SplashScreen.vue | 72 |
6 files changed, 158 insertions, 18 deletions
diff --git a/viewer/src/@types/gallery.d.ts b/viewer/src/@types/gallery.d.ts index d9e7534..9011f19 100644 --- a/viewer/src/@types/gallery.d.ts +++ b/viewer/src/@types/gallery.d.ts | |||
@@ -18,6 +18,7 @@ | |||
18 | */ | 18 | */ |
19 | 19 | ||
20 | import { ItemType } from "./ItemType"; | 20 | import { ItemType } from "./ItemType"; |
21 | import { SplashScreenConfig } from "./splashscreen"; | ||
21 | 22 | ||
22 | export type ItemSortStr = "title_asc" | "date_asc" | "date_desc"; | 23 | export type ItemSortStr = "title_asc" | "date_asc" | "date_desc"; |
23 | 24 | ||
@@ -26,6 +27,7 @@ export interface Config { | |||
26 | galleryIndex?: string; | 27 | galleryIndex?: string; |
27 | initialItemSort?: ItemSortStr; | 28 | initialItemSort?: ItemSortStr; |
28 | initialTagDisplayLimit?: number; | 29 | initialTagDisplayLimit?: number; |
30 | splashScreen?: SplashScreenConfig; | ||
29 | } | 31 | } |
30 | 32 | ||
31 | export interface Properties { | 33 | export interface Properties { |
diff --git a/viewer/src/@types/splashscreen.d.ts b/viewer/src/@types/splashscreen.d.ts new file mode 100644 index 0000000..bd79f80 --- /dev/null +++ b/viewer/src/@types/splashscreen.d.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | /* ldgallery - A static generator which turns a collection of tagged | ||
2 | -- pictures into a searchable web gallery. | ||
3 | -- | ||
4 | -- Copyright (C) 2019-2021 Guillaume FOUET | ||
5 | -- | ||
6 | -- This program is free software: you can redistribute it and/or modify | ||
7 | -- it under the terms of the GNU Affero General Public License as | ||
8 | -- published by the Free Software Foundation, either version 3 of the | ||
9 | -- License, or (at your option) any later version. | ||
10 | -- | ||
11 | -- This program is distributed in the hope that it will be useful, | ||
12 | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | -- GNU Affero General Public License for more details. | ||
15 | -- | ||
16 | -- You should have received a copy of the GNU Affero General Public License | ||
17 | -- along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | export interface SplashScreenConfig { | ||
21 | resource?: string; | ||
22 | dontshowagainUID?: string; | ||
23 | buttonAcknowledgeLabel?: string; | ||
24 | style?: any; | ||
25 | } | ||
diff --git a/viewer/src/locales/en.json b/viewer/src/locales/en.json index b21262a..d0a7a26 100644 --- a/viewer/src/locales/en.json +++ b/viewer/src/locales/en.json | |||
@@ -14,6 +14,8 @@ | |||
14 | "panelLeft.propositions": "Related filters", | 14 | "panelLeft.propositions": "Related filters", |
15 | "panelLeft.propositions.other": "other filters", | 15 | "panelLeft.propositions.other": "other filters", |
16 | "search.no-result-fmt": "No result<br>({0} match in other folders) | No result<br>({0} matches in other folders)", | 16 | "search.no-result-fmt": "No result<br>({0} match in other folders) | No result<br>({0} matches in other folders)", |
17 | "snack.retry": "Retry", | ||
18 | "splashScreen.button.acknowledge": "Acknowledge", | ||
17 | "tag-propositions.addition": "Include all items with this tag", | 19 | "tag-propositions.addition": "Include all items with this tag", |
18 | "tag-propositions.intersection": "Search for this tag", | 20 | "tag-propositions.intersection": "Search for this tag", |
19 | "tag-propositions.item-count": "Item count", | 21 | "tag-propositions.item-count": "Item count", |
diff --git a/viewer/src/store/uiStore.ts b/viewer/src/store/uiStore.ts index f5bb898..2c45136 100644 --- a/viewer/src/store/uiStore.ts +++ b/viewer/src/store/uiStore.ts | |||
@@ -18,6 +18,7 @@ | |||
18 | */ | 18 | */ |
19 | 19 | ||
20 | import { Config } from "@/@types/gallery"; | 20 | import { Config } from "@/@types/gallery"; |
21 | import { SplashScreenConfig } from "@/@types/splashscreen"; | ||
21 | import ItemComparators, { ItemSort } from "@/services/itemComparators"; | 22 | import ItemComparators, { ItemSort } from "@/services/itemComparators"; |
22 | import { action, createModule, mutation } from "vuex-class-component"; | 23 | import { action, createModule, mutation } from "vuex-class-component"; |
23 | 24 | ||
@@ -26,12 +27,17 @@ const VuexModule = createModule({ | |||
26 | strict: true, | 27 | strict: true, |
27 | }); | 28 | }); |
28 | 29 | ||
30 | const STORAGE_SPLASHSCREEN_VALIDATION = "splashScreenValidation"; | ||
31 | |||
29 | export default class UIStore extends VuexModule { | 32 | export default class UIStore extends VuexModule { |
30 | fullscreen: boolean = false; | 33 | fullscreen: boolean = false; |
31 | fullWidth: boolean = window.innerWidth < Number(process.env.VUE_APP_FULLWIDTH_LIMIT); | 34 | fullWidth: boolean = window.innerWidth < Number(process.env.VUE_APP_FULLWIDTH_LIMIT); |
32 | searchMode: boolean = false; | 35 | searchMode: boolean = false; |
33 | sort: ItemSort = ItemComparators.DEFAULT; | 36 | sort: ItemSort = ItemComparators.DEFAULT; |
34 | 37 | ||
38 | splashScreenConfig: SplashScreenConfig | null = null; | ||
39 | splashScreenEnabled: boolean = false; | ||
40 | |||
35 | // --- | 41 | // --- |
36 | 42 | ||
37 | @mutation toggleFullscreen(value?: boolean) { | 43 | @mutation toggleFullscreen(value?: boolean) { |
@@ -50,11 +56,34 @@ export default class UIStore extends VuexModule { | |||
50 | this.sort = sort; | 56 | this.sort = sort; |
51 | } | 57 | } |
52 | 58 | ||
59 | @mutation setSplashScreenConfig(splashScreenConfig: SplashScreenConfig) { | ||
60 | this.splashScreenConfig = splashScreenConfig; | ||
61 | } | ||
62 | |||
63 | @mutation setSplashScreenEnabled(enabled: boolean) { | ||
64 | this.splashScreenEnabled = enabled; | ||
65 | } | ||
66 | |||
67 | // --- | ||
68 | |||
53 | @action async initFromConfig(config: Config) { | 69 | @action async initFromConfig(config: Config) { |
54 | if (config.initialItemSort) { | 70 | if (config.initialItemSort) { |
55 | const itemSort = ItemComparators.ITEM_SORTS[config.initialItemSort]; | 71 | const itemSort = ItemComparators.ITEM_SORTS[config.initialItemSort]; |
56 | if (itemSort) this.setSort(itemSort); | 72 | if (itemSort) this.setSort(itemSort); |
57 | else throw new Error("Unknown sort type: " + config.initialItemSort); | 73 | else throw new Error("Unknown sort type: " + config.initialItemSort); |
58 | } | 74 | } |
75 | if (config.splashScreen) { | ||
76 | this.setSplashScreenConfig(config.splashScreen); | ||
77 | const uid = config.splashScreen.dontshowagainUID; | ||
78 | this.setSplashScreenEnabled(!uid || localStorage.getItem(STORAGE_SPLASHSCREEN_VALIDATION) !== uid); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | // --- | ||
83 | |||
84 | @action async validateSpashScreen() { | ||
85 | this.setSplashScreenEnabled(false); | ||
86 | const uid = this.splashScreenConfig?.dontshowagainUID; | ||
87 | if (uid) localStorage.setItem(STORAGE_SPLASHSCREEN_VALIDATION, String(uid)); | ||
59 | } | 88 | } |
60 | } | 89 | } |
diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue index c54f183..2347ba7 100644 --- a/viewer/src/views/MainLayout.vue +++ b/viewer/src/views/MainLayout.vue | |||
@@ -22,14 +22,13 @@ | |||
22 | <ld-title :gallery-title="$galleryStore.galleryTitle" :current-item="$galleryStore.currentItem" /> | 22 | <ld-title :gallery-title="$galleryStore.galleryTitle" :current-item="$galleryStore.currentItem" /> |
23 | <PanelTop v-if="isReady" :class="[$style.layout, $style.layoutTop]" /> | 23 | <PanelTop v-if="isReady" :class="[$style.layout, $style.layoutTop]" /> |
24 | <PanelLeft v-if="isReady" :class="[$style.layout, $style.layoutLeft]" /> | 24 | <PanelLeft v-if="isReady" :class="[$style.layout, $style.layoutLeft]" /> |
25 | <router-view | 25 | <b-loading v-if="isLoading" active /> |
26 | v-if="!isLoading" | 26 | <SplashScreen |
27 | ref="content" | 27 | v-else-if="$uiStore.splashScreenEnabled" |
28 | :class="[$style.layout, $style.layoutContent]" | 28 | :class="$style.layout" |
29 | class="scrollbar" | 29 | @validation="$uiStore.validateSpashScreen()" |
30 | tabindex="01" | ||
31 | /> | 30 | /> |
32 | <b-loading :active="isLoading" is-full-page /> | 31 | <router-view v-else ref="content" :class="[$style.layout, $style.layoutContent]" class="scrollbar" tabindex="01" /> |
33 | <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" /> | 32 | <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" /> |
34 | </div> | 33 | </div> |
35 | </template> | 34 | </template> |
@@ -40,18 +39,32 @@ import { Component, Ref, Vue, Watch } from "vue-property-decorator"; | |||
40 | import { Route } from "vue-router"; | 39 | import { Route } from "vue-router"; |
41 | import PanelLeft from "./PanelLeft.vue"; | 40 | import PanelLeft from "./PanelLeft.vue"; |
42 | import PanelTop from "./PanelTop.vue"; | 41 | import PanelTop from "./PanelTop.vue"; |
42 | import SplashScreen from "./SplashScreen.vue"; | ||
43 | 43 | ||
44 | @Component({ | 44 | @Component({ |
45 | components: { PanelLeft, PanelTop }, | 45 | components: { |
46 | PanelLeft, | ||
47 | PanelTop, | ||
48 | SplashScreen, | ||
49 | }, | ||
46 | }) | 50 | }) |
47 | export default class MainLayout extends Vue { | 51 | export default class MainLayout extends Vue { |
48 | @Ref() readonly content!: Vue; | 52 | @Ref() readonly content?: Vue; |
49 | 53 | ||
50 | isLoading: boolean = true; | 54 | isLoading: boolean = true; |
51 | scrollPositions: ScrollPosition = {}; | 55 | scrollPositions: ScrollPosition = {}; |
52 | 56 | ||
53 | get contentDiv() { | 57 | get contentDiv(): HTMLDivElement | null { |
54 | return this.content.$el as HTMLDivElement; | 58 | return (this.content?.$el as HTMLDivElement) ?? null; |
59 | } | ||
60 | |||
61 | get isReady(): boolean { | ||
62 | return ( | ||
63 | !this.$uiStore.splashScreenEnabled && | ||
64 | !this.isLoading && | ||
65 | this.$galleryStore.config !== null && | ||
66 | this.$galleryStore.currentPath !== null | ||
67 | ); | ||
55 | } | 68 | } |
56 | 69 | ||
57 | mounted() { | 70 | mounted() { |
@@ -65,13 +78,14 @@ export default class MainLayout extends Vue { | |||
65 | } | 78 | } |
66 | 79 | ||
67 | moveFocusToContentDiv() { | 80 | moveFocusToContentDiv() { |
68 | setTimeout(() => this.contentDiv.focus()); | 81 | setTimeout(() => this.contentDiv?.focus()); |
69 | } | 82 | } |
70 | 83 | ||
71 | @Watch("$route") | 84 | @Watch("$route") |
72 | routeChanged(newRoute: Route, oldRoute: Route) { | 85 | routeChanged(newRoute: Route, oldRoute: Route) { |
86 | if (!this.contentDiv) return; | ||
73 | this.scrollPositions[oldRoute.path] = this.contentDiv.scrollTop; | 87 | this.scrollPositions[oldRoute.path] = this.contentDiv.scrollTop; |
74 | this.$nextTick(() => (this.contentDiv.scrollTop = this.scrollPositions[newRoute.path])); | 88 | this.$nextTick(() => (this.contentDiv!.scrollTop = this.scrollPositions[newRoute.path])); |
75 | this.moveFocusToContentDiv(); | 89 | this.moveFocusToContentDiv(); |
76 | } | 90 | } |
77 | 91 | ||
@@ -86,14 +100,10 @@ export default class MainLayout extends Vue { | |||
86 | .catch(this.displayError); | 100 | .catch(this.displayError); |