aboutsummaryrefslogtreecommitdiff
path: root/viewer/src
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src')
-rw-r--r--viewer/src/@types/gallery.d.ts2
-rw-r--r--viewer/src/@types/splashscreen.d.ts25
-rw-r--r--viewer/src/locales/en.json2
-rw-r--r--viewer/src/store/uiStore.ts29
-rw-r--r--viewer/src/views/MainLayout.vue46
-rw-r--r--viewer/src/views/SplashScreen.vue68
6 files changed, 154 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
20import { ItemType } from "./ItemType"; 20import { ItemType } from "./ItemType";
21import { SplashScreenConfig } from "./splashscreen";
21 22
22export type ItemSortStr = "title_asc" | "date_asc" | "date_desc"; 23export 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
31export interface Properties { 33export interface Properties {
diff --git a/viewer/src/@types/splashscreen.d.ts b/viewer/src/@types/splashscreen.d.ts
new file mode 100644
index 0000000..4e03fa8
--- /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
20export interface SplashScreenConfig {
21 resource?: string;
22 acknowledgmentKey?: 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..520fcf4 100644
--- a/viewer/src/store/uiStore.ts
+++ b/viewer/src/store/uiStore.ts
@@ -18,6 +18,7 @@
18*/ 18*/
19 19
20import { Config } from "@/@types/gallery"; 20import { Config } from "@/@types/gallery";
21import { SplashScreenConfig } from "@/@types/splashscreen";
21import ItemComparators, { ItemSort } from "@/services/itemComparators"; 22import ItemComparators, { ItemSort } from "@/services/itemComparators";
22import { action, createModule, mutation } from "vuex-class-component"; 23import { 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
30const STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT = "splashScreenAcknowledgment";
31
29export default class UIStore extends VuexModule { 32export 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.acknowledgmentKey;
78 this.setSplashScreenEnabled(!uid || localStorage.getItem(STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT) !== uid);
79 }
80 }
81
82 // ---
83
84 @action async validateSpashScreen() {
85 this.setSplashScreenEnabled(false);
86 const uid = this.splashScreenConfig?.acknowledgmentKey;
87 if (uid) localStorage.setItem(STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT, 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";
40import { Route } from "vue-router"; 39import { Route } from "vue-router";
41import PanelLeft from "./PanelLeft.vue"; 40import PanelLeft from "./PanelLeft.vue";
42import PanelTop from "./PanelTop.vue"; 41import PanelTop from "./PanelTop.vue";
42import 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})
47export default class MainLayout extends Vue { 51export 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 }