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/services/ui/ldFullscreen.ts | 41 +++++++++
viewer/src/services/ui/ldItemResourceUrl.ts | 15 ++++
viewer/src/services/ui/ldKeepFocus.ts | 34 ++++++++
viewer/src/services/ui/ldKeyboard.ts | 28 ++++++
viewer/src/services/ui/ldSaveScroll.ts | 37 ++++++++
viewer/src/services/ui/ldTitle.ts | 34 ++++++++
viewer/src/services/ui/ldZoom.ts | 128 ++++++++++++++++++++++++++++
7 files changed, 317 insertions(+)
create mode 100644 viewer/src/services/ui/ldFullscreen.ts
create mode 100644 viewer/src/services/ui/ldItemResourceUrl.ts
create mode 100644 viewer/src/services/ui/ldKeepFocus.ts
create mode 100644 viewer/src/services/ui/ldKeyboard.ts
create mode 100644 viewer/src/services/ui/ldSaveScroll.ts
create mode 100644 viewer/src/services/ui/ldTitle.ts
create mode 100644 viewer/src/services/ui/ldZoom.ts
(limited to 'viewer/src/services/ui')
diff --git a/viewer/src/services/ui/ldFullscreen.ts b/viewer/src/services/ui/ldFullscreen.ts
new file mode 100644
index 0000000..80e755d
--- /dev/null
+++ b/viewer/src/services/ui/ldFullscreen.ts
@@ -0,0 +1,41 @@
+/* ldgallery - A static generator which turns a collection of tagged
+-- pictures into a searchable web gallery.
+--
+-- 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
+-- 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 { useUiStore } from '@/store/uiStore';
+import { useEventListener } from '@vueuse/core';
+import { watch } from 'vue';
+
+export const useLdFullscreen = () => {
+ const uiStore = useUiStore();
+
+ useEventListener(document, 'fullscreenchange', onFullscreenChange);
+
+ function onFullscreenChange() {
+ uiStore.toggleFullscreen(isFullscreenActive());
+ }
+
+ function isFullscreenActive(): boolean {
+ return Boolean(document.fullscreenElement);
+ }
+
+ watch(() => uiStore.fullscreen, (fullscreen) => {
+ if (fullscreen && !isFullscreenActive()) document.body.requestFullscreen();
+ else if (isFullscreenActive()) document.exitFullscreen();
+ });
+};
diff --git a/viewer/src/services/ui/ldItemResourceUrl.ts b/viewer/src/services/ui/ldItemResourceUrl.ts
new file mode 100644
index 0000000..7db7ab9
--- /dev/null
+++ b/viewer/src/services/ui/ldItemResourceUrl.ts
@@ -0,0 +1,15 @@
+import { Item } from '@/@types/gallery';
+import { useGalleryStore } from '@/store/galleryStore';
+import { computed } from 'vue';
+import { isDownloadableItem } from '../itemGuards';
+
+export const useItemResource = (item: Item) => {
+ const galleryStore = useGalleryStore();
+ const itemResourceUrl = computed(() => isDownloadableItem(item) ? galleryStore.resourceRoot + item.properties.resource : '');
+ const thumbnailResourceUrl = computed(() => item.thumbnail ? galleryStore.resourceRoot + item.thumbnail.resource : '');
+
+ return {
+ itemResourceUrl,
+ thumbnailResourceUrl,
+ };
+};
diff --git a/viewer/src/services/ui/ldKeepFocus.ts b/viewer/src/services/ui/ldKeepFocus.ts
new file mode 100644
index 0000000..ce9cbc8
--- /dev/null
+++ b/viewer/src/services/ui/ldKeepFocus.ts
@@ -0,0 +1,34 @@
+/* ldgallery - A static generator which turns a collection of tagged
+-- pictures into a searchable web gallery.
+--
+-- 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
+-- 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 { MaybeElementRef, useFocus } from '@vueuse/core';
+import { watch } from 'vue';
+import { useRoute } from 'vue-router';
+
+export const useLdKeepFocus = (element: MaybeElementRef) => {
+ const contentFocus = useFocus(element);
+ const route = useRoute();
+
+ watch(() => route.path, moveFocus);
+
+ function moveFocus() {
+ contentFocus.focused.value = true;
+ }
+ return { moveFocus };
+};
diff --git a/viewer/src/services/ui/ldKeyboard.ts b/viewer/src/services/ui/ldKeyboard.ts
new file mode 100644
index 0000000..8576435
--- /dev/null
+++ b/viewer/src/services/ui/ldKeyboard.ts
@@ -0,0 +1,28 @@
+/* ldgallery - A static generator which turns a collection of tagged
+-- pictures into a searchable web gallery.
+--
+-- 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
+-- 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 { useUiStore } from '@/store/uiStore';
+import { onKeyStroke } from '@vueuse/core';
+
+export const useLdKeyboard = () => {
+ const uiStore = useUiStore();
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
+ onKeyStroke('Escape', () => uiStore.toggleFullscreen(false));
+};
diff --git a/viewer/src/services/ui/ldSaveScroll.ts b/viewer/src/services/ui/ldSaveScroll.ts
new file mode 100644
index 0000000..34e277b
--- /dev/null
+++ b/viewer/src/services/ui/ldSaveScroll.ts
@@ -0,0 +1,37 @@
+/* ldgallery - A static generator which turns a collection of tagged
+-- pictures into a searchable web gallery.
+--
+-- 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
+-- 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 { MaybeElementRef, unrefElement } from '@vueuse/core';
+import { nextTick, watch } from 'vue';
+import { useRoute } from 'vue-router';
+
+type ScrollPosition = Record;
+
+export const useLdSaveScroll = (element: MaybeElementRef) => {
+ const scrollPositions: ScrollPosition = {};
+ const route = useRoute();
+
+ watch(() => decodeURIComponent(route.path), (newRoute, oldRoute) => {
+ const el = unrefElement(element);
+ if (!el) return;
+
+ scrollPositions[oldRoute] = el.scrollTop / el.scrollHeight;
+ nextTick(() => (el.scrollTop = scrollPositions[newRoute] * el.scrollHeight));
+ });
+};
diff --git a/viewer/src/services/ui/ldTitle.ts b/viewer/src/services/ui/ldTitle.ts
new file mode 100644
index 0000000..df80140
--- /dev/null
+++ b/viewer/src/services/ui/ldTitle.ts
@@ -0,0 +1,34 @@
+/* ldgallery - A static generator which turns a collection of tagged
+-- pictures into a searchable web gallery.
+--
+-- 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
+-- 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 { useGalleryStore } from '@/store/galleryStore';
+import { useTitle } from '@vueuse/core';
+import { computed } from 'vue';
+
+export const useLdTitle = () => {
+ const galleryStore = useGalleryStore();
+
+ const title = computed(() => {
+ const { currentItem, galleryTitle } = galleryStore;
+ return currentItem?.title
+ ? `${currentItem.title} • ${galleryTitle}`
+ : galleryTitle;
+ });
+ useTitle(title);
+};
diff --git a/viewer/src/services/ui/ldZoom.ts b/viewer/src/services/ui/ldZoom.ts
new file mode 100644
index 0000000..9f77dea
--- /dev/null
+++ b/viewer/src/services/ui/ldZoom.ts
@@ -0,0 +1,128 @@
+/* ldgallery - A static generator which turns a collection of tagged
+-- pictures into a searchable web gallery.
+--
+-- Copyright (C) 2020 Pacien TRAN-GIRARD
+--
+-- 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 { PictureProperties, Resolution } from '@/@types/gallery';
+import { importHammer } from '@/plugins/asyncLib';
+import { CSSProperties, Ref } from 'vue';
+
+/**
+ * Mousewheel and pinch zoom handler.
+ */
+export const useLdZoom = (
+ imageStyle: Ref,
+ containerElement: HTMLDivElement,
+ imageElement: HTMLImageElement,
+ pictureProperties: PictureProperties,
+ maxScaleFactor = 10,
+ scrollZoomSpeed: number = 1 / 7,
+) => {
+ let scaleFactor = 0.0;
+
+ /**
+ * Register event listeners.
+ */
+ updateImageScale(scaleFactor);
+
+ new ResizeObserver(() => {
+ updateImageScale(scaleFactor);
+ }).observe(containerElement);
+
+ containerElement.addEventListener('wheel', wheelEvent => {
+ wheelEvent.preventDefault();
+ const newScaleFactor = scaleFactor - Math.sign(wheelEvent.deltaY) * (scrollZoomSpeed * scaleFactor);
+ zoom(wheelEvent.offsetX, wheelEvent.offsetY, newScaleFactor);
+ });
+
+ importHammer().then(() => {
+ const pinchListener = new Hammer(containerElement);
+ pinchListener.get('pinch').set({ enable: true });
+ installPinchHandler(pinchListener);
+ });
+
+ return { imageStyle };
+
+ // ---
+
+ function installPinchHandler(pinchListener: HammerManager) {
+ let startScaleFactor = 0.0;
+
+ pinchListener.on('pinchstart', () => {
+ startScaleFactor = scaleFactor;
+ });
+
+ pinchListener.on('pinchmove', (pinchEvent: HammerInput) => {
+ const focusX = pinchEvent.center.x + containerElement.scrollLeft;
+ const focusY = pinchEvent.center.y + containerElement.scrollTop;
+ const scaleFactor = pinchEvent.scale * startScaleFactor;
+ zoom(focusX, focusY, scaleFactor);
+ });
+ }
+
+ /**
+ * Returns the picture resolution as it should be displayed.
+ */
+ function getDisplayResolution(): Resolution {
+ return {
+ width: pictureProperties.resolution.width * scaleFactor,
+ height: pictureProperties.resolution.height * scaleFactor,
+ };
+ }
+
+ /**
+ * Applies scaling to the DOM image element.
+ * To call after internal intermediate computations because DOM properties aren't stable.
+ */
+ function resizeImageElement() {
+ const imageDim = getDisplayResolution();
+ imageElement.width = imageDim.width;
+ imageElement.height = imageDim.height;
+ }
+
+ /**
+ * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise.
+ * It's depressingly hard to do in pure CSS…
+ */
+ function recenterImageElement() {
+ const imageDim = getDisplayResolution();
+ const marginLeft = Math.max((containerElement.clientWidth - imageDim.width) / 2, 0);
+ const marginTop = Math.max((containerElement.clientHeight - imageDim.height) / 2, 0);
+ imageStyle.value.marginLeft = `${marginLeft}px`;
+ imageStyle.value.marginTop = `${marginTop}px`;
+ }
+
+ function zoom(focusX: number, focusY: number, scaleFactor: number) {
+ const imageDim = getDisplayResolution();
+ const ratioX = focusX / imageDim.width;
+ const ratioY = focusY / imageDim.height;
+ updateImageScale(Math.min(scaleFactor, maxScaleFactor));
+
+ const newImageDim = getDisplayResolution();
+ containerElement.scrollLeft -= focusX - ratioX * newImageDim.width;
+ containerElement.scrollTop -= focusY - ratioY * newImageDim.height;
+ }
+
+ function updateImageScale(newScaleFactor: number) {
+ const horizontalFillRatio = containerElement.clientWidth / pictureProperties.resolution.width;
+ const verticalFillRatio = containerElement.clientHeight / pictureProperties.resolution.height;
+ const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0);
+ scaleFactor = Math.max(newScaleFactor, minScaleFactor);
+ resizeImageElement();
+ recenterImageElement();
+ }
+};
--
cgit v1.2.3