@l1yp/file-viewer 0.1.2 → 0.1.4
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/dist/components/ArchiveBrowser.js +1 -1
- package/dist/components/ArchiveBrowser.js.map +1 -1
- package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js +105 -94
- package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerBody.js +1 -1
- package/dist/components/FileViewerBody.js.map +1 -1
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ImageLightbox.js +1 -1
- package/dist/components/ImageLightbox.js.map +1 -1
- package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +101 -55
- package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/JsonlViewer.js +1 -1
- package/dist/components/JsonlViewer.js.map +1 -1
- package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/LightboxGalleryStrip.js +9 -0
- package/dist/components/LightboxGalleryStrip.js.map +1 -0
- package/dist/components/LightboxGalleryStrip.vue.d.ts +12 -0
- package/dist/components/LightboxGalleryStrip.vue_vue_type_script_setup_true_lang.js +60 -0
- package/dist/components/LightboxGalleryStrip.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/PdfNavigationPane.js +9 -0
- package/dist/components/PdfNavigationPane.js.map +1 -0
- package/dist/components/PdfNavigationPane.vue.d.ts +24 -0
- package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js +149 -0
- package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/PdfViewer.js +1 -1
- package/dist/components/PdfViewer.js.map +1 -1
- package/dist/components/PdfViewer.vue.d.ts +0 -11
- package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +131 -69
- package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/SevenZipBrowser.js +1 -1
- package/dist/components/SevenZipBrowser.js.map +1 -1
- package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +157 -146
- package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ZipBrowser.js +1 -1
- package/dist/components/ZipBrowser.js.map +1 -1
- package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +70 -61
- package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ZoomImage.js +1 -1
- package/dist/components/ZoomImage.js.map +1 -1
- package/dist/components/ZoomImage.vue.d.ts +2 -0
- package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +104 -95
- package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/useArchiveImageGallery.d.ts +15 -0
- package/dist/composables/useArchiveImageGallery.js +62 -0
- package/dist/composables/useArchiveImageGallery.js.map +1 -0
- package/dist/composables/useLightbox.d.ts +29 -21
- package/dist/composables/useLightbox.js +72 -12
- package/dist/composables/useLightbox.js.map +1 -1
- package/dist/lib/archiveImages.d.ts +14 -0
- package/dist/lib/archiveImages.js +15 -0
- package/dist/lib/archiveImages.js.map +1 -0
- package/dist/lib/pdfNavigation.d.ts +11 -0
- package/dist/lib/pdfNavigation.js +32 -0
- package/dist/lib/pdfNavigation.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createElementBlock as e, createElementVNode as t, defineComponent as n, normalizeClass as r, normalizeStyle as i, onBeforeUnmount as a, onMounted as o, openBlock as s,
|
|
1
|
+
import { createElementBlock as e, createElementVNode as t, defineComponent as n, normalizeClass as r, normalizeStyle as i, onBeforeUnmount as a, onMounted as o, openBlock as s, shallowRef as c, toDisplayString as l, useTemplateRef as u, watch as d, withModifiers as f } from "vue";
|
|
2
2
|
//#region src/components/ZoomImage.vue?vue&type=script&setup=true&lang.ts
|
|
3
|
-
var
|
|
3
|
+
var p = { class: "zi__center" }, m = ["src", "alt"], h = 24, g = /*@__PURE__*/ n({
|
|
4
4
|
__name: "ZoomImage",
|
|
5
5
|
props: {
|
|
6
6
|
src: {},
|
|
@@ -8,142 +8,151 @@ var f = ["src", "alt"], p = /*@__PURE__*/ n({
|
|
|
8
8
|
long: {
|
|
9
9
|
type: Boolean,
|
|
10
10
|
default: !1
|
|
11
|
-
}
|
|
11
|
+
},
|
|
12
|
+
toolbarBottom: { default: 14 }
|
|
12
13
|
},
|
|
13
14
|
emits: ["emptyClick"],
|
|
14
|
-
setup(n, { expose:
|
|
15
|
-
let
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
function
|
|
20
|
-
let e =
|
|
21
|
-
return e
|
|
15
|
+
setup(n, { expose: g, emit: _ }) {
|
|
16
|
+
let v = n, y = _, b = u("stageEl"), x = u("imgEl"), S = c(0), C = c(0), w = c(1), T = c(0), E = c(0), D = c(1), O = c(!1), k = c(100);
|
|
17
|
+
function A() {
|
|
18
|
+
k.value = Math.round(w.value * 100);
|
|
19
|
+
}
|
|
20
|
+
function j() {
|
|
21
|
+
let e = b.value;
|
|
22
|
+
return !e || !e.clientWidth || !e.clientHeight ? null : {
|
|
22
23
|
w: e.clientWidth,
|
|
23
24
|
h: e.clientHeight
|
|
24
|
-
} : {
|
|
25
|
-
w: 1024,
|
|
26
|
-
h: 768
|
|
27
25
|
};
|
|
28
26
|
}
|
|
29
|
-
function k() {
|
|
30
|
-
return Math.max(.05, Math.min(w.value, 1) * .5);
|
|
31
|
-
}
|
|
32
|
-
function A() {
|
|
33
|
-
return Math.max(w.value, 1) * 8;
|
|
34
|
-
}
|
|
35
|
-
function j(e = !1) {
|
|
36
|
-
let { w: t, h: n } = O(), r = y.value * x.value, i = b.value * x.value;
|
|
37
|
-
S.value = r <= t ? (t - r) / 2 : Math.min(0, Math.max(t - r, S.value)), i <= n ? C.value = (n - i) / 2 : e ? C.value = 0 : C.value = Math.min(0, Math.max(n - i, C.value)), D();
|
|
38
|
-
}
|
|
39
27
|
function M() {
|
|
40
|
-
|
|
41
|
-
let { w: e, h: t } = O();
|
|
42
|
-
w.value = Math.min(e / y.value, t / b.value), x.value = h.long ? e * .98 / y.value : Math.min(w.value, 1), j(h.long);
|
|
28
|
+
return Math.max(.05, Math.min(D.value, 1) * .5);
|
|
43
29
|
}
|
|
44
|
-
function N(
|
|
45
|
-
return Math.
|
|
30
|
+
function N() {
|
|
31
|
+
return Math.max(D.value, 1) * 8;
|
|
46
32
|
}
|
|
47
|
-
function P(e
|
|
48
|
-
let
|
|
49
|
-
if (
|
|
50
|
-
let
|
|
51
|
-
|
|
33
|
+
function P(e = !1) {
|
|
34
|
+
let t = j();
|
|
35
|
+
if (!t) return;
|
|
36
|
+
let { w: n, h: r } = t, i = S.value * w.value, a = C.value * w.value, o = Math.max(0, (i - n) / 2), s = Math.max(0, (a - r) / 2);
|
|
37
|
+
T.value = i <= n ? 0 : Math.min(o, Math.max(-o, T.value)), a <= r ? E.value = 0 : e ? E.value = s : E.value = Math.min(s, Math.max(-s, E.value)), A();
|
|
52
38
|
}
|
|
53
|
-
function F(
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
function F() {
|
|
40
|
+
if (!S.value || !C.value) return !1;
|
|
41
|
+
let e = j();
|
|
42
|
+
if (!e) return !1;
|
|
43
|
+
let { w: t, h: n } = e, r = v.long ? 0 : h * 2, i = Math.max(1, t - r), a = Math.max(1, n - r);
|
|
44
|
+
return D.value = Math.min(i / S.value, a / C.value), w.value = v.long ? t * .98 / S.value : Math.min(D.value, 1), T.value = 0, E.value = 0, P(v.long), !0;
|
|
56
45
|
}
|
|
57
46
|
function I(e) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
47
|
+
return Math.min(N(), Math.max(M(), e));
|
|
48
|
+
}
|
|
49
|
+
function L(e, t, n) {
|
|
50
|
+
let r = j();
|
|
51
|
+
if (!r) return;
|
|
52
|
+
let i = I(w.value * n);
|
|
53
|
+
if (i === w.value) return;
|
|
54
|
+
let a = (e - r.w / 2 - T.value) / w.value, o = (t - r.h / 2 - E.value) / w.value;
|
|
55
|
+
w.value = i, T.value = e - r.w / 2 - a * i, E.value = t - r.h / 2 - o * i, P(!1);
|
|
56
|
+
}
|
|
57
|
+
function R(e) {
|
|
58
|
+
let t = j();
|
|
59
|
+
if (!t) return;
|
|
60
|
+
let { w: n, h: r } = t;
|
|
61
|
+
L(n / 2, r / 2, e);
|
|
62
|
+
}
|
|
63
|
+
function z(e) {
|
|
64
|
+
let t = b.value?.getBoundingClientRect();
|
|
65
|
+
L(t ? e.clientX - t.left : e.clientX, t ? e.clientY - t.top : e.clientY, e.deltaY < 0 ? 1.15 : 1 / 1.15);
|
|
66
|
+
}
|
|
67
|
+
function B(e) {
|
|
68
|
+
let t = b.value?.getBoundingClientRect(), n = t ? e.clientX - t.left : e.clientX, r = t ? e.clientY - t.top : e.clientY;
|
|
69
|
+
w.value <= D.value * 1.05 ? L(n, r, Math.max(2, 1 / Math.max(D.value, .001))) : F();
|
|
70
|
+
}
|
|
71
|
+
let V = !1, H = !1, U = 0, W = 0, G = null;
|
|
72
|
+
function K(e) {
|
|
73
|
+
e.button === 0 && (V = !0, H = !1, U = e.clientX, W = e.clientY, G = e.target, b.value?.setPointerCapture(e.pointerId));
|
|
74
|
+
}
|
|
75
|
+
function q(e) {
|
|
76
|
+
if (!V) return;
|
|
77
|
+
let t = e.clientX - U, n = e.clientY - W;
|
|
78
|
+
!H && Math.abs(t) + Math.abs(n) > 3 && (H = !0), U = e.clientX, W = e.clientY, T.value += t, E.value += n, P(!1);
|
|
79
|
+
}
|
|
80
|
+
function J(e) {
|
|
81
|
+
if (V) {
|
|
82
|
+
V = !1;
|
|
77
83
|
try {
|
|
78
|
-
|
|
84
|
+
b.value?.releasePointerCapture(e.pointerId);
|
|
79
85
|
} catch {}
|
|
80
|
-
!
|
|
86
|
+
!H && G !== x.value && y("emptyClick");
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
|
-
function
|
|
84
|
-
let e =
|
|
85
|
-
e && (
|
|
89
|
+
function Y() {
|
|
90
|
+
let e = x.value;
|
|
91
|
+
e && (S.value = e.naturalWidth || 1, C.value = e.naturalHeight || 1, O.value = F());
|
|
86
92
|
}
|
|
87
|
-
let
|
|
88
|
-
function
|
|
89
|
-
|
|
93
|
+
let X = null;
|
|
94
|
+
function Z() {
|
|
95
|
+
S.value && C.value && (O.value = F());
|
|
90
96
|
}
|
|
91
|
-
return
|
|
92
|
-
|
|
97
|
+
return d(() => v.src, () => {
|
|
98
|
+
O.value = !1, S.value = 0, C.value = 0, w.value = 1, T.value = 0, E.value = 0, D.value = 1, A();
|
|
93
99
|
}), o(() => {
|
|
94
|
-
typeof ResizeObserver < "u" &&
|
|
95
|
-
}), a(() =>
|
|
96
|
-
zoomIn: () =>
|
|
97
|
-
zoomOut: () =>
|
|
98
|
-
fit: () =>
|
|
100
|
+
typeof ResizeObserver < "u" && b.value && (X = new ResizeObserver(Z), X.observe(b.value));
|
|
101
|
+
}), a(() => X?.disconnect()), g({
|
|
102
|
+
zoomIn: () => R(1.2),
|
|
103
|
+
zoomOut: () => R(1 / 1.2),
|
|
104
|
+
fit: () => {
|
|
105
|
+
F();
|
|
106
|
+
}
|
|
99
107
|
}), (a, o) => (s(), e("div", {
|
|
100
108
|
ref_key: "stageEl",
|
|
101
|
-
ref:
|
|
102
|
-
class: r(["zi", { "zi--grab":
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
ref: b,
|
|
110
|
+
class: r(["zi", { "zi--grab": w.value > D.value }]),
|
|
111
|
+
style: i({ "--zi-toolbar-bottom": `${n.toolbarBottom}px` }),
|
|
112
|
+
onWheel: f(z, ["prevent"]),
|
|
113
|
+
onPointerdown: K,
|
|
114
|
+
onPointermove: q,
|
|
115
|
+
onPointerup: J,
|
|
116
|
+
onPointercancel: J,
|
|
117
|
+
onDblclick: B
|
|
118
|
+
}, [t("div", p, [t("img", {
|
|
110
119
|
ref_key: "imgEl",
|
|
111
|
-
ref:
|
|
112
|
-
class: r(["zi__img", { "zi__img--ready":
|
|
120
|
+
ref: x,
|
|
121
|
+
class: r(["zi__img", { "zi__img--ready": O.value }]),
|
|
113
122
|
src: n.src,
|
|
114
123
|
alt: n.alt,
|
|
115
124
|
draggable: "false",
|
|
116
|
-
style: i({ transform: `translate(${
|
|
117
|
-
onLoad:
|
|
118
|
-
onDragstart: o[0] ||=
|
|
119
|
-
}, null, 46,
|
|
125
|
+
style: i({ transform: `translate(${T.value}px, ${E.value}px) scale(${w.value})` }),
|
|
126
|
+
onLoad: Y,
|
|
127
|
+
onDragstart: o[0] ||= f(() => {}, ["prevent"])
|
|
128
|
+
}, null, 46, m)]), t("div", {
|
|
120
129
|
class: "zi__toolbar",
|
|
121
|
-
onPointerdown: o[4] ||=
|
|
122
|
-
onDblclick: o[5] ||=
|
|
123
|
-
onWheel: o[6] ||=
|
|
130
|
+
onPointerdown: o[4] ||= f(() => {}, ["stop"]),
|
|
131
|
+
onDblclick: o[5] ||= f(() => {}, ["stop"]),
|
|
132
|
+
onWheel: o[6] ||= f(() => {}, ["stop"])
|
|
124
133
|
}, [
|
|
125
134
|
t("button", {
|
|
126
135
|
type: "button",
|
|
127
136
|
class: "zi__tool",
|
|
128
137
|
title: "缩小 (-)",
|
|
129
|
-
onClick: o[1] ||= (e) =>
|
|
138
|
+
onClick: o[1] ||= (e) => R(1 / 1.2)
|
|
130
139
|
}, " − "),
|
|
131
140
|
t("button", {
|
|
132
141
|
type: "button",
|
|
133
142
|
class: "zi__pct",
|
|
134
143
|
title: "适应窗口 (0)",
|
|
135
|
-
onClick: o[2] ||= (e) =>
|
|
136
|
-
}, l(
|
|
144
|
+
onClick: o[2] ||= (e) => F()
|
|
145
|
+
}, l(k.value) + "% ", 1),
|
|
137
146
|
t("button", {
|
|
138
147
|
type: "button",
|
|
139
148
|
class: "zi__tool",
|
|
140
149
|
title: "放大 (+)",
|
|
141
|
-
onClick: o[3] ||= (e) =>
|
|
150
|
+
onClick: o[3] ||= (e) => R(1.2)
|
|
142
151
|
}, "+")
|
|
143
|
-
], 32)],
|
|
152
|
+
], 32)], 38));
|
|
144
153
|
}
|
|
145
154
|
});
|
|
146
155
|
//#endregion
|
|
147
|
-
export {
|
|
156
|
+
export { g as default };
|
|
148
157
|
|
|
149
158
|
//# sourceMappingURL=ZoomImage.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZoomImage.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/ZoomImage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\n/**\n * A pan + zoom image stage, shared by the full-screen {@link ImageLightbox} and the in-archive image preview\n * ({@link ZipBrowser}/{@link SevenZipBrowser}/{@link ArchiveBrowser}). Fills its parent (which must give it a\n * size). Wheel zooms toward the cursor, drag pans, double-click toggles fit↔2×, and a bottom toolbar offers\n * −/百分比/+. Initial fit is \"contain\" for a normal image, or fill-width-top-aligned for a `long` one (a tall\n * screenshot) so it reads top-down and pans vertically. A no-drag click on empty area emits `emptyClick` (the\n * lightbox closes on it; the archive preview ignores it).\n *\n * Exposes `zoomIn`/`zoomOut`/`fit` so a host can wire keyboard shortcuts to them.\n */\nconst props = withDefaults(defineProps<{ src: string; alt?: string; long?: boolean }>(), {\n alt: '',\n long: false,\n})\nconst emit = defineEmits<{ emptyClick: [] }>()\n\nconst stageEl = ref<HTMLElement | null>(null)\nconst imgEl = ref<HTMLImageElement | null>(null)\nconst natW = ref(0)\nconst natH = ref(0)\nconst scale = ref(1) // CSS px per image px (1 = 100%)\nconst tx = ref(0) // translate of the image's top-left within the stage\nconst ty = ref(0)\nconst fitScale = ref(1) // scale that \"contains\" the image in the stage\nconst ready = ref(false) // image measured → show it (avoids a pre-fit flash)\n\nconst percent = ref(100)\nfunction syncPercent(): void {\n percent.value = Math.round(scale.value * 100)\n}\n\nfunction stageSize(): { w: number; h: number } {\n const el = stageEl.value\n if (el && el.clientWidth) return { w: el.clientWidth, h: el.clientHeight }\n return { w: 1024, h: 768 }\n}\nfunction minScale(): number {\n return Math.max(0.05, Math.min(fitScale.value, 1) * 0.5)\n}\nfunction maxScale(): number {\n return Math.max(fitScale.value, 1) * 8\n}\n\n// Keep the image within the stage: center an axis whose content is smaller than the stage, otherwise clamp so\n// its edges can't be dragged inside the viewport. `topAlign` pins a too-tall image to its top (initial long view).\nfunction clampView(topAlign = false): void {\n const { w, h } = stageSize()\n const cw = natW.value * scale.value\n const ch = natH.value * scale.value\n tx.value = cw <= w ? (w - cw) / 2 : Math.min(0, Math.max(w - cw, tx.value))\n if (ch <= h) ty.value = (h - ch) / 2\n else if (topAlign) ty.value = 0\n else ty.value = Math.min(0, Math.max(h - ch, ty.value))\n syncPercent()\n}\n\n// (Re)fit the measured image: contain it, or — for a long image — fill the width and pin to the top.\nfunction fitView(): void {\n if (!natW.value || !natH.value) return\n const { w, h } = stageSize()\n fitScale.value = Math.min(w / natW.value, h / natH.value)\n scale.value = props.long ? (w * 0.98) / natW.value : Math.min(fitScale.value, 1)\n clampView(props.long)\n}\n\nfunction clampScale(s: number): number {\n return Math.min(maxScale(), Math.max(minScale(), s))\n}\n\n// Zoom keeping the image point under (cx, cy) — stage-relative coords — fixed in place.\nfunction zoomAt(cx: number, cy: number, factor: number): void {\n const next = clampScale(scale.value * factor)\n if (next === scale.value) return\n const ix = (cx - tx.value) / scale.value\n const iy = (cy - ty.value) / scale.value\n scale.value = next\n tx.value = cx - ix * next\n ty.value = cy - iy * next\n clampView(false)\n}\nfunction zoomCenter(factor: number): void {\n const { w, h } = stageSize()\n zoomAt(w / 2, h / 2, factor)\n}\n\nfunction onWheel(e: WheelEvent): void {\n const rect = stageEl.value?.getBoundingClientRect()\n const cx = rect ? e.clientX - rect.left : e.clientX\n const cy = rect ? e.clientY - rect.top : e.clientY\n zoomAt(cx, cy, e.deltaY < 0 ? 1.15 : 1 / 1.15)\n}\nfunction onDblClick(e: MouseEvent): void {\n const rect = stageEl.value?.getBoundingClientRect()\n const cx = rect ? e.clientX - rect.left : e.clientX\n const cy = rect ? e.clientY - rect.top : e.clientY\n // Near the fit scale → zoom in toward the cursor; otherwise snap back to fit.\n if (scale.value <= fitScale.value * 1.05)\n zoomAt(cx, cy, Math.max(2, 1 / Math.max(fitScale.value, 0.001)))\n else fitView()\n}\n\n// Pointer drag → pan (covers mouse + touch). A no-move pointerup on empty area emits `emptyClick`.\nlet dragging = false\nlet moved = false\nlet lastX = 0\nlet lastY = 0\nlet downTarget: EventTarget | null = null\n\nfunction onPointerDown(e: PointerEvent): void {\n if (e.button !== 0) return\n dragging = true\n moved = false\n lastX = e.clientX\n lastY = e.clientY\n downTarget = e.target\n stageEl.value?.setPointerCapture(e.pointerId)\n}\nfunction onPointerMove(e: PointerEvent): void {\n if (!dragging) return\n const dx = e.clientX - lastX\n const dy = e.clientY - lastY\n if (!moved && Math.abs(dx) + Math.abs(dy) > 3) moved = true\n lastX = e.clientX\n lastY = e.clientY\n tx.value += dx\n ty.value += dy\n clampView(false)\n}\nfunction onPointerUp(e: PointerEvent): void {\n if (!dragging) return\n dragging = false\n try {\n stageEl.value?.releasePointerCapture(e.pointerId)\n } catch {\n /* capture already released — ignore */\n }\n if (!moved && downTarget !== imgEl.value) emit('emptyClick')\n}\n\nfunction onImgLoad(): void {\n const img = imgEl.value\n if (!img) return\n natW.value = img.naturalWidth || 1\n natH.value = img.naturalHeight || 1\n fitView()\n ready.value = true\n}\n\n// Re-fit when the container is resized (the side panel's drag splitter, or the window).\nlet ro: ResizeObserver | null = null\nfunction onResize(): void {\n if (ready.value) fitView()\n}\n\n// Reset for a new source; the transform is recomputed on the <img>'s load.\nwatch(\n () => props.src,\n () => {\n ready.value = false\n natW.value = 0\n natH.value = 0\n scale.value = 1\n tx.value = 0\n ty.value = 0\n fitScale.value = 1\n syncPercent()\n },\n)\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined' && stageEl.value) {\n ro = new ResizeObserver(onResize)\n ro.observe(stageEl.value)\n }\n})\nonBeforeUnmount(() => ro?.disconnect())\n\ndefineExpose({\n zoomIn: () => zoomCenter(1.2),\n zoomOut: () => zoomCenter(1 / 1.2),\n fit: () => fitView(),\n})\n</script>\n\n<template>\n <div\n ref=\"stageEl\"\n class=\"zi\"\n :class=\"{ 'zi--grab': scale > fitScale }\"\n @wheel.prevent=\"onWheel\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerUp\"\n @dblclick=\"onDblClick\"\n >\n <img\n ref=\"imgEl\"\n class=\"zi__img\"\n :class=\"{ 'zi__img--ready': ready }\"\n :src=\"src\"\n :alt=\"alt\"\n draggable=\"false\"\n :style=\"{ transform: `translate(${tx}px, ${ty}px) scale(${scale})` }\"\n @load=\"onImgLoad\"\n @dragstart.prevent\n />\n <div class=\"zi__toolbar\" @pointerdown.stop @dblclick.stop @wheel.stop>\n <button type=\"button\" class=\"zi__tool\" title=\"缩小 (-)\" @click=\"zoomCenter(1 / 1.2)\">\n −\n </button>\n <button type=\"button\" class=\"zi__pct\" title=\"适应窗口 (0)\" @click=\"fitView()\">\n {{ percent }}%\n </button>\n <button type=\"button\" class=\"zi__tool\" title=\"放大 (+)\" @click=\"zoomCenter(1.2)\">+</button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.zi {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n cursor: zoom-out;\n touch-action: none;\n}\n.zi--grab {\n cursor: grab;\n}\n.zi--grab:active {\n cursor: grabbing;\n}\n.zi__img {\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n will-change: transform;\n user-select: none;\n -webkit-user-drag: none;\n opacity: 0;\n}\n.zi__img--ready {\n opacity: 1;\n}\n.zi__toolbar {\n position: absolute;\n bottom: 14px;\n left: 50%;\n transform: translateX(-50%);\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 4px;\n border-radius: 12px;\n background: rgba(28, 28, 30, 0.82);\n backdrop-filter: blur(8px);\n box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);\n}\n.zi__tool {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 32px;\n border: none;\n border-radius: 9px;\n background: transparent;\n color: #fff;\n font-size: 19px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.zi__tool:hover {\n background: rgba(255, 255, 255, 0.16);\n}\n.zi__pct {\n min-width: 56px;\n height: 32px;\n padding: 0 8px;\n border: none;\n border-radius: 9px;\n background: transparent;\n color: #fff;\n font-family: inherit;\n font-size: 12.5px;\n font-variant-numeric: tabular-nums;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.zi__pct:hover {\n background: rgba(255, 255, 255, 0.16);\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ZoomImage.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/ZoomImage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue'\n\n/**\n * A pan + zoom image stage, shared by the full-screen {@link ImageLightbox} and the in-archive image preview\n * ({@link ZipBrowser}/{@link SevenZipBrowser}/{@link ArchiveBrowser}). Fills its parent (which must give it a\n * size). Wheel zooms toward the cursor, drag pans, double-click toggles fit↔2×, and a bottom toolbar offers\n * −/百分比/+. Initial fit is \"contain\" for a normal image, or fill-width-top-aligned for a `long` one (a tall\n * screenshot) so it reads top-down and pans vertically. A no-drag click on empty area emits `emptyClick` (the\n * lightbox closes on it; the archive preview ignores it).\n *\n * Exposes `zoomIn`/`zoomOut`/`fit` so a host can wire keyboard shortcuts to them.\n */\nconst props = withDefaults(\n defineProps<{ src: string; alt?: string; long?: boolean; toolbarBottom?: number }>(),\n {\n alt: '',\n long: false,\n toolbarBottom: 14,\n },\n)\nconst emit = defineEmits<{ emptyClick: [] }>()\n\nconst stageEl = useTemplateRef<HTMLElement>('stageEl')\nconst imgEl = useTemplateRef<HTMLImageElement>('imgEl')\nconst natW = shallowRef(0)\nconst natH = shallowRef(0)\nconst scale = shallowRef(1) // CSS px per image px (1 = 100%)\nconst tx = shallowRef(0) // pan offset from the stage's centered image position\nconst ty = shallowRef(0)\nconst fitScale = shallowRef(1) // scale that \"contains\" the image in the stage\nconst ready = shallowRef(false) // image measured → show it (avoids a pre-fit flash)\n\nconst percent = shallowRef(100)\nconst FIT_PADDING = 24\nfunction syncPercent(): void {\n percent.value = Math.round(scale.value * 100)\n}\n\nfunction stageSize(): { w: number; h: number } | null {\n const el = stageEl.value\n if (!el || !el.clientWidth || !el.clientHeight) return null\n return { w: el.clientWidth, h: el.clientHeight }\n}\nfunction minScale(): number {\n return Math.max(0.05, Math.min(fitScale.value, 1) * 0.5)\n}\nfunction maxScale(): number {\n return Math.max(fitScale.value, 1) * 8\n}\n\n// Keep the image within the stage: reset an axis whose content is smaller than the stage to the centered base,\n// otherwise clamp its center-relative pan offset. `topAlign` pins a too-tall image to its top (initial long view).\nfunction clampView(topAlign = false): void {\n const size = stageSize()\n if (!size) return\n const { w, h } = size\n const cw = natW.value * scale.value\n const ch = natH.value * scale.value\n const maxX = Math.max(0, (cw - w) / 2)\n const maxY = Math.max(0, (ch - h) / 2)\n tx.value = cw <= w ? 0 : Math.min(maxX, Math.max(-maxX, tx.value))\n if (ch <= h) ty.value = 0\n else if (topAlign) ty.value = maxY\n else ty.value = Math.min(maxY, Math.max(-maxY, ty.value))\n syncPercent()\n}\n\n// (Re)fit the measured image: contain it, or — for a long image — fill the width and pin to the top.\nfunction fitView(): boolean {\n if (!natW.value || !natH.value) return false\n const size = stageSize()\n if (!size) return false\n const { w, h } = size\n const padding = props.long ? 0 : FIT_PADDING * 2\n const availableWidth = Math.max(1, w - padding)\n const availableHeight = Math.max(1, h - padding)\n fitScale.value = Math.min(availableWidth / natW.value, availableHeight / natH.value)\n scale.value = props.long ? (w * 0.98) / natW.value : Math.min(fitScale.value, 1)\n tx.value = 0\n ty.value = 0\n clampView(props.long)\n return true\n}\n\nfunction clampScale(s: number): number {\n return Math.min(maxScale(), Math.max(minScale(), s))\n}\n\n// Zoom keeping the image point under (cx, cy) — stage-relative coords — fixed in place.\nfunction zoomAt(cx: number, cy: number, factor: number): void {\n const size = stageSize()\n if (!size) return\n const next = clampScale(scale.value * factor)\n if (next === scale.value) return\n const ix = (cx - size.w / 2 - tx.value) / scale.value\n const iy = (cy - size.h / 2 - ty.value) / scale.value\n scale.value = next\n tx.value = cx - size.w / 2 - ix * next\n ty.value = cy - size.h / 2 - iy * next\n clampView(false)\n}\nfunction zoomCenter(factor: number): void {\n const size = stageSize()\n if (!size) return\n const { w, h } = size\n zoomAt(w / 2, h / 2, factor)\n}\n\nfunction onWheel(e: WheelEvent): void {\n const rect = stageEl.value?.getBoundingClientRect()\n const cx = rect ? e.clientX - rect.left : e.clientX\n const cy = rect ? e.clientY - rect.top : e.clientY\n zoomAt(cx, cy, e.deltaY < 0 ? 1.15 : 1 / 1.15)\n}\nfunction onDblClick(e: MouseEvent): void {\n const rect = stageEl.value?.getBoundingClientRect()\n const cx = rect ? e.clientX - rect.left : e.clientX\n const cy = rect ? e.clientY - rect.top : e.clientY\n // Near the fit scale → zoom in toward the cursor; otherwise snap back to fit.\n if (scale.value <= fitScale.value * 1.05)\n zoomAt(cx, cy, Math.max(2, 1 / Math.max(fitScale.value, 0.001)))\n else fitView()\n}\n\n// Pointer drag → pan (covers mouse + touch). A no-move pointerup on empty area emits `emptyClick`.\nlet dragging = false\nlet moved = false\nlet lastX = 0\nlet lastY = 0\nlet downTarget: EventTarget | null = null\n\nfunction onPointerDown(e: PointerEvent): void {\n if (e.button !== 0) return\n dragging = true\n moved = false\n lastX = e.clientX\n lastY = e.clientY\n downTarget = e.target\n stageEl.value?.setPointerCapture(e.pointerId)\n}\nfunction onPointerMove(e: PointerEvent): void {\n if (!dragging) return\n const dx = e.clientX - lastX\n const dy = e.clientY - lastY\n if (!moved && Math.abs(dx) + Math.abs(dy) > 3) moved = true\n lastX = e.clientX\n lastY = e.clientY\n tx.value += dx\n ty.value += dy\n clampView(false)\n}\nfunction onPointerUp(e: PointerEvent): void {\n if (!dragging) return\n dragging = false\n try {\n stageEl.value?.releasePointerCapture(e.pointerId)\n } catch {\n /* capture already released — ignore */\n }\n if (!moved && downTarget !== imgEl.value) emit('emptyClick')\n}\n\nfunction onImgLoad(): void {\n const img = imgEl.value\n if (!img) return\n natW.value = img.naturalWidth || 1\n natH.value = img.naturalHeight || 1\n ready.value = fitView()\n}\n\n// Re-fit when the container is resized (the side panel's drag splitter, or the window).\nlet ro: ResizeObserver | null = null\nfunction onResize(): void {\n if (natW.value && natH.value) ready.value = fitView()\n}\n\n// Reset for a new source; the transform is recomputed on the <img>'s load.\nwatch(\n () => props.src,\n () => {\n ready.value = false\n natW.value = 0\n natH.value = 0\n scale.value = 1\n tx.value = 0\n ty.value = 0\n fitScale.value = 1\n syncPercent()\n },\n)\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined' && stageEl.value) {\n ro = new ResizeObserver(onResize)\n ro.observe(stageEl.value)\n }\n})\nonBeforeUnmount(() => ro?.disconnect())\n\ndefineExpose({\n zoomIn: () => zoomCenter(1.2),\n zoomOut: () => zoomCenter(1 / 1.2),\n fit: () => {\n fitView()\n },\n})\n</script>\n\n<template>\n <div\n ref=\"stageEl\"\n class=\"zi\"\n :class=\"{ 'zi--grab': scale > fitScale }\"\n :style=\"{ '--zi-toolbar-bottom': `${toolbarBottom}px` }\"\n @wheel.prevent=\"onWheel\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerUp\"\n @dblclick=\"onDblClick\"\n >\n <div class=\"zi__center\">\n <img\n ref=\"imgEl\"\n class=\"zi__img\"\n :class=\"{ 'zi__img--ready': ready }\"\n :src=\"src\"\n :alt=\"alt\"\n draggable=\"false\"\n :style=\"{ transform: `translate(${tx}px, ${ty}px) scale(${scale})` }\"\n @load=\"onImgLoad\"\n @dragstart.prevent\n />\n </div>\n <div class=\"zi__toolbar\" @pointerdown.stop @dblclick.stop @wheel.stop>\n <button type=\"button\" class=\"zi__tool\" title=\"缩小 (-)\" @click=\"zoomCenter(1 / 1.2)\">\n −\n </button>\n <button type=\"button\" class=\"zi__pct\" title=\"适应窗口 (0)\" @click=\"fitView()\">\n {{ percent }}%\n </button>\n <button type=\"button\" class=\"zi__tool\" title=\"放大 (+)\" @click=\"zoomCenter(1.2)\">+</button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.zi {\n position: relative;\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n cursor: zoom-out;\n touch-action: none;\n}\n.zi--grab {\n cursor: grab;\n}\n.zi--grab:active {\n cursor: grabbing;\n}\n.zi__center {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n min-height: 0;\n}\n.zi__img {\n flex: none;\n max-width: none;\n max-height: none;\n transform-origin: center;\n will-change: transform;\n user-select: none;\n -webkit-user-drag: none;\n opacity: 0;\n}\n.zi__img--ready {\n opacity: 1;\n}\n.zi__toolbar {\n position: absolute;\n bottom: var(--zi-toolbar-bottom, 14px);\n left: 50%;\n transform: translateX(-50%);\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 4px;\n border-radius: 12px;\n background: rgba(28, 28, 30, 0.82);\n backdrop-filter: blur(8px);\n box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);\n}\n.zi__tool {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 32px;\n border: none;\n border-radius: 9px;\n background: transparent;\n color: #fff;\n font-size: 19px;\n line-height: 1;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.zi__tool:hover {\n background: rgba(255, 255, 255, 0.16);\n}\n.zi__pct {\n min-width: 56px;\n height: 32px;\n padding: 0 8px;\n border: none;\n border-radius: 9px;\n background: transparent;\n color: #fff;\n font-family: inherit;\n font-size: 12.5px;\n font-variant-numeric: tabular-nums;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n.zi__pct:hover {\n background: rgba(255, 255, 255, 0.16);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ArchivePathEntry } from '@/lib/archiveImages';
|
|
2
|
+
interface ArchiveImageGalleryOptions<T extends ArchivePathEntry> {
|
|
3
|
+
entries: () => readonly T[];
|
|
4
|
+
extract: (entry: T) => Uint8Array | Promise<Uint8Array>;
|
|
5
|
+
archiveName: () => string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Builds a temporary lightbox gallery from image entries inside one archive. The current image opens first;
|
|
9
|
+
* remaining images are extracted in flat-path order, and every owned Blob URL is revoked when the gallery closes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useArchiveImageGallery<T extends ArchivePathEntry>(options: ArchiveImageGalleryOptions<T>): {
|
|
12
|
+
open: (entry: T, bytes: Uint8Array) => Promise<void>;
|
|
13
|
+
clear: () => void;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { mediaMimeForName as e } from "../lib/viewerKind.js";
|
|
2
|
+
import { useLightbox as t } from "./useLightbox.js";
|
|
3
|
+
import { orderedArchiveImages as n } from "../lib/archiveImages.js";
|
|
4
|
+
import { onBeforeUnmount as r, watch as i } from "vue";
|
|
5
|
+
//#region src/composables/useArchiveImageGallery.ts
|
|
6
|
+
function a(a) {
|
|
7
|
+
let o = t(), s = /* @__PURE__ */ new Set(), c = 0;
|
|
8
|
+
function l(t, n) {
|
|
9
|
+
let r = URL.createObjectURL(new Blob([n], { type: e(t.entry.name) }));
|
|
10
|
+
return s.add(r), {
|
|
11
|
+
id: `${a.archiveName()}:${t.entry.path}#${t.sourceIndex}`,
|
|
12
|
+
src: r,
|
|
13
|
+
thumbSrc: r,
|
|
14
|
+
alt: t.entry.path
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function u() {
|
|
18
|
+
for (let e of s) URL.revokeObjectURL(e);
|
|
19
|
+
s.clear();
|
|
20
|
+
}
|
|
21
|
+
function d() {
|
|
22
|
+
let e = o.current.value;
|
|
23
|
+
e && s.has(e.src) && o.close(), c++, u();
|
|
24
|
+
}
|
|
25
|
+
async function f(e, t) {
|
|
26
|
+
let r = ++c;
|
|
27
|
+
u();
|
|
28
|
+
let i = n(a.entries()), s = i.findIndex((t) => t.entry === e);
|
|
29
|
+
if (s < 0) return;
|
|
30
|
+
let d = l(i[s], t);
|
|
31
|
+
o.open(d), await new Promise((e) => requestAnimationFrame(() => e()));
|
|
32
|
+
let f = [], p = 0;
|
|
33
|
+
for (let e = 0; e < i.length; e++) {
|
|
34
|
+
if (r !== c || !o.isOpen.value) return;
|
|
35
|
+
if (e === s) {
|
|
36
|
+
p = f.length, f.push(d);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
let t;
|
|
40
|
+
try {
|
|
41
|
+
t = await a.extract(i[e].entry);
|
|
42
|
+
} catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (r !== c || !o.isOpen.value) return;
|
|
46
|
+
f.push(l(i[e], t));
|
|
47
|
+
}
|
|
48
|
+
r === c && o.openGallery(f, p);
|
|
49
|
+
}
|
|
50
|
+
return i(o.current, (e) => {
|
|
51
|
+
e && s.has(e.src) || (c++, u());
|
|
52
|
+
}), r(() => {
|
|
53
|
+
d();
|
|
54
|
+
}), {
|
|
55
|
+
open: f,
|
|
56
|
+
clear: d
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { a as useArchiveImageGallery };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=useArchiveImageGallery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useArchiveImageGallery.js","names":[],"sources":["../../src/composables/useArchiveImageGallery.ts"],"sourcesContent":["import { onBeforeUnmount, watch } from 'vue'\nimport { useLightbox, type LightboxImage } from '@/composables/useLightbox'\nimport {\n orderedArchiveImages,\n type ArchivePathEntry,\n type OrderedArchiveImage,\n} from '@/lib/archiveImages'\nimport { mediaMimeForName } from '@/lib/viewerKind'\n\ninterface ArchiveImageGalleryOptions<T extends ArchivePathEntry> {\n entries: () => readonly T[]\n extract: (entry: T) => Uint8Array | Promise<Uint8Array>\n archiveName: () => string\n}\n\n/**\n * Builds a temporary lightbox gallery from image entries inside one archive. The current image opens first;\n * remaining images are extracted in flat-path order, and every owned Blob URL is revoked when the gallery closes.\n */\nexport function useArchiveImageGallery<T extends ArchivePathEntry>(\n options: ArchiveImageGalleryOptions<T>,\n): {\n open: (entry: T, bytes: Uint8Array) => Promise<void>\n clear: () => void\n} {\n const lightbox = useLightbox()\n const ownedUrls = new Set<string>()\n let run = 0\n\n function makeImage(item: OrderedArchiveImage<T>, bytes: Uint8Array): LightboxImage {\n const url = URL.createObjectURL(new Blob([bytes], { type: mediaMimeForName(item.entry.name) }))\n ownedUrls.add(url)\n return {\n id: `${options.archiveName()}:${item.entry.path}#${item.sourceIndex}`,\n src: url,\n thumbSrc: url,\n alt: item.entry.path,\n }\n }\n\n function revokeOwned(): void {\n for (const url of ownedUrls) URL.revokeObjectURL(url)\n ownedUrls.clear()\n }\n\n function clear(): void {\n const media = lightbox.current.value\n if (media && ownedUrls.has(media.src)) lightbox.close()\n run++\n revokeOwned()\n }\n\n async function open(entry: T, bytes: Uint8Array): Promise<void> {\n const token = ++run\n revokeOwned()\n const ordered = orderedArchiveImages(options.entries())\n const currentIndex = ordered.findIndex((item) => item.entry === entry)\n if (currentIndex < 0) return\n\n const current = makeImage(ordered[currentIndex], bytes)\n lightbox.open(current)\n await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))\n\n const gallery: LightboxImage[] = []\n let galleryCurrentIndex = 0\n for (let index = 0; index < ordered.length; index++) {\n if (token !== run || !lightbox.isOpen.value) return\n if (index === currentIndex) {\n galleryCurrentIndex = gallery.length\n gallery.push(current)\n continue\n }\n let imageBytes: Uint8Array\n try {\n imageBytes = await options.extract(ordered[index].entry)\n } catch {\n continue\n }\n if (token !== run || !lightbox.isOpen.value) return\n gallery.push(makeImage(ordered[index], imageBytes))\n }\n if (token === run) lightbox.openGallery(gallery, galleryCurrentIndex)\n }\n\n watch(lightbox.current, (media) => {\n if (media && ownedUrls.has(media.src)) return\n run++\n revokeOwned()\n })\n\n onBeforeUnmount(() => {\n clear()\n })\n\n return { open, clear }\n}\n"],"mappings":";;;;;AAmBA,SAAgB,EACd,GAIA;CACA,IAAM,IAAW,EAAY,GACvB,oBAAY,IAAI,IAAY,GAC9B,IAAM;CAEV,SAAS,EAAU,GAA8B,GAAkC;EACjF,IAAM,IAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,CAAK,GAAG,EAAE,MAAM,EAAiB,EAAK,MAAM,IAAI,EAAE,CAAC,CAAC;EAE9F,OADA,EAAU,IAAI,CAAG,GACV;GACL,IAAI,GAAG,EAAQ,YAAY,EAAE,GAAG,EAAK,MAAM,KAAK,GAAG,EAAK;GACxD,KAAK;GACL,UAAU;GACV,KAAK,EAAK,MAAM;EAClB;CACF;CAEA,SAAS,IAAoB;EAC3B,KAAK,IAAM,KAAO,GAAW,IAAI,gBAAgB,CAAG;EACpD,EAAU,MAAM;CAClB;CAEA,SAAS,IAAc;EACrB,IAAM,IAAQ,EAAS,QAAQ;EAG/B,AAFI,KAAS,EAAU,IAAI,EAAM,GAAG,KAAG,EAAS,MAAM,GACtD,KACA,EAAY;CACd;CAEA,eAAe,EAAK,GAAU,GAAkC;EAC9D,IAAM,IAAQ,EAAE;EAChB,EAAY;EACZ,IAAM,IAAU,EAAqB,EAAQ,QAAQ,CAAC,GAChD,IAAe,EAAQ,WAAW,MAAS,EAAK,UAAU,CAAK;EACrE,IAAI,IAAe,GAAG;EAEtB,IAAM,IAAU,EAAU,EAAQ,IAAe,CAAK;EAEtD,AADA,EAAS,KAAK,CAAO,GACrB,MAAM,IAAI,SAAe,MAAY,4BAA4B,EAAQ,CAAC,CAAC;EAE3E,IAAM,IAA2B,CAAC,GAC9B,IAAsB;EAC1B,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAQ,QAAQ,KAAS;GACnD,IAAI,MAAU,KAAO,CAAC,EAAS,OAAO,OAAO;GAC7C,IAAI,MAAU,GAAc;IAE1B,AADA,IAAsB,EAAQ,QAC9B,EAAQ,KAAK,CAAO;IACpB;GACF;GACA,IAAI;GACJ,IAAI;IACF,IAAa,MAAM,EAAQ,QAAQ,EAAQ,EAAM,CAAC,KAAK;GACzD,QAAQ;IACN;GACF;GACA,IAAI,MAAU,KAAO,CAAC,EAAS,OAAO,OAAO;GAC7C,EAAQ,KAAK,EAAU,EAAQ,IAAQ,CAAU,CAAC;EACpD;EACA,AAAI,MAAU,KAAK,EAAS,YAAY,GAAS,CAAmB;CACtE;CAYA,OAVA,EAAM,EAAS,UAAU,MAAU;EAC7B,KAAS,EAAU,IAAI,EAAM,GAAG,MACpC,KACA,EAAY;CACd,CAAC,GAED,QAAsB;EACpB,EAAM;CACR,CAAC,GAEM;EAAE;EAAM;CAAM;AACvB"}
|
|
@@ -1,33 +1,41 @@
|
|
|
1
1
|
/** Media shown full-screen in the lightbox: its source URL, an alt/title, and the kind to render. */
|
|
2
2
|
export interface LightboxImage {
|
|
3
|
+
/** Stable identity inside a gallery. Falls back to `src` when omitted. */
|
|
4
|
+
id?: string;
|
|
3
5
|
src: string;
|
|
6
|
+
/** Small image used by the gallery strip; falls back to `src`. */
|
|
7
|
+
thumbSrc?: string;
|
|
4
8
|
alt?: string;
|
|
9
|
+
/** Optional placeholder color shown behind its thumbnail. */
|
|
10
|
+
color?: string;
|
|
5
11
|
/** 'video' fetches the source and plays it in a <video> (blob-backed); default 'image' shows an <img>. */
|
|
6
12
|
kind?: 'image' | 'video';
|
|
7
13
|
/** A "long image" (tall screenshot): show it at a readable width inside a vertical-scroll frame, not letterboxed. */
|
|
8
14
|
long?: boolean;
|
|
9
15
|
}
|
|
16
|
+
declare function setGallery(images: readonly LightboxImage[]): void;
|
|
17
|
+
declare function clearGallery(): void;
|
|
18
|
+
declare function open(image: LightboxImage): void;
|
|
19
|
+
/** Open an explicit gallery without replacing the ambient conversation gallery. */
|
|
20
|
+
declare function openGallery(images: readonly LightboxImage[], index?: number): void;
|
|
21
|
+
declare function close(): void;
|
|
22
|
+
declare function select(index: number): void;
|
|
23
|
+
declare function previous(): void;
|
|
24
|
+
declare function next(): void;
|
|
10
25
|
export declare function useLightbox(): {
|
|
11
|
-
current: import("vue").
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/** 'video' fetches the source and plays it in a <video> (blob-backed); default 'image' shows an <img>. */
|
|
15
|
-
| undefined;
|
|
16
|
-
kind?: "image" | "video"
|
|
17
|
-
/** A "long image" (tall screenshot): show it at a readable width inside a vertical-scroll frame, not letterboxed. */
|
|
18
|
-
| undefined;
|
|
19
|
-
long?: boolean | undefined;
|
|
20
|
-
} | null, LightboxImage | {
|
|
21
|
-
src: string;
|
|
22
|
-
alt?: string
|
|
23
|
-
/** 'video' fetches the source and plays it in a <video> (blob-backed); default 'image' shows an <img>. */
|
|
24
|
-
| undefined;
|
|
25
|
-
kind?: "image" | "video"
|
|
26
|
-
/** A "long image" (tall screenshot): show it at a readable width inside a vertical-scroll frame, not letterboxed. */
|
|
27
|
-
| undefined;
|
|
28
|
-
long?: boolean | undefined;
|
|
29
|
-
} | null>;
|
|
26
|
+
current: import("vue").ComputedRef<LightboxImage>;
|
|
27
|
+
gallery: import("vue").ComputedRef<readonly LightboxImage[]>;
|
|
28
|
+
currentIndex: import("vue").ComputedRef<number>;
|
|
30
29
|
isOpen: import("vue").ComputedRef<boolean>;
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
hasPrevious: import("vue").ComputedRef<boolean>;
|
|
31
|
+
hasNext: import("vue").ComputedRef<boolean>;
|
|
32
|
+
open: typeof open;
|
|
33
|
+
openGallery: typeof openGallery;
|
|
34
|
+
close: typeof close;
|
|
35
|
+
select: typeof select;
|
|
36
|
+
previous: typeof previous;
|
|
37
|
+
next: typeof next;
|
|
38
|
+
setGallery: typeof setGallery;
|
|
39
|
+
clearGallery: typeof clearGallery;
|
|
33
40
|
};
|
|
41
|
+
export {};
|
|
@@ -1,19 +1,79 @@
|
|
|
1
|
-
import { computed as e,
|
|
1
|
+
import { computed as e, shallowRef as t } from "vue";
|
|
2
2
|
//#region src/composables/useLightbox.ts
|
|
3
|
-
var n = t(null);
|
|
4
|
-
function
|
|
3
|
+
var n = t([]), r = t([]), i = t(-1), a = e(() => r.value[i.value] ?? null), o = e(() => r.value), s = e(() => i.value), c = e(() => a.value !== null), l = e(() => i.value > 0), u = e(() => i.value >= 0 && i.value < r.value.length - 1);
|
|
4
|
+
function d(e) {
|
|
5
|
+
return e.id?.trim() || e.src;
|
|
6
|
+
}
|
|
7
|
+
function f(e, t) {
|
|
8
|
+
return d(e) === d(t) || e.src === t.src;
|
|
9
|
+
}
|
|
10
|
+
function p(e) {
|
|
11
|
+
let t = [], n = /* @__PURE__ */ new Set();
|
|
12
|
+
for (let r of e) {
|
|
13
|
+
if (!r.src || r.kind === "video") continue;
|
|
14
|
+
let e = d(r);
|
|
15
|
+
n.has(e) || (n.add(e), t.push(r));
|
|
16
|
+
}
|
|
17
|
+
return t;
|
|
18
|
+
}
|
|
19
|
+
function m(e, t) {
|
|
20
|
+
r.value = e, i.value = t;
|
|
21
|
+
}
|
|
22
|
+
function h(e) {
|
|
23
|
+
let t = p(e), i = a.value;
|
|
24
|
+
if (n.value = t, !i || i.kind === "video") return;
|
|
25
|
+
let o = t.findIndex((e) => f(e, i));
|
|
26
|
+
o >= 0 ? m(t, o) : r.value.length > 1 && m([i], 0);
|
|
27
|
+
}
|
|
28
|
+
function g() {
|
|
29
|
+
n.value = [];
|
|
30
|
+
let e = a.value;
|
|
31
|
+
e && r.value.length > 1 && m([e], 0);
|
|
32
|
+
}
|
|
33
|
+
function _(e) {
|
|
34
|
+
if (!e.src) return;
|
|
35
|
+
if (e.kind === "video") {
|
|
36
|
+
m([e], 0);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let t = n.value.findIndex((t) => f(t, e));
|
|
40
|
+
t >= 0 ? m(n.value, t) : m([e], 0);
|
|
41
|
+
}
|
|
42
|
+
function v(e, t = 0) {
|
|
43
|
+
let n = p(e);
|
|
44
|
+
n.length && m(n, Math.min(n.length - 1, Math.max(0, t)));
|
|
45
|
+
}
|
|
46
|
+
function y() {
|
|
47
|
+
m([], -1);
|
|
48
|
+
}
|
|
49
|
+
function b(e) {
|
|
50
|
+
e >= 0 && e < r.value.length && (i.value = e);
|
|
51
|
+
}
|
|
52
|
+
function x() {
|
|
53
|
+
l.value && i.value--;
|
|
54
|
+
}
|
|
55
|
+
function S() {
|
|
56
|
+
u.value && i.value++;
|
|
57
|
+
}
|
|
58
|
+
function C() {
|
|
5
59
|
return {
|
|
6
|
-
current:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
60
|
+
current: a,
|
|
61
|
+
gallery: o,
|
|
62
|
+
currentIndex: s,
|
|
63
|
+
isOpen: c,
|
|
64
|
+
hasPrevious: l,
|
|
65
|
+
hasNext: u,
|
|
66
|
+
open: _,
|
|
67
|
+
openGallery: v,
|
|
68
|
+
close: y,
|
|
69
|
+
select: b,
|
|
70
|
+
previous: x,
|
|
71
|
+
next: S,
|
|
72
|
+
setGallery: h,
|
|
73
|
+
clearGallery: g
|
|
14
74
|
};
|
|
15
75
|
}
|
|
16
76
|
//#endregion
|
|
17
|
-
export {
|
|
77
|
+
export { C as useLightbox };
|
|
18
78
|
|
|
19
79
|
//# sourceMappingURL=useLightbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLightbox.js","names":[],"sources":["../../src/composables/useLightbox.ts"],"sourcesContent":["import { computed,
|
|
1
|
+
{"version":3,"file":"useLightbox.js","names":[],"sources":["../../src/composables/useLightbox.ts"],"sourcesContent":["import { computed, shallowRef } from 'vue'\n\n/** Media shown full-screen in the lightbox: its source URL, an alt/title, and the kind to render. */\nexport interface LightboxImage {\n /** Stable identity inside a gallery. Falls back to `src` when omitted. */\n id?: string\n src: string\n /** Small image used by the gallery strip; falls back to `src`. */\n thumbSrc?: string\n alt?: string\n /** Optional placeholder color shown behind its thumbnail. */\n color?: string\n /** 'video' fetches the source and plays it in a <video> (blob-backed); default 'image' shows an <img>. */\n kind?: 'image' | 'video'\n /** A \"long image\" (tall screenshot): show it at a readable width inside a vertical-scroll frame, not letterboxed. */\n long?: boolean\n}\n\n// Module-level singleton: thumbnails anywhere in the message tree open the one ImageLightbox mounted in App.vue.\n// A conversation renderer publishes its image gallery as context; `open` automatically joins that gallery when\n// the requested image matches, while standalone images/videos continue to open as a one-item lightbox.\nconst contextGallery = shallowRef<readonly LightboxImage[]>([])\nconst activeGallery = shallowRef<readonly LightboxImage[]>([])\nconst currentIndex = shallowRef(-1)\n\nconst current = computed(() => activeGallery.value[currentIndex.value] ?? null)\nconst gallery = computed(() => activeGallery.value)\nconst activeIndex = computed(() => currentIndex.value)\nconst isOpen = computed(() => current.value !== null)\nconst hasPrevious = computed(() => currentIndex.value > 0)\nconst hasNext = computed(\n () => currentIndex.value >= 0 && currentIndex.value < activeGallery.value.length - 1,\n)\n\nfunction imageKey(image: LightboxImage): string {\n return image.id?.trim() || image.src\n}\n\nfunction sameImage(a: LightboxImage, b: LightboxImage): boolean {\n return imageKey(a) === imageKey(b) || a.src === b.src\n}\n\nfunction normalizeGallery(images: readonly LightboxImage[]): LightboxImage[] {\n const out: LightboxImage[] = []\n const seen = new Set<string>()\n for (const image of images) {\n if (!image.src || image.kind === 'video') continue\n const key = imageKey(image)\n if (seen.has(key)) continue\n seen.add(key)\n out.push(image)\n }\n return out\n}\n\nfunction activate(images: readonly LightboxImage[], index: number): void {\n activeGallery.value = images\n currentIndex.value = index\n}\n\nfunction setGallery(images: readonly LightboxImage[]): void {\n const next = normalizeGallery(images)\n const active = current.value\n contextGallery.value = next\n if (!active || active.kind === 'video') return\n const index = next.findIndex((image) => sameImage(image, active))\n if (index >= 0) activate(next, index)\n else if (activeGallery.value.length > 1) activate([active], 0)\n}\n\nfunction clearGallery(): void {\n contextGallery.value = []\n const active = current.value\n if (active && activeGallery.value.length > 1) activate([active], 0)\n}\n\nfunction open(image: LightboxImage): void {\n if (!image.src) return\n if (image.kind === 'video') {\n activate([image], 0)\n return\n }\n const index = contextGallery.value.findIndex((candidate) => sameImage(candidate, image))\n if (index >= 0) activate(contextGallery.value, index)\n else activate([image], 0)\n}\n\n/** Open an explicit gallery without replacing the ambient conversation gallery. */\nfunction openGallery(images: readonly LightboxImage[], index = 0): void {\n const next = normalizeGallery(images)\n if (!next.length) return\n activate(next, Math.min(next.length - 1, Math.max(0, index)))\n}\n\nfunction close(): void {\n activate([], -1)\n}\n\nfunction select(index: number): void {\n if (index >= 0 && index < activeGallery.value.length) currentIndex.value = index\n}\n\nfunction previous(): void {\n if (hasPrevious.value) currentIndex.value--\n}\n\nfunction next(): void {\n if (hasNext.value) currentIndex.value++\n}\n\nexport function useLightbox() {\n return {\n current,\n gallery,\n currentIndex: activeIndex,\n isOpen,\n hasPrevious,\n hasNext,\n open,\n openGallery,\n close,\n select,\n previous,\n next,\n setGallery,\n clearGallery,\n }\n}\n"],"mappings":";;AAqBA,IAAM,IAAiB,EAAqC,CAAC,CAAC,GACxD,IAAgB,EAAqC,CAAC,CAAC,GACvD,IAAe,EAAW,EAAE,GAE5B,IAAU,QAAe,EAAc,MAAM,EAAa,UAAU,IAAI,GACxE,IAAU,QAAe,EAAc,KAAK,GAC5C,IAAc,QAAe,EAAa,KAAK,GAC/C,IAAS,QAAe,EAAQ,UAAU,IAAI,GAC9C,IAAc,QAAe,EAAa,QAAQ,CAAC,GACnD,IAAU,QACR,EAAa,SAAS,KAAK,EAAa,QAAQ,EAAc,MAAM,SAAS,CACrF;AAEA,SAAS,EAAS,GAA8B;CAC9C,OAAO,EAAM,IAAI,KAAK,KAAK,EAAM;AACnC;AAEA,SAAS,EAAU,GAAkB,GAA2B;CAC9D,OAAO,EAAS,CAAC,MAAM,EAAS,CAAC,KAAK,EAAE,QAAQ,EAAE;AACpD;AAEA,SAAS,EAAiB,GAAmD;CAC3E,IAAM,IAAuB,CAAC,GACxB,oBAAO,IAAI,IAAY;CAC7B,KAAK,IAAM,KAAS,GAAQ;EAC1B,IAAI,CAAC,EAAM,OAAO,EAAM,SAAS,SAAS;EAC1C,IAAM,IAAM,EAAS,CAAK;EACtB,EAAK,IAAI,CAAG,MAChB,EAAK,IAAI,CAAG,GACZ,EAAI,KAAK,CAAK;CAChB;CACA,OAAO;AACT;AAEA,SAAS,EAAS,GAAkC,GAAqB;CAEvE,AADA,EAAc,QAAQ,GACtB,EAAa,QAAQ;AACvB;AAEA,SAAS,EAAW,GAAwC;CAC1D,IAAM,IAAO,EAAiB,CAAM,GAC9B,IAAS,EAAQ;CAEvB,IADA,EAAe,QAAQ,GACnB,CAAC,KAAU,EAAO,SAAS,SAAS;CACxC,IAAM,IAAQ,EAAK,WAAW,MAAU,EAAU,GAAO,CAAM,CAAC;CAChE,AAAI,KAAS,IAAG,EAAS,GAAM,CAAK,IAC3B,EAAc,MAAM,SAAS,KAAG,EAAS,CAAC,CAAM,GAAG,CAAC;AAC/D;AAEA,SAAS,IAAqB;CAC5B,EAAe,QAAQ,CAAC;CACxB,IAAM,IAAS,EAAQ;CACvB,AAAI,KAAU,EAAc,MAAM,SAAS,KAAG,EAAS,CAAC,CAAM,GAAG,CAAC;AACpE;AAEA,SAAS,EAAK,GAA4B;CACxC,IAAI,CAAC,EAAM,KAAK;CAChB,IAAI,EAAM,SAAS,SAAS;EAC1B,EAAS,CAAC,CAAK,GAAG,CAAC;EACnB;CACF;CACA,IAAM,IAAQ,EAAe,MAAM,WAAW,MAAc,EAAU,GAAW,CAAK,CAAC;CACvF,AAAI,KAAS,IAAG,EAAS,EAAe,OAAO,CAAK,IAC/C,EAAS,CAAC,CAAK,GAAG,CAAC;AAC1B;AAGA,SAAS,EAAY,GAAkC,IAAQ,GAAS;CACtE,IAAM,IAAO,EAAiB,CAAM;CAC/B,EAAK,UACV,EAAS,GAAM,KAAK,IAAI,EAAK,SAAS,GAAG,KAAK,IAAI,GAAG,CAAK,CAAC,CAAC;AAC9D;AAEA,SAAS,IAAc;CACrB,EAAS,CAAC,GAAG,EAAE;AACjB;AAEA,SAAS,EAAO,GAAqB;CACnC,AAAI,KAAS,KAAK,IAAQ,EAAc,MAAM,WAAQ,EAAa,QAAQ;AAC7E;AAEA,SAAS,IAAiB;CACxB,AAAI,EAAY,SAAO,EAAa;AACtC;AAEA,SAAS,IAAa;CACpB,AAAI,EAAQ,SAAO,EAAa;AAClC;AAEA,SAAgB,IAAc;CAC5B,OAAO;EACL;EACA;EACA,cAAc;EACd;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ArchivePathEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
name: string;
|
|
4
|
+
isDir: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface OrderedArchiveImage<T extends ArchivePathEntry> {
|
|
7
|
+
entry: T;
|
|
8
|
+
sourceIndex: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Browser-decodable images in one archive, flattened across all folders and naturally sorted by their complete
|
|
12
|
+
* paths. The original entry index breaks identical-path ties deterministically.
|
|
13
|
+
*/
|
|
14
|
+
export declare function orderedArchiveImages<T extends ArchivePathEntry>(entries: readonly T[]): OrderedArchiveImage<T>[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ebookKind as e, isImageFile as t, rasterImageKind as n } from "./fileType.js";
|
|
2
|
+
//#region src/lib/archiveImages.ts
|
|
3
|
+
function r(r) {
|
|
4
|
+
return r.map((e, t) => ({
|
|
5
|
+
entry: e,
|
|
6
|
+
sourceIndex: t
|
|
7
|
+
})).filter(({ entry: r }) => !r.isDir && t(r.name) && n(r.name) == null && e(r.name) == null).sort((e, t) => e.entry.path.localeCompare(t.entry.path, void 0, {
|
|
8
|
+
numeric: !0,
|
|
9
|
+
sensitivity: "base"
|
|
10
|
+
}) || e.sourceIndex - t.sourceIndex);
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { r as orderedArchiveImages };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=archiveImages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archiveImages.js","names":[],"sources":["../../src/lib/archiveImages.ts"],"sourcesContent":["import { ebookKind, isImageFile, rasterImageKind } from '@/lib/fileType'\n\nexport interface ArchivePathEntry {\n path: string\n name: string\n isDir: boolean\n}\n\nexport interface OrderedArchiveImage<T extends ArchivePathEntry> {\n entry: T\n sourceIndex: number\n}\n\n/**\n * Browser-decodable images in one archive, flattened across all folders and naturally sorted by their complete\n * paths. The original entry index breaks identical-path ties deterministically.\n */\nexport function orderedArchiveImages<T extends ArchivePathEntry>(\n entries: readonly T[],\n): OrderedArchiveImage<T>[] {\n return entries\n .map((entry, sourceIndex) => ({ entry, sourceIndex }))\n .filter(\n ({ entry }) =>\n !entry.isDir &&\n isImageFile(entry.name) &&\n rasterImageKind(entry.name) == null &&\n ebookKind(entry.name) == null,\n )\n .sort(\n (a, b) =>\n a.entry.path.localeCompare(b.entry.path, undefined, {\n numeric: true,\n sensitivity: 'base',\n }) || a.sourceIndex - b.sourceIndex,\n )\n}\n"],"mappings":";;AAiBA,SAAgB,EACd,GAC0B;CAC1B,OAAO,EACJ,KAAK,GAAO,OAAiB;EAAE;EAAO;CAAY,EAAE,CAAC,CACrD,QACE,EAAE,eACD,CAAC,EAAM,SACP,EAAY,EAAM,IAAI,KACtB,EAAgB,EAAM,IAAI,KAAK,QAC/B,EAAU,EAAM,IAAI,KAAK,IAC7B,CAAC,CACA,MACE,GAAG,MACF,EAAE,MAAM,KAAK,cAAc,EAAE,MAAM,MAAM,KAAA,GAAW;EAClD,SAAS;EACT,aAAa;CACf,CAAC,KAAK,EAAE,cAAc,EAAE,WAC5B;AACJ"}
|