From 5e179a35b23282c4613582755de06a91f6991309 Mon Sep 17 00:00:00 2001
From: zeroinformatique
Date: Sun, 18 Oct 2020 00:22:04 +0200
Subject: viewer: accessibility: set focus on the main area (#273)

github: resolves #177---
 viewer/src/views/MainLayout.vue | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

(limited to 'viewer/src/views/MainLayout.vue')

diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index 6ef9a3b..9cd518b 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -22,7 +22,7 @@
     <ld-title :gallery-title="$galleryStore.galleryTitle" :current-item="$galleryStore.currentItem" />
     <panel-top v-if="isReady" class="layout layout-top" />
     <panel-left v-if="isReady" class="layout layout-left" />
-    <router-view v-if="!isLoading" ref="content" class="layout layout-content scrollbar" />
+    <router-view v-if="!isLoading" ref="content" class="layout layout-content scrollbar" tabindex="01" />
     <b-loading :active="isLoading" is-full-page />
     <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" />
   </div>
@@ -43,6 +43,10 @@ export default class MainLayout extends Vue {
   isLoading: boolean = true;
   scrollPositions: ScrollPosition = {};
 
+  get contentDiv() {
+    return this.content.$el as HTMLDivElement;
+  }
+
   mounted() {
     history.replaceState({ ldgallery: "ENTRYPOINT" }, "");
     this.fetchGalleryItems();
@@ -53,11 +57,15 @@ export default class MainLayout extends Vue {
     document.body.removeEventListener("fullscreenchange", this.onFullscreenChange);
   }
 
+  moveFocusToContentDiv() {
+    setTimeout(() => this.contentDiv.focus());
+  }
+
   @Watch("$route")
   routeChanged(newRoute: Route, oldRoute: Route) {
-    const el = this.content.$el;
-    this.scrollPositions[oldRoute.path] = el.scrollTop;
-    this.$nextTick(() => (el.scrollTop = this.scrollPositions[newRoute.path]));
+    this.scrollPositions[oldRoute.path] = this.contentDiv.scrollTop;
+    this.$nextTick(() => (this.contentDiv.scrollTop = this.scrollPositions[newRoute.path]));
+    this.moveFocusToContentDiv();
   }
 
   fetchGalleryItems() {
@@ -66,6 +74,7 @@ export default class MainLayout extends Vue {
       .fetchConfig()
       .then(this.$uiStore.initFromConfig)
       .then(this.$galleryStore.fetchGalleryItems)
+      .then(this.moveFocusToContentDiv)
       .finally(() => (this.isLoading = false))
       .catch(this.displayError);
   }
@@ -130,6 +139,9 @@ html {
     left: var(--layout-left);
     z-index: 3;
     overflow-x: hidden;
+    &:focus {
+      outline: none;
+    }
   }
 }
 .fullscreen {
-- 
cgit v1.2.3


From 9165cc1efcf7791f78b61b2c51a9de651b1b09aa Mon Sep 17 00:00:00 2001
From: Zero~Informatique
Date: Fri, 2 Jul 2021 22:53:16 +0200
Subject: viewer: types normalization - gallery.d.ts

GitHub: closes #301
---
 viewer/src/views/MainLayout.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'viewer/src/views/MainLayout.vue')

diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index 9cd518b..edca4bf 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -29,10 +29,10 @@
 </template>
 
 <script lang="ts">
-import { Component, Vue, Ref, Watch } from "vue-property-decorator";
+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 { Route } from "vue-router";
 
 @Component({
   components: { PanelLeft, PanelTop },
-- 
cgit v1.2.3


From 1f0377c1b4c2959c73fe4e368673f057ef369917 Mon Sep 17 00:00:00 2001
From: Zero~Informatique
Date: Fri, 2 Jul 2021 23:04:52 +0200
Subject: viewer: types normalization - scrollposition.d.ts

GitHub: closes #301
---
 viewer/src/views/MainLayout.vue | 1 +
 1 file changed, 1 insertion(+)

(limited to 'viewer/src/views/MainLayout.vue')

diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index edca4bf..13b17b4 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -29,6 +29,7 @@
 </template>
 
 <script lang="ts">
+import { ScrollPosition } from "@/@types/scrollposition";
 import { Component, Ref, Vue, Watch } from "vue-property-decorator";
 import { Route } from "vue-router";
 import PanelLeft from "./PanelLeft.vue";
-- 
cgit v1.2.3


From de6827776283d06eab889790a238979fb8156a3e Mon Sep 17 00:00:00 2001
From: Zero~Informatique
Date: Sat, 3 Jul 2021 01:36:32 +0200
Subject: viewer: document.fullscreen is deprecated, use
 document.fullscreenElement instead

---
 viewer/src/views/MainLayout.vue | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

(limited to 'viewer/src/views/MainLayout.vue')

diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index 13b17b4..eee813f 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -95,14 +95,19 @@ export default class MainLayout extends Vue {
     });
   }
 
+  isFullscreenActive(): boolean {
+    return Boolean(document.fullscreenElement);
+  }
+
   @Watch("$uiStore.fullscreen")
   applyFullscreen(fullscreen: boolean) {
-    if (fullscreen && !document.fullscreen) document.body.requestFullscreen();
-    else if (document.fullscreen) document.exitFullscreen();
+    const isFullscreenActive = this.isFullscreenActive();
+    if (fullscreen && !isFullscreenActive) document.body.requestFullscreen();
+    else if (isFullscreenActive) document.exitFullscreen();
   }
 
   onFullscreenChange() {
-    this.$uiStore.toggleFullscreen(document.fullscreen);
+    this.$uiStore.toggleFullscreen(this.isFullscreenActive());
   }
 }
 </script>
-- 
cgit v1.2.3


From cddf5d5c42795388dbd9058268160f1e867d64f5 Mon Sep 17 00:00:00 2001
From: Zero~Informatique
Date: Sat, 3 Jul 2021 05:06:44 +0200
Subject: viewer: use CSS module (MainLayout)

Extracted from 0f4a1d1 (GH PR #304).
---
 viewer/src/views/MainLayout.vue | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)

(limited to 'viewer/src/views/MainLayout.vue')

diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index eee813f..c54f183 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -18,11 +18,17 @@
 -->
 
 <template>
-  <div :class="{ fullscreen: $uiStore.fullscreen, fullwidth: $uiStore.fullWidth }">
+  <div :class="{ [$style.fullscreen]: $uiStore.fullscreen, [$style.fullwidth]: $uiStore.fullWidth }">
     <ld-title :gallery-title="$galleryStore.galleryTitle" :current-item="$galleryStore.currentItem" />
-    <panel-top v-if="isReady" class="layout layout-top" />
-    <panel-left v-if="isReady" class="layout layout-left" />
-    <router-view v-if="!isLoading" ref="content" class="layout layout-content scrollbar" tabindex="01" />
+    <PanelTop v-if="isReady" :class="[$style.layout, $style.layoutTop]" />
+    <PanelLeft v-if="isReady" :class="[$style.layout, $style.layoutLeft]" />
+    <router-view
+      v-if="!isLoading"
+      ref="content"
+      :class="[$style.layout, $style.layoutContent]"
+      class="scrollbar"
+      tabindex="01"
+    />
     <b-loading :active="isLoading" is-full-page />
     <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" />
   </div>
@@ -112,11 +118,11 @@ export default class MainLayout extends Vue {
 }
 </script>
 
-<style lang="scss">
+<style lang="scss" module>
 @import "~@/assets/scss/theme.scss";
 
-body,
-html {
+:global(body),
+:global(html) {
   height: 100%;
   overflow: hidden;
   touch-action: none;
@@ -131,16 +137,16 @@ html {
   bottom: 0;
   left: 0;
   right: 0;
-  &.layout-top {
+  &.layoutTop {
     height: $layout-top;
     z-index: 1;
   }
-  &.layout-left {
+  &.layoutLeft {
     top: $layout-top;
     width: $layout-left;
     z-index: 2;
   }
-  &.layout-content {
+  &.layoutContent {
     top: var(--layout-top);
     left: var(--layout-left);
     z-index: 3;
@@ -164,17 +170,16 @@ html {
 }
 
 .layout {
-  &.layout-top {
+  &.layoutTop {
     background-color: $panel-top-bgcolor;
     color: $panel-top-txtcolor;
   }
-  &.layout-left {
+  &.layoutLeft {
     background-color: $panel-left-bgcolor;
     color: $panel-left-txtcolor;
   }
-  &.layout-content {
+  &.layoutContent {
     background-color: $content-bgcolor;
   }
 }
-// =====
 </style>
-- 
cgit v1.2.3


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/src/views/MainLayout.vue | 46 +++++++++++++++++++++++++----------------
 1 file changed, 28 insertions(+), 18 deletions(-)

(limited to 'viewer/src/views/MainLayout.vue')

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 @@
     <ld-title :gallery-title="$galleryStore.galleryTitle" :current-item="$galleryStore.currentItem" />
     <PanelTop v-if="isReady" :class="[$style.layout, $style.layoutTop]" />
     <PanelLeft v-if="isReady" :class="[$style.layout, $style.layoutLeft]" />
-    <router-view
-      v-if="!isLoading"
-      ref="content"
-      :class="[$style.layout, $style.layoutContent]"
-      class="scrollbar"
-      tabindex="01"
+    <b-loading v-if="isLoading" active />
+    <SplashScreen
+      v-else-if="$uiStore.splashScreenEnabled"
+      :class="$style.layout"
+      @validation="$uiStore.validateSpashScreen()"
     />
-    <b-loading :active="isLoading" is-full-page />
+    <router-view v-else ref="content" :class="[$style.layout, $style.layoutContent]" class="scrollbar" tabindex="01" />
     <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" />
   </div>
 </template>
@@ -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,
-- 
cgit v1.2.3


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/views/MainLayout.vue | 173 ++++++++++++++++------------------------
 1 file changed, 68 insertions(+), 105 deletions(-)

(limited to 'viewer/src/views/MainLayout.vue')

diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index 2347ba7..9c84116 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -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
@@ -18,131 +18,94 @@
 -->
 
 <template>
-  <div :class="{ [$style.fullscreen]: $uiStore.fullscreen, [$style.fullwidth]: $uiStore.fullWidth }">
-    <ld-title :gallery-title="$galleryStore.galleryTitle" :current-item="$galleryStore.currentItem" />
-    <PanelTop v-if="isReady" :class="[$style.layout, $style.layoutTop]" />
-    <PanelLeft v-if="isReady" :class="[$style.layout, $style.layoutLeft]" />
-    <b-loading v-if="isLoading" active />
+  <div :class="{ [$style.fullscreen]: uiStore.fullscreen, [$style.fullwidth]: uiStore.fullWidth }">
+    <LdLoading v-if="isLoading" />
     <SplashScreen
-      v-else-if="$uiStore.splashScreenEnabled"
-      :class="$style.layout"
-      @validation="$uiStore.validateSpashScreen()"
+      v-else-if="uiStore.splashScreenEnabled"
+      @validation="validateSpashScreen"
     />
-    <router-view v-else ref="content" :class="[$style.layout, $style.layoutContent]" class="scrollbar" tabindex="01" />
-    <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" />
+    <template v-else-if="galleryStore.config && galleryStore.galleryIndex">
+      <LayoutTop :class="[$style.layout, $style.layoutTop]" />
+      <LayoutLeft :class="[$style.layout, $style.layoutLeft]" />
+      <router-view
+        ref="content"
+        :class="[$style.layout, $style.layoutContent]"
+        class="scrollbar"
+        tabindex="2"
+      />
+    </template>
   </div>
 </template>
 
-<script lang="ts">
-import { ScrollPosition } from "@/@types/scrollposition";
-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,
-    SplashScreen,
-  },
-})
-export default class MainLayout extends Vue {
-  @Ref() readonly content?: Vue;
-
-  isLoading: boolean = true;
-  scrollPositions: ScrollPosition = {};
-
-  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() {
-    history.replaceState({ ldgallery: "ENTRYPOINT" }, "");
-    this.fetchGalleryItems();
-    document.body.addEventListener("fullscreenchange", this.onFullscreenChange);
-  }
-
-  destroyed() {
-    document.body.removeEventListener("fullscreenchange", this.onFullscreenChange);
-  }
-
-  moveFocusToContentDiv() {
-    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.moveFocusToContentDiv();
-  }
-
-  fetchGalleryItems() {
-    this.isLoading = true;
-    this.$galleryStore
-      .fetchConfig()
-      .then(this.$uiStore.initFromConfig)
-      .then(this.$galleryStore.fetchGalleryItems)
-      .then(this.moveFocusToContentDiv)
-      .finally(() => (this.isLoading = false))
-      .catch(this.displayError);
-  }
-
-  displayError(reason: any) {
-    this.$buefy.snackbar.open({
-      message: `${reason}`,
-      actionText: this.$t("snack.retry"),
-      position: "is-top",
-      type: "is-danger",
-      indefinite: true,
-      onAction: this.fetchGalleryItems,
-    });
-  }
-
-  isFullscreenActive(): boolean {
-    return Boolean(document.fullscreenElement);
-  }
+<script setup lang="ts">
+import LdLoading from '@/components/LdLoading.vue';
+import { useLdKeepFocus } from '@/services/ui/ldKeepFocus';
+import { useLdSaveScroll } from '@/services/ui/ldSaveScroll';
+import { useGalleryStore } from '@/store/galleryStore';
+import { useUiStore } from '@/store/uiStore';
+import { VueInstance } from '@vueuse/core';
+import { createToast } from 'mosha-vue-toastify';
+import { nextTick, ref } from 'vue';
+import LayoutLeft from './layout/left/LayoutLeft.vue';
+import LayoutTop from './layout/top/LayoutTop.vue';
+import SplashScreen from './SplashScreen.vue';
+
+const uiStore = useUiStore();
+const galleryStore = useGalleryStore();
+
+const content = ref<VueInstance>();
+const isLoading = ref(true);
+
+useLdSaveScroll(content);
+const { moveFocus } = useLdKeepFocus(content);
+
+history.replaceState({ ldgallery: 'ENTRYPOINT' }, '');
+fetchGalleryItems();
+
+function fetchGalleryItems() {
+  isLoading.value = true;
+  galleryStore
+    .fetchConfig()
+    .then(uiStore.initFromConfig)
+    .then(galleryStore.fetchGalleryItems)
+    .then(moveFocus)
+    .finally(() => (isLoading.value = false))
+    .catch(displayError);
+}
 
-  @Watch("$uiStore.fullscreen")
-  applyFullscreen(fullscreen: boolean) {
-    const isFullscreenActive = this.isFullscreenActive();
-    if (fullscreen && !isFullscreenActive) document.body.requestFullscreen();
-    else if (isFullscreenActive) document.exitFullscreen();
-  }
+function displayError(reason: unknown) {
+  createToast(String(reason), {
+    type: 'danger',
+    position: 'top-center',
+    timeout: 10000,
+    showIcon: true,
+    onClose: () => !isLoading.value && fetchGalleryItems(),
+  });
+}
 
-  onFullscreenChange() {
-    this.$uiStore.toggleFullscreen(this.isFullscreenActive());
-  }
+function validateSpashScreen() {
+  uiStore.validateSpashScreen();
+  nextTick(moveFocus);
 }
 </script>
 
 <style lang="scss" module>
-@import "~@/assets/scss/theme.scss";
+@import "~@/assets/scss/theme";
 
+:root {
+  --layout-top: #{$layout-top};
+  --layout-left: #{$layout-left};
+}
 :global(body),
 :global(html) {
+  font-family: $family-sans-serif;
   height: 100%;
   overflow: hidden;
   touch-action: none;
   background-color: $content-bgcolor;
-  --layout-top: #{$layout-top};
-  --layout-left: #{$layout-left};
 }
 .layout {
   position: fixed;
-  transition: all $transition-flex-expand linear;
   top: 0;
   bottom: 0;
   left: 0;
-- 
cgit v1.2.3


From e704198437fb589ec8c954d12700d2a1f911522c Mon Sep 17 00:00:00 2001
From: Zéro~Informatique
Date: Sat, 3 Sep 2022 04:41:03 +0200
Subject: viewer: refactoring for tag and sort dropdowns

---
 viewer/src/views/MainLayout.vue | 1 +
 1 file changed, 1 insertion(+)

(limited to 'viewer/src/views/MainLayout.vue')

diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index 9c84116..d8b3300 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -103,6 +103,7 @@ function validateSpashScreen() {
   overflow: hidden;
   touch-action: none;
   background-color: $content-bgcolor;
+  margin: 0;
 }
 .layout {
   position: fixed;
-- 
cgit v1.2.3

