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 (limited to 'viewer') 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