From 00510820a2794efcadbc83f7f8b54318fe198ecb Mon Sep 17 00:00:00 2001 From: Zéro~Informatique Date: Tue, 26 Jul 2022 08:44:34 +0200 Subject: viewer: migrate to vue 3, general refactoring and cleanup Non-exhaustive list of fixes and improvements done at the same time: - html default background to grey (avoids white flash during init) - unified links behavior - added more theme variables - removed the flex-expand transition (it wasn't working) and replaced it with a slide - fixed LdLoading not centered on the content - title on removable tags - fixed an issue with encoded URI from vue-router - unified Item resource URLs - removed the iframe for PlainTextViewer (it wasn't working properly) and replaced it with a pre - fixed clear and search buttons tabindex - fixed the information panel bumping up during the fade animation of tag's dropdown - fixed some focus outlines not appearing correctly - moved CSS variables to the :root context - Code cleaning GitHub: closes #217 GitHub: closes #300 GitHub: closes #297 GitHub: closes #105 GitHub: closes #267 GitHub: closes #275 GitHub: closes #228 GitHub: closes #215 GitHub: closes #112 --- viewer/src/store/galleryStore.ts | 201 ++++++++++++++++----------------------- viewer/src/store/index.ts | 46 --------- viewer/src/store/uiStore.ts | 116 +++++++++------------- 3 files changed, 129 insertions(+), 234 deletions(-) delete mode 100644 viewer/src/store/index.ts (limited to 'viewer/src/store') diff --git a/viewer/src/store/galleryStore.ts b/viewer/src/store/galleryStore.ts index e2adf18..7ee660a 100644 --- a/viewer/src/store/galleryStore.ts +++ b/viewer/src/store/galleryStore.ts @@ -1,7 +1,7 @@ /* ldgallery - A static generator which turns a collection of tagged -- pictures into a searchable web gallery. -- --- Copyright (C) 2019-2020 Guillaume FOUET +-- Copyright (C) 2019-2022 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 @@ -17,124 +17,89 @@ -- along with this program. If not, see . */ -import { Config, Index, Item } from "@/@types/gallery"; -import { TagCategory, TagIndex, TagSearch } from "@/@types/tag"; -import IndexFactory from "@/services/indexfactory"; -import Navigation from "@/services/navigation"; -import { action, createModule, mutation } from "vuex-class-component"; +import { Config, Index, Item } from '@/@types/gallery'; +import { TagCategory, TagIndex, TagSearch } from '@/@types/tag'; +import { useIndexFactory } from '@/services/indexFactory'; +import { useNavigation } from '@/services/navigation'; +import { defineStore } from 'pinia'; -const VuexModule = createModule({ - namespaced: "galleryStore", - strict: true, -}); - -export default class GalleryStore extends VuexModule { - config: Config | null = null; - galleryIndex: Index | null = null; - tagsIndex: TagIndex = {}; - tagsCategories: TagCategory[] = []; - currentPath: string | null = null; - currentSearch: TagSearch[] = []; - - // --- - - @mutation private setConfig(config: Config) { - this.config = config; - } - - @mutation setGalleryIndex(galleryIndex: Index) { - this.galleryIndex = Object.freeze(galleryIndex); - } - - @mutation private setTagsIndex(tagsIndex: TagIndex) { - this.tagsIndex = Object.freeze(tagsIndex); - } - - @mutation private setTagsCategories(tagsCategories: TagCategory[]) { - this.tagsCategories = tagsCategories; - } - - @mutation setCurrentPath(currentPath: string) { - this.currentPath = currentPath; - } - - @mutation setCurrentSearch(currentSearch: TagSearch[]) { - this.currentSearch = currentSearch; - } - - // --- - - get currentItemPath(): Item[] { - const root = this.galleryIndex?.tree; - if (root && this.currentPath) return Navigation.searchCurrentItemPath(root, this.currentPath); - return []; - } - - get currentItem(): Item | null { - const path = this.currentItemPath; - return path.length > 0 ? path[path.length - 1] : null; - } +const navigation = useNavigation(); +const indexFactory = useIndexFactory(); - get galleryTitle(): string { - return this.galleryIndex?.properties.galleryTitle ?? "ldgallery"; - } - - get resourceRoot(): string { - return process.env.VUE_APP_DATA_URL + this.config!.galleryRoot; - } - - // --- - - // Fetches the gallery's JSON config - @action async fetchConfig() { - await fetch(`${process.env.VUE_APP_DATA_URL}${GalleryStore.getUrlConfig()}`, { cache: "no-cache" }) - .then(GalleryStore.responseToJson) - .then(this.setConfig); - return this.config!; - } - - // Fetches the gallery's JSON metadata - @action async fetchGalleryItems() { - const root = this.config?.galleryRoot ?? ""; - const index = this.config?.galleryIndex ?? "index.json"; - await fetch(`${process.env.VUE_APP_DATA_URL}${root}${index}`, { cache: "no-cache" }) - .then(GalleryStore.responseToJson) - .then(this.setGalleryIndex) - .then(this.indexTags) - .then(this.indexTagCategories); - return this.galleryIndex!; - } - - // Indexes the gallery - @action async indexTags() { - const root = this.galleryIndex?.tree ?? null; - const index = IndexFactory.generateTags(root); - this.setTagsIndex(index); - return index; - } - - // Indexes the proposed categories - @action async indexTagCategories() { - const categories = IndexFactory.generateCategories(this.tagsIndex, this.galleryIndex?.properties.tagCategories); - this.setTagsCategories(categories); - return categories; - } - - // Searches for tags - @action async search(filters: string[]) { - const results = filters.flatMap(filter => IndexFactory.searchTags(this.tagsIndex, filter, true)); - this.setCurrentSearch(results); - return results; - } - - private static getUrlConfig() { - const search = window.location.search; - if (search.length > 1) return search.substr(1) + ".json"; - return "config.json"; - } +function getUrlConfig() { + const search = window.location.search; + if (search.length > 1) return search.substring(1) + '.json'; + return 'config.json'; +} - private static responseToJson(response: Response) { - if (!response.ok) throw new Error(`${response.status}: ${response.statusText}`); - return response.json(); - } +function responseToJson(response: Response) { + if (!response.ok) throw new Error(`${response.status}: ${response.statusText}`); + return response.json(); } + +export const useGalleryStore = defineStore('gallery', { + state: () => ({ + config: null as Config | null, + galleryIndex: null as Index | null, + tagsIndex: {} as TagIndex, + tagsCategories: [] as TagCategory[], + currentPath: null as string | null, + currentSearch: [] as TagSearch[], + }), + getters: { + currentItemPath(): Item[] { + const root = this.galleryIndex?.tree; + if (root && this.currentPath) return navigation.searchCurrentItemPath(root, this.currentPath); + return []; + }, + currentItem(): Item | null { + const path = this.currentItemPath; + return path.length > 0 ? path[path.length - 1] : null; + }, + galleryTitle(): string { + return this.galleryIndex?.properties.galleryTitle ?? 'ldgallery'; + }, + resourceRoot(): string { + return process.env.VUE_APP_DATA_URL + (this.config?.galleryRoot ?? ''); + }, + }, + actions: { + // Fetches the gallery's JSON config + async fetchConfig() { + await fetch(`${process.env.VUE_APP_DATA_URL}${getUrlConfig()}`, { cache: 'no-cache' }) + .then(responseToJson) + .then(v => (this.config = v)); + return this.config as Config; + }, + // Fetches the gallery's JSON metadata + async fetchGalleryItems() { + const root = this.config?.galleryRoot ?? ''; + const index = this.config?.galleryIndex ?? 'index.json'; + await fetch(`${process.env.VUE_APP_DATA_URL}${root}${index}`, { cache: 'no-cache' }) + .then(responseToJson) + .then(v => (this.galleryIndex = v)) + .then(this.indexTags) + .then(this.indexTagCategories); + return this.galleryIndex; + }, + // Indexes the gallery + async indexTags() { + const root = this.galleryIndex?.tree ?? null; + const index = indexFactory.generateTags(root); + this.tagsIndex = index; + return index; + }, + // Indexes the proposed categories + async indexTagCategories() { + const categories = indexFactory.generateCategories(this.tagsIndex, this.galleryIndex?.properties.tagCategories); + this.tagsCategories = categories; + return categories; + }, + // Searches for tags + async search(filters: string[]) { + const results = filters.flatMap(filter => indexFactory.searchTags(this.tagsIndex, filter, true)); + this.currentSearch = results; + return results; + }, + }, +}); diff --git a/viewer/src/store/index.ts b/viewer/src/store/index.ts deleted file mode 100644 index 1f49589..0000000 --- a/viewer/src/store/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* ldgallery - A static generator which turns a collection of tagged --- pictures into a searchable web gallery. --- --- Copyright (C) 2019-2020 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 . -*/ - -import GalleryStore from "@/store/galleryStore"; -import UIStore from "@/store/uiStore"; -import Vue from "vue"; -import Vuex from "vuex"; -import { createProxy, extractVuexModule } from "vuex-class-component"; - -Vue.use(Vuex); - -const store = new Vuex.Store({ - modules: { - ...extractVuexModule(UIStore), - ...extractVuexModule(GalleryStore), - }, - strict: process.env.NODE_ENV !== "production", -}); - -Vue.use(vue => (vue.prototype.$uiStore = createProxy(store, UIStore))); -Vue.use(vue => (vue.prototype.$galleryStore = createProxy(store, GalleryStore))); - -declare module "vue/types/vue" { - interface Vue { - $uiStore: UIStore; - $galleryStore: GalleryStore; - } -} - -export default store; diff --git a/viewer/src/store/uiStore.ts b/viewer/src/store/uiStore.ts index 520fcf4..df8dacc 100644 --- a/viewer/src/store/uiStore.ts +++ b/viewer/src/store/uiStore.ts @@ -1,7 +1,7 @@ /* ldgallery - A static generator which turns a collection of tagged -- pictures into a searchable web gallery. -- --- Copyright (C) 2019-2020 Guillaume FOUET +-- Copyright (C) 2019-2022 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 @@ -17,73 +17,49 @@ -- along with this program. If not, see . */ -import { Config } from "@/@types/gallery"; -import { SplashScreenConfig } from "@/@types/splashscreen"; -import ItemComparators, { ItemSort } from "@/services/itemComparators"; -import { action, createModule, mutation } from "vuex-class-component"; - -const VuexModule = createModule({ - namespaced: "uiStore", - strict: true, +import { Config } from '@/@types/gallery'; +import { SplashScreenConfig } from '@/@types/splashscreen'; +import { ItemSort, useItemComparator } from '@/services/itemComparator'; +import { useLocalStorage } from '@vueuse/core'; +import { defineStore } from 'pinia'; + +const itemComparator = useItemComparator(); +const splashScreenAcknowledgment = useLocalStorage('splashScreenAcknowledgment', ''); + +export const useUiStore = defineStore('ui', { + state: () => ({ + fullscreen: false, + fullWidth: window.innerWidth < Number(process.env.VUE_APP_FULLWIDTH_LIMIT), + searchMode: false, + sort: itemComparator.DEFAULT as ItemSort, + + splashScreenConfig: null as SplashScreenConfig | null, + splashScreenEnabled: false, + }), + getters: { + }, + actions: { + toggleFullscreen(value?: boolean) { + this.fullscreen = value ?? !this.fullscreen; + }, + toggleFullWidth(value?: boolean) { + this.fullWidth = value ?? !this.fullWidth; + }, + validateSpashScreen() { + this.splashScreenEnabled = false; + splashScreenAcknowledgment.value = this.splashScreenConfig?.acknowledgmentKey ?? ''; + }, + async initFromConfig(config: Config) { + if (config.initialItemSort) { + const itemSort = itemComparator.ITEM_SORTS.find(sort => sort.name === config.initialItemSort); + if (itemSort) this.sort = itemSort; + else throw new Error('Unknown sort type: ' + config.initialItemSort); + } + if (config.splashScreen) { + this.splashScreenConfig = config.splashScreen; + const uid = config.splashScreen.acknowledgmentKey; + this.splashScreenEnabled = !uid || splashScreenAcknowledgment.value !== uid; + } + }, + }, }); - -const STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT = "splashScreenAcknowledgment"; - -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) { - this.fullscreen = value ?? !this.fullscreen; - } - - @mutation toggleFullWidth(value?: boolean) { - this.fullWidth = value ?? !this.fullWidth; - } - - @mutation toggleSearchMode(value?: boolean) { - this.searchMode = value ?? !this.searchMode; - } - - @mutation setSort(sort: ItemSort) { - 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.acknowledgmentKey; - this.setSplashScreenEnabled(!uid || localStorage.getItem(STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT) !== uid); - } - } - - // --- - - @action async validateSpashScreen() { - this.setSplashScreenEnabled(false); - const uid = this.splashScreenConfig?.acknowledgmentKey; - if (uid) localStorage.setItem(STORAGE_SPLASHSCREEN_ACKNOWLEDGMENT, String(uid)); - } -} -- cgit v1.2.3