From 928c501dda0c3580e3cb0389efc16fc1dde16b68 Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Sat, 3 Jul 2021 05:06:44 +0200 Subject: viewer: optional user-defined markdown splash screen GitHub: closes #284 --- viewer/ldgallery-viewer.7.md | 47 ++++++++++++++++++++++++ viewer/src/@types/gallery.d.ts | 2 ++ viewer/src/@types/splashscreen.d.ts | 25 +++++++++++++ viewer/src/locales/en.json | 2 ++ viewer/src/store/uiStore.ts | 29 +++++++++++++++ viewer/src/views/MainLayout.vue | 46 ++++++++++++++---------- viewer/src/views/SplashScreen.vue | 72 +++++++++++++++++++++++++++++++++++++ 7 files changed, 205 insertions(+), 18 deletions(-) create mode 100644 viewer/src/@types/splashscreen.d.ts create mode 100644 viewer/src/views/SplashScreen.vue diff --git a/viewer/ldgallery-viewer.7.md b/viewer/ldgallery-viewer.7.md index 96070dc..5012e3b 100644 --- a/viewer/ldgallery-viewer.7.md +++ b/viewer/ldgallery-viewer.7.md @@ -96,6 +96,53 @@ An alternative viewer configuration file located in the viewer's directory can b without the ".json" extension, as a query parameter given before the page anchor; for example, some alternative configuration named "config_2.json" can be loaded with "http://gallery/?config_2#". +splashScreen +: Displays an information notice before opening the gallery (see below). + + +# SPLASH SCREEN CONFIGURATION + +splashScreen.resource +: Absolute or relative path to the information notice. The user is prompted to explicitly acknowledge such notice before being allowed to browse the gallery. + Rich text formatting is possible through the use of the [GitHub Flavoured Markdown syntax][GFM]. + Inline HTML and CSS are also supported. + +splashScreen.dontshowagainUID +: Optional unique ID; when set, the information notice will appear only the first time it is proposed to the user. To display the notice again, change this UID. + When left empty, the notice will appear every time. + +splashScreen.buttonAcknowledgeLabel +: Optional label for the acknowledge button shown below the notice. + *Defaults to "Acknowledge"* + +splashScreen.style +: Optional CSS attributes for the information notice's container. + String or JSON formats are supported. + + [GFM]: https://github.github.com/gfm/ + +# CONFIGURATION EXAMPLE + +Viewer __config.json__: + +```json +{ + "galleryRoot": "./gallery/", + "galleryIndex": "index.json", + "initialItemSort": "date_desc", + "initialTagDisplayLimit": 10, + "splashScreen": { + "resource": "./splashscreen.md", + "dontshowagainUID": "v001", + "buttonAcknowledgeLabel": "I agree", + "style": { + "max-width": "45em", + "font-size": "20px", + "padding-top": "20vh" + } + } +} +``` # PROGRESSIVE WEB APPLICATION 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 @@ */ import { ItemType } from "./ItemType"; +import { SplashScreenConfig } from "./splashscreen"; export type ItemSortStr = "title_asc" | "date_asc" | "date_desc"; @@ -26,6 +27,7 @@ export interface Config { galleryIndex?: string; initialItemSort?: ItemSortStr; initialTagDisplayLimit?: number; + splashScreen?: SplashScreenConfig; } 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 @@ +/* ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019-2021 Guillaume FOUET +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . +*/ + +export interface SplashScreenConfig { + resource?: string; + dontshowagainUID?: string; + buttonAcknowledgeLabel?: string; + style?: any; +} 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 @@ "panelLeft.propositions": "Related filters", "panelLeft.propositions.other": "other filters", "search.no-result-fmt": "No result
({0} match in other folders) | No result
({0} matches in other folders)", + "snack.retry": "Retry", + "splashScreen.button.acknowledge": "Acknowledge", "tag-propositions.addition": "Include all items with this tag", "tag-propositions.intersection": "Search for this tag", "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 @@ */ import { Config } from "@/@types/gallery"; +import { SplashScreenConfig } from "@/@types/splashscreen"; import ItemComparators, { ItemSort } from "@/services/itemComparators"; import { action, createModule, mutation } from "vuex-class-component"; @@ -26,12 +27,17 @@ const VuexModule = createModule({ strict: true, }); +const STORAGE_SPLASHSCREEN_VALIDATION = "splashScreenValidation"; + export default class UIStore extends VuexModule { fullscreen: boolean = false; fullWidth: boolean = window.innerWidth < Number(process.env.VUE_APP_FULLWIDTH_LIMIT); searchMode: boolean = false; sort: ItemSort = ItemComparators.DEFAULT; + splashScreenConfig: SplashScreenConfig | null = null; + splashScreenEnabled: boolean = false; + // --- @mutation toggleFullscreen(value?: boolean) { @@ -50,11 +56,34 @@ export default class UIStore extends VuexModule { this.sort = sort; } + @mutation setSplashScreenConfig(splashScreenConfig: SplashScreenConfig) { + this.splashScreenConfig = splashScreenConfig; + } + + @mutation setSplashScreenEnabled(enabled: boolean) { + this.splashScreenEnabled = enabled; + } + + // --- + @action async initFromConfig(config: Config) { if (config.initialItemSort) { const itemSort = ItemComparators.ITEM_SORTS[config.initialItemSort]; if (itemSort) this.setSort(itemSort); else throw new Error("Unknown sort type: " + config.initialItemSort); } + if (config.splashScreen) { + this.setSplashScreenConfig(config.splashScreen); + const uid = config.splashScreen.dontshowagainUID; + this.setSplashScreenEnabled(!uid || localStorage.getItem(STORAGE_SPLASHSCREEN_VALIDATION) !== uid); + } + } + + // --- + + @action async validateSpashScreen() { + this.setSplashScreenEnabled(false); + const uid = this.splashScreenConfig?.dontshowagainUID; + if (uid) localStorage.setItem(STORAGE_SPLASHSCREEN_VALIDATION, String(uid)); } } 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 @@ - + - + @@ -40,18 +39,32 @@ import { Component, Ref, Vue, Watch } from "vue-property-decorator"; import { Route } from "vue-router"; import PanelLeft from "./PanelLeft.vue"; import PanelTop from "./PanelTop.vue"; +import SplashScreen from "./SplashScreen.vue"; @Component({ - components: { PanelLeft, PanelTop }, + components: { + PanelLeft, + PanelTop, + SplashScreen, + }, }) export default class MainLayout extends Vue { - @Ref() readonly content!: Vue; + @Ref() readonly content?: Vue; isLoading: boolean = true; scrollPositions: ScrollPosition = {}; - get contentDiv() { - return this.content.$el as HTMLDivElement; + get contentDiv(): HTMLDivElement | null { + return (this.content?.$el as HTMLDivElement) ?? null; + } + + get isReady(): boolean { + return ( + !this.$uiStore.splashScreenEnabled && + !this.isLoading && + this.$galleryStore.config !== null && + this.$galleryStore.currentPath !== null + ); } mounted() { @@ -65,13 +78,14 @@ export default class MainLayout extends Vue { } moveFocusToContentDiv() { - setTimeout(() => this.contentDiv.focus()); + setTimeout(() => this.contentDiv?.focus()); } @Watch("$route") routeChanged(newRoute: Route, oldRoute: Route) { + if (!this.contentDiv) return; this.scrollPositions[oldRoute.path] = this.contentDiv.scrollTop; - this.$nextTick(() => (this.contentDiv.scrollTop = this.scrollPositions[newRoute.path])); + this.$nextTick(() => (this.contentDiv!.scrollTop = this.scrollPositions[newRoute.path])); this.moveFocusToContentDiv(); } @@ -86,14 +100,10 @@ export default class MainLayout extends Vue { .catch(this.displayError); } - get isReady() { - return !this.isLoading && this.$galleryStore.config && this.$galleryStore.currentPath !== null; - } - displayError(reason: any) { this.$buefy.snackbar.open({ message: `${reason}`, - actionText: "Retry", + actionText: this.$t("snack.retry"), position: "is-top", type: "is-danger", indefinite: true, diff --git a/viewer/src/views/SplashScreen.vue b/viewer/src/views/SplashScreen.vue new file mode 100644 index 0000000..808567e --- /dev/null +++ b/viewer/src/views/SplashScreen.vue @@ -0,0 +1,72 @@ + + + + + -- cgit v1.2.3 From c28b29b12810548a5927a02ca80714fcce24a61b Mon Sep 17 00:00:00 2001 From: pacien Date: Sun, 4 Jul 2021 18:37:46 +0200 Subject: viewer/SplashScreen: remove useless style class --- viewer/src/views/SplashScreen.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/viewer/src/views/SplashScreen.vue b/viewer/src/views/SplashScreen.vue index 808567e..a2f499a 100644 --- a/viewer/src/views/SplashScreen.vue +++ b/viewer/src/views/SplashScreen.vue @@ -2,7 +2,7 @@
- +
@@ -65,8 +65,4 @@ export default class SplashScreen extends Vue { align-items: center; padding: 32px; } -.buttonAcknowledge { - min-width: 310px; - align-self: center; -} -- cgit v1.2.3 From c83f44cd69a227f873a026c01653ef434b6ae045 Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Mon, 5 Jul 2021 19:10:20 +0200 Subject: viewer: viewer: optional user-defined markdown splash screen Code review changes --- viewer/ldgallery-viewer.7.md | 14 +++++++------- viewer/src/@types/splashscreen.d.ts | 2 +- viewer/src/store/uiStore.ts | 10 +++++----- viewer/src/views/SplashScreen.vue | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/viewer/ldgallery-viewer.7.md b/viewer/ldgallery-viewer.7.md index 5012e3b..1e914ff 100644 --- a/viewer/ldgallery-viewer.7.md +++ b/viewer/ldgallery-viewer.7.md @@ -92,13 +92,13 @@ initialTagDisplayLimit Set to -1 to disable the limit on suggestions. Defaults to 10. +splashScreen +: Displays an information notice before opening the gallery (see below). + An alternative viewer configuration file located in the viewer's directory can be loaded by specifying its name, without the ".json" extension, as a query parameter given before the page anchor; for example, some alternative configuration named "config_2.json" can be loaded with "http://gallery/?config_2#". -splashScreen -: Displays an information notice before opening the gallery (see below). - # SPLASH SCREEN CONFIGURATION @@ -106,9 +106,10 @@ splashScreen.resource : Absolute or relative path to the information notice. The user is prompted to explicitly acknowledge such notice before being allowed to browse the gallery. Rich text formatting is possible through the use of the [GitHub Flavoured Markdown syntax][GFM]. Inline HTML and CSS are also supported. + [GFM]: https://github.github.com/gfm/ -splashScreen.dontshowagainUID -: Optional unique ID; when set, the information notice will appear only the first time it is proposed to the user. To display the notice again, change this UID. +splashScreen.acknowledgmentKey +: Optional key; when set to an arbitrary string, the information notice will appear only the first time it is proposed to the user. Once the notice acknowledged, the key is saved to the device's local storage. To display the notice again, change this key to another value. When left empty, the notice will appear every time. splashScreen.buttonAcknowledgeLabel @@ -119,7 +120,6 @@ splashScreen.style : Optional CSS attributes for the information notice's container. String or JSON formats are supported. - [GFM]: https://github.github.com/gfm/ # CONFIGURATION EXAMPLE @@ -133,7 +133,7 @@ Viewer __config.json__: "initialTagDisplayLimit": 10, "splashScreen": { "resource": "./splashscreen.md", - "dontshowagainUID": "v001", + "acknowledgmentKey": "v001", "buttonAcknowledgeLabel": "I agree", "style": { "max-width": "45em", diff --git a/viewer/src/@types/splashscreen.d.ts b/viewer/src/@types/splashscreen.d.ts index bd79f80..4e03fa8 100644 --- a/viewer/src/@types/splashscreen.d.ts +++ b/viewer/src/@types/splashscreen.d.ts @@ -19,7 +19,7 @@ export interface SplashScreenConfig { resource?: string; - dontshowagainUID?: string; + acknowledgmentKey?: string; buttonAcknowledgeLabel?: string; style?: any; } diff --git a/viewer/src/store/uiStore.ts b/viewer/src/store/uiStore.ts index 2c45136..520fcf4 100644 --- a/viewer/src/store/uiStore.ts +++ b/viewer/src/store/uiStore.ts @@ -27,7 +27,7 @@ const VuexModule = createModule({ strict: true, }); -const STORAGE_SPLASHSCREEN_VALIDATION = "splashScreenValidation"; +const STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT = "splashScreenAcknowledgment"; export default class UIStore extends VuexModule { fullscreen: boolean = false; @@ -74,8 +74,8 @@ export default class UIStore extends VuexModule { } if (config.splashScreen) { this.setSplashScreenConfig(config.splashScreen); - const uid = config.splashScreen.dontshowagainUID; - this.setSplashScreenEnabled(!uid || localStorage.getItem(STORAGE_SPLASHSCREEN_VALIDATION) !== uid); + const uid = config.splashScreen.acknowledgmentKey; + this.setSplashScreenEnabled(!uid || localStorage.getItem(STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT) !== uid); } } @@ -83,7 +83,7 @@ export default class UIStore extends VuexModule { @action async validateSpashScreen() { this.setSplashScreenEnabled(false); - const uid = this.splashScreenConfig?.dontshowagainUID; - if (uid) localStorage.setItem(STORAGE_SPLASHSCREEN_VALIDATION, String(uid)); + const uid = this.splashScreenConfig?.acknowledgmentKey; + if (uid) localStorage.setItem(STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT, String(uid)); } } diff --git a/viewer/src/views/SplashScreen.vue b/viewer/src/views/SplashScreen.vue index a2f499a..dcb845d 100644 --- a/viewer/src/views/SplashScreen.vue +++ b/viewer/src/views/SplashScreen.vue @@ -31,7 +31,7 @@ export default class SplashScreen extends Vue { } fetchMarkdown() { - FetchWithCheck.get(`${process.env.VUE_APP_DATA_URL}${this.config.resource}?${this.config.dontshowagainUID ?? ""}`) + FetchWithCheck.get(`${process.env.VUE_APP_DATA_URL}${this.config.resource}?${this.config.acknowledgmentKey ?? ""}`) .then(response => response.text()) .then(text => (this.markdown = text)) .finally(() => (this.isLoading = false)) -- cgit v1.2.3