diff options
Diffstat (limited to 'viewer/src')
-rw-r--r-- | viewer/src/@types/gallery.d.ts | 10 | ||||
-rw-r--r-- | viewer/src/components/LdPicture.vue | 56 | ||||
-rw-r--r-- | viewer/src/services/ldzoom.ts | 134 | ||||
-rw-r--r-- | viewer/src/views/MainLayout.vue | 1 |
4 files changed, 168 insertions, 33 deletions
diff --git a/viewer/src/@types/gallery.d.ts b/viewer/src/@types/gallery.d.ts index de1c0dd..956ab6b 100644 --- a/viewer/src/@types/gallery.d.ts +++ b/viewer/src/@types/gallery.d.ts | |||
@@ -49,12 +49,17 @@ declare namespace Gallery { | |||
49 | thumbnail?: Thumbnail | 49 | thumbnail?: Thumbnail |
50 | properties: OtherProperties | PictureProperties | DirectoryProperties, | 50 | properties: OtherProperties | PictureProperties | DirectoryProperties, |
51 | } | 51 | } |
52 | interface Resolution { | ||
53 | width: number, | ||
54 | height: number, | ||
55 | } | ||
52 | interface OtherProperties { | 56 | interface OtherProperties { |
53 | type: "other", | 57 | type: "other", |
54 | } | 58 | } |
55 | interface PictureProperties { | 59 | interface PictureProperties { |
56 | type: "picture", | 60 | type: "picture", |
57 | resource: string, | 61 | resource: string, |
62 | resolution: Resolution | ||
58 | } | 63 | } |
59 | interface DirectoryProperties { | 64 | interface DirectoryProperties { |
60 | type: "directory", | 65 | type: "directory", |
@@ -62,10 +67,7 @@ declare namespace Gallery { | |||
62 | } | 67 | } |
63 | interface Thumbnail { | 68 | interface Thumbnail { |
64 | resource: string, | 69 | resource: string, |
65 | resolution: { | 70 | resolution: Resolution |
66 | width: number, | ||
67 | height: number, | ||
68 | } | ||
69 | } | 71 | } |
70 | type RawTag = string; | 72 | type RawTag = string; |
71 | type ItemType = "other" | "picture" | "directory"; | 73 | type ItemType = "other" | "picture" | "directory"; |
diff --git a/viewer/src/components/LdPicture.vue b/viewer/src/components/LdPicture.vue index 3170c81..de46bcb 100644 --- a/viewer/src/components/LdPicture.vue +++ b/viewer/src/components/LdPicture.vue | |||
@@ -18,17 +18,21 @@ | |||
18 | --> | 18 | --> |
19 | 19 | ||
20 | <template> | 20 | <template> |
21 | <!-- FIXME: v-dragscroll interferes with pinch-to-zoom --> | ||
21 | <div | 22 | <div |
23 | ref="containerElement" | ||
22 | v-dragscroll | 24 | v-dragscroll |
23 | class="scrollbar" | 25 | class="scrollbar ld-picture-container" |
24 | :class="{'fit-to-screen': !$uiStore.fullscreen, 'original-size': $uiStore.fullscreen}" | ||
25 | @click.capture="e => dragScrollClickFix.onClickCapture(e)" | 26 | @click.capture="e => dragScrollClickFix.onClickCapture(e)" |
26 | @click="$uiStore.toggleFullscreen()" | 27 | @dblclick="$uiStore.toggleFullscreen()" |
28 | @dragstart.prevent | ||
27 | @dragscrollstart="dragScrollClickFix.onDragScrollStart()" | 29 | @dragscrollstart="dragScrollClickFix.onDragScrollStart()" |
28 | @dragscrollend="dragScrollClickFix.onDragScrollEnd()" | 30 | @dragscrollend="dragScrollClickFix.onDragScrollEnd()" |
29 | > | 31 | > |
30 | <v-lazy-image | 32 | <v-lazy-image |
33 | ref="imageElement" | ||
31 | :src="pictureSrc(picture.properties.resource)" | 34 | :src="pictureSrc(picture.properties.resource)" |
35 | class="ld-picture-element" | ||
32 | :class="{'slow-loading': Boolean(slowLoadingStyle)}" | 36 | :class="{'slow-loading': Boolean(slowLoadingStyle)}" |
33 | :style="slowLoadingStyle" | 37 | :style="slowLoadingStyle" |
34 | @load="clearSlowLoading" | 38 | @load="clearSlowLoading" |
@@ -38,12 +42,15 @@ | |||
38 | </template> | 42 | </template> |
39 | 43 | ||
40 | <script lang="ts"> | 44 | <script lang="ts"> |
41 | import { Component, Vue, Prop } from "vue-property-decorator"; | 45 | import { Component, Vue, Prop, Ref } from "vue-property-decorator"; |
46 | import LdZoom from "@/services/ldzoom"; | ||
42 | import DragScrollClickFix from "@/services/dragscrollclickfix"; | 47 | import DragScrollClickFix from "@/services/dragscrollclickfix"; |
43 | 48 | ||
44 | @Component | 49 | @Component |
45 | export default class LdPicture extends Vue { | 50 | export default class LdPicture extends Vue { |
46 | @Prop({ required: true }) readonly picture!: Gallery.Picture; | 51 | @Prop({ required: true }) readonly picture!: Gallery.Picture; |
52 | @Ref() readonly containerElement!: HTMLDivElement; | ||
53 | @Ref() readonly imageElement!: Vue; | ||
47 | 54 | ||
48 | readonly SLOW_LOADING_TIMEOUT_MS: number = 1500; | 55 | readonly SLOW_LOADING_TIMEOUT_MS: number = 1500; |
49 | readonly dragScrollClickFix = new DragScrollClickFix(); | 56 | readonly dragScrollClickFix = new DragScrollClickFix(); |
@@ -54,6 +61,7 @@ export default class LdPicture extends Vue { | |||
54 | 61 | ||
55 | mounted() { | 62 | mounted() { |
56 | this.timer = setTimeout(this.generateSlowLoadingStyle, this.SLOW_LOADING_TIMEOUT_MS); | 63 | this.timer = setTimeout(this.generateSlowLoadingStyle, this.SLOW_LOADING_TIMEOUT_MS); |
64 | new LdZoom(this.containerElement, this.imageElement.$el as HTMLImageElement, this.picture.properties, 10, 1 / 5).install(); | ||
57 | } | 65 | } |
58 | 66 | ||
59 | destroyed() { | 67 | destroyed() { |
@@ -85,13 +93,17 @@ export default class LdPicture extends Vue { | |||
85 | <style lang="scss"> | 93 | <style lang="scss"> |
86 | @import "~@/assets/scss/theme.scss"; | 94 | @import "~@/assets/scss/theme.scss"; |
87 | 95 | ||
88 | .ld-picture-loader { | 96 | .ld-picture-container { |
89 | position: relative; | 97 | height: 100%; |
90 | & .loading-background { | ||
91 | background: none !important; | ||
92 | } | ||
93 | } | 98 | } |
94 | img.slow-loading { | 99 | |
100 | .ld-picture-element { | ||
101 | max-width: unset; | ||
102 | max-height: unset; | ||
103 | cursor: grab; | ||
104 | } | ||
105 | |||
106 | .slow-loading { | ||
95 | background-repeat: no-repeat; | 107 | background-repeat: no-repeat; |
96 | background-position: center; | 108 | background-position: center; |
97 | background-size: contain; | 109 | background-size: contain; |
@@ -99,25 +111,11 @@ img.slow-loading { | |||
99 | background-blend-mode: soft-light; | 111 | background-blend-mode: soft-light; |
100 | opacity: 1 !important; | 112 | opacity: 1 !important; |
101 | } | 113 | } |
102 | .fit-to-screen { | 114 | |
103 | display: flex; | 115 | .ld-picture-loader { |
104 | justify-content: space-around; | 116 | position: relative; |
105 | height: 100%; | 117 | & .loading-background { |
106 | & > img { | 118 | background: none !important; |
107 | object-fit: contain; | ||
108 | cursor: zoom-in; | ||
109 | } | ||
110 | } | ||
111 | .original-size { | ||
112 | display: block; | ||
113 | text-align: center; | ||
114 | cursor: grab; | ||
115 | height: 100%; | ||
116 | & > img { | ||
117 | max-width: unset; | ||
118 | max-height: unset; | ||
119 | object-fit: none; | ||
120 | cursor: zoom-out; | ||
121 | } | 119 | } |
122 | } | 120 | } |
123 | </style> | 121 | </style> |
diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts new file mode 100644 index 0000000..c28c2c8 --- /dev/null +++ b/viewer/src/services/ldzoom.ts | |||
@@ -0,0 +1,134 @@ | |||
1 | /* ldgallery - A static generator which turns a collection of tagged | ||
2 | -- pictures into a searchable web gallery. | ||
3 | -- | ||
4 | -- Copyright (C) 2020 Pacien TRAN-GIRARD | ||
5 | -- | ||
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 | ||
8 | -- published by the Free Software Foundation, either version 3 of the | ||
9 | -- License, or (at your option) any later version. | ||
10 | -- | ||
11 | -- This program is distributed in the hope that it will be useful, | ||
12 | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | -- GNU Affero General Public License for more details. | ||
15 | -- | ||
16 | -- You should have received a copy of the GNU Affero General Public License | ||
17 | -- along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | // polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver | ||
21 | import ResizeObserver from 'resize-observer-polyfill'; | ||
22 | import "hammerjs"; | ||
23 | |||
24 | /** | ||
25 | * Mousewheel and pinch zoom handler. | ||
26 | */ | ||
27 | export default class LdZoom { | ||
28 | readonly containerElement: HTMLDivElement; | ||
29 | readonly imageElement: HTMLImageElement; | ||
30 | readonly pictureProperties: Gallery.PictureProperties; | ||
31 | readonly maxScaleFactor: number; | ||
32 | readonly scrollZoomSpeed: number; | ||
33 | scaleFactor: number = 0.0; | ||
34 | |||
35 | constructor( | ||
36 | containerElement: HTMLDivElement, imageElement: HTMLImageElement, | ||
37 | pictureProperties: Gallery.PictureProperties, | ||
38 | maxScaleFactor: number, scrollZoomSpeed: number | ||
39 | ) { | ||
40 | this.containerElement = containerElement; | ||
41 | this.imageElement = imageElement; | ||
42 | this.pictureProperties = pictureProperties; | ||
43 | this.maxScaleFactor = maxScaleFactor; | ||
44 | this.scrollZoomSpeed = scrollZoomSpeed; | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * Register event listeners. | ||
49 | */ | ||
50 | public install() { | ||
51 | this.updateImageScale(this.scaleFactor); | ||
52 | |||
53 | new ResizeObserver(() => { | ||
54 | this.updateImageScale(this.scaleFactor); | ||
55 | }).observe(this.containerElement); | ||
56 | |||
57 | this.containerElement.addEventListener('wheel', wheelEvent => { | ||
58 | wheelEvent.preventDefault(); | ||
59 | const scaleFactor = this.scaleFactor - Math.sign(wheelEvent.deltaY) * this.scrollZoomSpeed; | ||
60 | this.zoom(wheelEvent.offsetX, wheelEvent.offsetY, scaleFactor); | ||
61 | }); | ||
62 | |||
63 | const pinchListener = new Hammer(this.containerElement); | ||
64 | pinchListener.get('pinch').set({enable: true}); | ||
65 | this.installPinchHandler(pinchListener); | ||
66 | } | ||
67 | |||
68 | private installPinchHandler(pinchListener: HammerManager) { | ||
69 | let startScaleFactor = 0.0; | ||
70 | |||
71 | pinchListener.on('pinchstart', (pinchEvent: HammerInput) => { | ||
72 | startScaleFactor = this.scaleFactor; | ||
< |