postext-folio 0.52.5 → 0.52.7
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/postext/book.d.ts +35 -0
- package/dist/postext/book.d.ts.map +1 -0
- package/dist/postext/book.js +96 -0
- package/dist/postext/book.js.map +1 -0
- package/dist/postext/constants.d.ts +36 -0
- package/dist/postext/constants.d.ts.map +1 -0
- package/dist/postext/constants.js +13 -0
- package/dist/postext/constants.js.map +1 -0
- package/dist/postext/pageVideos.d.ts +26 -0
- package/dist/postext/pageVideos.d.ts.map +1 -0
- package/dist/postext/pageVideos.js +276 -0
- package/dist/postext/pageVideos.js.map +1 -0
- package/dist/postext/painting.d.ts +30 -0
- package/dist/postext/painting.d.ts.map +1 -0
- package/dist/postext/painting.js +167 -0
- package/dist/postext/painting.js.map +1 -0
- package/dist/postext/sharp.d.ts +19 -0
- package/dist/postext/sharp.d.ts.map +1 -0
- package/dist/postext/sharp.js +121 -0
- package/dist/postext/sharp.js.map +1 -0
- package/dist/postext/state.d.ts +29 -0
- package/dist/postext/state.d.ts.map +1 -0
- package/dist/postext/state.js +2 -0
- package/dist/postext/state.js.map +1 -0
- package/dist/postext/sweep.d.ts +24 -0
- package/dist/postext/sweep.d.ts.map +1 -0
- package/dist/postext/sweep.js +37 -0
- package/dist/postext/sweep.js.map +1 -0
- package/dist/postext/types.d.ts +77 -0
- package/dist/postext/types.d.ts.map +1 -0
- package/dist/postext/types.js +2 -0
- package/dist/postext/types.js.map +1 -0
- package/dist/postext.d.ts +6 -124
- package/dist/postext.d.ts.map +1 -1
- package/dist/postext.js +87 -732
- package/dist/postext.js.map +1 -1
- package/dist/viewer/constants.d.ts +30 -0
- package/dist/viewer/constants.d.ts.map +1 -0
- package/dist/viewer/constants.js +49 -0
- package/dist/viewer/constants.js.map +1 -0
- package/dist/viewer/input.d.ts +15 -0
- package/dist/viewer/input.d.ts.map +1 -0
- package/dist/viewer/input.js +142 -0
- package/dist/viewer/input.js.map +1 -0
- package/dist/viewer/layout.d.ts +24 -0
- package/dist/viewer/layout.d.ts.map +1 -0
- package/dist/viewer/layout.js +183 -0
- package/dist/viewer/layout.js.map +1 -0
- package/dist/viewer/lens.d.ts +42 -0
- package/dist/viewer/lens.d.ts.map +1 -0
- package/dist/viewer/lens.js +166 -0
- package/dist/viewer/lens.js.map +1 -0
- package/dist/viewer/pointer.d.ts +37 -0
- package/dist/viewer/pointer.d.ts.map +1 -0
- package/dist/viewer/pointer.js +250 -0
- package/dist/viewer/pointer.js.map +1 -0
- package/dist/viewer/state.d.ts +70 -0
- package/dist/viewer/state.d.ts.map +1 -0
- package/dist/viewer/state.js +2 -0
- package/dist/viewer/state.js.map +1 -0
- package/dist/viewer/turning.d.ts +34 -0
- package/dist/viewer/turning.d.ts.map +1 -0
- package/dist/viewer/turning.js +156 -0
- package/dist/viewer/turning.js.map +1 -0
- package/dist/viewer/types.d.ts +241 -0
- package/dist/viewer/types.d.ts.map +1 -0
- package/dist/viewer/types.js +2 -0
- package/dist/viewer/types.js.map +1 -0
- package/dist/viewer.d.ts +3 -242
- package/dist/viewer.d.ts.map +1 -1
- package/dist/viewer.js +104 -976
- package/dist/viewer.js.map +1 -1
- package/package.json +3 -3
package/dist/viewer.js
CHANGED
|
@@ -1,55 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
/** A press on a page that moves less than this (px) is a click: it turns
|
|
12
|
-
* the page (the recto forward, the verso back). */
|
|
13
|
-
const CLICK_SLOP = 6;
|
|
14
|
-
/** A horizontal swipe longer than this (px) turns the spread. */
|
|
15
|
-
const SWIPE = 40;
|
|
16
|
-
/** Below this width (px) `mode: 'auto'` shows one page at a time. */
|
|
17
|
-
export const SINGLE_BELOW = 560;
|
|
18
|
-
/** Space kept round the book (CSS px): beside it for the ‹ › buttons, and
|
|
19
|
-
* above and below it for the page count (and a lifted leaf's corner). */
|
|
20
|
-
const GAP = 12;
|
|
21
|
-
const MIN_MARGIN_Y = 20;
|
|
22
|
-
/** The magnifying glass's range, and the default's aim: the page shown at
|
|
23
|
-
* this many CSS px a millimetre. */
|
|
24
|
-
const ZOOM_MIN = 1.5;
|
|
25
|
-
const ZOOM_MAX = 10;
|
|
26
|
-
const LENS_PX_PER_MM = 5.5;
|
|
27
|
-
const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x));
|
|
28
|
-
const CHEVRON = (dir) => `<svg aria-hidden="true" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${dir === "left" ? "m15 18-6-6 6-6" : "m9 18 6-6-6-6"}"/></svg>`;
|
|
29
|
-
const sourceOf = (page) => (page && typeof page === "object" && "src" in page ? page.src : (page ?? null));
|
|
30
|
-
const altOf = (page) => (page && typeof page === "object" && "src" in page ? page.alt : undefined);
|
|
31
|
-
const paperOf = (page) => page && typeof page === "object" && "src" in page ? page.paper : undefined;
|
|
32
|
-
/** The natural width / height of a page source, when it is known. */
|
|
33
|
-
function aspectOf(src) {
|
|
34
|
-
if (!src || typeof src === "string")
|
|
35
|
-
return null;
|
|
36
|
-
const w = src instanceof HTMLCanvasElement ? src.width : src.naturalWidth;
|
|
37
|
-
const h = src instanceof HTMLCanvasElement ? src.height : src.naturalHeight;
|
|
38
|
-
return w > 0 && h > 0 ? w / h : null;
|
|
39
|
-
}
|
|
40
|
-
/** A CSS colour as 0 … 1 channels. */
|
|
41
|
-
function rgbOf(color) {
|
|
42
|
-
const ctx = document.createElement("canvas").getContext("2d");
|
|
43
|
-
if (!ctx)
|
|
44
|
-
return null;
|
|
45
|
-
// Over white: a transparent page is white paper.
|
|
46
|
-
ctx.fillStyle = "#fff";
|
|
47
|
-
ctx.fillRect(0, 0, 1, 1);
|
|
48
|
-
ctx.fillStyle = color;
|
|
49
|
-
ctx.fillRect(0, 0, 1, 1);
|
|
50
|
-
const d = ctx.getImageData(0, 0, 1, 1).data;
|
|
51
|
-
return [d[0] / 255, d[1] / 255, d[2] / 255];
|
|
52
|
-
}
|
|
1
|
+
import { spreadOfPage } from './spreads.js';
|
|
2
|
+
import { injectStyles } from './styles.js';
|
|
3
|
+
import { DEFAULT_LABELS, paperOf, sourceOf, SINGLE_BELOW, clamp, ZOOM_MIN, ZOOM_MAX } from './viewer/constants.js';
|
|
4
|
+
import { createFolioLayout } from './viewer/layout.js';
|
|
5
|
+
import { createFolioTurning } from './viewer/turning.js';
|
|
6
|
+
import { createFolioInput } from './viewer/input.js';
|
|
7
|
+
import { createFolioLens } from './viewer/lens.js';
|
|
8
|
+
import { createFolioPointer } from './viewer/pointer.js';
|
|
9
|
+
export { SINGLE_BELOW } from './viewer/constants.js';
|
|
53
10
|
/**
|
|
54
11
|
* A book on the page: the pages in spreads by the recto rule, turned with
|
|
55
12
|
* the ‹ › buttons, ←/→, a swipe, a click on a page, or taken by hand and
|
|
@@ -61,21 +18,23 @@ function rgbOf(color) {
|
|
|
61
18
|
* simply change.
|
|
62
19
|
*/
|
|
63
20
|
export function createFolio(container, options) {
|
|
21
|
+
const vs = {};
|
|
22
|
+
const vlate = {};
|
|
64
23
|
injectStyles(container);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let papersKey = JSON.stringify(pages.map(paperOf));
|
|
24
|
+
vs.labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
25
|
+
vs.pages = options.pages;
|
|
26
|
+
vs.firstPageRecto = options.firstPageRecto ?? true;
|
|
27
|
+
vs.binding = options.binding ?? "left";
|
|
28
|
+
vs.mode = options.mode ?? "auto";
|
|
29
|
+
vs.paper = options.paper ?? "#fff";
|
|
30
|
+
vs.appearance = options.appearance ?? {};
|
|
31
|
+
let papersKey = JSON.stringify(vs.pages.map(paperOf));
|
|
73
32
|
/** The appearance the flipper was last given, as text. */
|
|
74
|
-
|
|
33
|
+
vs.appearanceKey = "";
|
|
75
34
|
const animate = options.animate ?? true;
|
|
76
35
|
const controls = options.controls ?? true;
|
|
77
36
|
const showCount = options.showCount ?? true;
|
|
78
|
-
|
|
37
|
+
vs.interaction = options.interaction ?? "hand";
|
|
79
38
|
const root = document.createElement("div");
|
|
80
39
|
root.className = "postext-folio";
|
|
81
40
|
root.tabIndex = 0;
|
|
@@ -103,888 +62,57 @@ export function createFolio(container, options) {
|
|
|
103
62
|
if (!controls)
|
|
104
63
|
prevBtn.hidden = nextBtn.hidden = true;
|
|
105
64
|
container.append(root);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
65
|
+
vs.single = false;
|
|
66
|
+
vs.spreads = [];
|
|
67
|
+
vs.current = 0;
|
|
68
|
+
vs.shown = 0;
|
|
69
|
+
vs.flipper = null;
|
|
70
|
+
vs.flipCanvas = null;
|
|
71
|
+
vs.holding = null;
|
|
72
|
+
vs.press = null;
|
|
73
|
+
vs.swipe = null;
|
|
74
|
+
vs.disposed = false;
|
|
75
|
+
vs.slot = { width: 0, height: 0, deviceWidth: 0, deviceHeight: 0 };
|
|
76
|
+
vs.pageVideos = [];
|
|
118
77
|
/** Where a press on a page began, on the page (a click there may act on
|
|
119
78
|
* it rather than turn it). */
|
|
120
|
-
|
|
121
|
-
const rtl = () => binding === "right";
|
|
79
|
+
vs.pressPoint = null;
|
|
80
|
+
const rtl = () => vs.binding === "right";
|
|
122
81
|
const pagesOf = (s) => (s ?? [null, null]).filter((i) => i !== null);
|
|
123
82
|
/** One page at a time on a WebGL book: the spine runs along the page's
|
|
124
83
|
* inner edge, the leaf turns over it and out of the box. */
|
|
125
|
-
const singleGl = () => single && !!flipper;
|
|
84
|
+
const singleGl = () => vs.single && !!vs.flipper;
|
|
126
85
|
// In single mode every sheet carries one page: its back is blank paper
|
|
127
86
|
// (the page after it lies under it, not on its back).
|
|
128
|
-
const bookOf = () => spreads.map((s, k) => (single ? [k === 0 ? null : "", sourceOf(pages[s[1]])] : s.map((i) => (i === null ? null : sourceOf(pages[i])))));
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
function flipAppearance() {
|
|
132
|
-
const extra = appearance.extraPages ?? { before: 0, after: 0 };
|
|
133
|
-
const leafPapers = spreads.slice(0, -1).map((s, k) => single ? paperOf(pages[s[1]]) : (paperOf(pages[s[1] ?? -1]) ?? paperOf(pages[spreads[k + 1]?.[0] ?? -1])));
|
|
134
|
-
return {
|
|
135
|
-
folio: appearance.folio,
|
|
136
|
-
pageWidthMm: appearance.pageWidthMm,
|
|
137
|
-
extraLeaves: single ? { before: extra.before, after: extra.after } : { before: Math.ceil(extra.before / 2), after: Math.ceil(extra.after / 2) },
|
|
138
|
-
leafPapers,
|
|
139
|
-
singlePage: single,
|
|
140
|
-
textureBaseUrl: appearance.textureBaseUrl,
|
|
141
|
-
coverLeaves: coverLeavesOf(),
|
|
142
|
-
...(appearance.spineImage ? { spineImage: appearance.spineImage } : {}),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
/** The leaves that are the covers: the first, when the book opens on its
|
|
146
|
-
* first page alone; the last, when the last page is a verso alone. */
|
|
147
|
-
function coverLeavesOf() {
|
|
148
|
-
const c = appearance.covers;
|
|
149
|
-
if (!c || spreads.length < 2)
|
|
150
|
-
return undefined;
|
|
151
|
-
const last = spreads[spreads.length - 1];
|
|
152
|
-
return {
|
|
153
|
-
...(c.front && (single || spreads[0][0] === null) ? { front: 0 } : {}),
|
|
154
|
-
...(c.back && !single && last[1] === null ? { back: spreads.length - 2 } : {}),
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
function aspect() {
|
|
158
|
-
if (options.aspect)
|
|
159
|
-
return options.aspect;
|
|
160
|
-
for (const page of pages) {
|
|
161
|
-
const ar = aspectOf(sourceOf(page));
|
|
162
|
-
if (ar)
|
|
163
|
-
return ar;
|
|
164
|
-
}
|
|
165
|
-
return 1 / Math.SQRT2;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Fits the book into the box, as large as it goes: the ‹ › buttons and
|
|
169
|
-
* the count sit in the margins round it. The spread lies on the device
|
|
170
|
-
* pixel grid, its page slots a whole number of device pixels wide, so a
|
|
171
|
-
* page painted at that size is shown 1:1 in the DOM and on the canvas
|
|
172
|
-
* alike. The WebGL canvas covers the box, centred on the spread (the
|
|
173
|
-
* camera maps the spread onto it 1:1), with room for a lifted leaf.
|
|
174
|
-
*/
|
|
175
|
-
function fit() {
|
|
176
|
-
const dpr = window.devicePixelRatio || 1;
|
|
177
|
-
const box = root.getBoundingClientRect();
|
|
178
|
-
const boxW = Math.floor(box.width * dpr);
|
|
179
|
-
const boxH = Math.floor(box.height * dpr);
|
|
180
|
-
const ar = aspect();
|
|
181
|
-
const gap = Math.round(GAP * dpr);
|
|
182
|
-
// The widest page up to `max` device px whose width and height are
|
|
183
|
-
// even: the spread's centre and the spine (the camera's centre in one
|
|
184
|
-
// or the other mode) then lie on the pixel grid.
|
|
185
|
-
const fitWidth = (max) => {
|
|
186
|
-
let w = Math.max(2, Math.floor(max));
|
|
187
|
-
while (w > 2 && (w % 2 || Math.round(w / ar) % 2))
|
|
188
|
-
w--;
|
|
189
|
-
return w;
|
|
190
|
-
};
|
|
191
|
-
let deviceWidth;
|
|
192
|
-
let left;
|
|
193
|
-
let top;
|
|
194
|
-
let across;
|
|
195
|
-
if (single) {
|
|
196
|
-
// One page across the box, the buttons and the count in a bar below.
|
|
197
|
-
const bar = controls ? Math.round((prevBtn.offsetWidth + 2 * GAP) * dpr) : gap;
|
|
198
|
-
const availW = Math.max(0, boxW - 2 * gap);
|
|
199
|
-
const availH = Math.max(0, boxH - gap - bar);
|
|
200
|
-
deviceWidth = fitWidth(Math.min(availW, availH > 0 ? availH * ar : Infinity));
|
|
201
|
-
const deviceHeight = Math.round(deviceWidth / ar);
|
|
202
|
-
const pageLeft = gap + Math.floor((availW - deviceWidth) / 2);
|
|
203
|
-
top = gap + Math.max(0, Math.floor((availH - deviceHeight) / 2));
|
|
204
|
-
if (flipper) {
|
|
205
|
-
// The WebGL book is a spread whose other page lies off the box: the
|
|
206
|
-
// spine on the page's inner edge (its left, or its right when the
|
|
207
|
-
// book is bound on the right).
|
|
208
|
-
across = 2;
|
|
209
|
-
left = rtl() ? pageLeft : pageLeft - deviceWidth;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
across = 1;
|
|
213
|
-
left = pageLeft;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
across = 2;
|
|
218
|
-
const marginX = Math.round((controls ? prevBtn.offsetWidth + 2 * GAP : GAP) * dpr);
|
|
219
|
-
const marginY = Math.round(Math.max(MIN_MARGIN_Y, showCount ? count.offsetHeight + 2 * GAP : GAP) * dpr);
|
|
220
|
-
const availW = Math.max(0, boxW - 2 * marginX);
|
|
221
|
-
const availH = Math.max(0, boxH - 2 * marginY);
|
|
222
|
-
// A tilted book is foreshortened: its pages may be taller than the
|
|
223
|
-
// box before their image is.
|
|
224
|
-
const tall = flipper ? 1 / Math.max(0.7, Math.cos((resolveFolioConfig(appearance.folio).tilt * Math.PI) / 180)) : 1;
|
|
225
|
-
deviceWidth = fitWidth(Math.min(availW / 2, availH > 0 ? availH * tall * ar : Infinity));
|
|
226
|
-
left = Math.floor((boxW - 2 * deviceWidth) / 2);
|
|
227
|
-
// Centred even when the tilted book's spread is taller than the box
|
|
228
|
-
// (it overflows both edges): the camera centres the book on it.
|
|
229
|
-
top = Math.floor((boxH - Math.round(deviceWidth / ar)) / 2);
|
|
230
|
-
}
|
|
231
|
-
const deviceHeight = Math.max(1, Math.round(deviceWidth / ar));
|
|
232
|
-
Object.assign(spreadEl.style, {
|
|
233
|
-
left: `${left / dpr}px`,
|
|
234
|
-
top: `${top / dpr}px`,
|
|
235
|
-
width: `${(across * deviceWidth) / dpr}px`,
|
|
236
|
-
height: `${deviceHeight / dpr}px`,
|
|
237
|
-
});
|
|
238
|
-
if (flipCanvas) {
|
|
239
|
-
// Centred on the spine (the camera maps the spread onto the canvas
|
|
240
|
-
// 1:1 about its centre) and reaching every edge of the box; what
|
|
241
|
-
// falls outside the box is clipped.
|
|
242
|
-
const cx = left + deviceWidth;
|
|
243
|
-
const cy = top + deviceHeight / 2;
|
|
244
|
-
const hw = Math.max(cx, boxW - cx);
|
|
245
|
-
const hh = Math.max(cy, boxH - cy);
|
|
246
|
-
Object.assign(flipCanvas.style, {
|
|
247
|
-
left: `${(cx - hw) / dpr}px`,
|
|
248
|
-
top: `${(cy - hh) / dpr}px`,
|
|
249
|
-
width: `${(2 * hw) / dpr}px`,
|
|
250
|
-
height: `${(2 * hh) / dpr}px`,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
const next = { width: deviceWidth / dpr, height: deviceHeight / dpr, deviceWidth, deviceHeight };
|
|
254
|
-
if (next.deviceWidth !== slot.deviceWidth || next.deviceHeight !== slot.deviceHeight) {
|
|
255
|
-
slot = next;
|
|
256
|
-
options.onLayout?.(slot);
|
|
257
|
-
scheduleDetail(300);
|
|
258
|
-
}
|
|
259
|
-
placeDomVideo();
|
|
260
|
-
flipper?.redraw();
|
|
261
|
-
}
|
|
262
|
-
function pageEl(i, slot) {
|
|
263
|
-
const el = document.createElement("div");
|
|
264
|
-
el.className = "postext-folio-page";
|
|
265
|
-
if (!single || singleGl())
|
|
266
|
-
el.classList.add(slot === 0 ? "is-verso" : "is-recto");
|
|
267
|
-
const src = i === null ? null : sourceOf(pages[i]);
|
|
268
|
-
if (src === null) {
|
|
269
|
-
el.classList.add("is-empty");
|
|
270
|
-
el.setAttribute("aria-hidden", "true");
|
|
271
|
-
}
|
|
272
|
-
else if (src === "") {
|
|
273
|
-
el.classList.add("is-blank");
|
|
274
|
-
el.setAttribute("aria-hidden", "true");
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
const alt = altOf(pages[i]) ?? "";
|
|
278
|
-
if (typeof src === "string") {
|
|
279
|
-
const img = document.createElement("img");
|
|
280
|
-
img.src = src;
|
|
281
|
-
img.alt = alt;
|
|
282
|
-
img.draggable = false;
|
|
283
|
-
img.decoding = "async";
|
|
284
|
-
el.append(img);
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
// The page's own element (a canvas, or a decoded image), shown as is.
|
|
288
|
-
src.classList.add("postext-folio-surface");
|
|
289
|
-
if (src instanceof HTMLCanvasElement) {
|
|
290
|
-
src.setAttribute("role", "img");
|
|
291
|
-
if (alt)
|
|
292
|
-
src.setAttribute("aria-label", alt);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
src.alt = alt;
|
|
296
|
-
src.draggable = false;
|
|
297
|
-
}
|
|
298
|
-
el.append(src);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return el;
|
|
302
|
-
}
|
|
303
|
-
/** Shows the page videos: on the WebGL book, drawn by the leaves that
|
|
304
|
-
* carry their pages; on the DOM spread, laid over the pages. */
|
|
305
|
-
function applyVideo() {
|
|
306
|
-
flipper?.setVideos(pageVideos.map((v) => ({ src: sourceOf(pages[v.page]), element: v.element, frame: v })));
|
|
307
|
-
placeDomVideo();
|
|
308
|
-
}
|
|
309
|
-
/** Takes a video element laid over a DOM page off it. */
|
|
310
|
-
const unplace = (v) => {
|
|
311
|
-
if (!v.element.classList.contains("postext-folio-video"))
|
|
312
|
-
return;
|
|
313
|
-
v.element.classList.remove("postext-folio-video");
|
|
314
|
-
v.element.remove();
|
|
315
|
-
};
|
|
316
|
-
/** The DOM spread (no WebGL): each video element over its page, mapped
|
|
317
|
-
* onto the picture's place by a transform (it may lie turned). */
|
|
318
|
-
function placeDomVideo() {
|
|
319
|
-
if (flipper) {
|
|
320
|
-
for (const v of pageVideos)
|
|
321
|
-
unplace(v);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const els = [...spreadEl.children];
|
|
325
|
-
const s = spreads[shown] ?? [null, null];
|
|
326
|
-
const slots = single && !singleGl() ? [s[0] ?? s[1]] : s;
|
|
327
|
-
for (const v of pageVideos) {
|
|
328
|
-
const at = slots.indexOf(v.page);
|
|
329
|
-
const el = at >= 0 ? els[at] : undefined;
|
|
330
|
-
if (!el) {
|
|
331
|
-
v.element.remove();
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
const w = el.clientWidth;
|
|
335
|
-
const h = el.clientHeight;
|
|
336
|
-
const unit = 100;
|
|
337
|
-
v.element.classList.add("postext-folio-video");
|
|
338
|
-
Object.assign(v.element.style, {
|
|
339
|
-
width: `${unit}px`,
|
|
340
|
-
height: `${unit}px`,
|
|
341
|
-
transform: `matrix(${(v.across.x * w) / unit}, ${(v.across.y * h) / unit}, ${(v.down.x * w) / unit}, ${(v.down.y * h) / unit}, ${v.origin.x * w}, ${v.origin.y * h})`,
|
|
342
|
-
});
|
|
343
|
-
if (v.element.parentElement !== el)
|
|
344
|
-
el.append(v.element);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
function render() {
|
|
348
|
-
const s = spreads[shown] ?? [null, null];
|
|
349
|
-
const slots = single && !singleGl() ? [s[0] ?? s[1]] : s;
|
|
350
|
-
spreadEl.replaceChildren(...slots.map((i, side) => pageEl(i, side)));
|
|
351
|
-
spreadEl.classList.toggle("is-solo", single && !singleGl());
|
|
352
|
-
spreadEl.classList.toggle("is-by-hand", !!flipper);
|
|
353
|
-
// The canvas draws the book, still or turning; the DOM pages stay as
|
|
354
|
-
// the textures' sources and the pages' text alternatives.
|
|
355
|
-
spreadEl.classList.toggle("is-gl", !!flipper);
|
|
356
|
-
const on = pagesOf(spreads[current]);
|
|
357
|
-
count.textContent = on.length ? labels.count(on, pages.length) : "";
|
|
358
|
-
prevBtn.disabled = current <= 0;
|
|
359
|
-
nextBtn.disabled = current >= spreads.length - 1;
|
|
360
|
-
placeDomVideo();
|
|
361
|
-
// The settled spread, drawn at rest.
|
|
362
|
-
flipper?.clear();
|
|
363
|
-
scheduleDetail(120);
|
|
364
|
-
}
|
|
365
|
-
function settle(index) {
|
|
366
|
-
shown = index;
|
|
367
|
-
render();
|
|
368
|
-
options.onChange?.({ spread: index, pages: pagesOf(spreads[index]) });
|
|
369
|
-
}
|
|
370
|
-
function go(index) {
|
|
371
|
-
const to = Math.max(0, Math.min(spreads.length - 1, index));
|
|
372
|
-
if (to === current && to === shown)
|
|
373
|
-
return;
|
|
374
|
-
current = to;
|
|
375
|
-
options.onTarget?.({ spread: to, pages: pagesOf(spreads[to]) });
|
|
376
|
-
if (flipper) {
|
|
377
|
-
flipper.go(to);
|
|
378
|
-
// The count and buttons follow the hand at once.
|
|
379
|
-
count.textContent = labels.count(pagesOf(spreads[to]), pages.length);
|
|
380
|
-
prevBtn.disabled = to <= 0;
|
|
381
|
-
nextBtn.disabled = to >= spreads.length - 1;
|
|
382
|
-
}
|
|
383
|
-
else
|
|
384
|
-
settle(to);
|
|
385
|
-
}
|
|
386
|
-
/** Opens spread `index` at once, no leaf turning. */
|
|
387
|
-
function jump(index) {
|
|
388
|
-
const to = Math.max(0, Math.min(spreads.length - 1, index));
|
|
389
|
-
if (to === current && to === shown)
|
|
390
|
-
return;
|
|
391
|
-
current = to;
|
|
392
|
-
options.onTarget?.({ spread: to, pages: pagesOf(spreads[to]) });
|
|
393
|
-
flipper?.setBook(bookOf(), to);
|
|
394
|
-
settle(to);
|
|
395
|
-
}
|
|
396
|
-
function setUpFlipper() {
|
|
397
|
-
flipper?.dispose();
|
|
398
|
-
flipper = null;
|
|
399
|
-
flipCanvas?.remove();
|
|
400
|
-
flipCanvas = null;
|
|
401
|
-
if (!animate || spreads.length < 2 || !canFlip())
|
|
402
|
-
return;
|
|
403
|
-
flipCanvas = document.createElement("canvas");
|
|
404
|
-
flipCanvas.className = "postext-folio-flip";
|
|
405
|
-
flipCanvas.setAttribute("aria-hidden", "true");
|
|
406
|
-
root.append(flipCanvas);
|
|
407
|
-
const initial = flipAppearance();
|
|
408
|
-
appearanceKey = JSON.stringify(initial);
|
|
409
|
-
flipper = new PageFlipper(flipCanvas, spreadEl, bookOf(), shown, settle, (i) => {
|
|
410
|
-
current = i;
|
|
411
|
-
options.onTarget?.({ spread: i, pages: pagesOf(spreads[i]) });
|
|
412
|
-
count.textContent = labels.count(pagesOf(spreads[i]), pages.length);
|
|
413
|
-
}, binding,
|
|
414
|
-
// One page at a time the spine is the box's edge: the leaf cannot be
|
|
415
|
-
// carried past halfway, so a shorter pull turns it.
|
|
416
|
-
{ persistent: true, turnAt: single ? 0.2 : 0.5, appearance: initial });
|
|
417
|
-
const rgb = rgbOf(paper);
|
|
418
|
-
if (rgb)
|
|
419
|
-
flipper.setPaper(...rgb);
|
|
420
|
-
if (pageVideos.length)
|
|
421
|
-
applyVideo();
|
|
422
|
-
// A new book: its glass and paintings start afresh.
|
|
423
|
-
details = new Map();
|
|
424
|
-
detailAsked = new Map();
|
|
425
|
-
if (lensAt)
|
|
426
|
-
flipper.setLens(lensOf(lensAt));
|
|
427
|
-
}
|
|
428
|
-
/** Lays the book out again: spreads, mode, flipper. */
|
|
429
|
-
function rebuild(atPage) {
|
|
430
|
-
single = mode === "single" || (mode === "auto" && root.clientWidth > 0 && root.clientWidth < SINGLE_BELOW);
|
|
431
|
-
spreads = single ? pages.map((_, i) => [null, i]) : spreadsOf(pages.length, firstPageRecto);
|
|
432
|
-
current = shown = spreadOfPage(spreads, atPage);
|
|
433
|
-
root.dir = rtl() ? "rtl" : "ltr";
|
|
434
|
-
root.style.setProperty("--postext-folio-paper", paper);
|
|
435
|
-
root.classList.toggle("is-single", single);
|
|
436
|
-
prevBtn.innerHTML = CHEVRON(rtl() ? "right" : "left");
|
|
437
|
-
nextBtn.innerHTML = CHEVRON(rtl() ? "left" : "right");
|
|
438
|
-
setUpFlipper();
|
|
439
|
-
fit();
|
|
440
|
-
render();
|
|
441
|
-
}
|
|
442
|
-
function applyLabels() {
|
|
443
|
-
root.setAttribute("aria-label", labels.region);
|
|
444
|
-
prevBtn.setAttribute("aria-label", labels.prev);
|
|
445
|
-
nextBtn.setAttribute("aria-label", labels.next);
|
|
446
|
-
}
|
|
447
|
-
// ── Input ──
|
|
448
|
-
prevBtn.addEventListener("click", () => go(current - 1));
|
|
449
|
-
nextBtn.addEventListener("click", () => go(current + 1));
|
|
450
|
-
root.addEventListener("keydown", (event) => {
|
|
451
|
-
if (event.target !== root)
|
|
452
|
-
return;
|
|
453
|
-
if (interaction === "magnify" && flipper && (event.key === "+" || event.key === "=" || event.key === "-" || event.key === "Escape")) {
|
|
454
|
-
if (event.key === "Escape") {
|
|
455
|
-
if (!lensAt)
|
|
456
|
-
return;
|
|
457
|
-
hideLens();
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
raiseLens();
|
|
461
|
-
zoomBy(event.key === "-" ? 1 / 1.25 : 1.25);
|
|
462
|
-
}
|
|
463
|
-
event.preventDefault();
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
const forward = rtl() ? "ArrowLeft" : "ArrowRight";
|
|
467
|
-
const back = rtl() ? "ArrowRight" : "ArrowLeft";
|
|
468
|
-
if (event.key === back || event.key === "PageUp")
|
|
469
|
-
go(current - 1);
|
|
470
|
-
else if (event.key === forward || event.key === "PageDown")
|
|
471
|
-
go(current + 1);
|
|
472
|
-
else if (event.key === "Home")
|
|
473
|
-
go(0);
|
|
474
|
-
else if (event.key === "End")
|
|
475
|
-
go(spreads.length - 1);
|
|
476
|
-
else
|
|
477
|
-
return;
|
|
478
|
-
event.preventDefault();
|
|
87
|
+
const bookOf = () => vs.spreads.map((s, k) => (vs.single ? [k === 0 ? null : "", sourceOf(vs.pages[s[1]])] : s.map((i) => (i === null ? null : sourceOf(vs.pages[i])))));
|
|
88
|
+
const { flipAppearance, aspect, fit, pageEl } = createFolioLayout({
|
|
89
|
+
options, controls, showCount, root, prevBtn, spreadEl, count, rtl, singleGl, vs, vlate
|
|
479
90
|
});
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (flipper)
|
|
484
|
-
event.preventDefault();
|
|
91
|
+
const { applyVideo, unplace, placeDomVideo, render, go, jump, rebuild, applyLabels } = createFolioTurning({
|
|
92
|
+
options, animate, root, prevBtn, nextBtn, spreadEl, count, rtl, pagesOf, singleGl, bookOf,
|
|
93
|
+
flipAppearance, fit, pageEl, vs, vlate
|
|
485
94
|
});
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (!orbits || !flipper || holding)
|
|
490
|
-
return;
|
|
491
|
-
if (event.target.closest(".postext-folio-nav"))
|
|
492
|
-
return;
|
|
493
|
-
event.preventDefault();
|
|
494
|
-
const mask = event.button === 2 ? 2 : 1;
|
|
495
|
-
const id = event.pointerId;
|
|
496
|
-
root.classList.add("is-orbiting");
|
|
497
|
-
let x = event.clientX;
|
|
498
|
-
let y = event.clientY;
|
|
499
|
-
const move = (e) => {
|
|
500
|
-
if (e.pointerId !== id)
|
|
501
|
-
return;
|
|
502
|
-
if ((e.buttons & mask) === 0)
|
|
503
|
-
return end();
|
|
504
|
-
flipper?.orbitBy(e.clientX - x, e.clientY - y);
|
|
505
|
-
x = e.clientX;
|
|
506
|
-
y = e.clientY;
|
|
507
|
-
};
|
|
508
|
-
const up = (e) => {
|
|
509
|
-
if (e.pointerId === id)
|
|
510
|
-
end();
|
|
511
|
-
};
|
|
512
|
-
const end = () => {
|
|
513
|
-
root.classList.remove("is-orbiting");
|
|
514
|
-
window.removeEventListener("pointermove", move);
|
|
515
|
-
window.removeEventListener("pointerup", up);
|
|
516
|
-
window.removeEventListener("pointercancel", up);
|
|
517
|
-
window.removeEventListener("blur", end);
|
|
518
|
-
};
|
|
519
|
-
window.addEventListener("pointermove", move);
|
|
520
|
-
window.addEventListener("pointerup", up);
|
|
521
|
-
window.addEventListener("pointercancel", up);
|
|
522
|
-
window.addEventListener("blur", end);
|
|
95
|
+
Object.assign(vlate, { placeDomVideo });
|
|
96
|
+
createFolioInput({
|
|
97
|
+
options, root, prevBtn, nextBtn, rtl, go, vs, vlate
|
|
523
98
|
});
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (event.buttons)
|
|
527
|
-
return;
|
|
528
|
-
const point = holding ? null : aimedPageAt(event);
|
|
529
|
-
if (interaction === "select" || interaction === "magnify")
|
|
530
|
-
root.classList.toggle("is-over-page", !!point);
|
|
531
|
-
if (options.isPageAction)
|
|
532
|
-
root.classList.toggle("is-over-action", !!point && options.isPageAction(point));
|
|
533
|
-
// Hand mode: the hand shows only where a page can be taken, its
|
|
534
|
-
// outer half (#494).
|
|
535
|
-
root.classList.toggle("is-over-grip", interaction === "hand" && !holding && !!flipper && flipper.hit(event) !== 0);
|
|
99
|
+
const { zoomNow, lensOf, heldAt, raiseLens, hideLens, zoomBy, scheduleDetail, releaseLens } = createFolioLens({
|
|
100
|
+
options, root, spreadEl, rtl, pagesOf, aspect, go, vs, vlate
|
|
536
101
|
});
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
// followed); in orbit mode a drag from it still turns the view.
|
|
541
|
-
let actionPress = null;
|
|
542
|
-
let swallowClick = false;
|
|
543
|
-
root.addEventListener("pointerdown", (event) => {
|
|
544
|
-
actionPress = null;
|
|
545
|
-
if (interaction === "hand" || event.button !== 0 || !options.onPageClick || !options.isPageAction)
|
|
546
|
-
return;
|
|
547
|
-
if (event.target.closest(".postext-folio-nav"))
|
|
548
|
-
return;
|
|
549
|
-
const point = aimedPageAt(event);
|
|
550
|
-
if (!point || !options.isPageAction(point))
|
|
551
|
-
return;
|
|
552
|
-
actionPress = { x: event.clientX, y: event.clientY, id: event.pointerId, point };
|
|
553
|
-
if (leftToHost())
|
|
554
|
-
event.stopPropagation();
|
|
555
|
-
}, true);
|
|
556
|
-
root.addEventListener("pointermove", (event) => {
|
|
557
|
-
if (actionPress && leftToHost() && event.pointerId === actionPress.id)
|
|
558
|
-
event.stopPropagation();
|
|
559
|
-
}, true);
|
|
560
|
-
root.addEventListener("pointerup", (event) => {
|
|
561
|
-
const press = actionPress;
|
|
562
|
-
if (!press || event.pointerId !== press.id)
|
|
563
|
-
return;
|
|
564
|
-
actionPress = null;
|
|
565
|
-
if (leftToHost()) {
|
|
566
|
-
event.stopPropagation();
|
|
567
|
-
swallowClick = true;
|
|
568
|
-
}
|
|
569
|
-
if (Math.hypot(event.clientX - press.x, event.clientY - press.y) < CLICK_SLOP)
|
|
570
|
-
options.onPageClick?.(press.point);
|
|
571
|
-
}, true);
|
|
572
|
-
root.addEventListener("click", (event) => {
|
|
573
|
-
if (!swallowClick)
|
|
574
|
-
return;
|
|
575
|
-
swallowClick = false;
|
|
576
|
-
event.stopPropagation();
|
|
577
|
-
}, true);
|
|
578
|
-
root.addEventListener("pointerleave", (event) => {
|
|
579
|
-
root.classList.remove("is-over-action", "is-over-grip", "is-over-page");
|
|
580
|
-
if (event.pointerType === "mouse")
|
|
581
|
-
hideLens();
|
|
102
|
+
Object.assign(vlate, { lensOf, raiseLens, hideLens, zoomBy, scheduleDetail });
|
|
103
|
+
const { onPage, leftToHost, aimedPageAt, pointOnScreen, applyInteraction } = createFolioPointer({
|
|
104
|
+
options, root, spreadEl, rtl, singleGl, go, heldAt, scheduleDetail, releaseLens, vs
|
|
582
105
|
});
|
|
583
|
-
|
|
584
|
-
/** Where the glass is held (client px), when it is. */
|
|
585
|
-
let lensAt = null;
|
|
586
|
-
let lensZoom = options.magnifier?.zoom ? clamp(options.magnifier.zoom, ZOOM_MIN, ZOOM_MAX) : null;
|
|
587
|
-
/** Page source → its sharper painting, and the width each was asked
|
|
588
|
-
* for at. */
|
|
589
|
-
let details = new Map();
|
|
590
|
-
let detailAsked = new Map();
|
|
591
|
-
let detailTimer = 0;
|
|
592
|
-
function zoomNow() {
|
|
593
|
-
if (lensZoom)
|
|
594
|
-
return lensZoom;
|
|
595
|
-
const perMm = slot.width / (appearance.pageWidthMm ?? 150);
|
|
596
|
-
return perMm > 0 ? clamp(LENS_PX_PER_MM / perMm, 2, 8) : 3;
|
|
597
|
-
}
|
|
598
|
-
function lensOf(at) {
|
|
599
|
-
const box = root.getBoundingClientRect();
|
|
600
|
-
const diameter = options.magnifier?.diameter ?? clamp(0.42 * Math.min(box.width, box.height), 180, 520);
|
|
601
|
-
const rim = clamp(diameter * 0.045, 8, 18);
|
|
602
|
-
return { x: at.x, y: at.y, radius: diameter / 2 - rim, rim, zoom: zoomNow() };
|
|
603
|
-
}
|
|
604
|
-
/** Where the glass is held for a pointer: under a mouse or a pen, but
|
|
605
|
-
* a finger holds it above itself, where it does not hide it. */
|
|
606
|
-
function heldAt(event) {
|
|
607
|
-
if (event.pointerType !== "touch")
|
|
608
|
-
return { x: event.clientX, y: event.clientY };
|
|
609
|
-
const lens = lensOf({ x: event.clientX, y: event.clientY });
|
|
610
|
-
return { x: lens.x, y: lens.y - (lens.radius + lens.rim + 28) };
|
|
611
|
-
}
|
|
612
|
-
function placeLens(event) {
|
|
613
|
-
if (!flipper)
|
|
614
|
-
return;
|
|
615
|
-
const lens = lensOf(heldAt(event));
|
|
616
|
-
lensAt = { x: lens.x, y: lens.y };
|
|
617
|
-
root.classList.add("is-magnifying");
|
|
618
|
-
flipper.setLens(lens);
|
|
619
|
-
}
|
|
620
|
-
/** Without a pointer to hold it (the keyboard, a host), the glass comes
|
|
621
|
-
* up over the spread's centre. */
|
|
622
|
-
function raiseLens() {
|
|
623
|
-
if (lensAt)
|
|
624
|
-
return;
|
|
625
|
-
const r = spreadEl.getBoundingClientRect();
|
|
626
|
-
lensAt = { x: r.left + r.width / 2, y: r.top + r.height / 2 };
|
|
627
|
-
root.classList.add("is-magnifying");
|
|
628
|
-
}
|
|
629
|
-
function hideLens() {
|
|
630
|
-
if (!lensAt)
|
|
631
|
-
return;
|
|
632
|
-
lensAt = null;
|
|
633
|
-
root.classList.remove("is-magnifying");
|
|
634
|
-
flipper?.setLens(null);
|
|
635
|
-
}
|
|
636
|
-
function zoomBy(factor) {
|
|
637
|
-
lensZoom = clamp(zoomNow() * factor, ZOOM_MIN, ZOOM_MAX);
|
|
638
|
-
if (lensAt)
|
|
639
|
-
flipper?.setLens(lensOf(lensAt));
|
|
640
|
-
scheduleDetail(250);
|
|
641
|
-
}
|
|
642
|
-
root.addEventListener("pointermove", (event) => {
|
|
643
|
-
if (interaction !== "magnify" || holding)
|
|
644
|
-
return;
|
|
645
|
-
if (event.pointerType === "touch" && !event.buttons)
|
|
646
|
-
return;
|
|
647
|
-
placeLens(event);
|
|
648
|
-
});
|
|
649
|
-
root.addEventListener("pointerdown", (event) => {
|
|
650
|
-
if (interaction !== "magnify" || event.button !== 0)
|
|
651
|
-
return;
|
|
652
|
-
if (event.target.closest(".postext-folio-nav"))
|
|
653
|
-
return;
|
|
654
|
-
placeLens(event);
|
|
655
|
-
});
|
|
656
|
-
for (const type of ["pointerup", "pointercancel"]) {
|
|
657
|
-
root.addEventListener(type, (event) => {
|
|
658
|
-
if (event.pointerType === "touch")
|
|
659
|
-
hideLens();
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
root.addEventListener("wheel", (event) => {
|
|
663
|
-
if (interaction !== "magnify" || !flipper)
|
|
664
|
-
return;
|
|
665
|
-
event.preventDefault();
|
|
666
|
-
const delta = event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY;
|
|
667
|
-
zoomBy(Math.exp(-delta * 0.0015));
|
|
668
|
-
}, { passive: false });
|
|
669
|
-
function scheduleDetail(delay) {
|
|
670
|
-
if (interaction !== "magnify" || !options.detail)
|
|
671
|
-
return;
|
|
672
|
-
clearTimeout(detailTimer);
|
|
673
|
-
detailTimer = window.setTimeout(askDetail, delay);
|
|
674
|
-
}
|
|
675
|
-
/** Asks the host for the open pages painted sharp enough for the glass
|
|
676
|
-
* (a texel for each screen pixel at its centre, within the largest
|
|
677
|
-
* texture the book shows), and lets go of the others' paintings. */
|
|
678
|
-
function askDetail() {
|
|
679
|
-
const host = options.detail;
|
|
680
|
-
const flip = flipper;
|
|
681
|
-
if (interaction !== "magnify" || !host || !flip || !slot.deviceWidth || disposed)
|
|
682
|
-
return;
|
|
683
|
-
const cap = flip.maxTextureSize;
|
|
684
|
-
const want = Math.max(slot.deviceWidth, Math.min(cap, Math.floor(cap * aspect()), Math.ceil((slot.deviceWidth * zoomNow()) / 256) * 256));
|
|
685
|
-
const on = new Map();
|
|
686
|
-
for (const i of new Set([...pagesOf(spreads[shown]), ...pagesOf(spreads[current])])) {
|
|
687
|
-
const src = sourceOf(pages[i]);
|
|
688
|
-
if (src)
|
|
689
|
-
on.set(src, i);
|
|
690
|
-
}
|
|
691
|
-
for (const src of [...details.keys()])
|
|
692
|
-
if (!on.has(src))
|
|
693
|
-
details.delete(src);
|
|
694
|
-
for (const src of [...detailAsked.keys()])
|
|
695
|
-
if (!on.has(src))
|
|
696
|
-
detailAsked.delete(src);
|
|
697
|
-
flip.setDetail(details);
|
|
698
|
-
for (const [src, i] of on) {
|
|
699
|
-
if ((detailAsked.get(src) ?? 0) >= want)
|
|
700
|
-
continue;
|
|
701
|
-
detailAsked.set(src, want);
|
|
702
|
-
void Promise.resolve(host.paint(i, want)).then((painting) => {
|
|
703
|
-
if (!painting || flipper !== flip || interaction !== "magnify" || sourceOf(pages[i]) !== src)
|
|
704
|
-
return;
|
|
705
|
-
details.set(src, painting);
|
|
706
|
-
flip.setDetail(details);
|
|
707
|
-
}, () => detailAsked.delete(src));
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
/** Puts the glass away and lets go of its paintings. */
|
|
711
|
-
function releaseLens() {
|
|
712
|
-
hideLens();
|
|
713
|
-
clearTimeout(detailTimer);
|
|
714
|
-
if (!details.size && !detailAsked.size)
|
|
715
|
-
return;
|
|
716
|
-
details = new Map();
|
|
717
|
-
detailAsked = new Map();
|
|
718
|
-
flipper?.setDetail(details);
|
|
719
|
-
options.detail?.release?.();
|
|
720
|
-
}
|
|
721
|
-
root.addEventListener("pointerdown", (event) => {
|
|
722
|
-
// Only the hand takes pages, swipes and clicks them over.
|
|
723
|
-
if (interaction !== "hand")
|
|
724
|
-
return;
|
|
725
|
-
if (onPage(event))
|
|
726
|
-
return;
|
|
727
|
-
if (event.pointerType !== "mouse")
|
|
728
|
-
swipe = { x: event.clientX, y: event.clientY };
|
|
729
|
-
});
|
|
730
|
-
root.addEventListener("pointerup", (event) => {
|
|
731
|
-
const start = swipe;
|
|
732
|
-
swipe = null;
|
|
733
|
-
if (!start)
|
|
734
|
-
return;
|
|
735
|
-
const dx = event.clientX - start.x;
|
|
736
|
-
if (Math.abs(dx) > SWIPE && Math.abs(dx) > Math.abs(event.clientY - start.y))
|
|
737
|
-
go(current + ((dx < 0) !== rtl() ? 1 : -1));
|
|
738
|
-
});
|
|
739
|
-
/** A press on a page: taken by hand (the WebGL book, hit in 3D, since
|
|
740
|
-
* the tilted book does not lie over the DOM spread), or remembered as a
|
|
741
|
-
* possible click. True when the stage's swipe should not hear it. */
|
|
742
|
-
function onPage(event) {
|
|
743
|
-
if (event.button !== 0 || holding)
|
|
744
|
-
return false;
|
|
745
|
-
const target = event.target;
|
|
746
|
-
if (target.closest(".postext-folio-nav"))
|
|
747
|
-
return false;
|
|
748
|
-
if (flipper ? !flipper.hit(event) : !target.closest(".postext-folio-spread"))
|
|
749
|
-
return false;
|
|
750
|
-
press = { x: event.clientX, y: event.clientY };
|
|
751
|
-
// Where it was pressed, read before the hand lifts the leaf.
|
|
752
|
-
pressPoint = options.onPageClick ? pageAt(event) : null;
|
|
753
|
-
if (singleGl()) {
|
|
754
|
-
decide(event);
|
|
755
|
-
return true;
|
|
756
|
-
}
|
|
757
|
-
if (!flipper?.grab(event))
|
|
758
|
-
return false;
|
|
759
|
-
event.preventDefault();
|
|
760
|
-
hold(event, press);
|
|
761
|
-
return true;
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* One page at a time, the page is taken only once the pointer shows
|
|
765
|
-
* which way it goes: towards the spine it is lifted and dragged over
|
|
766
|
-
* (from where it was pressed), away from it the book goes back a page (a
|
|
767
|
-
* swipe); a press that does not move is a tap and turns forward.
|
|
768
|
-
*/
|
|
769
|
-
function decide(down) {
|
|
770
|
-
down.stopPropagation();
|
|
771
|
-
swipe = null;
|
|
772
|
-
const id = down.pointerId;
|
|
773
|
-
const start = { x: down.clientX, y: down.clientY };
|
|
774
|
-
const toSpine = rtl() ? 1 : -1;
|
|
775
|
-
const done = () => {
|
|
776
|
-
window.removeEventListener("pointermove", onMove);
|
|
777
|
-
window.removeEventListener("pointerup", onUp, true);
|
|
778
|
-
window.removeEventListener("pointercancel", onCancel);
|
|
779
|
-
};
|
|
780
|
-
const onMove = (e) => {
|
|
781
|
-
if (e.pointerId !== id)
|
|
782
|
-
return;
|
|
783
|
-
const dx = e.clientX - start.x;
|
|
784
|
-
const dy = e.clientY - start.y;
|
|
785
|
-
if (Math.hypot(dx, dy) < CLICK_SLOP || Math.abs(dy) > Math.abs(dx))
|
|
786
|
-
return;
|
|
787
|
-
if (Math.sign(dx) !== toSpine)
|
|
788
|
-
return; // a swipe back: settled on release
|
|
789
|
-
done();
|
|
790
|
-
if (!flipper?.grab(down))
|
|
791
|
-
return;
|
|
792
|
-
hold(down, start);
|
|
793
|
-
flipper.drag(e);
|
|
794
|
-
};
|
|
795
|
-
const onUp = (e) => {
|
|
796
|
-
if (e.pointerId !== id)
|
|
797
|
-
return;
|
|
798
|
-
done();
|
|
799
|
-
press = null;
|
|
800
|
-
const dx = e.clientX - start.x;
|
|
801
|
-
const dy = e.clientY - start.y;
|
|
802
|
-
if (Math.hypot(dx, dy) < CLICK_SLOP) {
|
|
803
|
-
if (!pageClick())
|
|
804
|
-
go(current + 1);
|
|
805
|
-
}
|
|
806
|
-
else if (Math.abs(dx) > SWIPE && Math.abs(dx) > Math.abs(dy) && Math.sign(dx) === -toSpine)
|
|
807
|
-
go(current - 1);
|
|
808
|
-
e.stopPropagation();
|
|
809
|
-
};
|
|
810
|
-
const onCancel = (e) => {
|
|
811
|
-
if (e.pointerId === id)
|
|
812
|
-
done();
|
|
813
|
-
};
|
|
814
|
-
window.addEventListener("pointermove", onMove);
|
|
815
|
-
window.addEventListener("pointerup", onUp, true);
|
|
816
|
-
window.addEventListener("pointercancel", onCancel);
|
|
817
|
-
}
|
|
818
|
-
/** Holds the page taken at `event` until the pointer lets go. */
|
|
819
|
-
function hold(event, start) {
|
|
820
|
-
// The hold follows the pointer anywhere: the window hears it even if
|
|
821
|
-
// the capture is lost, and a move with the button up means the release
|
|
822
|
-
// was missed.
|
|
823
|
-
const id = event.pointerId;
|
|
824
|
-
const onMove = (e) => {
|
|
825
|
-
if (e.pointerId !== id)
|
|
826
|
-
return;
|
|
827
|
-
if ((e.buttons & 1) === 0)
|
|
828
|
-
end(false);
|
|
829
|
-
else
|
|
830
|
-
flipper?.drag(e);
|
|
831
|
-
};
|
|
832
|
-
const onUp = (e) => {
|
|
833
|
-
if (e.pointerId === id)
|
|
834
|
-
end(Math.hypot(e.clientX - start.x, e.clientY - start.y) < CLICK_SLOP);
|
|
835
|
-
};
|
|
836
|
-
const onCancel = (e) => {
|
|
837
|
-
if (e.pointerId === id)
|
|
838
|
-
end(false);
|
|
839
|
-
};
|
|
840
|
-
const onBlur = () => end(false);
|
|
841
|
-
const end = (click) => {
|
|
842
|
-
window.removeEventListener("pointermove", onMove);
|
|
843
|
-
window.removeEventListener("pointerup", onUp, true);
|
|
844
|
-
window.removeEventListener("pointercancel", onCancel);
|
|
845
|
-
window.removeEventListener("blur", onBlur);
|
|
846
|
-
spreadEl.classList.remove("is-held");
|
|
847
|
-
holding = null;
|
|
848
|
-
press = null;
|
|
849
|
-
// A click that acts on the page (a video) lets the leaf fall back.
|
|
850
|
-
flipper?.release(click && !pageClick());
|
|
851
|
-
};
|
|
852
|
-
window.addEventListener("pointermove", onMove);
|
|
853
|
-
// Captured, so the stage's swipe does not hear the hold's release.
|
|
854
|
-
window.addEventListener("pointerup", onUp, true);
|
|
855
|
-
window.addEventListener("pointercancel", onCancel);
|
|
856
|
-
window.addEventListener("blur", onBlur);
|
|
857
|
-
holding = end;
|
|
858
|
-
swipe = null;
|
|
859
|
-
spreadEl.classList.add("is-held");
|
|
860
|
-
try {
|
|
861
|
-
spreadEl.setPointerCapture(id);
|
|
862
|
-
}
|
|
863
|
-
catch {
|
|
864
|
-
// The window listeners carry the hold without it.
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
root.addEventListener("pointerup", (event) => {
|
|
868
|
-
if (holding || singleGl() || interaction !== "hand")
|
|
869
|
-
return;
|
|
870
|
-
const start = press;
|
|
871
|
-
press = null;
|
|
872
|
-
const click = !!start && Math.hypot(event.clientX - start.x, event.clientY - start.y) < CLICK_SLOP;
|
|
873
|
-
if (!click)
|
|
874
|
-
return;
|
|
875
|
-
if (pageClick()) {
|
|
876
|
-
swipe = null;
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
// A click on the recto turns forward, on the verso back (pages already
|
|
880
|
-
// in the air: it joins them).
|
|
881
|
-
if (flipper) {
|
|
882
|
-
const side = flipper.hit(event);
|
|
883
|
-
if (!side)
|
|
884
|
-
return;
|
|
885
|
-
swipe = null;
|
|
886
|
-
return go(current + (single ? 1 : side));
|
|
887
|
-
}
|
|
888
|
-
if (!event.target.closest(".postext-folio-page:not(.is-empty)"))
|
|
889
|
-
return;
|
|
890
|
-
const rect = spreadEl.getBoundingClientRect();
|
|
891
|
-
swipe = null;
|
|
892
|
-
if (single)
|
|
893
|
-
go(current + 1);
|
|
894
|
-
else
|
|
895
|
-
go(current + ((event.clientX > rect.left + rect.width / 2) !== rtl() ? 1 : -1));
|
|
896
|
-
});
|
|
897
|
-
/** A click at the point pressed: the host's to act on (true), or the
|
|
898
|
-
* page's to turn. */
|
|
899
|
-
function pageClick() {
|
|
900
|
-
const point = pressPoint;
|
|
901
|
-
pressPoint = null;
|
|
902
|
-
return !!point && !!options.onPageClick?.(point);
|
|
903
|
-
}
|
|
904
|
-
/** The page index in slot `side` (0 the verso, 1 the recto) of the
|
|
905
|
-
* spread on show. */
|
|
906
|
-
const pageInSlot = (side) => {
|
|
907
|
-
const s = spreads[shown];
|
|
908
|
-
if (!s)
|
|
909
|
-
return null;
|
|
910
|
-
return single && !singleGl() ? (s[0] ?? s[1]) : s[side];
|
|
911
|
-
};
|
|
912
|
-
function pageAt(event) {
|
|
913
|
-
if (flipper) {
|
|
914
|
-
const hit = flipper.pagePoint(event);
|
|
915
|
-
if (!hit)
|
|
916
|
-
return null;
|
|
917
|
-
const page = pageInSlot(hit.side);
|
|
918
|
-
return page === null || page === undefined ? null : { page, x: hit.x, y: hit.y };
|
|
919
|
-
}
|
|
920
|
-
// The DOM spread: its pages in slot order (`render`), whatever side
|
|
921
|
-
// the layout puts them on (a right-bound book runs right to left, so
|
|
922
|
-
// its verso stands on the right); each is found by its own box.
|
|
923
|
-
const els = [...spreadEl.children];
|
|
924
|
-
for (let k = 0; k < els.length; k++) {
|
|
925
|
-
const r = els[k].getBoundingClientRect();
|
|
926
|
-
if (event.clientX < r.left || event.clientX > r.right || event.clientY < r.top || event.clientY > r.bottom)
|
|
927
|
-
continue;
|
|
928
|
-
const side = (els.length === 1 ? 1 : k);
|
|
929
|
-
const page = els.length === 1 ? pageInSlot(0) : pageInSlot(side);
|
|
930
|
-
if (page === null || page === undefined)
|
|
931
|
-
return null;
|
|
932
|
-
return { page, x: (event.clientX - r.left) / r.width, y: (event.clientY - r.top) / r.height };
|
|
933
|
-
}
|
|
934
|
-
return null;
|
|
935
|
-
}
|
|
936
|
-
/** The pointer is the host's to select with (select mode, and under the
|
|
937
|
-
* glass in magnify mode, #543). */
|
|
938
|
-
function leftToHost() {
|
|
939
|
-
return interaction === "select" || interaction === "magnify";
|
|
940
|
-
}
|
|
941
|
-
/** The page point a pointer aims at: under it, or in magnify mode under
|
|
942
|
-
* the glass's centre (#543). */
|
|
943
|
-
function aimedPageAt(event) {
|
|
944
|
-
if (interaction !== "magnify")
|
|
945
|
-
return pageAt(event);
|
|
946
|
-
const at = heldAt(event);
|
|
947
|
-
return pageAt({ clientX: at.x, clientY: at.y });
|
|
948
|
-
}
|
|
949
|
-
function pointOnScreen(point) {
|
|
950
|
-
const s = spreads[shown];
|
|
951
|
-
if (!s)
|
|
952
|
-
return null;
|
|
953
|
-
const found = pageInSlot(1) === point.page ? 1 : pageInSlot(0) === point.page ? 0 : -1;
|
|
954
|
-
if (found < 0)
|
|
955
|
-
return null;
|
|
956
|
-
const side = found;
|
|
957
|
-
if (flipper)
|
|
958
|
-
return flipper.screenPoint(side, point.x, point.y);
|
|
959
|
-
const els = [...spreadEl.children];
|
|
960
|
-
const el = els.length === 1 ? els[0] : els[side];
|
|
961
|
-
if (!el)
|
|
962
|
-
return null;
|
|
963
|
-
const r = el.getBoundingClientRect();
|
|
964
|
-
return { x: r.left + point.x * r.width, y: r.top + point.y * r.height };
|
|
965
|
-
}
|
|
966
|
-
function applyInteraction() {
|
|
967
|
-
root.classList.toggle("is-orbit", interaction === "orbit");
|
|
968
|
-
root.classList.toggle("is-select", interaction === "select");
|
|
969
|
-
root.classList.toggle("is-magnify", interaction === "magnify");
|
|
970
|
-
if (!leftToHost())
|
|
971
|
-
root.classList.remove("is-over-page");
|
|
972
|
-
if (interaction !== "hand")
|
|
973
|
-
root.classList.remove("is-over-grip");
|
|
974
|
-
if (interaction === "magnify")
|
|
975
|
-
scheduleDetail(0);
|
|
976
|
-
else
|
|
977
|
-
releaseLens();
|
|
978
|
-
}
|
|
106
|
+
Object.assign(vlate, { onPage, leftToHost, aimedPageAt });
|
|
979
107
|
let lastWidth = -1;
|
|
980
108
|
const observer = new ResizeObserver(() => {
|
|
981
|
-
if (disposed)
|
|
109
|
+
if (vs.disposed)
|
|
982
110
|
return;
|
|
983
111
|
const w = root.clientWidth;
|
|
984
|
-
const wasSingle = single;
|
|
985
|
-
const nowSingle = mode === "single" || (mode === "auto" && w > 0 && w < SINGLE_BELOW);
|
|
112
|
+
const wasSingle = vs.single;
|
|
113
|
+
const nowSingle = vs.mode === "single" || (vs.mode === "auto" && w > 0 && w < SINGLE_BELOW);
|
|
986
114
|
if (lastWidth >= 0 && nowSingle !== wasSingle)
|
|
987
|
-
rebuild(pagesOf(spreads[shown])[0] ?? 0);
|
|
115
|
+
rebuild(pagesOf(vs.spreads[vs.shown])[0] ?? 0);
|
|
988
116
|
else
|
|
989
117
|
fit();
|
|
990
118
|
lastWidth = w;
|
|
@@ -995,123 +123,123 @@ export function createFolio(container, options) {
|
|
|
995
123
|
rebuild(options.at ?? 0);
|
|
996
124
|
return {
|
|
997
125
|
element: root,
|
|
998
|
-
goToPage: (index, opts) => (opts?.instant ? jump : go)(spreadOfPage(spreads, index)),
|
|
126
|
+
goToPage: (index, opts) => (opts?.instant ? jump : go)(spreadOfPage(vs.spreads, index)),
|
|
999
127
|
goToSpread: (index, opts) => (opts?.instant ? jump : go)(index),
|
|
1000
|
-
next: () => go(current + 1),
|
|
1001
|
-
prev: () => go(current - 1),
|
|
128
|
+
next: () => go(vs.current + 1),
|
|
129
|
+
prev: () => go(vs.current - 1),
|
|
1002
130
|
setLabels(next) {
|
|
1003
|
-
labels = { ...labels, ...next };
|
|
131
|
+
vs.labels = { ...vs.labels, ...next };
|
|
1004
132
|
applyLabels();
|
|
1005
133
|
render();
|
|
1006
134
|
},
|
|
1007
135
|
get pageSize() {
|
|
1008
|
-
return slot;
|
|
136
|
+
return vs.slot;
|
|
1009
137
|
},
|
|
1010
138
|
resetView() {
|
|
1011
|
-
flipper?.resetOrbit();
|
|
139
|
+
vs.flipper?.resetOrbit();
|
|
1012
140
|
},
|
|
1013
141
|
getView() {
|
|
1014
|
-
return flipper?.view() ?? null;
|
|
142
|
+
return vs.flipper?.view() ?? null;
|
|
1015
143
|
},
|
|
1016
144
|
setInteraction(next) {
|
|
1017
|
-
if (next === interaction)
|
|
145
|
+
if (next === vs.interaction)
|
|
1018
146
|
return;
|
|
1019
|
-
interaction = next;
|
|
147
|
+
vs.interaction = next;
|
|
1020
148
|
applyInteraction();
|
|
1021
149
|
},
|
|
1022
150
|
setMagnification(zoom) {
|
|
1023
|
-
lensZoom = zoom ? clamp(zoom, ZOOM_MIN, ZOOM_MAX) : null;
|
|
1024
|
-
if (lensAt)
|
|
1025
|
-
flipper?.setLens(lensOf(lensAt));
|
|
151
|
+
vs.lensZoom = zoom ? clamp(zoom, ZOOM_MIN, ZOOM_MAX) : null;
|
|
152
|
+
if (vs.lensAt)
|
|
153
|
+
vs.flipper?.setLens(lensOf(vs.lensAt));
|
|
1026
154
|
scheduleDetail(250);
|
|
1027
155
|
},
|
|
1028
156
|
getMagnifier() {
|
|
1029
|
-
return { zoom: zoomNow(), held: lensAt !== null };
|
|
157
|
+
return { zoom: zoomNow(), held: vs.lensAt !== null };
|
|
1030
158
|
},
|
|
1031
159
|
holdMagnifier(held) {
|
|
1032
160
|
if (!held) {
|
|
1033
161
|
hideLens();
|
|
1034
162
|
return;
|
|
1035
163
|
}
|
|
1036
|
-
if (interaction !== "magnify" || !flipper)
|
|
164
|
+
if (vs.interaction !== "magnify" || !vs.flipper)
|
|
1037
165
|
return;
|
|
1038
166
|
raiseLens();
|
|
1039
|
-
if (lensAt)
|
|
1040
|
-
flipper.setLens(lensOf(lensAt));
|
|
167
|
+
if (vs.lensAt)
|
|
168
|
+
vs.flipper.setLens(lensOf(vs.lensAt));
|
|
1041
169
|
scheduleDetail(250);
|
|
1042
170
|
},
|
|
1043
171
|
pageAt: aimedPageAt,
|
|
1044
172
|
pointOnScreen,
|
|
1045
173
|
refreshPage(src) {
|
|
1046
|
-
flipper?.touch(src);
|
|
174
|
+
vs.flipper?.touch(src);
|
|
1047
175
|
},
|
|
1048
176
|
setPageVideo(video) {
|
|
1049
177
|
this.setPageVideos(video ? [video] : []);
|
|
1050
178
|
},
|
|
1051
179
|
setPageVideos(videos) {
|
|
1052
|
-
for (const v of pageVideos)
|
|
180
|
+
for (const v of vs.pageVideos)
|
|
1053
181
|
if (!videos.some((n) => n.element === v.element))
|
|
1054
182
|
unplace(v);
|
|
1055
|
-
pageVideos = videos.slice();
|
|
183
|
+
vs.pageVideos = videos.slice();
|
|
1056
184
|
applyVideo();
|
|
1057
185
|
},
|
|
1058
186
|
setAppearance(next) {
|
|
1059
|
-
appearance = { ...appearance, ...next };
|
|
187
|
+
vs.appearance = { ...vs.appearance, ...next };
|
|
1060
188
|
const flip = flipAppearance();
|
|
1061
189
|
const key = JSON.stringify(flip);
|
|
1062
|
-
if (key === appearanceKey)
|
|
190
|
+
if (key === vs.appearanceKey)
|
|
1063
191
|
return;
|
|
1064
|
-
appearanceKey = key;
|
|
1065
|
-
flipper?.setAppearance(flip);
|
|
192
|
+
vs.appearanceKey = key;
|
|
193
|
+
vs.flipper?.setAppearance(flip);
|
|
1066
194
|
fit();
|
|
1067
195
|
},
|
|
1068
196
|
get state() {
|
|
1069
|
-
return { spread: current, pages: pagesOf(spreads[current]) };
|
|
197
|
+
return { spread: vs.current, pages: pagesOf(vs.spreads[vs.current]) };
|
|
1070
198
|
},
|
|
1071
199
|
setPages(next, opts = {}) {
|
|
1072
|
-
const atPage = opts.at ?? pagesOf(spreads[shown])[0] ?? 0;
|
|
1073
|
-
const relayout = next.length !== pages.length ||
|
|
1074
|
-
(opts.firstPageRecto ?? firstPageRecto) !== firstPageRecto ||
|
|
1075
|
-
(opts.binding ?? binding) !== binding ||
|
|
1076
|
-
(opts.mode ?? mode) !== mode ||
|
|
1077
|
-
(opts.paper ?? paper) !== paper;
|
|
1078
|
-
pages = next;
|
|
1079
|
-
firstPageRecto = opts.firstPageRecto ?? firstPageRecto;
|
|
1080
|
-
binding = opts.binding ?? binding;
|
|
1081
|
-
mode = opts.mode ?? mode;
|
|
1082
|
-
paper = opts.paper ?? paper;
|
|
200
|
+
const atPage = opts.at ?? pagesOf(vs.spreads[vs.shown])[0] ?? 0;
|
|
201
|
+
const relayout = next.length !== vs.pages.length ||
|
|
202
|
+
(opts.firstPageRecto ?? vs.firstPageRecto) !== vs.firstPageRecto ||
|
|
203
|
+
(opts.binding ?? vs.binding) !== vs.binding ||
|
|
204
|
+
(opts.mode ?? vs.mode) !== vs.mode ||
|
|
205
|
+
(opts.paper ?? vs.paper) !== vs.paper;
|
|
206
|
+
vs.pages = next;
|
|
207
|
+
vs.firstPageRecto = opts.firstPageRecto ?? vs.firstPageRecto;
|
|
208
|
+
vs.binding = opts.binding ?? vs.binding;
|
|
209
|
+
vs.mode = opts.mode ?? vs.mode;
|
|
210
|
+
vs.paper = opts.paper ?? vs.paper;
|
|
1083
211
|
if (relayout)
|
|
1084
212
|
return rebuild(atPage);
|
|
1085
213
|
// Same spreads: the leaves take their new pages where they lie.
|
|
1086
|
-
flipper?.setBook(bookOf(), opts.at !== undefined ? spreadOfPage(spreads, opts.at) : undefined);
|
|
214
|
+
vs.flipper?.setBook(bookOf(), opts.at !== undefined ? spreadOfPage(vs.spreads, opts.at) : undefined);
|
|
1087
215
|
// The glass's paintings follow pages that came as other sources (a
|
|
1088
216
|
// page decorated for the first time, #543).
|
|
1089
217
|
scheduleDetail(0);
|
|
1090
218
|
// Pages that came with another paper (a relayout moved a plate run).
|
|
1091
|
-
const papers = JSON.stringify(pages.map(paperOf));
|
|
1092
|
-
if (flipper && papers !== papersKey) {
|
|
219
|
+
const papers = JSON.stringify(vs.pages.map(paperOf));
|
|
220
|
+
if (vs.flipper && papers !== papersKey) {
|
|
1093
221
|
const flip = flipAppearance();
|
|
1094
|
-
appearanceKey = JSON.stringify(flip);
|
|
1095
|
-
flipper.setAppearance(flip);
|
|
222
|
+
vs.appearanceKey = JSON.stringify(flip);
|
|
223
|
+
vs.flipper.setAppearance(flip);
|
|
1096
224
|
}
|
|
1097
225
|
papersKey = papers;
|
|
1098
226
|
if (opts.at !== undefined)
|
|
1099
|
-
current = shown = spreadOfPage(spreads, opts.at);
|
|
227
|
+
vs.current = vs.shown = spreadOfPage(vs.spreads, opts.at);
|
|
1100
228
|
// The videos' pages may be painted on other canvases now.
|
|
1101
|
-
if (pageVideos.length)
|
|
1102
|
-
flipper?.setVideos(pageVideos.map((v) => ({ src: sourceOf(pages[v.page]), element: v.element, frame: v })));
|
|
229
|
+
if (vs.pageVideos.length)
|
|
230
|
+
vs.flipper?.setVideos(vs.pageVideos.map((v) => ({ src: sourceOf(vs.pages[v.page]), element: v.element, frame: v })));
|
|
1103
231
|
fit();
|
|
1104
232
|
render();
|
|
1105
233
|
},
|
|
1106
234
|
dispose() {
|
|
1107
|
-
disposed = true;
|
|
1108
|
-
clearTimeout(detailTimer);
|
|
1109
|
-
for (const v of pageVideos)
|
|
235
|
+
vs.disposed = true;
|
|
236
|
+
clearTimeout(vs.detailTimer);
|
|
237
|
+
for (const v of vs.pageVideos)
|
|
1110
238
|
v.element.remove();
|
|
1111
|
-
pageVideos = [];
|
|
239
|
+
vs.pageVideos = [];
|
|
1112
240
|
observer.disconnect();
|
|
1113
|
-
holding?.(false);
|
|
1114
|
-
flipper?.dispose();
|
|
241
|
+
vs.holding?.(false);
|
|
242
|
+
vs.flipper?.dispose();
|
|
1115
243
|
root.remove();
|
|
1116
244
|
},
|
|
1117
245
|
};
|