@cloudron/pankow 4.4.10 → 4.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/SideBar.vue +159 -22
- package/gestures.js +96 -4
- package/package.json +5 -5
- package/types/gestures.d.ts +27 -3
- package/viewers/ImageViewer.vue +29 -9
package/components/SideBar.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
|
|
3
|
-
import { ref, useTemplateRef, onMounted } from 'vue';
|
|
4
|
-
import {
|
|
3
|
+
import { ref, useTemplateRef, onMounted, onUnmounted, nextTick } from 'vue';
|
|
4
|
+
import { onPan } from '../gestures.js';
|
|
5
5
|
|
|
6
6
|
defineProps({
|
|
7
7
|
showOpenAction: {
|
|
@@ -11,7 +11,9 @@ defineProps({
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const sideBar = useTemplateRef('sideBar');
|
|
14
|
+
const backDrop = useTemplateRef('backDrop');
|
|
14
15
|
const isVisible = ref(false);
|
|
16
|
+
const dragging = ref(false);
|
|
15
17
|
|
|
16
18
|
function open() {
|
|
17
19
|
isVisible.value = true;
|
|
@@ -21,10 +23,130 @@ function close() {
|
|
|
21
23
|
isVisible.value = false;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
function width() {
|
|
27
|
+
return sideBar.value ? sideBar.value.offsetWidth : 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function clearPendingSettle() {
|
|
31
|
+
if (settleOnEnd && sideBar.value) sideBar.value.removeEventListener('transitionend', settleOnEnd);
|
|
32
|
+
settleOnEnd = null;
|
|
33
|
+
if (settleTimer) {
|
|
34
|
+
clearTimeout(settleTimer);
|
|
35
|
+
settleTimer = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function dragStart() {
|
|
40
|
+
clearPendingSettle();
|
|
41
|
+
dragging.value = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function dragMove({ deltaX }) {
|
|
45
|
+
const offset = Math.max(-width(), Math.min(0, deltaX));
|
|
46
|
+
sideBar.value.style.transform = `translateX(${offset}px)`;
|
|
47
|
+
if (backDrop.value) backDrop.value.style.opacity = String(1 + offset / width());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function settle(shouldClose) {
|
|
51
|
+
dragging.value = false;
|
|
52
|
+
isVisible.value = shouldClose ? false : true;
|
|
53
|
+
|
|
54
|
+
nextTick(() => {
|
|
55
|
+
const el = sideBar.value;
|
|
56
|
+
const bd = backDrop.value;
|
|
57
|
+
if (!el) return;
|
|
58
|
+
|
|
59
|
+
clearPendingSettle();
|
|
60
|
+
|
|
61
|
+
el.style.transform = shouldClose ? 'translateX(-100%)' : 'translateX(0)';
|
|
62
|
+
if (bd) bd.style.opacity = '';
|
|
63
|
+
|
|
64
|
+
function cleanup() {
|
|
65
|
+
if (settleTimer) {
|
|
66
|
+
clearTimeout(settleTimer);
|
|
67
|
+
settleTimer = null;
|
|
68
|
+
}
|
|
69
|
+
if (settleOnEnd) {
|
|
70
|
+
el.removeEventListener('transitionend', onEnd);
|
|
71
|
+
settleOnEnd = null;
|
|
72
|
+
}
|
|
73
|
+
el.style.transform = '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onEnd(event) {
|
|
77
|
+
if (event.propertyName === 'transform') cleanup();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
settleOnEnd = onEnd;
|
|
81
|
+
el.addEventListener('transitionend', onEnd);
|
|
82
|
+
settleTimer = setTimeout(cleanup, 300);
|
|
27
83
|
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function suppressClick() {
|
|
87
|
+
suppressBackdropClick = true;
|
|
88
|
+
setTimeout(() => { suppressBackdropClick = false; }, 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function onBackdropClick() {
|
|
92
|
+
if (suppressBackdropClick) {
|
|
93
|
+
suppressBackdropClick = false;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
close();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function dragEnd({ deltaX, velocity }) {
|
|
100
|
+
suppressClick();
|
|
101
|
+
const w = width();
|
|
102
|
+
settle(deltaX < -w / 2 || velocity < -0.5);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function dragCancel() {
|
|
106
|
+
suppressClick();
|
|
107
|
+
settle(false);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let removePans = [];
|
|
111
|
+
let settleTimer = null;
|
|
112
|
+
let settleOnEnd = null;
|
|
113
|
+
let suppressBackdropClick = false;
|
|
114
|
+
const media = typeof window !== 'undefined' ? window.matchMedia('(max-width: 576px)') : null;
|
|
115
|
+
|
|
116
|
+
function setup() {
|
|
117
|
+
if (!media || !media.matches || !sideBar.value || removePans.length) return;
|
|
118
|
+
const handlers = {
|
|
119
|
+
onStart: dragStart,
|
|
120
|
+
onMove: dragMove,
|
|
121
|
+
onEnd: dragEnd,
|
|
122
|
+
onCancel: dragCancel
|
|
123
|
+
};
|
|
124
|
+
removePans.push(onPan(sideBar.value, handlers));
|
|
125
|
+
if (backDrop.value) removePans.push(onPan(backDrop.value, handlers));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function teardown() {
|
|
129
|
+
removePans.forEach((remove) => { remove(); });
|
|
130
|
+
removePans = [];
|
|
131
|
+
dragging.value = false;
|
|
132
|
+
clearPendingSettle();
|
|
133
|
+
if (sideBar.value) sideBar.value.style.transform = '';
|
|
134
|
+
if (backDrop.value) backDrop.value.style.opacity = '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function onMediaChange() {
|
|
138
|
+
if (media && media.matches) setup();
|
|
139
|
+
else teardown();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
onMounted(() => {
|
|
143
|
+
setup();
|
|
144
|
+
media && media.addEventListener('change', onMediaChange);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
onUnmounted(() => {
|
|
148
|
+
media && media.removeEventListener('change', onMediaChange);
|
|
149
|
+
teardown();
|
|
28
150
|
});
|
|
29
151
|
|
|
30
152
|
defineExpose({ open, close });
|
|
@@ -32,15 +154,13 @@ defineExpose({ open, close });
|
|
|
32
154
|
</script>
|
|
33
155
|
|
|
34
156
|
<template>
|
|
35
|
-
<div class="pankow-sidebar-container" :class="{ 'pankow-sidebar-open': isVisible }">
|
|
36
|
-
<
|
|
37
|
-
|
|
157
|
+
<div class="pankow-sidebar-container" :class="{ 'pankow-sidebar-open': isVisible, 'pankow-sidebar-dragging': dragging }">
|
|
158
|
+
<div class="pankow-sidebar-backdrop" ref="backDrop" @click="onBackdropClick"></div>
|
|
159
|
+
<Transition name="pankow-scale">
|
|
160
|
+
<div class="pankow-sidebar-close-action" v-if="isVisible" tabindex="0" role="button" aria-label="Close sidebar" @click="close()" @keydown.enter="close()" @keydown.space.prevent="close()"><i class="fa-solid fa-xmark"></i></div>
|
|
161
|
+
<div class="pankow-sidebar-open-action" v-else-if="showOpenAction" tabindex="0" role="button" aria-label="Open sidebar" @click="open()" @keydown.enter="open()" @keydown.space.prevent="open()"><i class="fa-solid fa-bars"></i></div>
|
|
38
162
|
</Transition>
|
|
39
|
-
<div class="pankow-sidebar" ref="sideBar"
|
|
40
|
-
<Transition name="pankow-scale">
|
|
41
|
-
<div class="pankow-sidebar-close-action" v-if="isVisible" tabindex="0" role="button" aria-label="Close sidebar" @click="close()" @keydown.enter="close()" @keydown.space.prevent="close()"><i class="fa-solid fa-xmark"></i></div>
|
|
42
|
-
<div class="pankow-sidebar-open-action" v-else-if="showOpenAction" tabindex="0" role="button" aria-label="Open sidebar" @click="open()" @keydown.enter="open()" @keydown.space.prevent="open()"><i class="fa-solid fa-bars"></i></div>
|
|
43
|
-
</Transition>
|
|
163
|
+
<div class="pankow-sidebar" ref="sideBar">
|
|
44
164
|
<div class="pankow-sidebar-inner">
|
|
45
165
|
<slot></slot>
|
|
46
166
|
</div>
|
|
@@ -60,6 +180,8 @@ defineExpose({ open, close });
|
|
|
60
180
|
display: block;
|
|
61
181
|
height: 100%;
|
|
62
182
|
overflow: auto;
|
|
183
|
+
background-color: #0a2e4c;
|
|
184
|
+
color: white;
|
|
63
185
|
}
|
|
64
186
|
|
|
65
187
|
.pankow-sidebar-inner {
|
|
@@ -91,7 +213,7 @@ defineExpose({ open, close });
|
|
|
91
213
|
|
|
92
214
|
.pankow-sidebar-backdrop {
|
|
93
215
|
display: none;
|
|
94
|
-
position:
|
|
216
|
+
position: fixed;
|
|
95
217
|
top: 0;
|
|
96
218
|
bottom: 0;
|
|
97
219
|
left: 0;
|
|
@@ -115,19 +237,34 @@ defineExpose({ open, close });
|
|
|
115
237
|
}
|
|
116
238
|
|
|
117
239
|
.pankow-sidebar {
|
|
118
|
-
position:
|
|
119
|
-
|
|
240
|
+
position: fixed;
|
|
241
|
+
top: 0;
|
|
120
242
|
left: 0;
|
|
121
|
-
|
|
243
|
+
bottom: 0;
|
|
244
|
+
width: calc(70%);
|
|
245
|
+
transform: translateX(-100%);
|
|
246
|
+
transition: transform 250ms ease-in-out;
|
|
122
247
|
}
|
|
123
248
|
|
|
124
|
-
.pankow-sidebar-open .pankow-sidebar
|
|
249
|
+
.pankow-sidebar-open .pankow-sidebar {
|
|
250
|
+
transform: translateX(0);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.pankow-sidebar-dragging .pankow-sidebar,
|
|
254
|
+
.pankow-sidebar-dragging .pankow-sidebar-backdrop {
|
|
255
|
+
transition: none;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.pankow-sidebar-backdrop {
|
|
125
259
|
display: block;
|
|
260
|
+
opacity: 0;
|
|
261
|
+
pointer-events: none;
|
|
262
|
+
transition: opacity 250ms ease-in-out;
|
|
126
263
|
}
|
|
127
264
|
|
|
128
|
-
.pankow-sidebar-
|
|
129
|
-
|
|
130
|
-
|
|
265
|
+
.pankow-sidebar-open .pankow-sidebar-backdrop {
|
|
266
|
+
opacity: 1;
|
|
267
|
+
pointer-events: auto;
|
|
131
268
|
}
|
|
132
269
|
|
|
133
270
|
.pankow-sidebar-open-action {
|
|
@@ -143,4 +280,4 @@ defineExpose({ open, close });
|
|
|
143
280
|
}
|
|
144
281
|
}
|
|
145
282
|
|
|
146
|
-
</style>
|
|
283
|
+
</style>
|
package/gestures.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Gesture handlers
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* ```
|
|
6
|
-
* import { onSwipe } from 'pankow/gestures';
|
|
6
|
+
* import { onSwipe, onPan } from 'pankow/gestures';
|
|
7
7
|
* onSwipe(element, (direction) => {});
|
|
8
|
+
* onPan(element, { onStart, onMove, onEnd, onCancel });
|
|
8
9
|
* ```
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -55,10 +56,101 @@ function onSwipe(elem, callback, threshold = 50) {
|
|
|
55
56
|
elem.addEventListener('touchcancel', touchEnd);
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Registers pointer event listeners on an element for interactive dragging (pan).
|
|
61
|
+
* The drag is only "claimed" once the pointer moves past a small threshold and
|
|
62
|
+
* horizontal movement dominates vertical movement, so vertical scrolling inside
|
|
63
|
+
* the element still works as expected.
|
|
64
|
+
*
|
|
65
|
+
* Callbacks:
|
|
66
|
+
* onStart({ x, y }) - drag claimed (horizontal intent confirmed)
|
|
67
|
+
* onMove({ deltaX, deltaY }) - pointer moved (called continuously while dragging)
|
|
68
|
+
* onEnd({ deltaX, velocity }) - pointer released; velocity in px/ms (negative = leftward)
|
|
69
|
+
* onCancel() - pointer cancelled
|
|
70
|
+
*
|
|
71
|
+
* @param {HTMLElement} elem
|
|
72
|
+
* @param {{ onStart?: Function, onMove?: Function, onEnd?: Function, onCancel?: Function }} handlers
|
|
73
|
+
* @param {number} [threshold=10] - Minimum pixel distance before the drag is claimed
|
|
74
|
+
*/
|
|
75
|
+
function onPan(elem, handlers = {}, threshold = 10) {
|
|
76
|
+
let pointerId = null;
|
|
77
|
+
let dragging = false;
|
|
78
|
+
let startX = 0;
|
|
79
|
+
let startY = 0;
|
|
80
|
+
let lastX = 0;
|
|
81
|
+
let lastTime = 0;
|
|
82
|
+
let lastVelocity = 0;
|
|
83
|
+
|
|
84
|
+
function pointerDown(event) {
|
|
85
|
+
if (dragging || !event.isPrimary) return;
|
|
86
|
+
pointerId = event.pointerId;
|
|
87
|
+
startX = event.clientX;
|
|
88
|
+
startY = event.clientY;
|
|
89
|
+
lastX = startX;
|
|
90
|
+
lastTime = event.timeStamp;
|
|
91
|
+
lastVelocity = 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function pointerMove(event) {
|
|
95
|
+
if (pointerId === null || event.pointerId !== pointerId) return;
|
|
96
|
+
|
|
97
|
+
const deltaX = event.clientX - startX;
|
|
98
|
+
const deltaY = event.clientY - startY;
|
|
99
|
+
|
|
100
|
+
if (!dragging) {
|
|
101
|
+
const absX = Math.abs(deltaX);
|
|
102
|
+
const absY = Math.abs(deltaY);
|
|
103
|
+
if (absX < threshold && absY < threshold) return;
|
|
104
|
+
if (absY > absX) return; // vertical intent, keep it for scrolling
|
|
105
|
+
dragging = true;
|
|
106
|
+
try { elem.setPointerCapture(pointerId); } catch (e) { /* noop */ }
|
|
107
|
+
if (typeof handlers.onStart === 'function') handlers.onStart({ x: startX, y: startY });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const now = event.timeStamp;
|
|
111
|
+
const dt = now - lastTime;
|
|
112
|
+
if (dt > 0) {
|
|
113
|
+
lastVelocity = (event.clientX - lastX) / dt;
|
|
114
|
+
}
|
|
115
|
+
lastX = event.clientX;
|
|
116
|
+
lastTime = now;
|
|
117
|
+
|
|
118
|
+
if (typeof handlers.onMove === 'function') handlers.onMove({ deltaX, deltaY });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function end(event) {
|
|
122
|
+
if (pointerId === null || event.pointerId !== pointerId) return;
|
|
123
|
+
pointerId = null;
|
|
124
|
+
|
|
125
|
+
if (dragging) {
|
|
126
|
+
dragging = false;
|
|
127
|
+
if (event.type === 'pointercancel') {
|
|
128
|
+
if (typeof handlers.onCancel === 'function') handlers.onCancel();
|
|
129
|
+
} else if (typeof handlers.onEnd === 'function') {
|
|
130
|
+
handlers.onEnd({ deltaX: event.clientX - startX, velocity: lastVelocity });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
elem.addEventListener('pointerdown', pointerDown);
|
|
136
|
+
elem.addEventListener('pointermove', pointerMove);
|
|
137
|
+
elem.addEventListener('pointerup', end);
|
|
138
|
+
elem.addEventListener('pointercancel', end);
|
|
139
|
+
|
|
140
|
+
return function remove() {
|
|
141
|
+
elem.removeEventListener('pointerdown', pointerDown);
|
|
142
|
+
elem.removeEventListener('pointermove', pointerMove);
|
|
143
|
+
elem.removeEventListener('pointerup', end);
|
|
144
|
+
elem.removeEventListener('pointercancel', end);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
58
148
|
export default {
|
|
59
|
-
onSwipe
|
|
149
|
+
onSwipe,
|
|
150
|
+
onPan
|
|
60
151
|
};
|
|
61
152
|
|
|
62
153
|
export {
|
|
63
|
-
onSwipe
|
|
154
|
+
onSwipe,
|
|
155
|
+
onPan
|
|
64
156
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudron/pankow",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "4.4.
|
|
4
|
+
"version": "4.4.12",
|
|
5
5
|
"description": "Vue 3 UI component library for Cloudron apps",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "types/index.d.ts",
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@highlightjs/vue-plugin": "^2.1.0",
|
|
51
|
-
"@unhead/vue": "^3.
|
|
51
|
+
"@unhead/vue": "^3.4.0",
|
|
52
52
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
53
|
-
"highlight.js": "^11.
|
|
53
|
+
"highlight.js": "^11.12.0",
|
|
54
54
|
"typescript": "^7.0.2",
|
|
55
|
-
"vite": "^8.2.
|
|
55
|
+
"vite": "^8.2.2",
|
|
56
56
|
"vite-ssg": "^28.3.0",
|
|
57
|
-
"vue": "^3.5.
|
|
57
|
+
"vue": "^3.5.42",
|
|
58
58
|
"vue-router": "^5.2.0"
|
|
59
59
|
},
|
|
60
60
|
"allowScripts": {
|
package/types/gestures.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Gesture handlers
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* ```
|
|
6
|
-
* import { onSwipe } from 'pankow/gestures';
|
|
6
|
+
* import { onSwipe, onPan } from 'pankow/gestures';
|
|
7
7
|
* onSwipe(element, (direction) => {});
|
|
8
|
+
* onPan(element, { onStart, onMove, onEnd, onCancel });
|
|
8
9
|
* ```
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
@@ -14,8 +15,31 @@
|
|
|
14
15
|
* @param {number} [threshold=50] - Minimum pixel distance to trigger a swipe
|
|
15
16
|
*/
|
|
16
17
|
declare function onSwipe(elem: HTMLElement, callback: Function, threshold?: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* Registers pointer event listeners on an element for interactive dragging (pan).
|
|
20
|
+
* The drag is only "claimed" once the pointer moves past a small threshold and
|
|
21
|
+
* horizontal movement dominates vertical movement, so vertical scrolling inside
|
|
22
|
+
* the element still works as expected.
|
|
23
|
+
*
|
|
24
|
+
* Callbacks:
|
|
25
|
+
* onStart({ x, y }) - drag claimed (horizontal intent confirmed)
|
|
26
|
+
* onMove({ deltaX, deltaY }) - pointer moved (called continuously while dragging)
|
|
27
|
+
* onEnd({ deltaX, velocity }) - pointer released; velocity in px/ms (negative = leftward)
|
|
28
|
+
* onCancel() - pointer cancelled
|
|
29
|
+
*
|
|
30
|
+
* @param {HTMLElement} elem
|
|
31
|
+
* @param {{ onStart?: Function, onMove?: Function, onEnd?: Function, onCancel?: Function }} handlers
|
|
32
|
+
* @param {number} [threshold=10] - Minimum pixel distance before the drag is claimed
|
|
33
|
+
*/
|
|
34
|
+
declare function onPan(elem: HTMLElement, handlers?: {
|
|
35
|
+
onStart?: Function;
|
|
36
|
+
onMove?: Function;
|
|
37
|
+
onEnd?: Function;
|
|
38
|
+
onCancel?: Function;
|
|
39
|
+
}, threshold?: number): () => void;
|
|
17
40
|
declare const _default: {
|
|
18
41
|
onSwipe: typeof onSwipe;
|
|
42
|
+
onPan: typeof onPan;
|
|
19
43
|
};
|
|
20
44
|
export default _default;
|
|
21
|
-
export { onSwipe };
|
|
45
|
+
export { onSwipe, onPan };
|
package/viewers/ImageViewer.vue
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
<template>
|
|
3
3
|
<div ref="rootEl" @mousemove="onMouseMove" class="full-page" @keyup.esc.stop="onClose" @keyup.left.stop="onPrev" @keyup.right.stop="onNext" tabindex="1" :style="{ opacity: dismissDistance ? (200 - dismissDistance)/100 : 1 }">
|
|
4
4
|
<div class="image-layer" ref="imageContainer" @scroll="onScroll($event)">
|
|
5
|
-
<div class="image" v-for="(
|
|
6
|
-
<img :src="
|
|
7
|
-
<video v-if="getFileTypeGroup(
|
|
8
|
-
<source :src="
|
|
5
|
+
<div class="image" v-for="(imageEntry, index) in entries" :key="imageEntry.id" :id="`image${index}`" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd" :style="{ marginTop: dismissDistance ? (dismissDistance + 'px') : 0 }">
|
|
6
|
+
<img :src="imageEntry.fullFileUrl" v-if="getFileTypeGroup(imageEntry) === 'image'" draggable="false" @load="onImageLoad(index)"/>
|
|
7
|
+
<video v-if="getFileTypeGroup(imageEntry) !== 'image'" :ref="addVideoRef" controls>
|
|
8
|
+
<source :src="imageEntry.fullFileUrl" />
|
|
9
9
|
</video>
|
|
10
10
|
</div>
|
|
11
11
|
</div>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<script setup>
|
|
21
21
|
|
|
22
|
-
import { ref, computed, onBeforeUpdate,
|
|
22
|
+
import { ref, computed, onBeforeUpdate, useTemplateRef } from 'vue';
|
|
23
23
|
import { getFileTypeGroup } from '../utils.js';
|
|
24
24
|
import Button from '../components/Button.vue';
|
|
25
25
|
|
|
@@ -129,6 +129,18 @@ function onTouchEnd() {
|
|
|
129
129
|
dismissDistance.value = 0;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
function scrollToCurrent(behavior) {
|
|
133
|
+
const el = document.getElementById(`image${curIndex.value}`);
|
|
134
|
+
if (el && imageContainer.value) imageContainer.value.scrollTo({ left: el.offsetLeft, behavior });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function onImageLoad(index) {
|
|
138
|
+
if (lockScroll.value && index === curIndex.value) {
|
|
139
|
+
scrollToCurrent('auto');
|
|
140
|
+
lockScroll.value = false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
132
144
|
function open(e, fileList, instant = true) {
|
|
133
145
|
if (!e || e.isDirectory || !canHandle(e)) return;
|
|
134
146
|
|
|
@@ -139,8 +151,8 @@ function open(e, fileList, instant = true) {
|
|
|
139
151
|
curIndex.value = fileList.findIndex((item) => item.fileName === e.fileName);
|
|
140
152
|
|
|
141
153
|
setTimeout(() => {
|
|
142
|
-
|
|
143
|
-
rootEl.value.focus();
|
|
154
|
+
scrollToCurrent(instant ? 'auto' : 'smooth');
|
|
155
|
+
rootEl.value.focus({ preventScroll: true });
|
|
144
156
|
}, 0);
|
|
145
157
|
|
|
146
158
|
setTimeout(() => {
|
|
@@ -156,7 +168,7 @@ function onDownload() {
|
|
|
156
168
|
function onClose() {
|
|
157
169
|
emit('close');
|
|
158
170
|
|
|
159
|
-
if (videoElements.value && videoElements.value.length) videoElements.value.forEach(e => e.pause());
|
|
171
|
+
if (videoElements.value && videoElements.value.length) videoElements.value.forEach((e) => { e.pause(); });
|
|
160
172
|
|
|
161
173
|
setTimeout(() => {
|
|
162
174
|
entry.value = {};
|
|
@@ -218,12 +230,19 @@ defineExpose({ canHandle, open });
|
|
|
218
230
|
|
|
219
231
|
.image-layer {
|
|
220
232
|
display: flex;
|
|
221
|
-
position:
|
|
233
|
+
position: absolute;
|
|
234
|
+
top: 0;
|
|
235
|
+
left: 0;
|
|
222
236
|
height: 100%;
|
|
223
237
|
width: 100%;
|
|
224
238
|
overflow-y: hidden;
|
|
225
239
|
overflow-x: auto;
|
|
226
240
|
scroll-snap-type: x mandatory;
|
|
241
|
+
scrollbar-width: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.image-layer::-webkit-scrollbar {
|
|
245
|
+
display: none;
|
|
227
246
|
}
|
|
228
247
|
|
|
229
248
|
.image {
|
|
@@ -237,6 +256,7 @@ defineExpose({ canHandle, open });
|
|
|
237
256
|
}
|
|
238
257
|
|
|
239
258
|
img, video {
|
|
259
|
+
display: block;
|
|
240
260
|
object-fit: contain;
|
|
241
261
|
width: 100%;
|
|
242
262
|
height: 100%;
|