pixi-effects 0.28.2 → 0.29.0
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/CHANGELOG.md +52 -0
- package/README.md +10 -10
- package/ai/chat-template.html +5 -5
- package/ai/tools/check.mjs +18 -3
- package/ai/tools/pixi-effects.mjs +1 -1
- package/ai/tools/render.mjs +14 -9
- package/ai/tools/view.mjs +6 -2
- package/dist/Base-CB1_FPZD.d.cts +60 -0
- package/dist/Base-Ne74ztNl.d.ts +60 -0
- package/dist/Composition-MUKR3FYD.cjs +16 -0
- package/dist/{Composition-K6B3APM5.cjs.map → Composition-MUKR3FYD.cjs.map} +1 -1
- package/dist/Composition-RJGA7KDU.js +7 -0
- package/dist/{Composition-H7UNR5F5.js.map → Composition-RJGA7KDU.js.map} +1 -1
- package/dist/Controller.cjs +73 -45
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +16 -22
- package/dist/Controller.d.ts +16 -22
- package/dist/Controller.js +74 -40
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-CHcM8HNB.d.ts → Movie-C2rZFkrO.d.ts} +227 -119
- package/dist/{Movie-Cx5ozUGb.d.cts → Movie-DdnpLpw1.d.cts} +227 -119
- package/dist/{blendModes-HDOP6R7T.cjs → blendModes-ETMYWATF.cjs} +5 -5
- package/dist/{blendModes-HDOP6R7T.cjs.map → blendModes-ETMYWATF.cjs.map} +1 -1
- package/dist/{blendModes-PAVTW3BO.js → blendModes-RIVZXL5A.js} +4 -4
- package/dist/{blendModes-PAVTW3BO.js.map → blendModes-RIVZXL5A.js.map} +1 -1
- package/dist/{chunk-P4GY3Z4G.cjs → chunk-2DXPTKQZ.cjs} +6 -6
- package/dist/chunk-2DXPTKQZ.cjs.map +1 -0
- package/dist/{chunk-TYWFKCPY.js → chunk-4KB3GUEK.js} +5 -5
- package/dist/chunk-4KB3GUEK.js.map +1 -0
- package/dist/{chunk-5MZWXVTT.js → chunk-A3Q3SDSH.js} +35 -28
- package/dist/chunk-A3Q3SDSH.js.map +1 -0
- package/dist/chunk-CRQFDY7E.js +4182 -0
- package/dist/chunk-CRQFDY7E.js.map +1 -0
- package/dist/chunk-HREBQAY4.cjs +4206 -0
- package/dist/chunk-HREBQAY4.cjs.map +1 -0
- package/dist/{chunk-ZMAD3KJU.cjs → chunk-LYOKH7KE.cjs} +22 -3
- package/dist/chunk-LYOKH7KE.cjs.map +1 -0
- package/dist/{chunk-45QKLD6M.js → chunk-RCVZX2JP.js} +22 -3
- package/dist/chunk-RCVZX2JP.js.map +1 -0
- package/dist/{chunk-KBH33IQX.cjs → chunk-WHNADXXG.cjs} +121 -114
- package/dist/chunk-WHNADXXG.cjs.map +1 -0
- package/dist/{chunk-7VN2KI3U.js → chunk-ZAXYPXKW.js} +14 -6
- package/dist/chunk-ZAXYPXKW.js.map +1 -0
- package/dist/{chunk-MJW7KILM.cjs → chunk-ZVUO325O.cjs} +32 -24
- package/dist/chunk-ZVUO325O.cjs.map +1 -0
- package/dist/index.cjs +106 -4482
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -60
- package/dist/index.d.ts +15 -60
- package/dist/index.js +6 -4457
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/{Presenter.cjs → present.cjs} +370 -31
- package/dist/present.cjs.map +1 -0
- package/dist/present.d.cts +278 -0
- package/dist/present.d.ts +278 -0
- package/dist/{Presenter.js → present.js} +358 -32
- package/dist/present.js.map +1 -0
- package/dist/three.cjs +6 -6
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +2 -2
- package/dist/{types-DpK1vwcl.d.ts → types-BzIB65Vk.d.cts} +11 -265
- package/dist/{types-DpK1vwcl.d.cts → types-BzIB65Vk.d.ts} +11 -265
- package/docs/api.md +44 -77
- package/docs/dsl.md +16 -14
- package/llms-full.txt +86 -115
- package/llms.txt +1 -1
- package/package.json +5 -5
- package/skills/pixi-effects/SKILL.md +5 -5
- package/skills/pixi-effects/reference/cheatsheet.md +9 -9
- package/skills/pixi-effects/reference/direction.md +2 -0
- package/skills/pixi-effects/reference/pitfalls.md +6 -6
- package/skills/pixi-effects/reference/recipes.md +4 -4
- package/skills/pixi-effects/template.html +7 -7
- package/dist/Base-C6f0fE8j.d.ts +0 -109
- package/dist/Base-DI1FCGcH.d.cts +0 -109
- package/dist/Composition-H7UNR5F5.js +0 -7
- package/dist/Composition-K6B3APM5.cjs +0 -16
- package/dist/Presenter.cjs.map +0 -1
- package/dist/Presenter.d.cts +0 -122
- package/dist/Presenter.d.ts +0 -122
- package/dist/Presenter.js.map +0 -1
- package/dist/chunk-45QKLD6M.js.map +0 -1
- package/dist/chunk-5MZWXVTT.js.map +0 -1
- package/dist/chunk-7VN2KI3U.js.map +0 -1
- package/dist/chunk-KBH33IQX.cjs.map +0 -1
- package/dist/chunk-MJW7KILM.cjs.map +0 -1
- package/dist/chunk-P4GY3Z4G.cjs.map +0 -1
- package/dist/chunk-TYWFKCPY.js.map +0 -1
- package/dist/chunk-ZMAD3KJU.cjs.map +0 -1
|
@@ -1,6 +1,268 @@
|
|
|
1
|
+
import './chunk-CRQFDY7E.js';
|
|
2
|
+
import './chunk-A3Q3SDSH.js';
|
|
3
|
+
import './chunk-ZAXYPXKW.js';
|
|
4
|
+
import './chunk-4KB3GUEK.js';
|
|
5
|
+
import './chunk-RCVZX2JP.js';
|
|
1
6
|
import { warnUnknownOptions } from './chunk-BXOCEZ4U.js';
|
|
2
7
|
|
|
3
|
-
// src/
|
|
8
|
+
// src/present/stops.ts
|
|
9
|
+
function nextStopAfter(stops, frame) {
|
|
10
|
+
return stops.find((s) => s.frame > frame) ?? null;
|
|
11
|
+
}
|
|
12
|
+
function previousStopBefore(stops, frame) {
|
|
13
|
+
for (let i = stops.length - 1; i >= 0; i--) if (stops[i].frame < frame) return stops[i];
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function stopAtOrBefore(stops, frame) {
|
|
17
|
+
for (let i = stops.length - 1; i >= 0; i--) if (stops[i].frame <= frame) return stops[i];
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function pageStarts(stops) {
|
|
21
|
+
return stops.filter((s) => s.pageStart);
|
|
22
|
+
}
|
|
23
|
+
function pictureStops(stops, opts = {}) {
|
|
24
|
+
if (opts.which === "stops") return opts.pdf ? stops.filter((s) => s.pdf !== false) : [...stops];
|
|
25
|
+
const byPage = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const s of stops) byPage.set(s.pageIndex, [...byPage.get(s.pageIndex) ?? [], s]);
|
|
27
|
+
const take = (list) => opts.pick === "first" ? list[0] : list[list.length - 1];
|
|
28
|
+
const picks = [];
|
|
29
|
+
for (const list of byPage.values()) {
|
|
30
|
+
const flagged = list.filter((s) => s.pdf === true);
|
|
31
|
+
const usable = list.filter((s) => s.pdf !== false);
|
|
32
|
+
const one = take(flagged.length ? flagged : usable.length ? usable : opts.pdf ? [] : list);
|
|
33
|
+
if (one) picks.push(one);
|
|
34
|
+
}
|
|
35
|
+
return picks;
|
|
36
|
+
}
|
|
37
|
+
function changedFraction(a, b, threshold = 12) {
|
|
38
|
+
const n = Math.min(a.length, b.length) >> 2;
|
|
39
|
+
if (n === 0) return 0;
|
|
40
|
+
let changed = 0;
|
|
41
|
+
for (let i = 0; i < n; i++) {
|
|
42
|
+
const k = i << 2;
|
|
43
|
+
if (Math.abs(a[k] - b[k]) > threshold || Math.abs(a[k + 1] - b[k + 1]) > threshold || Math.abs(a[k + 2] - b[k + 2]) > threshold) changed++;
|
|
44
|
+
}
|
|
45
|
+
return changed / n;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/present/navigation.ts
|
|
49
|
+
var listeners = /* @__PURE__ */ new WeakMap();
|
|
50
|
+
var toward = /* @__PURE__ */ new WeakMap();
|
|
51
|
+
function onStop(movie, fn) {
|
|
52
|
+
let set = listeners.get(movie);
|
|
53
|
+
if (!set) {
|
|
54
|
+
set = /* @__PURE__ */ new Set();
|
|
55
|
+
listeners.set(movie, set);
|
|
56
|
+
}
|
|
57
|
+
set.add(fn);
|
|
58
|
+
return () => {
|
|
59
|
+
set.delete(fn);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function emit(movie, stop) {
|
|
63
|
+
const e = { index: stop.index, stop, pageIndex: stop.pageIndex };
|
|
64
|
+
for (const fn of [...listeners.get(movie) ?? []]) fn(e);
|
|
65
|
+
}
|
|
66
|
+
var stopIndex = (movie) => stopAtOrBefore(movie.stops, movie.currentFrame)?.index ?? -1;
|
|
67
|
+
var currentStop = (movie) => stopAtOrBefore(movie.stops, movie.currentFrame);
|
|
68
|
+
var pageIndex = (movie) => currentStop(movie)?.pageIndex ?? -1;
|
|
69
|
+
var pageCount = (movie) => pageStarts(movie.stops).length;
|
|
70
|
+
async function next(movie) {
|
|
71
|
+
if (!movie.isReady) return;
|
|
72
|
+
const going = toward.get(movie);
|
|
73
|
+
if (going && movie.isPlaying) {
|
|
74
|
+
toward.delete(movie);
|
|
75
|
+
movie.pause();
|
|
76
|
+
await movie.gotoFrame(going.frame, true);
|
|
77
|
+
emit(movie, going);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
toward.delete(movie);
|
|
81
|
+
if (movie.currentFrame >= movie.totalFrames) return;
|
|
82
|
+
const target = nextStopAfter(movie.stops, movie.currentFrame);
|
|
83
|
+
if (!target) {
|
|
84
|
+
await movie.playUntil();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
toward.set(movie, target);
|
|
88
|
+
const landed = await movie.playUntil(target.frame / movie.frameRate);
|
|
89
|
+
if (toward.get(movie) === target) toward.delete(movie);
|
|
90
|
+
if (landed) emit(movie, target);
|
|
91
|
+
}
|
|
92
|
+
async function prev(movie) {
|
|
93
|
+
if (!movie.isReady) return;
|
|
94
|
+
toward.delete(movie);
|
|
95
|
+
movie.pause();
|
|
96
|
+
const target = previousStopBefore(movie.stops, movie.currentFrame);
|
|
97
|
+
await movie.gotoFrame(target ? target.frame : 0, true);
|
|
98
|
+
if (target) emit(movie, target);
|
|
99
|
+
}
|
|
100
|
+
async function goToStop(movie, index) {
|
|
101
|
+
if (!movie.isReady) return;
|
|
102
|
+
const stop = movie.stops[index];
|
|
103
|
+
if (!stop) {
|
|
104
|
+
console.warn(`pixi-effects: goToStop(movie, ${index}): there are ${movie.stops.length} stops (0 to ${movie.stops.length - 1})`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
toward.delete(movie);
|
|
108
|
+
movie.pause();
|
|
109
|
+
await movie.gotoFrame(stop.frame, true);
|
|
110
|
+
emit(movie, stop);
|
|
111
|
+
}
|
|
112
|
+
async function goToPage(movie, page) {
|
|
113
|
+
const starts = pageStarts(movie.stops);
|
|
114
|
+
const stop = starts[page];
|
|
115
|
+
if (!stop) {
|
|
116
|
+
console.warn(`pixi-effects: goToPage(movie, ${page}): there are ${starts.length} pages (0 to ${starts.length - 1})`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
await goToStop(movie, stop.index);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/present/pdf.ts
|
|
123
|
+
var enc = (s) => {
|
|
124
|
+
const out = new Uint8Array(s.length);
|
|
125
|
+
for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 255;
|
|
126
|
+
return out;
|
|
127
|
+
};
|
|
128
|
+
var num = (n) => String(Math.round(n * 1e3) / 1e3);
|
|
129
|
+
var textString = (s) => {
|
|
130
|
+
if (/^[\x20-\x7e]*$/.test(s)) return `(${s.replace(/[\\()]/g, (m) => `\\${m}`)})`;
|
|
131
|
+
let hex = "FEFF";
|
|
132
|
+
for (let i = 0; i < s.length; i++) hex += s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
|
|
133
|
+
return `<${hex}>`;
|
|
134
|
+
};
|
|
135
|
+
function buildPdf(pages, opts = {}) {
|
|
136
|
+
if (pages.length === 0) throw new Error("pixi-effects: a PDF needs at least one page");
|
|
137
|
+
const first = (i) => 3 + i * 3;
|
|
138
|
+
const objects = [];
|
|
139
|
+
objects[1] = [enc("<< /Type /Catalog /Pages 2 0 R >>")];
|
|
140
|
+
objects[2] = [enc(`<< /Type /Pages /Kids [${pages.map((_, i) => `${first(i)} 0 R`).join(" ")}] /Count ${pages.length} >>`)];
|
|
141
|
+
pages.forEach((p, i) => {
|
|
142
|
+
const w = p.width * 0.75, h = p.height * 0.75, n = first(i);
|
|
143
|
+
objects[n] = [enc(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${num(w)} ${num(h)}] /Resources << /XObject << /Im0 ${n + 2} 0 R >> >> /Contents ${n + 1} 0 R >>`)];
|
|
144
|
+
const content = `q ${num(w)} 0 0 ${num(h)} 0 0 cm /Im0 Do Q`;
|
|
145
|
+
objects[n + 1] = [enc(`<< /Length ${content.length} >>
|
|
146
|
+
stream
|
|
147
|
+
${content}
|
|
148
|
+
endstream`)];
|
|
149
|
+
objects[n + 2] = [enc(`<< /Type /XObject /Subtype /Image /Width ${p.width} /Height ${p.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${p.jpeg.length} >>
|
|
150
|
+
stream
|
|
151
|
+
`), p.jpeg, enc("\nendstream")];
|
|
152
|
+
});
|
|
153
|
+
const infoNo = opts.title !== void 0 ? first(pages.length) : 0;
|
|
154
|
+
if (infoNo) objects[infoNo] = [enc(`<< /Title ${textString(opts.title)} /Producer (pixi-effects) >>`)];
|
|
155
|
+
const count = infoNo || first(pages.length);
|
|
156
|
+
const chunks = [enc("%PDF-1.4\n%\xE2\xE3\xCF\xD3\n")];
|
|
157
|
+
let offset = chunks[0].length;
|
|
158
|
+
const offsets = [];
|
|
159
|
+
for (let n = 1; n < count + (infoNo ? 1 : 0); n++) {
|
|
160
|
+
const body = objects[n];
|
|
161
|
+
if (!body) continue;
|
|
162
|
+
offsets[n] = offset;
|
|
163
|
+
const parts = [enc(`${n} 0 obj
|
|
164
|
+
`), ...body, enc("\nendobj\n")];
|
|
165
|
+
for (const part of parts) {
|
|
166
|
+
chunks.push(part);
|
|
167
|
+
offset += part.length;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const size = (infoNo || first(pages.length)) + (infoNo ? 1 : 0);
|
|
171
|
+
let xref = `xref
|
|
172
|
+
0 ${size}
|
|
173
|
+
0000000000 65535 f
|
|
174
|
+
`;
|
|
175
|
+
for (let n = 1; n < size; n++) xref += `${String(offsets[n] ?? 0).padStart(10, "0")} 00000 n
|
|
176
|
+
`;
|
|
177
|
+
xref += `trailer
|
|
178
|
+
<< /Size ${size} /Root 1 0 R${infoNo ? ` /Info ${infoNo} 0 R` : ""} >>
|
|
179
|
+
startxref
|
|
180
|
+
${offset}
|
|
181
|
+
%%EOF
|
|
182
|
+
`;
|
|
183
|
+
chunks.push(enc(xref));
|
|
184
|
+
const total = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
185
|
+
const out = new Uint8Array(total);
|
|
186
|
+
let at = 0;
|
|
187
|
+
for (const c of chunks) {
|
|
188
|
+
out.set(c, at);
|
|
189
|
+
at += c.length;
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/present/images.ts
|
|
195
|
+
function requireReady(movie, what) {
|
|
196
|
+
if (!movie.isReady) throw new Error(`pixi-effects: ${what}() needs a ready movie \u2014 await movie.init(...) first`);
|
|
197
|
+
}
|
|
198
|
+
async function stopImages(movie, opts = {}) {
|
|
199
|
+
warnUnknownOptions("stopImages()", opts, ["which", "pick", "scale", "type", "quality", "as", "motionBlur", "pdf", "onImage"]);
|
|
200
|
+
requireReady(movie, "stopImages");
|
|
201
|
+
if (movie.stops.length === 0) return [];
|
|
202
|
+
const picks = pictureStops(movie.stops, { which: opts.which, pick: opts.pick, pdf: opts.pdf });
|
|
203
|
+
const shot = { scale: opts.scale ?? 1, type: opts.type ?? "image/png", quality: opts.quality, as: opts.as ?? "blob", motionBlur: opts.motionBlur === void 0 ? false : opts.motionBlur };
|
|
204
|
+
return movie.peek(async () => {
|
|
205
|
+
const out = [];
|
|
206
|
+
for (const stop of picks) {
|
|
207
|
+
const image = await movie.snapshot(stop.frame, shot);
|
|
208
|
+
const item = { stop, page: stop.pageIndex, image };
|
|
209
|
+
out.push(item);
|
|
210
|
+
opts.onImage?.(item);
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async function inspectStops(movie, opts = {}) {
|
|
216
|
+
warnUnknownOptions("inspectStops()", opts, ["lookback", "tolerance"]);
|
|
217
|
+
requireReady(movie, "inspectStops");
|
|
218
|
+
const out = { stops: [], issues: [] };
|
|
219
|
+
if (movie.stops.length === 0) return out;
|
|
220
|
+
const lookback = Math.max(1, Math.round(opts.lookback ?? 3));
|
|
221
|
+
const tolerance = opts.tolerance ?? 4e-3;
|
|
222
|
+
const small = Math.min(1, 320 / Math.max(1, movie.width));
|
|
223
|
+
const pixels = async (frame) => (await movie.snapshot(frame, { scale: small, motionBlur: false, as: "imageData" })).data;
|
|
224
|
+
return movie.peek(async () => {
|
|
225
|
+
for (const stop of movie.stops) {
|
|
226
|
+
const before = Math.max(0, stop.frame - lookback);
|
|
227
|
+
let moving = 0;
|
|
228
|
+
if (before < stop.frame) {
|
|
229
|
+
const now = await pixels(stop.frame);
|
|
230
|
+
moving = changedFraction(now, await pixels(before));
|
|
231
|
+
}
|
|
232
|
+
const settled = moving <= tolerance;
|
|
233
|
+
out.stops.push({ index: stop.index, page: stop.page, at: stop.at, frame: stop.frame, moving, settled });
|
|
234
|
+
if (!settled) {
|
|
235
|
+
out.issues.push(`stop ${stop.index + 1}${stop.page ? ` (page "${stop.page}")` : ""} at ${stop.at.toFixed(1)} s: the picture is still changing there (${moving < 0.1 ? (moving * 100).toFixed(1) : Math.round(moving * 100)}% of it changed in the last ${stop.frame - before} frames), so the audience, the page overview and a PDF see it mid-animation. Move the stop to after the animation has finished, or flag a settled stop of the page with \`pdf: true\`.`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function exportPDF(movie, opts = {}) {
|
|
242
|
+
warnUnknownOptions("exportPDF()", opts, ["which", "pick", "scale", "quality", "title", "motionBlur", "onImage"]);
|
|
243
|
+
requireReady(movie, "exportPDF");
|
|
244
|
+
if (movie.stops.length === 0) throw new Error("pixi-effects: exportPDF(): this movie has no stops, so there are no pages; add `stops` to the composition (or build it with deck())");
|
|
245
|
+
const scale = opts.scale ?? 1;
|
|
246
|
+
const images = await stopImages(movie, {
|
|
247
|
+
which: opts.which,
|
|
248
|
+
pick: opts.pick,
|
|
249
|
+
scale,
|
|
250
|
+
type: "image/jpeg",
|
|
251
|
+
quality: opts.quality ?? 0.92,
|
|
252
|
+
as: "blob",
|
|
253
|
+
motionBlur: opts.motionBlur,
|
|
254
|
+
pdf: true,
|
|
255
|
+
onImage: opts.onImage
|
|
256
|
+
});
|
|
257
|
+
const pages = await Promise.all(images.map(async (i) => ({
|
|
258
|
+
jpeg: new Uint8Array(await i.image.arrayBuffer()),
|
|
259
|
+
width: Math.max(1, Math.round(movie.width * scale)),
|
|
260
|
+
height: Math.max(1, Math.round(movie.height * scale))
|
|
261
|
+
})));
|
|
262
|
+
return new Blob([buildPdf(pages, { title: opts.title })], { type: "application/pdf" });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/present/Presenter.ts
|
|
4
266
|
var OPTION_KEYS = ["canvas", "keyboard", "clickToAdvance", "swipe", "indicator", "autoAdvance", "loop", "accent", "onExit"];
|
|
5
267
|
var NEXT_KEYS = /* @__PURE__ */ new Set(["ArrowRight", "ArrowDown", " ", "Enter", "PageDown"]);
|
|
6
268
|
var PREV_KEYS = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowUp", "Backspace", "PageUp"]);
|
|
@@ -127,6 +389,7 @@ var Presenter = class {
|
|
|
127
389
|
wasWrapAttr = false;
|
|
128
390
|
onKey = (e) => this.handleKey(e);
|
|
129
391
|
onStop = (e) => this.handleStop(e);
|
|
392
|
+
offStop = null;
|
|
130
393
|
onSeeked = () => this.refresh();
|
|
131
394
|
onFrame = () => this.mirror();
|
|
132
395
|
onEnded = () => this.handleEnded();
|
|
@@ -200,7 +463,7 @@ var Presenter = class {
|
|
|
200
463
|
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
201
464
|
if (this.options.keyboard) document.addEventListener("keydown", this.onKey);
|
|
202
465
|
document.addEventListener("fullscreenchange", this.onFullscreenChange);
|
|
203
|
-
|
|
466
|
+
this.offStop = onStop(movie, this.onStop);
|
|
204
467
|
movie.on("seeked", this.onSeeked);
|
|
205
468
|
movie.on("ended", this.onEnded);
|
|
206
469
|
this.refresh();
|
|
@@ -224,11 +487,11 @@ var Presenter = class {
|
|
|
224
487
|
}
|
|
225
488
|
next() {
|
|
226
489
|
this.cancelAdvance();
|
|
227
|
-
return this.run(() => this.movie
|
|
490
|
+
return this.run(() => next(this.movie));
|
|
228
491
|
}
|
|
229
492
|
prev() {
|
|
230
493
|
this.cancelAdvance();
|
|
231
|
-
return this.run(() => this.movie
|
|
494
|
+
return this.run(() => prev(this.movie));
|
|
232
495
|
}
|
|
233
496
|
/** Black out (`'black'`), white out (`'white'`) or bring the picture back (`'off'`). */
|
|
234
497
|
setCover(mode) {
|
|
@@ -246,7 +509,8 @@ var Presenter = class {
|
|
|
246
509
|
canvas.removeEventListener("pointerup", this.onPointerUp);
|
|
247
510
|
canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
248
511
|
this.closePresenterView();
|
|
249
|
-
this.
|
|
512
|
+
this.offStop?.();
|
|
513
|
+
this.offStop = null;
|
|
250
514
|
this.movie.off("seeked", this.onSeeked);
|
|
251
515
|
this.movie.off("ended", this.onEnded);
|
|
252
516
|
for (const t of [this.idleTimer, this.digitsTimer, this.advanceTimer, this.loopTimer]) if (t) clearTimeout(t);
|
|
@@ -319,19 +583,19 @@ var Presenter = class {
|
|
|
319
583
|
this.loopTimer = null;
|
|
320
584
|
void this.run(async () => {
|
|
321
585
|
await this.movie.gotoFrame(0, true);
|
|
322
|
-
await this.movie
|
|
586
|
+
await next(this.movie);
|
|
323
587
|
});
|
|
324
588
|
}, 800);
|
|
325
589
|
}
|
|
326
590
|
refresh() {
|
|
327
591
|
const m = this.movie;
|
|
328
|
-
const pages = m
|
|
592
|
+
const pages = pageCount(m), stops = m.stops?.length ?? 0;
|
|
329
593
|
if (this.counterEl) {
|
|
330
|
-
this.counterEl.textContent = pages > 0 ? `${m
|
|
594
|
+
this.counterEl.textContent = pages > 0 ? `${pageIndex(m) >= 0 ? pageIndex(m) + 1 : "\u2013"} / ${pages}` : "";
|
|
331
595
|
this.counterEl.style.display = pages > 0 ? "" : "none";
|
|
332
596
|
}
|
|
333
|
-
if (this.titleEl) this.titleEl.textContent = m
|
|
334
|
-
if (this.progressEl) this.progressEl.style.setProperty("--mp-fill", String(stops > 0 ? (m
|
|
597
|
+
if (this.titleEl) this.titleEl.textContent = currentStop(m)?.page ?? "";
|
|
598
|
+
if (this.progressEl) this.progressEl.style.setProperty("--mp-fill", String(stops > 0 ? (stopIndex(m) + 1) / stops : 0));
|
|
335
599
|
this.refreshView();
|
|
336
600
|
}
|
|
337
601
|
wake() {
|
|
@@ -389,7 +653,7 @@ var Presenter = class {
|
|
|
389
653
|
this.showDigits();
|
|
390
654
|
this.navigate(() => {
|
|
391
655
|
this.cancelAdvance();
|
|
392
|
-
void this.run(() => this.movie
|
|
656
|
+
void this.run(() => goToPage(this.movie, page));
|
|
393
657
|
});
|
|
394
658
|
} else if (k === "Backspace" && this.digits) {
|
|
395
659
|
this.digits = this.digits.slice(0, -1);
|
|
@@ -398,11 +662,11 @@ var Presenter = class {
|
|
|
398
662
|
else if (PREV_KEYS.has(k)) this.navigate(() => void this.prev());
|
|
399
663
|
else if (k === "Home") this.navigate(() => {
|
|
400
664
|
this.cancelAdvance();
|
|
401
|
-
void this.run(() => this.movie
|
|
665
|
+
void this.run(() => goToPage(this.movie, 0));
|
|
402
666
|
});
|
|
403
667
|
else if (k === "End") this.navigate(() => {
|
|
404
668
|
this.cancelAdvance();
|
|
405
|
-
void this.run(() => this.movie
|
|
669
|
+
void this.run(() => goToStop(this.movie, Math.max(0, (this.movie.stops?.length ?? 1) - 1)));
|
|
406
670
|
});
|
|
407
671
|
else if (k === "b" || k === "B" || k === ".") this.setCover(this.coverEl.getAttribute("data-mode") === "black" ? "off" : "black");
|
|
408
672
|
else if (k === "w" || k === "W" || k === ",") this.setCover(this.coverEl.getAttribute("data-mode") === "white" ? "off" : "white");
|
|
@@ -505,18 +769,18 @@ var Presenter = class {
|
|
|
505
769
|
if (!v || v.win.closed) return;
|
|
506
770
|
const m = this.movie;
|
|
507
771
|
const stops = m.stops ?? [];
|
|
508
|
-
const pages = m
|
|
509
|
-
const cur = m
|
|
510
|
-
v.els.count.textContent = pages > 0 ? `${m
|
|
511
|
-
v.els.title.textContent = m
|
|
512
|
-
const upTo = cur < 0 ? 0 : cur,
|
|
772
|
+
const pages = pageCount(m);
|
|
773
|
+
const cur = stopIndex(m);
|
|
774
|
+
v.els.count.textContent = pages > 0 ? `${pageIndex(m) >= 0 ? pageIndex(m) + 1 : "\u2013"} / ${pages}` : "";
|
|
775
|
+
v.els.title.textContent = currentStop(m)?.page ?? (cur < 0 ? stops[0]?.page ?? "" : "");
|
|
776
|
+
const upTo = cur < 0 ? 0 : cur, pageIndex2 = stops[upTo]?.pageIndex;
|
|
513
777
|
let notes = "";
|
|
514
|
-
for (const s of stops) if (s.pageIndex ===
|
|
778
|
+
for (const s of stops) if (s.pageIndex === pageIndex2 && s.index <= upTo && s.notes) notes = s.notes;
|
|
515
779
|
v.els.notes.textContent = notes;
|
|
516
780
|
v.els.notes.setAttribute("data-empty", String(notes === ""));
|
|
517
|
-
const
|
|
518
|
-
v.els.nextbox.setAttribute("data-end", String(!
|
|
519
|
-
const src =
|
|
781
|
+
const next2 = stops[cur + 1];
|
|
782
|
+
v.els.nextbox.setAttribute("data-end", String(!next2));
|
|
783
|
+
const src = next2 ? this.thumbs.get(next2.index) : void 0;
|
|
520
784
|
if (src) v.els.next.setAttribute("src", src);
|
|
521
785
|
else v.els.next.removeAttribute("src");
|
|
522
786
|
}
|
|
@@ -544,10 +808,11 @@ var Presenter = class {
|
|
|
544
808
|
* Make a picture of every stop, once. Taking them moves the playhead through the stops and back, which the audience would see:
|
|
545
809
|
* `start()` does it before anything is shown (behind a "Preparing" cover), and an overview opened before that does it on the spot.
|
|
546
810
|
*/
|
|
811
|
+
/** Make the page pictures now (the overview and the presenter view use them): `start()` does it before the audience sees anything. */
|
|
547
812
|
ensureThumbs() {
|
|
548
813
|
this.thumbsPromise ??= (async () => {
|
|
549
814
|
try {
|
|
550
|
-
await this.movie
|
|
815
|
+
await stopImages(this.movie, {
|
|
551
816
|
which: "stops",
|
|
552
817
|
as: "dataURL",
|
|
553
818
|
type: "image/jpeg",
|
|
@@ -570,8 +835,8 @@ var Presenter = class {
|
|
|
570
835
|
const stops = (this.movie.stops ?? []).filter((s) => s.pageIndex === page);
|
|
571
836
|
return stops.length ? this.thumbs.get(stops[stops.length - 1].index) : void 0;
|
|
572
837
|
}
|
|
573
|
-
onThumb(
|
|
574
|
-
const stop = this.movie.stops?.[
|
|
838
|
+
onThumb(stopIndex2) {
|
|
839
|
+
const stop = this.movie.stops?.[stopIndex2];
|
|
575
840
|
if (stop) this.fillThumb(stop.pageIndex);
|
|
576
841
|
this.refreshView();
|
|
577
842
|
}
|
|
@@ -582,18 +847,18 @@ var Presenter = class {
|
|
|
582
847
|
this.gridEl.style.setProperty("--mp-cols", String(cols));
|
|
583
848
|
this.gridEl.style.setProperty("--mp-ratio", `${width} / ${height}`);
|
|
584
849
|
this.gridEl.innerHTML = "";
|
|
585
|
-
const current = Math.max(0, this.movie
|
|
850
|
+
const current = Math.max(0, pageIndex(this.movie));
|
|
586
851
|
pages.forEach((s, i) => {
|
|
587
852
|
const cell = document.createElement("div");
|
|
588
853
|
cell.className = "mp-cell";
|
|
589
|
-
cell.setAttribute("data-current", String(i === this.movie
|
|
854
|
+
cell.setAttribute("data-current", String(i === pageIndex(this.movie)));
|
|
590
855
|
cell.setAttribute("data-selected", String(i === current));
|
|
591
856
|
cell.innerHTML = `<div class="mp-cell-label"><span class="mp-cell-num">${i + 1}</span><span class="mp-cell-name"></span></div>`;
|
|
592
857
|
cell.querySelector(".mp-cell-name").textContent = s.page ?? "";
|
|
593
858
|
cell.addEventListener("click", () => {
|
|
594
859
|
this.overviewEl.setAttribute("data-open", "false");
|
|
595
860
|
this.cancelAdvance();
|
|
596
|
-
void this.run(() => this.movie
|
|
861
|
+
void this.run(() => goToPage(this.movie, i));
|
|
597
862
|
});
|
|
598
863
|
this.gridEl.appendChild(cell);
|
|
599
864
|
this.fillThumb(i);
|
|
@@ -634,7 +899,7 @@ var Presenter = class {
|
|
|
634
899
|
const page = this.overviewSel;
|
|
635
900
|
this.overviewEl.setAttribute("data-open", "false");
|
|
636
901
|
this.cancelAdvance();
|
|
637
|
-
void this.run(() => this.movie
|
|
902
|
+
void this.run(() => goToPage(this.movie, page));
|
|
638
903
|
return true;
|
|
639
904
|
}
|
|
640
905
|
case "Escape":
|
|
@@ -664,6 +929,67 @@ var Presenter = class {
|
|
|
664
929
|
}
|
|
665
930
|
};
|
|
666
931
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
932
|
+
// src/present/deck.ts
|
|
933
|
+
var PAGE_KEYS = ["name", "duration", "sequences", "stops", "notes", "advance", "initial", "keyframes", "filters", "filterArea", "blendMode", "mask"];
|
|
934
|
+
var DECK_KEYS = ["pages", "transition", "width", "height", "frameRate", "background", "canvas", "assets", "poster", "motionBlur", "loader"];
|
|
935
|
+
function deck(options) {
|
|
936
|
+
if (!options || !Array.isArray(options.pages) || options.pages.length === 0) throw new Error("deck(): pages must be a list with at least one page: { pages: [{ name, duration, stops, sequences }] }");
|
|
937
|
+
warnUnknownOptions("deck()", options, DECK_KEYS);
|
|
938
|
+
const { pages, transition, ...movieOptions } = options;
|
|
939
|
+
pages.forEach((p, i) => {
|
|
940
|
+
if (!p || typeof p !== "object") throw new Error(`deck(): pages[${i}] must be an object`);
|
|
941
|
+
warnUnknownOptions(`deck() pages[${i}]`, p, PAGE_KEYS);
|
|
942
|
+
if (!(typeof p.duration === "number" && Number.isFinite(p.duration) && p.duration > 0)) throw new Error(`deck(): pages[${i}].duration must be a positive number of seconds, got ${String(p.duration)}`);
|
|
943
|
+
if (!Array.isArray(p.sequences)) throw new Error(`deck(): pages[${i}].sequences must be a list of layers (it may be empty), got ${typeof p.sequences}`);
|
|
944
|
+
});
|
|
945
|
+
let overlap = 0;
|
|
946
|
+
if (transition) {
|
|
947
|
+
if (!(typeof transition.duration === "number" && transition.duration > 0)) throw new Error("deck(): transition needs a duration in seconds: { kind: 'crossfade', duration: 0.6 }");
|
|
948
|
+
const shortest = Math.min(...pages.map((p) => p.duration));
|
|
949
|
+
if (pages.length > 1 && transition.duration >= shortest) throw new Error(`deck(): the transition (${transition.duration} s) must be shorter than every page (the shortest is ${shortest} s)`);
|
|
950
|
+
overlap = pages.length > 1 ? transition.duration : 0;
|
|
951
|
+
}
|
|
952
|
+
const sequences = [];
|
|
953
|
+
const transitions = [];
|
|
954
|
+
const stops = [];
|
|
955
|
+
let start = 0;
|
|
956
|
+
pages.forEach((page, i) => {
|
|
957
|
+
const id = `page-${i + 1}`;
|
|
958
|
+
const label = page.name ? `"${page.name}"` : `pages[${i}]`;
|
|
959
|
+
const isLast = i === pages.length - 1;
|
|
960
|
+
const entries = (page.stops ?? [isLast ? page.duration : page.duration - overlap]).map((e, k) => {
|
|
961
|
+
if (e && typeof e === "object") warnUnknownOptions(`deck() page ${label} stops[${k}]`, e, ["at", "pdf"]);
|
|
962
|
+
return typeof e === "object" && e !== null ? e : { at: e };
|
|
963
|
+
});
|
|
964
|
+
const local = entries.map((e) => e.at);
|
|
965
|
+
local.forEach((s, k) => {
|
|
966
|
+
if (!(typeof s === "number" && Number.isFinite(s) && s > 0 && s <= page.duration + 1e-9)) throw new Error(`deck(): page ${label} has stop ${String(s)}, outside the page (0 to ${page.duration} s)`);
|
|
967
|
+
if (k > 0 && s <= local[k - 1]) throw new Error(`deck(): page ${label} stops must be in order, earliest first (got ${local.join(", ")})`);
|
|
968
|
+
});
|
|
969
|
+
const { name: _n, duration: _d, sequences: inner, stops: _s, notes: _no, advance: _a, ...layerProps } = page;
|
|
970
|
+
sequences.push({ type: "composition", name: id, at: round(start), duration: page.duration, ...layerProps, sequences: inner });
|
|
971
|
+
local.forEach((s, k) => {
|
|
972
|
+
const stop = { at: round(start + s) };
|
|
973
|
+
if (k === 0) {
|
|
974
|
+
stop.page = page.name ?? true;
|
|
975
|
+
if (page.notes !== void 0) stop.notes = page.notes;
|
|
976
|
+
}
|
|
977
|
+
if (k === local.length - 1 && page.advance !== void 0) stop.advance = page.advance;
|
|
978
|
+
if (entries[k].pdf !== void 0) stop.pdf = entries[k].pdf;
|
|
979
|
+
stops.push(stop);
|
|
980
|
+
});
|
|
981
|
+
if (transition && !isLast) {
|
|
982
|
+
const next2 = start + page.duration - overlap;
|
|
983
|
+
transitions.push({ ...transition, from: id, to: `page-${i + 2}`, at: round(next2), duration: transition.duration });
|
|
984
|
+
}
|
|
985
|
+
start = start + page.duration - (isLast ? 0 : overlap);
|
|
986
|
+
});
|
|
987
|
+
const composition = { sequences, stops };
|
|
988
|
+
if (transitions.length > 0) composition.transitions = transitions;
|
|
989
|
+
return { ...movieOptions, duration: round(start), composition };
|
|
990
|
+
}
|
|
991
|
+
var round = (v) => Math.round(v * 1e6) / 1e6;
|
|
992
|
+
|
|
993
|
+
export { Presenter, currentStop, deck, exportPDF, goToPage, goToStop, inspectStops, next, onStop, pageCount, pageIndex, prev, stopImages, stopIndex };
|
|
994
|
+
//# sourceMappingURL=present.js.map
|
|
995
|
+
//# sourceMappingURL=present.js.map
|