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