pixi-effects 0.12.0 → 0.13.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 +12 -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 +76 -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 +76 -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: 2px; height: calc(var(--mc-track-height, 3px) +
|
|
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: 2px; height: calc(var(--mc-track-height, 3px) + 10px); transform: translate(-50%, -50%); background: var(--mc-fg, #fff); opacity: 0.9; border-radius: 1px; box-shadow: 0 0 0 1px rgba(0,0,0,0.55); }\n.mc-stop-page { width: 4px; height: calc(var(--mc-track-height, 3px) + 20px); opacity: 1; }\n.mc-stop-page::before { content: ''; position: absolute; left: 50%; top: -3px; width: 8px; height: 8px; transform: translateX(-50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1px rgba(0,0,0,0.55); }\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,9 @@ 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: 2px; height: calc(var(--mc-track-height, 3px) +
|
|
50
|
+
.mc-stop-tick { position: absolute; top: 50%; width: 2px; height: calc(var(--mc-track-height, 3px) + 10px); transform: translate(-50%, -50%); background: var(--mc-fg, #fff); opacity: 0.9; border-radius: 1px; box-shadow: 0 0 0 1px rgba(0,0,0,0.55); }
|
|
51
|
+
.mc-stop-page { width: 4px; height: calc(var(--mc-track-height, 3px) + 20px); opacity: 1; }
|
|
52
|
+
.mc-stop-page::before { content: ''; position: absolute; left: 50%; top: -3px; width: 8px; height: 8px; transform: translateX(-50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1px rgba(0,0,0,0.55); }
|
|
51
53
|
.mc-progress-fill {
|
|
52
54
|
position: absolute; left: 12px; top: 50%;
|
|
53
55
|
height: var(--mc-track-height, 3px);
|
|
@@ -269,8 +271,10 @@ function pxToFraction(clientX, rect, inset = 0) {
|
|
|
269
271
|
const ratio = (clientX - innerLeft) / innerWidth;
|
|
270
272
|
return Math.min(Math.max(ratio, 0), 1);
|
|
271
273
|
}
|
|
274
|
+
var PDF_QUALITY = { "low": 0.6, "medium": 0.75, "high": 0.88, "very-high": 0.95 };
|
|
272
275
|
function extensionForMimeType(mime) {
|
|
273
276
|
const t = (mime || "").toLowerCase();
|
|
277
|
+
if (t.includes("pdf")) return "pdf";
|
|
274
278
|
if (t.includes("webm")) return "webm";
|
|
275
279
|
if (t.includes("quicktime") || t.includes("mov")) return "mov";
|
|
276
280
|
if (t.includes("matroska") || t.includes("mkv")) return "mkv";
|
|
@@ -317,6 +321,7 @@ var Controller = class _Controller {
|
|
|
317
321
|
wasPlayingBeforeScrub = false;
|
|
318
322
|
activePointerId = null;
|
|
319
323
|
isExporting = false;
|
|
324
|
+
exportPages = null;
|
|
320
325
|
exportOverlay = null;
|
|
321
326
|
exportFillEl = null;
|
|
322
327
|
exportTextEl = null;
|
|
@@ -357,6 +362,7 @@ var Controller = class _Controller {
|
|
|
357
362
|
enableKeyboardShortcuts: options.enableKeyboardShortcuts ?? true,
|
|
358
363
|
clickToPlay: options.clickToPlay ?? true,
|
|
359
364
|
present: options.present ?? true,
|
|
365
|
+
pauseAtStops: options.pauseAtStops ?? true,
|
|
360
366
|
className: options.className ?? "movie-controller"
|
|
361
367
|
};
|
|
362
368
|
installStyles();
|
|
@@ -564,6 +570,7 @@ var Controller = class _Controller {
|
|
|
564
570
|
refreshStops() {
|
|
565
571
|
this.progressEl.querySelector(".mc-stops")?.remove();
|
|
566
572
|
const stops = this.movie.stops ?? [];
|
|
573
|
+
this.refreshPdfChoices(stops.length > 0);
|
|
567
574
|
if (stops.length === 0) {
|
|
568
575
|
this.presentBtn?.remove();
|
|
569
576
|
this.presentBtn = null;
|
|
@@ -574,7 +581,7 @@ var Controller = class _Controller {
|
|
|
574
581
|
holder.className = "mc-stops";
|
|
575
582
|
for (const s of stops) {
|
|
576
583
|
const tick = document.createElement("div");
|
|
577
|
-
tick.className = "mc-stop-tick";
|
|
584
|
+
tick.className = s.pageStart ? "mc-stop-tick mc-stop-page" : "mc-stop-tick";
|
|
578
585
|
tick.style.left = `${s.frame / total * 100}%`;
|
|
579
586
|
if (s.page) tick.title = s.page;
|
|
580
587
|
holder.appendChild(tick);
|
|
@@ -593,6 +600,41 @@ var Controller = class _Controller {
|
|
|
593
600
|
this.presentBtn = btn;
|
|
594
601
|
}
|
|
595
602
|
}
|
|
603
|
+
/** A deck can be downloaded as a PDF: the format list gets "PDF" choices while the movie has stops. */
|
|
604
|
+
refreshPdfChoices(has) {
|
|
605
|
+
const select = this.settingsFormatSelect;
|
|
606
|
+
if (!select) return;
|
|
607
|
+
const present = !!select.querySelector('option[value="pdf"]');
|
|
608
|
+
if (has && !present) {
|
|
609
|
+
for (const [value, label] of [["pdf", "PDF (pages)"], ["pdf-steps", "PDF (every step)"]]) {
|
|
610
|
+
const o = document.createElement("option");
|
|
611
|
+
o.value = value;
|
|
612
|
+
o.textContent = label;
|
|
613
|
+
select.appendChild(o);
|
|
614
|
+
}
|
|
615
|
+
} else if (!has && present) {
|
|
616
|
+
select.querySelectorAll('option[value="pdf"], option[value="pdf-steps"]').forEach((o) => o.remove());
|
|
617
|
+
if (this.exportFormat === "pdf" || this.exportFormat === "pdf-steps") {
|
|
618
|
+
this.exportFormat = "mp4";
|
|
619
|
+
select.value = "mp4";
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/** Play, or pause: a movie with stops plays to the next stop (and from the end, again from the start). */
|
|
624
|
+
async togglePlay() {
|
|
625
|
+
if (this.movie.isPlaying) {
|
|
626
|
+
this.movie.pause();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
await this.startPlaying();
|
|
630
|
+
}
|
|
631
|
+
/** Start playing: to the next stop for a movie with stops, to the end for any other (or with `pauseAtStops: false`). */
|
|
632
|
+
async startPlaying() {
|
|
633
|
+
if (this.options.pauseAtStops && (this.movie.stops?.length ?? 0) > 0) {
|
|
634
|
+
if (this.movie.currentFrame >= this.movie.totalFrames) await this.movie.gotoFrame(0, true);
|
|
635
|
+
await this.movie.next();
|
|
636
|
+
} else this.movie.play();
|
|
637
|
+
}
|
|
596
638
|
/** Hand the page over to a Presenter (fullscreen, keys, click and swipe move through the stops); Escape gives it back. */
|
|
597
639
|
async startPresenting() {
|
|
598
640
|
if (this.presenter || this.destroyed) return;
|
|
@@ -609,11 +651,7 @@ var Controller = class _Controller {
|
|
|
609
651
|
}
|
|
610
652
|
bindPlayButton() {
|
|
611
653
|
this.playBtn.addEventListener("click", () => {
|
|
612
|
-
|
|
613
|
-
this.movie.pause();
|
|
614
|
-
} else {
|
|
615
|
-
this.movie.play();
|
|
616
|
-
}
|
|
654
|
+
void this.togglePlay();
|
|
617
655
|
});
|
|
618
656
|
}
|
|
619
657
|
/** Clicking the picture plays / pauses (a tap on a phone too). */
|
|
@@ -630,8 +668,7 @@ var Controller = class _Controller {
|
|
|
630
668
|
this.swallowCanvasClick = false;
|
|
631
669
|
return;
|
|
632
670
|
}
|
|
633
|
-
|
|
634
|
-
else this.movie.play();
|
|
671
|
+
void this.togglePlay();
|
|
635
672
|
};
|
|
636
673
|
canvas.addEventListener("pointerdown", this.canvasPointerDownHandler);
|
|
637
674
|
canvas.addEventListener("click", this.canvasClickHandler);
|
|
@@ -787,7 +824,7 @@ var Controller = class _Controller {
|
|
|
787
824
|
} catch {
|
|
788
825
|
}
|
|
789
826
|
if (this.wasPlayingBeforeScrub) {
|
|
790
|
-
this.
|
|
827
|
+
void this.startPlaying();
|
|
791
828
|
this.refreshPlayIcon();
|
|
792
829
|
}
|
|
793
830
|
};
|
|
@@ -836,7 +873,7 @@ var Controller = class _Controller {
|
|
|
836
873
|
if (this.settingsFormatSelect) {
|
|
837
874
|
this.settingsFormatSelect.addEventListener("change", (e) => {
|
|
838
875
|
const v = e.target.value;
|
|
839
|
-
if (v === "mp4" || v === "webm" || v === "mov") this.exportFormat = v;
|
|
876
|
+
if (v === "mp4" || v === "webm" || v === "mov" || v === "pdf" || v === "pdf-steps") this.exportFormat = v;
|
|
840
877
|
});
|
|
841
878
|
}
|
|
842
879
|
if (this.settingsQualitySelect) {
|
|
@@ -886,9 +923,19 @@ var Controller = class _Controller {
|
|
|
886
923
|
this.movie.pause();
|
|
887
924
|
this.refreshPlayIcon();
|
|
888
925
|
}
|
|
889
|
-
this.
|
|
926
|
+
const pdf = this.exportFormat === "pdf" || this.exportFormat === "pdf-steps";
|
|
927
|
+
if (pdf) {
|
|
928
|
+
const stops = this.movie.stops ?? [];
|
|
929
|
+
const total = this.exportFormat === "pdf-steps" ? stops.filter((s) => s.pdf !== false).length : new Set(stops.map((s) => s.pageIndex)).size;
|
|
930
|
+
this.showExportOverlay("Exporting PDF...", `0% (0 / ${total} pages)`);
|
|
931
|
+
this.exportPages = { done: 0, total };
|
|
932
|
+
} else this.showExportOverlay();
|
|
890
933
|
try {
|
|
891
|
-
const blob = await this.movie.
|
|
934
|
+
const blob = pdf ? await this.movie.exportPDF({
|
|
935
|
+
which: this.exportFormat === "pdf-steps" ? "stops" : "pages",
|
|
936
|
+
quality: PDF_QUALITY[this.exportQuality],
|
|
937
|
+
onImage: () => this.pdfPageDone()
|
|
938
|
+
}) : await this.movie.render({
|
|
892
939
|
format: this.exportFormat,
|
|
893
940
|
video: { bitrate: this.exportQuality },
|
|
894
941
|
audio: { bitrate: this.exportQuality }
|
|
@@ -904,7 +951,7 @@ var Controller = class _Controller {
|
|
|
904
951
|
this.hideExportOverlay();
|
|
905
952
|
this.isExporting = false;
|
|
906
953
|
if (wasPlaying) {
|
|
907
|
-
this.
|
|
954
|
+
void this.startPlaying();
|
|
908
955
|
this.refreshPlayIcon();
|
|
909
956
|
}
|
|
910
957
|
}
|
|
@@ -927,16 +974,27 @@ var Controller = class _Controller {
|
|
|
927
974
|
const ts = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
928
975
|
return `movie-${ts}.${ext}`;
|
|
929
976
|
}
|
|
930
|
-
|
|
977
|
+
/** One more PDF page is ready: move the bar. */
|
|
978
|
+
pdfPageDone() {
|
|
979
|
+
const p = this.exportPages;
|
|
980
|
+
if (!p || !this.exportFillEl || !this.exportTextEl) return;
|
|
981
|
+
p.done++;
|
|
982
|
+
const percent = p.total ? Math.min(100, Math.round(p.done / p.total * 100)) : 100;
|
|
983
|
+
this.exportFillEl.style.width = `${percent}%`;
|
|
984
|
+
this.exportTextEl.textContent = `${percent}% (${p.done} / ${p.total} pages)`;
|
|
985
|
+
}
|
|
986
|
+
showExportOverlay(title = "Exporting video...", text = "0% (0 / 0 frames)") {
|
|
931
987
|
const overlay = document.createElement("div");
|
|
932
988
|
overlay.className = "mc-export-overlay";
|
|
933
989
|
overlay.innerHTML = `
|
|
934
990
|
<div class="mc-export-panel">
|
|
935
|
-
<div class="mc-export-title"
|
|
991
|
+
<div class="mc-export-title"></div>
|
|
936
992
|
<div class="mc-export-track"><div class="mc-export-fill"></div></div>
|
|
937
|
-
<div class="mc-export-text"
|
|
993
|
+
<div class="mc-export-text"></div>
|
|
938
994
|
</div>
|
|
939
995
|
`;
|
|
996
|
+
overlay.querySelector(".mc-export-title").textContent = title;
|
|
997
|
+
overlay.querySelector(".mc-export-text").textContent = text;
|
|
940
998
|
this.root.appendChild(overlay);
|
|
941
999
|
this.exportOverlay = overlay;
|
|
942
1000
|
this.exportFillEl = overlay.querySelector(".mc-export-fill");
|
|
@@ -949,6 +1007,7 @@ var Controller = class _Controller {
|
|
|
949
1007
|
this.exportFillEl = null;
|
|
950
1008
|
this.exportTextEl = null;
|
|
951
1009
|
}
|
|
1010
|
+
this.exportPages = null;
|
|
952
1011
|
}
|
|
953
1012
|
bindVisibility() {
|
|
954
1013
|
const wrap = this.wrapper;
|