pixi-effects 0.12.0 → 0.13.1
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 +22 -0
- package/README.md +5 -5
- package/ai/reference/cheatsheet.md +3 -3
- package/ai/reference/pitfalls.md +2 -2
- package/ai/template.html +5 -5
- package/ai/tools/check.mjs +1 -1
- package/dist/{Base-W3sQzGPO.d.cts → Base-C6ZnJ9pD.d.cts} +1 -1
- package/dist/{Base-CYH9vPCH.d.ts → Base-CQOlB2VH.d.ts} +1 -1
- package/dist/Controller.cjs +75 -17
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +15 -3
- package/dist/Controller.d.ts +15 -3
- package/dist/Controller.js +75 -17
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-CY2QPVVM.d.cts → Movie-DnR4H1OO.d.ts} +5 -2
- package/dist/{Movie-EBF6_RfX.d.ts → Movie-Tl5dRLJl.d.cts} +5 -2
- package/dist/Presenter.d.cts +2 -2
- package/dist/Presenter.d.ts +2 -2
- package/dist/index.cjs +32 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +32 -13
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/{types-mpqQO8MJ.d.cts → types-CBSiUKau.d.cts} +18 -1
- package/dist/{types-mpqQO8MJ.d.ts → types-CBSiUKau.d.ts} +18 -1
- package/docs/api.md +4 -3
- package/docs/dsl.md +2 -2
- package/llms-full.txt +11 -10
- package/package.json +1 -1
package/dist/Controller.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { f as Movie } from './Movie-
|
|
1
|
+
import { f as Movie } from './Movie-DnR4H1OO.js';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import 'gsap';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-CBSiUKau.js';
|
|
5
5
|
|
|
6
6
|
/** The bar's default stylesheet. Its colours and sizes are CSS custom properties (`--mc-accent`, `--mc-fg`, `--mc-track`, `--mc-bar-bg`, `--mc-track-height`, `--mc-font`): set them in your page CSS, or with the `theme` option. */
|
|
7
|
-
declare const CONTROLLER_CSS = "\n.movie-controller-wrap { position: relative; display: inline-block; line-height: 0; }\n.movie-controller {\n position: absolute;\n pointer-events: none;\n font-family: var(--mc-font, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n opacity: 1;\n transition: opacity 200ms ease;\n}\n.movie-controller[data-state=\"hidden\"] { opacity: 0; }\n.movie-controller > * { pointer-events: auto; }\n\n.mc-progress {\n position: relative;\n width: 100%;\n height: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n outline: none;\n}\n.mc-progress:focus-visible {\n outline: 2px solid var(--mc-accent, #007AFF);\n outline-offset: 2px;\n}\n.mc-progress::before {\n content: \"\"; position: absolute; left: 12px; right: 12px;\n height: var(--mc-track-height, 3px); background: var(--mc-track, rgba(255,255,255,0.25));\n transition: height 120ms ease;\n}\n.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-stops { position: absolute; left: 12px; right: 12px; top: 0; bottom: 0; pointer-events: none; }\n.mc-stop-tick { position: absolute; top: 50%; width:
|
|
7
|
+
declare const CONTROLLER_CSS = "\n.movie-controller-wrap { position: relative; display: inline-block; line-height: 0; }\n.movie-controller {\n position: absolute;\n pointer-events: none;\n font-family: var(--mc-font, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n opacity: 1;\n transition: opacity 200ms ease;\n}\n.movie-controller[data-state=\"hidden\"] { opacity: 0; }\n.movie-controller > * { pointer-events: auto; }\n\n.mc-progress {\n position: relative;\n width: 100%;\n height: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n outline: none;\n}\n.mc-progress:focus-visible {\n outline: 2px solid var(--mc-accent, #007AFF);\n outline-offset: 2px;\n}\n.mc-progress::before {\n content: \"\"; position: absolute; left: 12px; right: 12px;\n height: var(--mc-track-height, 3px); background: var(--mc-track, rgba(255,255,255,0.25));\n transition: height 120ms ease;\n}\n.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-stops { position: absolute; left: 12px; right: 12px; top: 0; bottom: 0; pointer-events: none; }\n.mc-stop-tick { position: absolute; top: 50%; width: 7px; height: 7px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1.5px rgba(0,0,0,0.5); }\n.mc-stop-page { width: 11px; height: 11px; box-shadow: 0 0 0 2.5px var(--mc-accent, #007AFF), 0 0 0 4px rgba(0,0,0,0.35); }\n.mc-progress-fill {\n position: absolute; left: 12px; top: 50%;\n height: var(--mc-track-height, 3px);\n width: calc((100% - 24px) * var(--mc-fill, 0));\n background: var(--mc-accent, #007AFF);\n transform: translateY(-50%);\n transition: height 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-fill,\n.mc-progress.mc-scrubbing .mc-progress-fill { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-progress-thumb {\n position: absolute; top: 50%;\n left: calc(12px + (100% - 24px) * var(--mc-fill, 0));\n width: 12px; height: 12px; border-radius: 50%;\n background: var(--mc-accent, #007AFF);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-thumb,\n.mc-progress.mc-scrubbing .mc-progress-thumb { transform: translate(-50%, -50%) scale(1); }\n\n.mc-bar {\n position: relative;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 6px 12px 8px 12px;\n background: var(--mc-bar-bg, linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%));\n color: var(--mc-fg, #fff);\n line-height: 1;\n}\n.mc-btn {\n background: none; border: 0; padding: 0; margin: 0;\n width: 28px; height: 28px;\n display: inline-flex; align-items: center; justify-content: center;\n color: var(--mc-fg, #fff); opacity: 0.85; cursor: pointer;\n transition: opacity 120ms ease;\n}\n.mc-btn:hover { opacity: 1; }\n.mc-btn:disabled { opacity: 0.4; cursor: not-allowed; }\n.mc-time {\n font-size: 12px; font-family: Menlo, Monaco, monospace;\n color: var(--mc-fg, #fff); opacity: 0.85;\n min-width: 90px;\n}\n.mc-spacer { flex: 1; }\n\n.mc-volume { display: flex; align-items: center; }\n.mc-vol-slider {\n position: relative;\n width: 0;\n height: 28px;\n display: flex;\n align-items: center;\n cursor: pointer;\n margin-left: 0;\n outline: none;\n transition: width 200ms ease, margin-left 200ms ease;\n overflow: hidden;\n}\n.mc-volume:hover .mc-vol-slider,\n.mc-volume:focus-within .mc-vol-slider,\n.mc-vol-slider.mc-scrubbing {\n width: 70px;\n margin-left: 6px;\n}\n.mc-vol-slider:focus-visible { outline: 2px solid var(--mc-accent, #007AFF); outline-offset: 2px; }\n.mc-vol-slider::before {\n content: \"\"; position: absolute; left: 4px; right: 4px;\n height: 3px; background: var(--mc-track, rgba(255,255,255,0.25)); border-radius: 2px;\n}\n.mc-vol-fill {\n position: absolute; left: 4px; top: 50%;\n height: 3px;\n width: calc((100% - 8px) * var(--mc-volume, 1));\n background: var(--mc-fg, #fff);\n transform: translateY(-50%);\n border-radius: 2px;\n pointer-events: none;\n}\n.mc-vol-thumb {\n position: absolute; top: 50%;\n left: calc(4px + (100% - 8px) * var(--mc-volume, 1));\n width: 10px; height: 10px; border-radius: 50%;\n background: var(--mc-fg, #fff);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n[data-mc-wrap]:fullscreen {\n width: 100vw; height: 100vh;\n display: flex; align-items: center; justify-content: center;\n background: #000;\n}\n[data-mc-wrap]:fullscreen > canvas {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n max-height: none !important;\n object-fit: contain;\n border-radius: 0;\n}\n\n.mc-settings-popover {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 12px;\n background: rgba(20, 20, 20, 0.96);\n border-radius: 8px;\n padding: 10px 12px;\n min-width: 180px;\n color: #fff;\n font-size: 12px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.mc-settings-popover[data-open=\"false\"] { display: none; }\n.mc-settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.mc-settings-row > span { color: rgba(255,255,255,0.85); }\n.mc-settings-row > select {\n font: inherit;\n background: rgba(255,255,255,0.1);\n color: #fff;\n border: 1px solid rgba(255,255,255,0.18);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n}\n.mc-export-confirm {\n margin-top: 4px;\n width: 100%;\n background: var(--mc-accent, #007AFF);\n color: #fff;\n border: 0;\n border-radius: 4px;\n padding: 6px 10px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: background 120ms ease;\n}\n.mc-export-confirm:hover { background: #0066d6; }\n.mc-export-confirm:disabled { background: rgba(255,255,255,0.18); cursor: not-allowed; }\n\n.mc-export-overlay {\n position: absolute; inset: 0;\n background: rgba(0,0,0,0.7);\n display: flex; justify-content: center; align-items: center;\n z-index: 2;\n}\n.mc-export-panel {\n background: rgba(20,20,20,0.96); color: #fff;\n border-radius: 8px; padding: 16px 20px;\n text-align: center; min-width: 200px; max-width: 80%;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n}\n.mc-export-title { font-size: 13px; margin-bottom: 10px; opacity: 0.9; }\n.mc-export-track {\n width: 100%; height: 6px;\n background: rgba(255,255,255,0.15); border-radius: 3px;\n margin-bottom: 8px; overflow: hidden;\n}\n.mc-export-fill {\n height: 100%; width: 0%;\n background: linear-gradient(90deg, var(--mc-accent, #007AFF), var(--mc-accent, #0056CC));\n transition: width 0.3s ease; border-radius: 3px;\n}\n.mc-export-text {\n color: rgba(255,255,255,0.7); font-size: 11px;\n font-family: Menlo, Monaco, monospace;\n}\n";
|
|
8
8
|
declare function formatTime(seconds: number): string;
|
|
9
9
|
declare function frameToPercent(frame: number, totalFrames: number): number;
|
|
10
10
|
declare function pxToFrame(clientX: number, rect: {
|
|
@@ -44,6 +44,8 @@ interface ControllerOptions {
|
|
|
44
44
|
clickToPlay?: boolean;
|
|
45
45
|
/** A movie with `stops` gets a Present button that starts a {@link Presenter} (keys, click and swipe move through the stops). Default true; `false` keeps only the marks on the seek bar. */
|
|
46
46
|
present?: boolean;
|
|
47
|
+
/** A movie with `stops`: the play button (and a click on the picture, and Space) plays to the next stop and pauses there, like a slide. Default true; `false` plays straight through to the end. */
|
|
48
|
+
pauseAtStops?: boolean;
|
|
47
49
|
className?: string;
|
|
48
50
|
}
|
|
49
51
|
interface ResolvedOptions {
|
|
@@ -52,6 +54,7 @@ interface ResolvedOptions {
|
|
|
52
54
|
enableKeyboardShortcuts: boolean;
|
|
53
55
|
clickToPlay: boolean;
|
|
54
56
|
present: boolean;
|
|
57
|
+
pauseAtStops: boolean;
|
|
55
58
|
className: string;
|
|
56
59
|
}
|
|
57
60
|
declare class Controller {
|
|
@@ -87,6 +90,7 @@ declare class Controller {
|
|
|
87
90
|
private wasPlayingBeforeScrub;
|
|
88
91
|
private activePointerId;
|
|
89
92
|
private isExporting;
|
|
93
|
+
private exportPages;
|
|
90
94
|
private exportOverlay;
|
|
91
95
|
private exportFillEl;
|
|
92
96
|
private exportTextEl;
|
|
@@ -132,6 +136,12 @@ declare class Controller {
|
|
|
132
136
|
private bindMovieEvents;
|
|
133
137
|
/** The stops of a presentation as marks on the seek bar, and the Present button. */
|
|
134
138
|
private refreshStops;
|
|
139
|
+
/** A deck can be downloaded as a PDF: the format list gets "PDF" choices while the movie has stops. */
|
|
140
|
+
private refreshPdfChoices;
|
|
141
|
+
/** Play, or pause: a movie with stops plays to the next stop (and from the end, again from the start). */
|
|
142
|
+
private togglePlay;
|
|
143
|
+
/** Start playing: to the next stop for a movie with stops, to the end for any other (or with `pauseAtStops: false`). */
|
|
144
|
+
private startPlaying;
|
|
135
145
|
/** Hand the page over to a Presenter (fullscreen, keys, click and swipe move through the stops); Escape gives it back. */
|
|
136
146
|
private startPresenting;
|
|
137
147
|
private stopPresenting;
|
|
@@ -160,6 +170,8 @@ declare class Controller {
|
|
|
160
170
|
private handleExport;
|
|
161
171
|
private triggerDownload;
|
|
162
172
|
private makeFilename;
|
|
173
|
+
/** One more PDF page is ready: move the bar. */
|
|
174
|
+
private pdfPageDone;
|
|
163
175
|
private showExportOverlay;
|
|
164
176
|
private hideExportOverlay;
|
|
165
177
|
private bindVisibility;
|
package/dist/Controller.js
CHANGED
|
@@ -47,7 +47,8 @@ var CONTROLLER_CSS = `
|
|
|
47
47
|
}
|
|
48
48
|
.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }
|
|
49
49
|
.mc-stops { position: absolute; left: 12px; right: 12px; top: 0; bottom: 0; pointer-events: none; }
|
|
50
|
-
.mc-stop-tick { position: absolute; top: 50%; width:
|
|
50
|
+
.mc-stop-tick { position: absolute; top: 50%; width: 7px; height: 7px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1.5px rgba(0,0,0,0.5); }
|
|
51
|
+
.mc-stop-page { width: 11px; height: 11px; box-shadow: 0 0 0 2.5px var(--mc-accent, #007AFF), 0 0 0 4px rgba(0,0,0,0.35); }
|
|
51
52
|
.mc-progress-fill {
|
|
52
53
|
position: absolute; left: 12px; top: 50%;
|
|
53
54
|
height: var(--mc-track-height, 3px);
|
|
@@ -269,8 +270,10 @@ function pxToFraction(clientX, rect, inset = 0) {
|
|
|
269
270
|
const ratio = (clientX - innerLeft) / innerWidth;
|
|
270
271
|
return Math.min(Math.max(ratio, 0), 1);
|
|
271
272
|
}
|
|
273
|
+
var PDF_QUALITY = { "low": 0.6, "medium": 0.75, "high": 0.88, "very-high": 0.95 };
|
|
272
274
|
function extensionForMimeType(mime) {
|
|
273
275
|
const t = (mime || "").toLowerCase();
|
|
276
|
+
if (t.includes("pdf")) return "pdf";
|
|
274
277
|
if (t.includes("webm")) return "webm";
|
|
275
278
|
if (t.includes("quicktime") || t.includes("mov")) return "mov";
|
|
276
279
|
if (t.includes("matroska") || t.includes("mkv")) return "mkv";
|
|
@@ -317,6 +320,7 @@ var Controller = class _Controller {
|
|
|
317
320
|
wasPlayingBeforeScrub = false;
|
|
318
321
|
activePointerId = null;
|
|
319
322
|
isExporting = false;
|
|
323
|
+
exportPages = null;
|
|
320
324
|
exportOverlay = null;
|
|
321
325
|
exportFillEl = null;
|
|
322
326
|
exportTextEl = null;
|
|
@@ -357,6 +361,7 @@ var Controller = class _Controller {
|
|
|
357
361
|
enableKeyboardShortcuts: options.enableKeyboardShortcuts ?? true,
|
|
358
362
|
clickToPlay: options.clickToPlay ?? true,
|
|
359
363
|
present: options.present ?? true,
|
|
364
|
+
pauseAtStops: options.pauseAtStops ?? true,
|
|
360
365
|
className: options.className ?? "movie-controller"
|
|
361
366
|
};
|
|
362
367
|
installStyles();
|
|
@@ -564,6 +569,7 @@ var Controller = class _Controller {
|
|
|
564
569
|
refreshStops() {
|
|
565
570
|
this.progressEl.querySelector(".mc-stops")?.remove();
|
|
566
571
|
const stops = this.movie.stops ?? [];
|
|
572
|
+
this.refreshPdfChoices(stops.length > 0);
|
|
567
573
|
if (stops.length === 0) {
|
|
568
574
|
this.presentBtn?.remove();
|
|
569
575
|
this.presentBtn = null;
|
|
@@ -574,7 +580,7 @@ var Controller = class _Controller {
|
|
|
574
580
|
holder.className = "mc-stops";
|
|
575
581
|
for (const s of stops) {
|
|
576
582
|
const tick = document.createElement("div");
|
|
577
|
-
tick.className = "mc-stop-tick";
|
|
583
|
+
tick.className = s.pageStart ? "mc-stop-tick mc-stop-page" : "mc-stop-tick";
|
|
578
584
|
tick.style.left = `${s.frame / total * 100}%`;
|
|
579
585
|
if (s.page) tick.title = s.page;
|
|
580
586
|
holder.appendChild(tick);
|
|
@@ -593,6 +599,41 @@ var Controller = class _Controller {
|
|
|
593
599
|
this.presentBtn = btn;
|
|
594
600
|
}
|
|
595
601
|
}
|
|
602
|
+
/** A deck can be downloaded as a PDF: the format list gets "PDF" choices while the movie has stops. */
|
|
603
|
+
refreshPdfChoices(has) {
|
|
604
|
+
const select = this.settingsFormatSelect;
|
|
605
|
+
if (!select) return;
|
|
606
|
+
const present = !!select.querySelector('option[value="pdf"]');
|
|
607
|
+
if (has && !present) {
|
|
608
|
+
for (const [value, label] of [["pdf", "PDF (pages)"], ["pdf-steps", "PDF (every step)"]]) {
|
|
609
|
+
const o = document.createElement("option");
|
|
610
|
+
o.value = value;
|
|
611
|
+
o.textContent = label;
|
|
612
|
+
select.appendChild(o);
|
|
613
|
+
}
|
|
614
|
+
} else if (!has && present) {
|
|
615
|
+
select.querySelectorAll('option[value="pdf"], option[value="pdf-steps"]').forEach((o) => o.remove());
|
|
616
|
+
if (this.exportFormat === "pdf" || this.exportFormat === "pdf-steps") {
|
|
617
|
+
this.exportFormat = "mp4";
|
|
618
|
+
select.value = "mp4";
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/** Play, or pause: a movie with stops plays to the next stop (and from the end, again from the start). */
|
|
623
|
+
async togglePlay() {
|
|
624
|
+
if (this.movie.isPlaying) {
|
|
625
|
+
this.movie.pause();
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
await this.startPlaying();
|
|
629
|
+
}
|
|
630
|
+
/** Start playing: to the next stop for a movie with stops, to the end for any other (or with `pauseAtStops: false`). */
|
|
631
|
+
async startPlaying() {
|
|
632
|
+
if (this.options.pauseAtStops && (this.movie.stops?.length ?? 0) > 0) {
|
|
633
|
+
if (this.movie.currentFrame >= this.movie.totalFrames) await this.movie.gotoFrame(0, true);
|
|
634
|
+
await this.movie.next();
|
|
635
|
+
} else this.movie.play();
|
|
636
|
+
}
|
|
596
637
|
/** Hand the page over to a Presenter (fullscreen, keys, click and swipe move through the stops); Escape gives it back. */
|
|
597
638
|
async startPresenting() {
|
|
598
639
|
if (this.presenter || this.destroyed) return;
|
|
@@ -609,11 +650,7 @@ var Controller = class _Controller {
|
|
|
609
650
|
}
|
|
610
651
|
bindPlayButton() {
|
|
611
652
|
this.playBtn.addEventListener("click", () => {
|
|
612
|
-
|
|
613
|
-
this.movie.pause();
|
|
614
|
-
} else {
|
|
615
|
-
this.movie.play();
|
|
616
|
-
}
|
|
653
|
+
void this.togglePlay();
|
|
617
654
|
});
|
|
618
655
|
}
|
|
619
656
|
/** Clicking the picture plays / pauses (a tap on a phone too). */
|
|
@@ -630,8 +667,7 @@ var Controller = class _Controller {
|
|
|
630
667
|
this.swallowCanvasClick = false;
|
|
631
668
|
return;
|
|
632
669
|
}
|
|
633
|
-
|
|
634
|
-
else this.movie.play();
|
|
670
|
+
void this.togglePlay();
|
|
635
671
|
};
|
|
636
672
|
canvas.addEventListener("pointerdown", this.canvasPointerDownHandler);
|
|
637
673
|
canvas.addEventListener("click", this.canvasClickHandler);
|
|
@@ -787,7 +823,7 @@ var Controller = class _Controller {
|
|
|
787
823
|
} catch {
|
|
788
824
|
}
|
|
789
825
|
if (this.wasPlayingBeforeScrub) {
|
|
790
|
-
this.
|
|
826
|
+
void this.startPlaying();
|
|
791
827
|
this.refreshPlayIcon();
|
|
792
828
|
}
|
|
793
829
|
};
|
|
@@ -836,7 +872,7 @@ var Controller = class _Controller {
|
|
|
836
872
|
if (this.settingsFormatSelect) {
|
|
837
873
|
this.settingsFormatSelect.addEventListener("change", (e) => {
|
|
838
874
|
const v = e.target.value;
|
|
839
|
-
if (v === "mp4" || v === "webm" || v === "mov") this.exportFormat = v;
|
|
875
|
+
if (v === "mp4" || v === "webm" || v === "mov" || v === "pdf" || v === "pdf-steps") this.exportFormat = v;
|
|
840
876
|
});
|
|
841
877
|
}
|
|
842
878
|
if (this.settingsQualitySelect) {
|
|
@@ -886,9 +922,19 @@ var Controller = class _Controller {
|
|
|
886
922
|
this.movie.pause();
|
|
887
923
|
this.refreshPlayIcon();
|
|
888
924
|
}
|
|
889
|
-
this.
|
|
925
|
+
const pdf = this.exportFormat === "pdf" || this.exportFormat === "pdf-steps";
|
|
926
|
+
if (pdf) {
|
|
927
|
+
const stops = this.movie.stops ?? [];
|
|
928
|
+
const total = this.exportFormat === "pdf-steps" ? stops.filter((s) => s.pdf !== false).length : new Set(stops.map((s) => s.pageIndex)).size;
|
|
929
|
+
this.showExportOverlay("Exporting PDF...", `0% (0 / ${total} pages)`);
|
|
930
|
+
this.exportPages = { done: 0, total };
|
|
931
|
+
} else this.showExportOverlay();
|
|
890
932
|
try {
|
|
891
|
-
const blob = await this.movie.
|
|
933
|
+
const blob = pdf ? await this.movie.exportPDF({
|
|
934
|
+
which: this.exportFormat === "pdf-steps" ? "stops" : "pages",
|
|
935
|
+
quality: PDF_QUALITY[this.exportQuality],
|
|
936
|
+
onImage: () => this.pdfPageDone()
|
|
937
|
+
}) : await this.movie.render({
|
|
892
938
|
format: this.exportFormat,
|
|
893
939
|
video: { bitrate: this.exportQuality },
|
|
894
940
|
audio: { bitrate: this.exportQuality }
|
|
@@ -904,7 +950,7 @@ var Controller = class _Controller {
|
|
|
904
950
|
this.hideExportOverlay();
|
|
905
951
|
this.isExporting = false;
|
|
906
952
|
if (wasPlaying) {
|
|
907
|
-
this.
|
|
953
|
+
void this.startPlaying();
|
|
908
954
|
this.refreshPlayIcon();
|
|
909
955
|
}
|
|
910
956
|
}
|
|
@@ -927,16 +973,27 @@ var Controller = class _Controller {
|
|
|
927
973
|
const ts = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
928
974
|
return `movie-${ts}.${ext}`;
|
|
929
975
|
}
|
|
930
|
-
|
|
976
|
+
/** One more PDF page is ready: move the bar. */
|
|
977
|
+
pdfPageDone() {
|
|
978
|
+
const p = this.exportPages;
|
|
979
|
+
if (!p || !this.exportFillEl || !this.exportTextEl) return;
|
|
980
|
+
p.done++;
|
|
981
|
+
const percent = p.total ? Math.min(100, Math.round(p.done / p.total * 100)) : 100;
|
|
982
|
+
this.exportFillEl.style.width = `${percent}%`;
|
|
983
|
+
this.exportTextEl.textContent = `${percent}% (${p.done} / ${p.total} pages)`;
|
|
984
|
+
}
|
|
985
|
+
showExportOverlay(title = "Exporting video...", text = "0% (0 / 0 frames)") {
|
|
931
986
|
const overlay = document.createElement("div");
|
|
932
987
|
overlay.className = "mc-export-overlay";
|
|
933
988
|
overlay.innerHTML = `
|
|
934
989
|
<div class="mc-export-panel">
|
|
935
|
-
<div class="mc-export-title"
|
|
990
|
+
<div class="mc-export-title"></div>
|
|
936
991
|
<div class="mc-export-track"><div class="mc-export-fill"></div></div>
|
|
937
|
-
<div class="mc-export-text"
|
|
992
|
+
<div class="mc-export-text"></div>
|
|
938
993
|
</div>
|
|
939
994
|
`;
|
|
995
|
+
overlay.querySelector(".mc-export-title").textContent = title;
|
|
996
|
+
overlay.querySelector(".mc-export-text").textContent = text;
|
|
940
997
|
this.root.appendChild(overlay);
|
|
941
998
|
this.exportOverlay = overlay;
|
|
942
999
|
this.exportFillEl = overlay.querySelector(".mc-export-fill");
|
|
@@ -949,6 +1006,7 @@ var Controller = class _Controller {
|
|
|
949
1006
|
this.exportFillEl = null;
|
|
950
1007
|
this.exportTextEl = null;
|
|
951
1008
|
}
|
|
1009
|
+
this.exportPages = null;
|
|
952
1010
|
}
|
|
953
1011
|
bindVisibility() {
|
|
954
1012
|
const wrap = this.wrapper;
|