diff options
Diffstat (limited to 'viewer/src/views/MainLayout.vue')
-rw-r--r-- | viewer/src/views/MainLayout.vue | 159 |
1 files changed, 78 insertions, 81 deletions
diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue index 6ef9a3b..d8b3300 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,118 +18,116 @@ | |||
18 | --> | 18 | --> |
19 | 19 | ||
20 | <template> | 20 | <template> |
21 | <div :class="{ fullscreen: $uiStore.fullscreen, 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 | <panel-top v-if="isReady" class="layout layout-top" /> | 23 | <SplashScreen |
24 | <panel-left v-if="isReady" class="layout layout-left" /> | 24 | v-else-if="uiStore.splashScreenEnabled" |
25 | <router-view v-if="!isLoading" ref="content" class="layout layout-content scrollbar" /> | 25 | @validation="validateSpashScreen" |
26 | <b-loading :active="isLoading" is-full-page /> | 26 | /> |
27 | <ld-key-press :keycode="27" @action="$uiStore.toggleFullscreen(false)" /> | 27 | <template v-else-if="galleryStore.config && galleryStore.galleryIndex"> |
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> | ||
28 | </div> | 37 | </div> |
29 | </template> | 38 | </template> |
30 | 39 | ||
31 | <script lang="ts"> | 40 | <script setup lang="ts"> |
32 | import { Component, Vue, Ref, Watch } from "vue-property-decorator"; | 41 | import LdLoading from '@/components/LdLoading.vue'; |
33 | import PanelLeft from "./PanelLeft.vue"; | 42 | import { useLdKeepFocus } from '@/services/ui/ldKeepFocus'; |
34 | import PanelTop from "./PanelTop.vue"; | 43 | import { useLdSaveScroll } from '@/services/ui/ldSaveScroll'; |
35 | import { Route } from "vue-router"; | 44 | import { useGalleryStore } from '@/store/galleryStore'; |
45 | import { useUiStore } from '@/store/uiStore'; | ||
46 | import { VueInstance } from '@vueuse/core'; | ||
47 | import { createToast } from 'mosha-vue-toastify'; | ||
48 | import { nextTick, ref } from 'vue'; | ||
49 | import LayoutLeft from './layout/left/LayoutLeft.vue'; | ||
50 | import LayoutTop from './layout/top/LayoutTop.vue'; | ||
51 | import SplashScreen from './SplashScreen.vue'; | ||
36 | 52 | ||
37 | @Component({ | 53 | const uiStore = useUiStore(); |
38 | components: { PanelLeft, PanelTop }, | 54 | const galleryStore = useGalleryStore(); |
39 | }) | ||
40 | export default class MainLayout extends Vue { | ||
41 | @Ref() readonly content!: Vue; | ||
42 | 55 | ||
43 | isLoading: boolean = true; | 56 | const content = ref<VueInstance>(); |
44 | scrollPositions: ScrollPosition = {}; | 57 | const isLoading = ref(true); |
45 | 58 | ||
46 | mounted() { | 59 | useLdSaveScroll(content); |
47 | history.replaceState({ ldgallery: "ENTRYPOINT" }, ""); | 60 | const { moveFocus } = useLdKeepFocus(content); |
48 | this.fetchGalleryItems(); | ||
49 | document.body.addEventListener("fullscreenchange", this.onFullscreenChange); | ||
50 | } | ||
51 | |||
52 | destroyed() { | ||
53 | document.body.removeEventListener("fullscreenchange", this.onFullscreenChange); | ||
54 | } | ||
55 | |||
56 | @Watch("$route") | ||
57 | routeChanged(newRoute: Route, oldRoute: Route) { | ||
58 | const el = this.content.$el; | ||
59 | this.scrollPositions[oldRoute.path] = el.scrollTop; | ||
60 | this.$nextTick(() => (el.scrollTop = this.scrollPositions[newRoute.path])); | ||
61 | } | ||
62 | 61 | ||
63 | fetchGalleryItems() { | 62 | history.replaceState({ ldgallery: 'ENTRYPOINT' }, ''); |
64 | this.isLoading = true; | 63 | fetchGalleryItems(); |
65 | this.$galleryStore | ||
66 | .fetchConfig() | ||
67 | .then(this.$uiStore.initFromConfig) | ||
68 | .then(this.$galleryStore.fetchGalleryItems) | ||
69 | .finally(() => (this.isLoading = false)) | ||
70 | .catch(this.displayError); | ||
71 | } | ||
72 | |||
73 | get isReady() { | ||
74 | return !this.isLoading && this.$galleryStore.config && this.$galleryStore.currentPath !== null; | ||
75 | } | ||
76 | 64 | ||
77 | displayError(reason: any) { | 65 | function fetchGalleryItems() { |
78 | this.$buefy.snackbar.open({ | 66 | isLoading.value = true; |
79 | message: `${reason}`, | 67 | galleryStore |
80 | actionText: "Retry", | 68 | .fetchConfig() |
81 | position: "is-top", | 69 | .then(uiStore.initFromConfig) |
82 | type: "is-danger", | 70 | .then(galleryStore.fetchGalleryItems) |
83 | indefinite: true, | 71 | .then(moveFocus) |
84 | onAction: this.fetchGalleryItems, | 72 | .finally(() => (isLoading.value = false)) |
85 | }); | 73 | .catch(displayError); |
86 | } | 74 | } |
87 | 75 | ||
88 | @Watch("$uiStore.fullscreen") | 76 | function displayError(reason: unknown) { |
89 | applyFullscreen(fullscreen: boolean) { | 77 | createToast(String(reason), { |
90 | if (fullscreen && !document.fullscreen) document.body.requestFullscreen(); | 78 | type: 'danger', |
91 | else if (document.fullscreen) document.exitFullscreen(); | 79 | position: 'top-center', |
92 | } | 80 | timeout: 10000, |
81 | showIcon: true, | ||
82 | onClose: () => !isLoading.value && fetchGalleryItems(), | ||
83 | }); | ||
84 | } | ||
93 | 85 | ||
94 | onFullscreenChange() { | 86 | function validateSpashScreen() { |
95 | this.$uiStore.toggleFullscreen(document.fullscreen); | 87 | uiStore.validateSpashScreen(); |
96 | } | 88 | nextTick(moveFocus); |
97 | } | 89 | } |
98 | </script> | 90 | </script> |
99 | 91 | ||
100 | <style lang="scss"> | 92 | <style lang="scss" module> |
101 | @import "~@/assets/scss/theme.scss"; | 93 | @import "~@/assets/scss/theme"; |
102 | 94 | ||
103 | body, | 95 | :root { |
104 | html { | 96 | --layout-top: #{$layout-top}; |
97 | --layout-left: #{$layout-left}; | ||
98 | } | ||
99 | :global(body), | ||
100 | :global(html) { | ||
101 | font-family: $family-sans-serif; | ||
105 | height: 100%; | 102 | height: 100%; |
106 | overflow: hidden; | 103 | overflow: hidden; |
107 | touch-action: none; | 104 | touch-action: none; |
108 | background-color: $content-bgcolor; | 105 | background-color: $content-bgcolor; |
109 | --layout-top: #{$layout-top}; | 106 | margin: 0; |
110 | --layout-left: #{$layout-left}; | ||
111 | } | 107 | } |
112 | .layout { | 108 | .layout { |
113 | position: fixed; | 109 | position: fixed; |
114 | transition: all $transition-flex-expand linear; | ||
115 | top: 0; | 110 | top: 0; |
116 | bottom: 0; | 111 | bottom: 0; |
117 | left: 0; | 112 | left: 0; |
118 | right: 0; | 113 | right: 0; |
119 | &.layout-top { | 114 | &.layoutTop { |
120 | height: $layout-top; | 115 | height: $layout-top; |
121 | z-index: 1; | 116 | z-index: 1; |
122 | } | 117 | } |
123 | &.layout-left { | 118 | &.layoutLeft { |
124 | top: $layout-top; | 119 | top: $layout-top; |
125 | width: $layout-left; | 120 | width: $layout-left; |
126 | z-index: 2; | 121 | z-index: 2; |
127 | } | 122 | } |
128 | &.layout-content { | 123 | &.layoutContent { |
129 | top: var(--layout-top); | 124 | top: var(--layout-top); |
130 | left: var(--layout-left); | 125 | left: var(--layout-left); |
131 | z-index: 3; | 126 | z-index: 3; |
132 | overflow-x: hidden; | 127 | overflow-x: hidden; |
128 | &:focus { | ||
129 | outline: none; | ||
130 | } | ||
133 | } | 131 | } |
134 | } | 132 | } |
135 | .fullscreen { | 133 | .fullscreen { |
@@ -146,17 +144,16 @@ html { | |||
146 | } | 144 | } |
147 | 145 | ||
148 | .layout { | 146 | .layout { |
149 | &.layout-top { | 147 | &.layoutTop { |
150 | background-color: $panel-top-bgcolor; | 148 | background-color: $panel-top-bgcolor; |
151 | color: $panel-top-txtcolor; | 149 | color: $panel-top-txtcolor; |
152 | } | 150 | } |
153 | &.layout-left { | 151 | &.layoutLeft { |
154 | background-color: $panel-left-bgcolor; | 152 | background-color: $panel-left-bgcolor; |
155 | color: $panel-left-txtcolor; | 153 | color: $panel-left-txtcolor; |
156 | } | 154 | } |
157 | &.layout-cont |