diff options
Diffstat (limited to 'viewer/src/views/MainLayout.vue')
-rw-r--r-- | viewer/src/views/MainLayout.vue | 173 |
1 files changed, 68 insertions, 105 deletions
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 @@ | |||
1 | <!-- ldgallery - A static generator which turns a collection of tagged | 1 | <!-- ldgallery - A static generator which turns a collection of tagged |
2 | -- pictures into a searchable web gallery. | 2 | -- pictures into a searchable web gallery. |
3 | -- | 3 | -- |
4 | -- Copyright (C) 2019-2020 Guillaume FOUET | 4 | -- Copyright (C) 2019-2022 Guillaume FOUET |
5 | -- | 5 | -- |
6 | -- This program is free software: you can redistribute it and/or modify | 6 | -- This program is free software: you can redistribute it and/or modify |
7 | -- it under the terms of the GNU Affero General Public License as | 7 | -- it under the terms of the GNU Affero General Public License as |
@@ -18,131 +18,94 @@ | |||
18 | --> | 18 | --> |
19 | 19 | ||
20 | <template> | 20 | <template> |
21 | <div :class="{ [$style.fullscreen]: $uiStore.fullscreen, [$style.fullwidth]: $uiStore.fullWidth }"> | 21 | <div :class="{ [$style.fullscreen]: uiStore.fullscreen, [$style.fullwidth]: uiStore.fullWidth }"> |
22 | <ld-title :gallery-title="$galleryStore.galleryTitle" :current-item="$galleryStore.currentItem" /> | 22 | <LdLoading v-if="isLoading" /> |
23 | <PanelTop v-if="isReady" :class="[$style.layout, $style.layoutTop]" /> | ||
24 | <PanelLeft v-if="isReady" :class="[$style.layout, $style.layoutLeft]" /> | ||
25 | <b-loading v-if="isLoading" active /> | ||
26 | <SplashScreen | 23 | <SplashScreen |
27 | v-else-if="$uiStore.splashScreenEnabled" | 24 | v-else-if="uiStore.splashScreenEnabled" |
28 | :class="$style.layout" | 25 | @validation="validateSpashScreen" |
29 | @validation="$uiStore.validateSpashScreen()" | ||
30 | /> | 26 | /> |
31 | <router-view v-else ref="content" :class="[$style.layout, $style.layoutContent]" class="scrollbar" tabindex="01" /> | 27 | <template v-else-if="galleryStore.config && galleryStore.galleryIndex"> |
32 | <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" /> | 28 | <LayoutTop :class="[$style.layout, $style.layoutTop]" /> |
29 | <LayoutLeft :class="[$style.layout, $style.layoutLeft]" /> | ||
30 | <router-view | ||
31 | ref="content" | ||
32 | :class="[$style.layout, $style.layoutContent]" | ||
33 | class="scrollbar" | ||
34 | tabindex="2" | ||
35 | /> | ||
36 | </template> | ||
33 | </div> | 37 | </div> |
34 | </template> | 38 | </template> |
35 | 39 | ||
36 | <script lang="ts"> | 40 | <script setup lang="ts"> |
37 | import { ScrollPosition } from "@/@types/scrollposition"; | 41 | import LdLoading from '@/components/LdLoading.vue'; |
38 | import { Component, Ref, Vue, Watch } from "vue-property-decorator"; | 42 | import { useLdKeepFocus } from '@/services/ui/ldKeepFocus'; |
39 | import { Route } from "vue-router"; | 43 | import { useLdSaveScroll } from '@/services/ui/ldSaveScroll'; |
40 | import PanelLeft from "./PanelLeft.vue"; | 44 | import { useGalleryStore } from '@/store/galleryStore'; |
41 | import PanelTop from "./PanelTop.vue"; | 45 | import { useUiStore } from '@/store/uiStore'; |
42 | import SplashScreen from "./SplashScreen.vue"; | 46 | import { VueInstance } from '@vueuse/core'; |
43 | 47 | import { createToast } from 'mosha-vue-toastify'; | |
44 | @Component({ | 48 | import { nextTick, ref } from 'vue'; |
45 | components: { | 49 | import LayoutLeft from './layout/left/LayoutLeft.vue'; |
46 | PanelLeft, | 50 | import LayoutTop from './layout/top/LayoutTop.vue'; |
47 | PanelTop, | 51 | import SplashScreen from './SplashScreen.vue'; |
48 | SplashScreen, | 52 | |
49 | }, | 53 | const uiStore = useUiStore(); |
50 | }) | 54 | const galleryStore = useGalleryStore(); |
51 | export default class MainLayout extends Vue { | 55 | |
52 | @Ref() readonly content?: Vue; | 56 | const content = ref<VueInstance>(); |
53 | 57 | const isLoading = ref(true); | |
54 | isLoading: boolean = true; | 58 | |
55 | scrollPositions: ScrollPosition = {}; | 59 | useLdSaveScroll(content); |
56 | 60 | const { moveFocus } = useLdKeepFocus(content); | |
57 | get contentDiv(): HTMLDivElement | null { | 61 | |
58 | return (this.content?.$el as HTMLDivElement) ?? null; | 62 | history.replaceState({ ldgallery: 'ENTRYPOINT' }, ''); |
59 | } | 63 | fetchGalleryItems(); |
60 | 64 | ||
61 | get isReady(): boolean { | 65 | function fetchGalleryItems() { |
62 | return ( | 66 | isLoading.value = true; |
63 | !this.$uiStore.splashScreenEnabled && | 67 | galleryStore |
64 | !this.isLoading && | 68 | .fetchConfig() |
65 | this.$galleryStore.config !== null && | 69 | .then(uiStore.initFromConfig) |
66 | this.$galleryStore.currentPath !== null | 70 | .then(galleryStore.fetchGalleryItems) |
67 | ); | 71 | .then(moveFocus) |
68 | } | 72 | .finally(() => (isLoading.value = false)) |
69 | 73 | .catch(displayError); | |
70 | mounted() { | 74 | } |
71 | history.replaceState({ ldgallery: "ENTRYPOINT" }, ""); | ||
72 | this.fetchGalleryItems(); | ||
73 | document.body.addEventListener("fullscreenchange", this.onFullscreenChange); | ||
74 | } | ||
75 | |||
76 | destroyed() { | ||
77 | document.body.removeEventListener("fullscreenchange", this.onFullscreenChange); | ||
78 | } | ||
79 | |||
80 | moveFocusToContentDiv() { | ||
81 | setTimeout(() => this.contentDiv?.focus()); | ||
82 | } | ||
83 | |||
84 | @Watch("$route") | ||
85 | routeChanged(newRoute: Route, oldRoute: Route) { | ||
86 | if (!this.contentDiv) return; | ||
87 | this.scrollPositions[oldRoute.path] = this.contentDiv.scrollTop; | ||
88 | this.$nextTick(() => (this.contentDiv!.scrollTop = this.scrollPositions[newRoute.path])); | ||
89 | this.moveFocusToContentDiv(); | ||
90 | } | ||
91 | |||
92 | fetchGalleryItems() { | ||
93 | this.isLoading = true; | ||
94 | this.$galleryStore | ||
95 | .fetchConfig() | ||
96 | .then(this.$uiStore.initFromConfig) | ||
97 | .then(this.$galleryStore.fetchGalleryItems) | ||
98 | .then(this.moveFocusToContentDiv) | ||
99 | .finally(() => (this.isLoading = false)) | ||
100 | .catch(this.displayError); | ||
101 | } | ||
102 | |||
103 | displayError(reason: any) { | ||
104 | this.$buefy.snackbar.open({ | ||
105 | message: `${reason}`, | ||
106 | actionText: this.$t("snack.retry"), | ||
107 | position: "is-top", | ||
108 | type: "is-danger", | ||
109 | indefinite: true, | ||
110 | onAction: this.fetchGalleryItems, | ||
111 | }); | ||
112 | } | ||
113 | |||
114 | isFullscreenActive(): boolean { | ||
115 | return Boolean(document.fullscreenElement); | ||
116 | } | ||
117 | 75 | ||
118 | @Watch("$uiStore.fullscreen") | 76 | function displayError(reason: unknown) { |
119 | applyFullscreen(fullscreen: boolean) { | 77 | createToast(String(reason), { |
120 | const isFullscreenActive = this.isFullscreenActive(); | 78 | type: 'danger', |
121 | if (fullscreen && !isFullscreenActive) document.body.requestFullscreen(); | 79 | position: 'top-center', |
122 | else if (isFullscreenActive) document.exitFullscreen(); | 80 | timeout: 10000, |
123 | } | 81 | showIcon: true, |
82 | onClose: () => !isLoading.value && fetchGalleryItems(), | ||
83 | }); | ||
84 | } | ||
124 | 85 | ||
125 | onFullscreenChange() { | 86 | function validateSpashScreen() { |
126 | this.$uiStore.toggleFullscreen(this.isFullscreenActive()); | 87 | uiStore.validateSpashScreen(); |
127 | } | 88 | nextTick(moveFocus); |
128 | } | 89 | } |
129 | </script> | 90 | </script> |
130 | 91 | ||
131 | <style lang="scss" module> | 92 | <style lang="scss" module> |
132 | @import "~@/assets/scss/theme.scss"; | 93 | @import "~@/assets/scss/theme"; |
133 | 94 | ||
95 | :root { | ||
96 | --layout-top: #{$layout-top}; | ||
97 | --layout-left: #{$layout-left}; | ||
98 | } | ||
134 | :global(body), | 99 | :global(body), |
135 | :global(html) { | 100 | :global(html) { |
101 | font-family: $family-sans-serif; | ||
136 | height: 100%; | 102 | height: 100%; |
137 | overflow: hidden; | 103 | overflow: hidden; |
138 | touch-action: none; | 104 | touch-action: none; |
139 | background-color: $content-bgcolor; | 105 | background-color: $content-bgcolor; |
140 | --layout-top: #{$layout-top}; | ||
141 | --layout-left: #{$layout-left}; | ||
142 | } | 106 | } |
143 | .layout { | 107 | .layout { |
144 | position: fixed; | 108 | position: fixed; |
145 | transition: all $transition-flex-expand linear; | ||
146 | top: 0; | 109 | top: 0; |
147 | bottom: 0; | 110 | bottom: 0; |
148 | left: 0; | 111 | left: 0; |