cursedbelt 5.112.1 → 5.113.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/dist/react/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/react/components/ErrorBoundary.js +23 -13
- package/dist/react/components/ErrorBoundary.js.map +1 -1
- package/dist/react/lazy/staleChunk.d.ts +19 -0
- package/dist/react/lazy/staleChunk.d.ts.map +1 -1
- package/dist/react/lazy/staleChunk.js +49 -2
- package/dist/react/lazy/staleChunk.js.map +1 -1
- package/dist/react/media/CutTimeline.d.ts +31 -0
- package/dist/react/media/CutTimeline.d.ts.map +1 -0
- package/dist/react/media/CutTimeline.js +197 -0
- package/dist/react/media/CutTimeline.js.map +1 -0
- package/dist/react/media/VideoCutEditor.d.ts +32 -6
- package/dist/react/media/VideoCutEditor.d.ts.map +1 -1
- package/dist/react/media/VideoCutEditor.js +97 -63
- package/dist/react/media/VideoCutEditor.js.map +1 -1
- package/dist/react/media/cutTimelineMath.d.ts +80 -0
- package/dist/react/media/cutTimelineMath.d.ts.map +1 -0
- package/dist/react/media/cutTimelineMath.js +116 -0
- package/dist/react/media/cutTimelineMath.js.map +1 -0
- package/dist/react/media/useFrameRate.d.ts +34 -0
- package/dist/react/media/useFrameRate.d.ts.map +1 -0
- package/dist/react/media/useFrameRate.js +66 -0
- package/dist/react/media/useFrameRate.js.map +1 -0
- package/dist/react/media/videoCutEditor.css +249 -63
- package/dist/react/media-gallery/MediaGallery.d.ts +10 -1
- package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaGallery.js +29 -7
- package/dist/react/media-gallery/MediaGallery.js.map +1 -1
- package/dist/react/media-gallery/galleryDeepLink.d.ts +14 -0
- package/dist/react/media-gallery/galleryDeepLink.d.ts.map +1 -1
- package/dist/react/media-gallery/galleryDeepLink.js +44 -5
- package/dist/react/media-gallery/galleryDeepLink.js.map +1 -1
- package/dist/react/media-gallery/index.d.ts +1 -1
- package/dist/react/media-gallery/index.d.ts.map +1 -1
- package/dist/react/media-gallery/index.js +1 -1
- package/dist/react/media-gallery/index.js.map +1 -1
- package/dist/react/media-gallery/types.d.ts +5 -0
- package/dist/react/media-gallery/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/scripts/guardrails-baseline.json +1 -1
- package/src/react/components/ErrorBoundary.tsx +19 -11
- package/src/react/components/StaleDeployHealer.spec.tsx +27 -0
- package/src/react/lazy/staleChunk.spec.tsx +63 -1
- package/src/react/lazy/staleChunk.ts +55 -2
- package/src/react/media/CutTimeline.tsx +379 -0
- package/src/react/media/VideoCutEditor.spec.tsx +1 -1
- package/src/react/media/VideoCutEditor.tsx +277 -214
- package/src/react/media/cutTimelineMath.spec.ts +90 -0
- package/src/react/media/cutTimelineMath.ts +140 -0
- package/src/react/media/useFrameRate.ts +96 -0
- package/src/react/media/videoCutEditor.css +249 -63
- package/src/react/media-gallery/MediaGallery.tsx +38 -7
- package/src/react/media-gallery/galleryDeepLink.spec.tsx +87 -2
- package/src/react/media-gallery/galleryDeepLink.ts +58 -5
- package/src/react/media-gallery/index.ts +1 -0
- package/src/react/media-gallery/types.ts +5 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAIvF,OAAO,EAAwC,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAerG;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC;IACxE,iEAAiE;IACjE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,qFAAqF;IACrF,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC/B;;;kFAG8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAC;CACxC;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAQD,qBAAa,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAClF,KAAK,EAAE,kBAAkB,CAAmB;IAE5C,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAIjE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAItD,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAMlD,KAAK,QAAO,IAAI,CAEd;IAEF,MAAM,IAAI,SAAS;CA0BpB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAGnD;AAED,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,+BAA+B,sBAAsB,
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAIvF,OAAO,EAAwC,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAerG;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC;IACxE,iEAAiE;IACjE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,qFAAqF;IACrF,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC/B;;;kFAG8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAC;CACxC;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAQD,qBAAa,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAClF,KAAK,EAAE,kBAAkB,CAAmB;IAE5C,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAIjE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAItD,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAMlD,KAAK,QAAO,IAAI,CAEd;IAEF,MAAM,IAAI,SAAS;CA0BpB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAGnD;AAED,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,+BAA+B,sBAAsB,gCA8CtF,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Component, useEffect, useState } from 'react';
|
|
3
3
|
import { Alert } from './Alert.js';
|
|
4
4
|
import { Spinner } from './Spinner.js';
|
|
5
|
-
import { claimStaleChunkReload, staleChunkReloadedRecently } from '../lazy/staleChunk.js';
|
|
5
|
+
import { claimStaleChunkReload, leavingFor, staleChunkReloadedRecently } from '../lazy/staleChunk.js';
|
|
6
6
|
import { ConnectionHealer, isUnreachableError } from './ConnectionHealer.js';
|
|
7
7
|
const keysChanged = (a, b) => {
|
|
8
8
|
if (!a || !b)
|
|
@@ -129,18 +129,28 @@ export const StaleDeployHealer = ({ error, onRecover, ...rest }) => {
|
|
|
129
129
|
useEffect(() => {
|
|
130
130
|
if (typeof window === 'undefined')
|
|
131
131
|
return;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
let timer;
|
|
133
|
+
const heal = () => {
|
|
134
|
+
if (!claimStaleChunkReload()) {
|
|
135
|
+
setExhausted(true);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// A tick of delay so the banner paints first — an instant reload on a slow
|
|
139
|
+
// connection reads as a flicker with no explanation.
|
|
140
|
+
timer = setTimeout(() => {
|
|
141
|
+
if (onRecover)
|
|
142
|
+
onRecover();
|
|
143
|
+
else
|
|
144
|
+
window.location.reload();
|
|
145
|
+
}, 400);
|
|
146
|
+
};
|
|
147
|
+
// A chunk cut off by the page being left (Safari cancels its loads at `beforeunload`) is
|
|
148
|
+
// not stale, and a reload would pull the tab back from where it was going — `staleChunk.ts`.
|
|
149
|
+
const leaving = leavingFor();
|
|
150
|
+
if (leaving > 0)
|
|
151
|
+
timer = setTimeout(heal, leaving);
|
|
152
|
+
else
|
|
153
|
+
heal();
|
|
144
154
|
return () => clearTimeout(timer);
|
|
145
155
|
}, [onRecover]);
|
|
146
156
|
if (exhausted)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAkC,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,17 @@
|
|
|
27
27
|
* because a chunk genuinely missing from the deployed build would otherwise reload
|
|
28
28
|
* forever with nothing able to remember trying.
|
|
29
29
|
*
|
|
30
|
+
* 🔴 A chunk cut off by the page being LEFT is not stale. Safari (WebKit) cancels the
|
|
31
|
+
* old document's in-flight loads the moment a document navigation begins, right after
|
|
32
|
+
* `beforeunload`, and every lazy import still on its way rejects "Importing a module
|
|
33
|
+
* script failed." — the same words a deleted chunk gets. Reloading then sends the tab
|
|
34
|
+
* back to the page it was leaving: measured 2026-11-04 on roms' stage, a `goto
|
|
35
|
+
* /settings` 0–300 ms after the library loaded was "interrupted by another navigation
|
|
36
|
+
* to /" in 9 of 12 WebKit runs (forge task 4425). So a failure inside
|
|
37
|
+
* {@link LEAVING_GRACE_MS} of `beforeunload` waits instead: a page that goes never
|
|
38
|
+
* runs the timer, and one still here after it (the navigation was canceled, or was a
|
|
39
|
+
* download) retries the chunk once before treating it as stale.
|
|
40
|
+
*
|
|
30
41
|
* `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
|
|
31
42
|
* is refused, so the next lazy view cannot quietly reopen this.
|
|
32
43
|
*/
|
|
@@ -34,12 +45,20 @@ import { type ComponentType, type LazyExoticComponent } from 'react';
|
|
|
34
45
|
export declare const STALE_CHUNK_RELOADED_AT_KEY = "cursedbelt.staleChunkReloadAt";
|
|
35
46
|
/** A reload within this long of the last one is a loop, not a stale page. */
|
|
36
47
|
export declare const STALE_CHUNK_RELOAD_WINDOW_MS = 60000;
|
|
48
|
+
/** How long after `beforeunload` a failed chunk is read as the navigation's doing, not a deploy's. */
|
|
49
|
+
export declare const LEAVING_GRACE_MS = 10000;
|
|
37
50
|
export interface StaleChunkEnv {
|
|
38
51
|
storage: Pick<Storage, 'getItem' | 'setItem'> | null;
|
|
39
52
|
reload: () => void;
|
|
40
53
|
now: () => number;
|
|
54
|
+
/** When this page last heard `beforeunload` (0: never, or it came back from the bfcache since). */
|
|
55
|
+
leftAt?: () => number;
|
|
56
|
+
/** A timer — one that never fires on a page that has gone. */
|
|
57
|
+
sleep?: (ms: number) => Promise<void>;
|
|
41
58
|
}
|
|
42
59
|
export declare function browserEnv(): StaleChunkEnv;
|
|
60
|
+
/** How much longer a chunk failure now is the page being left (0: it is not being left). */
|
|
61
|
+
export declare function leavingFor(env?: StaleChunkEnv): number;
|
|
43
62
|
/**
|
|
44
63
|
* Claim this tab's one stale-chunk reload for the window: true when a reload may
|
|
45
64
|
* happen now, and the claim is already recorded. False when one happened inside the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staleChunk.d.ts","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"staleChunk.d.ts","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAQ,MAAM,OAAO,CAAC;AAE3E,eAAO,MAAM,2BAA2B,kCAAkC,CAAC;AAE3E,6EAA6E;AAC7E,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,sGAAsG;AACtG,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;IACrD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAqBD,wBAAgB,UAAU,IAAI,aAAa,CAQ1C;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,GAAG,GAAE,aAA4B,GAAG,MAAM,CAIpE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,aAA4B,GAAG,OAAO,CAShF;AAED,sGAAsG;AACtG,wBAAgB,0BAA0B,CAAC,GAAG,GAAE,aAA4B,GAAG,OAAO,CASrF;AAED,iGAAiG;AACjG,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,GAAG,GAAE,MAAM,aAA0B,GACpC,MAAM,OAAO,CAAC,CAAC,CAAC,CAoBlB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,CAAC,GAAG,CAAC,EACzD,IAAI,EAAE,MAAM,OAAO,CAAC;IAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CAAC,EACnC,GAAG,CAAC,EAAE,MAAM,aAAa,GACxB,mBAAmB,CAAC,CAAC,CAAC,CAExB"}
|
|
@@ -27,6 +27,17 @@
|
|
|
27
27
|
* because a chunk genuinely missing from the deployed build would otherwise reload
|
|
28
28
|
* forever with nothing able to remember trying.
|
|
29
29
|
*
|
|
30
|
+
* 🔴 A chunk cut off by the page being LEFT is not stale. Safari (WebKit) cancels the
|
|
31
|
+
* old document's in-flight loads the moment a document navigation begins, right after
|
|
32
|
+
* `beforeunload`, and every lazy import still on its way rejects "Importing a module
|
|
33
|
+
* script failed." — the same words a deleted chunk gets. Reloading then sends the tab
|
|
34
|
+
* back to the page it was leaving: measured 2026-11-04 on roms' stage, a `goto
|
|
35
|
+
* /settings` 0–300 ms after the library loaded was "interrupted by another navigation
|
|
36
|
+
* to /" in 9 of 12 WebKit runs (forge task 4425). So a failure inside
|
|
37
|
+
* {@link LEAVING_GRACE_MS} of `beforeunload` waits instead: a page that goes never
|
|
38
|
+
* runs the timer, and one still here after it (the navigation was canceled, or was a
|
|
39
|
+
* download) retries the chunk once before treating it as stale.
|
|
40
|
+
*
|
|
30
41
|
* `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
|
|
31
42
|
* is refused, so the next lazy view cannot quietly reopen this.
|
|
32
43
|
*/
|
|
@@ -34,6 +45,25 @@ import { lazy } from 'react';
|
|
|
34
45
|
export const STALE_CHUNK_RELOADED_AT_KEY = 'cursedbelt.staleChunkReloadAt';
|
|
35
46
|
/** A reload within this long of the last one is a loop, not a stale page. */
|
|
36
47
|
export const STALE_CHUNK_RELOAD_WINDOW_MS = 60_000;
|
|
48
|
+
/** How long after `beforeunload` a failed chunk is read as the navigation's doing, not a deploy's. */
|
|
49
|
+
export const LEAVING_GRACE_MS = 10_000;
|
|
50
|
+
let leftAt = 0;
|
|
51
|
+
let watching = false;
|
|
52
|
+
/** Note when the page starts being left. Installed on import, so it hears the first navigation. */
|
|
53
|
+
function watchLeaving() {
|
|
54
|
+
if (watching || typeof window === 'undefined' || typeof window.addEventListener !== 'function')
|
|
55
|
+
return;
|
|
56
|
+
watching = true;
|
|
57
|
+
window.addEventListener('beforeunload', () => {
|
|
58
|
+
leftAt = Date.now();
|
|
59
|
+
});
|
|
60
|
+
// Back from the bfcache: the navigation that was leaving it is over.
|
|
61
|
+
window.addEventListener('pageshow', () => {
|
|
62
|
+
leftAt = 0;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
watchLeaving();
|
|
66
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
67
|
export function browserEnv() {
|
|
38
68
|
let storage = null;
|
|
39
69
|
try {
|
|
@@ -42,7 +72,14 @@ export function browserEnv() {
|
|
|
42
72
|
catch {
|
|
43
73
|
// Storage refused (blocked site data) — no memory, so no reload: the guard fails closed.
|
|
44
74
|
}
|
|
45
|
-
return { storage, reload: () => window.location.reload(), now: Date.now };
|
|
75
|
+
return { storage, reload: () => window.location.reload(), now: Date.now, leftAt: () => leftAt, sleep };
|
|
76
|
+
}
|
|
77
|
+
/** How much longer a chunk failure now is the page being left (0: it is not being left). */
|
|
78
|
+
export function leavingFor(env = browserEnv()) {
|
|
79
|
+
const at = env.leftAt?.() ?? 0;
|
|
80
|
+
if (at <= 0)
|
|
81
|
+
return 0;
|
|
82
|
+
return Math.max(0, at + LEAVING_GRACE_MS - env.now());
|
|
46
83
|
}
|
|
47
84
|
/**
|
|
48
85
|
* Claim this tab's one stale-chunk reload for the window: true when a reload may
|
|
@@ -81,7 +118,17 @@ export function reloadOnStaleChunk(load, env = browserEnv) {
|
|
|
81
118
|
return await load();
|
|
82
119
|
}
|
|
83
120
|
catch (error) {
|
|
84
|
-
|
|
121
|
+
let current = env();
|
|
122
|
+
const wait = leavingFor(current);
|
|
123
|
+
if (wait > 0) {
|
|
124
|
+
await (current.sleep ?? sleep)(wait);
|
|
125
|
+
try {
|
|
126
|
+
return await load();
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
current = env();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
85
132
|
if (!claimStaleChunkReload(current))
|
|
86
133
|
throw error;
|
|
87
134
|
current.reload();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staleChunk.js","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"staleChunk.js","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type PlanSegment, type VideoCut } from 'cursedbelt-core/media/video-cuts';
|
|
3
|
+
/**
|
|
4
|
+
* The Trim page's timeline: a ruler, the clip's pieces, and a playhead, at any zoom from the
|
|
5
|
+
* whole clip down to one frame per 12 px (forge task 4311).
|
|
6
|
+
*
|
|
7
|
+
* The gestures are a map's and an NLE's, so nothing here has to be learned:
|
|
8
|
+
* · ⌘/Ctrl + wheel, or a trackpad pinch (which Chromium and Safari deliver as a ctrl-wheel),
|
|
9
|
+
* zooms around the POINTER; two fingers on a touch screen pinch the same way.
|
|
10
|
+
* · a plain wheel, or a drag anywhere on the track, pans.
|
|
11
|
+
* · a click on the ruler moves the playhead there, onto the frame under the pointer.
|
|
12
|
+
* · a click on a piece is the ✗ it always was — a drag that started on one never toggles it.
|
|
13
|
+
* The slider, ± and Fit do the same with no gesture at all (CNV-1: the wheel alone never zooms;
|
|
14
|
+
* CNV-3: a fit control on every canvas).
|
|
15
|
+
*
|
|
16
|
+
* 🔴 Drawn VIRTUALLY: only what is on screen is in the DOM, each piece clipped to the view. At
|
|
17
|
+
* 12 px a frame a two-hour 60 fps film is ten million pixels wide, which no browser will lay out
|
|
18
|
+
* as one element — so the track is always exactly the view, and the view is two numbers.
|
|
19
|
+
*/
|
|
20
|
+
export interface CutTimelineProps {
|
|
21
|
+
duration: number;
|
|
22
|
+
fps: number;
|
|
23
|
+
playhead: number;
|
|
24
|
+
segments: readonly PlanSegment[];
|
|
25
|
+
onSeek: (seconds: number) => void;
|
|
26
|
+
onToggle: (segment: VideoCut) => void;
|
|
27
|
+
/** The transport, drawn at the start of the timeline's one-line toolbar (FRM-14). */
|
|
28
|
+
transport?: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
export declare function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggle, transport, ...passThrough }: CutTimelineProps): React.JSX.Element;
|
|
31
|
+
//# sourceMappingURL=CutTimeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CutTimeline.d.ts","sourceRoot":"","sources":["../../../src/react/media/CutTimeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAe,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAmBhG;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,qFAAqF;IACrF,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAeD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,EAAE,gBAAgB,qBA4T/H"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Maximize2, X, ZoomIn, ZoomOut } from 'lucide-react';
|
|
4
|
+
import { cutTimecode } from 'cursedbelt-core/media/video-cuts';
|
|
5
|
+
import { IconButton } from '../components/IconButton.js';
|
|
6
|
+
import { Button } from '../components/Button.js';
|
|
7
|
+
import { Slider } from '../components/Slider.js';
|
|
8
|
+
import { Tooltip } from '../components/Tooltip.js';
|
|
9
|
+
import { cn, RAW_BUTTON } from '../lib/cn.js';
|
|
10
|
+
import { clampViewStart, frameAt, frameStart, rulerLabel, rulerStep, sliderToZoom, snapToFrame, zoomAround, zoomBounds, zoomToSlider, } from './cutTimelineMath.js';
|
|
11
|
+
/** A drag shorter than this is a click — the slop every pointer library uses. */
|
|
12
|
+
const DRAG_SLOP_PX = 4;
|
|
13
|
+
/** The width drawn at before the track is measured. */
|
|
14
|
+
const NOMINAL_WIDTH_PX = 1000;
|
|
15
|
+
/** Frame ticks are drawn once frames are at least this far apart. */
|
|
16
|
+
const FRAME_TICK_MIN_PX = 6;
|
|
17
|
+
export function CutTimeline({ duration, fps, playhead, segments, onSeek, onToggle, transport, ...passThrough }) {
|
|
18
|
+
const trackRef = React.useRef(null);
|
|
19
|
+
const [measured, setWidth] = React.useState(0);
|
|
20
|
+
// Until the track has been measured (and in a DOM with no layout at all), draw at a nominal
|
|
21
|
+
// width rather than at zero — zero would put every piece off screen, and the first real
|
|
22
|
+
// measurement lands in a layout effect, before anything paints.
|
|
23
|
+
const width = measured > 0 ? measured : NOMINAL_WIDTH_PX;
|
|
24
|
+
const [view, setView] = React.useState({ start: 0, pps: null });
|
|
25
|
+
React.useLayoutEffect(() => {
|
|
26
|
+
const track = trackRef.current;
|
|
27
|
+
if (!track)
|
|
28
|
+
return;
|
|
29
|
+
const measure = () => setWidth(track.clientWidth);
|
|
30
|
+
measure();
|
|
31
|
+
const observer = new ResizeObserver(measure);
|
|
32
|
+
observer.observe(track);
|
|
33
|
+
return () => observer.disconnect();
|
|
34
|
+
}, []);
|
|
35
|
+
const bounds = zoomBounds(width, duration, fps);
|
|
36
|
+
const pps = Math.min(bounds.max, Math.max(bounds.fit, view.pps ?? bounds.fit));
|
|
37
|
+
const start = clampViewStart(view.start, pps, width, duration);
|
|
38
|
+
const span = pps > 0 ? width / pps : 0;
|
|
39
|
+
const canZoom = bounds.max > bounds.fit && bounds.fit > 0;
|
|
40
|
+
// The handlers below are bound once (the wheel has to be non-passive, so it is a native
|
|
41
|
+
// listener) and read the current numbers from here.
|
|
42
|
+
const now = React.useRef({ pps, start, width, duration, bounds });
|
|
43
|
+
now.current = { pps, start, width, duration, bounds };
|
|
44
|
+
const zoomTo = React.useCallback((next, anchorX) => {
|
|
45
|
+
const n = now.current;
|
|
46
|
+
setView(zoomAround({ start: n.start, pps: n.pps }, next, anchorX, n.width, n.duration, n.bounds));
|
|
47
|
+
}, []);
|
|
48
|
+
/** Zoom by a factor around the playhead when it is on screen, else around the middle. */
|
|
49
|
+
const zoomBy = (factor) => {
|
|
50
|
+
const x = playhead >= start && playhead <= start + span ? (playhead - start) * pps : width / 2;
|
|
51
|
+
zoomTo(pps * factor, x);
|
|
52
|
+
};
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
const track = trackRef.current;
|
|
55
|
+
if (!track)
|
|
56
|
+
return;
|
|
57
|
+
const onWheel = (event) => {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
const n = now.current;
|
|
60
|
+
const lines = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? n.width : 1;
|
|
61
|
+
if (event.ctrlKey || event.metaKey) {
|
|
62
|
+
const x = event.clientX - track.getBoundingClientRect().left;
|
|
63
|
+
zoomTo(n.pps * Math.exp(-event.deltaY * lines * 0.01), x);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// A vertical wheel pans too: most mice have no other, and the page has nothing to scroll here.
|
|
67
|
+
const dx = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY;
|
|
68
|
+
setView({ pps: n.pps, start: clampViewStart(n.start + (dx * lines) / n.pps, n.pps, n.width, n.duration) });
|
|
69
|
+
};
|
|
70
|
+
track.addEventListener('wheel', onWheel, { passive: false });
|
|
71
|
+
return () => track.removeEventListener('wheel', onWheel);
|
|
72
|
+
}, [zoomTo]);
|
|
73
|
+
// Keep the playhead in view while it moves (playing, stepping, a seek from the cuts list) —
|
|
74
|
+
// a page-turn, as Resolve does, rather than a scroll that never lets the eye settle.
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
const n = now.current;
|
|
77
|
+
if (n.width <= 0 || n.pps <= n.bounds.fit + 1e-9)
|
|
78
|
+
return;
|
|
79
|
+
const viewSpan = n.width / n.pps;
|
|
80
|
+
if (playhead < n.start || playhead > n.start + viewSpan) {
|
|
81
|
+
setView({ pps: n.pps, start: clampViewStart(playhead - viewSpan * 0.1, n.pps, n.width, n.duration) });
|
|
82
|
+
}
|
|
83
|
+
}, [playhead]);
|
|
84
|
+
/*
|
|
85
|
+
* Pointers. One pointer that moves past the slop pans; two pinch. One that does not move is a
|
|
86
|
+
* click: on the ruler it seeks, on a piece the piece's own button does its job.
|
|
87
|
+
*/
|
|
88
|
+
const gesture = React.useRef({ pointers: new Map(), fromX: 0, fromStart: 0, dragged: false, pinch: null });
|
|
89
|
+
const onPointerDown = (event) => {
|
|
90
|
+
if (event.pointerType === 'mouse' && event.button !== 0)
|
|
91
|
+
return;
|
|
92
|
+
const g = gesture.current;
|
|
93
|
+
g.pointers.set(event.pointerId, event.clientX);
|
|
94
|
+
if (g.pointers.size === 1) {
|
|
95
|
+
g.fromX = event.clientX;
|
|
96
|
+
g.fromStart = start;
|
|
97
|
+
g.dragged = false;
|
|
98
|
+
g.pinch = null;
|
|
99
|
+
}
|
|
100
|
+
else if (g.pointers.size === 2) {
|
|
101
|
+
const [a, b] = [...g.pointers.values()];
|
|
102
|
+
g.pinch = { distance: Math.max(1, Math.abs(a - b)), pps };
|
|
103
|
+
g.dragged = true;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const onPointerMove = (event) => {
|
|
107
|
+
const g = gesture.current;
|
|
108
|
+
if (!g.pointers.has(event.pointerId))
|
|
109
|
+
return;
|
|
110
|
+
g.pointers.set(event.pointerId, event.clientX);
|
|
111
|
+
const left = event.currentTarget.getBoundingClientRect().left;
|
|
112
|
+
if (g.pinch && g.pointers.size >= 2) {
|
|
113
|
+
const [a, b] = [...g.pointers.values()];
|
|
114
|
+
zoomTo(g.pinch.pps * (Math.max(1, Math.abs(a - b)) / g.pinch.distance), (a + b) / 2 - left);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const dx = event.clientX - g.fromX;
|
|
118
|
+
if (!g.dragged && Math.abs(dx) < DRAG_SLOP_PX)
|
|
119
|
+
return;
|
|
120
|
+
if (!g.dragged) {
|
|
121
|
+
g.dragged = true;
|
|
122
|
+
// Captured only once it is a drag: capturing on press would retarget the click of a
|
|
123
|
+
// piece's button to the track, and the ✗ would stop working.
|
|
124
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
125
|
+
}
|
|
126
|
+
const n = now.current;
|
|
127
|
+
setView({ pps: n.pps, start: clampViewStart(g.fromStart - dx / n.pps, n.pps, n.width, n.duration) });
|
|
128
|
+
};
|
|
129
|
+
const onPointerUp = (event) => {
|
|
130
|
+
const g = gesture.current;
|
|
131
|
+
if (!g.pointers.delete(event.pointerId))
|
|
132
|
+
return;
|
|
133
|
+
if (g.pointers.size > 0)
|
|
134
|
+
return;
|
|
135
|
+
g.pinch = null;
|
|
136
|
+
if (g.dragged || event.target.closest('.cbvc-seg'))
|
|
137
|
+
return;
|
|
138
|
+
const n = now.current;
|
|
139
|
+
const x = event.clientX - event.currentTarget.getBoundingClientRect().left;
|
|
140
|
+
onSeek(Math.min(n.duration, snapToFrame(n.start + x / n.pps, fps)));
|
|
141
|
+
};
|
|
142
|
+
// A drag that ended over a piece must not also toggle it.
|
|
143
|
+
const onClickCapture = (event) => {
|
|
144
|
+
if (gesture.current.dragged) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
event.stopPropagation();
|
|
147
|
+
gesture.current.dragged = false;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const x = (seconds) => (seconds - start) * pps;
|
|
151
|
+
const end = start + span;
|
|
152
|
+
// The ruler: a labeled tick per step, and a tick per FRAME once frames are far enough apart.
|
|
153
|
+
const step = rulerStep(pps, fps);
|
|
154
|
+
const labels = [];
|
|
155
|
+
if (pps > 0 && duration > 0) {
|
|
156
|
+
// Counted in frames when the step is, so 0.1 s × 3 lands on frame 3 and not on 0.30000000000000004.
|
|
157
|
+
const stepFrames = Math.round(step.label * fps);
|
|
158
|
+
for (let k = Math.floor(start / step.label); k * step.label <= end + 1e-9; k += 1) {
|
|
159
|
+
const t = step.frames ? frameStart(k * stepFrames, fps) : k * step.label;
|
|
160
|
+
if (t >= start - 1e-9 && t <= duration + 1e-9)
|
|
161
|
+
labels.push(t);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const frameTicks = [];
|
|
165
|
+
if (pps / fps >= FRAME_TICK_MIN_PX) {
|
|
166
|
+
for (let k = frameAt(start, fps); frameStart(k, fps) <= Math.min(end, duration) + 1e-9; k += 1)
|
|
167
|
+
frameTicks.push(k);
|
|
168
|
+
}
|
|
169
|
+
return (_jsxs("div", { ...passThrough, className: 'cbvc-timeline', children: [_jsxs("div", { className: 'cbvc-tools', children: [transport, _jsxs("div", { className: 'cbvc-zoom', children: [_jsx(IconButton, { size: 'sm', label: 'Zoom out', tooltip: 'Zoom out (\u2318 or Ctrl + wheel, or pinch)', disabled: !canZoom || pps <= bounds.fit + 1e-9, onClick: () => zoomBy(0.5), children: _jsx(ZoomOut, { "aria-hidden": 'true' }) }), _jsx(Slider, { className: 'cbvc-zoom-slider', "aria-label": 'Zoom', "aria-valuetext": `${(pps / fps).toFixed(pps / fps < 1 ? 2 : 1)} px per frame`, min: 0, max: 1000, step: 1, disabled: !canZoom, value: Math.round(zoomToSlider(pps, bounds) * 1000), onChange: (value) => {
|
|
170
|
+
const next = sliderToZoom(value / 1000, bounds);
|
|
171
|
+
const x0 = playhead >= start && playhead <= end ? (playhead - start) * pps : width / 2;
|
|
172
|
+
zoomTo(next, x0);
|
|
173
|
+
} }), _jsx(IconButton, { size: 'sm', label: 'Zoom in', tooltip: 'Zoom in \u2014 down to single frames', disabled: !canZoom || pps >= bounds.max - 1e-9, onClick: () => zoomBy(2), children: _jsx(ZoomIn, { "aria-hidden": 'true' }) }), _jsxs(Button, { size: 'sm', variant: 'ghost', tooltip: 'Fit the whole clip in the timeline', disabled: view.pps === null || pps <= bounds.fit + 1e-9, onClick: () => setView({ start: 0, pps: null }), children: [_jsx(Maximize2, { "aria-hidden": 'true', size: 14 }), " Fit"] })] })] }), _jsxs("div", { ref: trackRef, className: 'cbvc-track', "data-px-per-second": pps, "data-view-start": start, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, onClickCapture: onClickCapture, children: [_jsxs("div", { className: 'cbvc-ruler', "aria-hidden": 'true', children: [frameTicks.map((k) => (_jsx("span", { className: 'cbvc-frame-tick', "data-frame": k, style: { left: `${x(frameStart(k, fps))}px` } }, `f${k}`))), labels.map((t) => (_jsx("span", { className: 'cbvc-tick', style: { left: `${x(t)}px` }, children: _jsx("span", { className: 'cbvc-tick-label', children: rulerLabel(t, fps, step.frames, duration) }) }, `l${t}`)))] }), _jsx("ul", { className: 'cbvc-strip', "aria-label": segments.length <= 1
|
|
174
|
+
? 'The whole clip, uncut'
|
|
175
|
+
: `${segments.length} sections; ${segments.filter((segment) => segment.removed).length} marked for removal`, children: segments
|
|
176
|
+
.filter((segment) => segment.end > start && segment.start < end)
|
|
177
|
+
.map((segment) => {
|
|
178
|
+
// Clipped to the view (a pixel past each edge, so no border shows at the cut-off).
|
|
179
|
+
const left = Math.max(-1, x(segment.start));
|
|
180
|
+
const right = Math.min(width + 1, x(segment.end));
|
|
181
|
+
const words = segment.removed
|
|
182
|
+
? `Keep ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)} after all`
|
|
183
|
+
: `Remove ${cutTimecode(segment.start)} to ${cutTimecode(segment.end)}`;
|
|
184
|
+
return (_jsx("li", { className: 'cbvc-seg-item', style: { left: `${left}px`, width: `${Math.max(0, right - left)}px` }, children: _jsx(Tooltip, { content: words, children: _jsxs("button", {
|
|
185
|
+
// guardrails-ignore no-raw-action-button: a segment of the timeline, sized to
|
|
186
|
+
// its own duration — a `Button` carries padding and a min-width, which is
|
|
187
|
+
// exactly wrong for a control whose WIDTH is the information.
|
|
188
|
+
type: 'button', className: cn(RAW_BUTTON, `cbvc-seg${segment.removed ? ' is-removed' : ''}`), "aria-pressed": segment.removed, "aria-label": words, "data-start": segment.start, "data-end": segment.end, onClick: () => onToggle(segment), children: [_jsx(X, { className: 'cbvc-seg-x', "aria-hidden": 'true', size: 12 }), _jsx("span", { className: 'cbvc-seg-label', children: segment.removed ? 'Removed' : cutTimecode(segment.end - segment.start) })] }) }) }, segment.key));
|
|
189
|
+
}) }), playhead >= start - 1e-9 && playhead <= end + 1e-9 ? (_jsx("span", { className: 'cbvc-strip-head', style: { left: `${x(playhead)}px` } })) : null] }), pps > bounds.fit + 1e-9 ? (_jsxs("div", { className: 'cbvc-overview', "aria-hidden": 'true', onClick: (event) => {
|
|
190
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
191
|
+
const t = ((event.clientX - rect.left) / rect.width) * duration;
|
|
192
|
+
setView({ pps, start: clampViewStart(t - span / 2, pps, width, duration) });
|
|
193
|
+
}, children: [segments
|
|
194
|
+
.filter((segment) => segment.removed)
|
|
195
|
+
.map((segment) => (_jsx("span", { className: 'cbvc-overview-cut', style: { left: `${(segment.start / duration) * 100}%`, width: `${((segment.end - segment.start) / duration) * 100}%` } }, segment.key))), _jsx("span", { className: 'cbvc-overview-window', style: { left: `${(start / duration) * 100}%`, width: `${Math.max(0.5, (span / duration) * 100)}%` } }), _jsx("span", { className: 'cbvc-overview-head', style: { left: `${(playhead / duration) * 100}%` } })] })) : null] }));
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=CutTimeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CutTimeline.js","sourceRoot":"","sources":["../../../src/react/media/CutTimeline.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAmC,MAAM,kCAAkC,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,cAAc,EACd,OAAO,EACP,UAAU,EACV,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,sBAAsB,CAAC;AA8B9B,iFAAiF;AACjF,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,uDAAuD;AACvD,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,qEAAqE;AACrE,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAQ5B,MAAM,UAAU,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,WAAW,EAAoB;IAC9H,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAiB,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC/C,4FAA4F;IAC5F,wFAAwF;IACxF,gEAAgE;IAChE,MAAM,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC;IACzD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtE,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE;QACzB,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAClD,OAAO,EAAE,CAAC;QACV,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;QAC7C,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACrC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC;IAE1D,wFAAwF;IACxF,oDAAoD;IACpD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,GAAG,CAAC,OAAO,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAEtD,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE;QACjE,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,OAAO,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACpG,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,yFAAyF;IACzF,MAAM,MAAM,GAAG,CAAC,MAAc,EAAQ,EAAE;QACtC,MAAM,CAAC,GAAG,QAAQ,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;QAC/F,MAAM,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,OAAO,GAAG,CAAC,KAAiB,EAAE,EAAE;YACpC,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;YACtB,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/E,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,qBAAqB,EAAE,CAAC,IAAI,CAAC;gBAC7D,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1D,OAAO;YACT,CAAC;YACD,+FAA+F;YAC/F,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YACzF,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC7G,CAAC,CAAC;QACF,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7D,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,4FAA4F;IAC5F,qFAAqF;IACrF,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI;YAAE,OAAO;QACzD,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;QACjC,IAAI,QAAQ,GAAG,CAAC,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,QAAQ,EAAE,CAAC;YACxD,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,QAAQ,GAAG,QAAQ,GAAG,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACxG,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf;;;OAGG;IACH,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAMzB,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjF,MAAM,aAAa,GAAG,CAAC,KAAyC,EAAQ,EAAE;QACxE,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChE,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;QAC1B,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC1B,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC;YACxB,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC;YACpB,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC;YAClB,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAqB,CAAC;YAC5D,CAAC,CAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;YAC1D,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,KAAyC,EAAQ,EAAE;QACxE,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;QAC1B,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC;YAAE,OAAO;QAC7C,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC,IAAI,CAAC;QAC9D,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAqB,CAAC;YAC5D,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;YAC5F,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY;YAAE,OAAO;QACtD,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YACf,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC;YACjB,oFAAoF;YACpF,6DAA6D;YAC7D,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvG,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,KAAyC,EAAQ,EAAE;QACtE,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;QAC1B,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;YAAE,OAAO;QAChD,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC;YAAE,OAAO;QAChC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;QACf,IAAI,CAAC,CAAC,OAAO,IAAK,KAAK,CAAC,MAAkB,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO;QACxE,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC,IAAI,CAAC;QAC3E,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,0DAA0D;IAC1D,MAAM,cAAc,GAAG,CAAC,KAAuB,EAAQ,EAAE;QACvD,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAC5B,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,KAAK,CAAC,eAAe,EAAE,CAAC;YACxB,OAAO,CAAC,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC;QAClC,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,CAAC,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;IAC/D,MAAM,GAAG,GAAG,KAAK,GAAG,IAAI,CAAC;IAEzB,6FAA6F;IAC7F,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACjC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,GAAG,GAAG,CAAC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAC5B,oGAAoG;QACpG,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;QAChD,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAClF,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YACzE,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,QAAQ,GAAG,IAAI;gBAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,GAAG,GAAG,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACnC,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrH,CAAC;IAED,OAAO,CACL,kBAAS,WAAW,EAAE,SAAS,EAAC,eAAe,aAC7C,eAAK,SAAS,EAAC,YAAY,aACxB,SAAS,EACV,eAAK,SAAS,EAAC,WAAW,aACxB,KAAC,UAAU,IACT,IAAI,EAAC,IAAI,EACT,KAAK,EAAC,UAAU,EAChB,OAAO,EAAC,6CAAwC,EAChD,QAAQ,EAAE,CAAC,OAAO,IAAI,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,EAC9C,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,YAE1B,KAAC,OAAO,mBAAa,MAAM,GAAG,GACnB,EACb,KAAC,MAAM,IACL,SAAS,EAAC,kBAAkB,gBACjB,MAAM,oBACD,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,EAC5E,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,IAAI,EACT,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,OAAO,EAClB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,EACnD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;oCAClB,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;oCAChD,MAAM,EAAE,GAAG,QAAQ,IAAI,KAAK,IAAI,QAAQ,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;oCACvF,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gCACnB,CAAC,GACD,EACF,KAAC,UAAU,IACT,IAAI,EAAC,IAAI,EACT,KAAK,EAAC,SAAS,EACf,OAAO,EAAC,sCAAiC,EACzC,QAAQ,EAAE,CAAC,OAAO,IAAI,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,EAC9C,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,YAExB,KAAC,MAAM,mBAAa,MAAM,GAAG,GAClB,EACb,MAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,OAAO,EACf,OAAO,EAAC,oCAAoC,EAC5C,QAAQ,EAAE,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,EACvD,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,aAE/C,KAAC,SAAS,mBAAa,MAAM,EAAC,IAAI,EAAE,EAAE,GAAI,YACnC,IACL,IACF,EAEN,eACE,GAAG,EAAE,QAAQ,EACb,SAAS,EAAC,YAAY,wBACF,GAAG,qBACN,KAAK,EACtB,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,WAAW,EAC5B,cAAc,EAAE,cAAc,aAE9B,eAAK,SAAS,EAAC,YAAY,iBAAa,MAAM,aAC3C,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACrB,eAAoB,SAAS,EAAC,iBAAiB,gBAAa,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,IAAjG,IAAI,CAAC,EAAE,CAA8F,CACjH,CAAC,EACD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACjB,eAAoB,SAAS,EAAC,WAAW,EAAC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,YACpE,eAAM,SAAS,EAAC,iBAAiB,YAAE,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAQ,IAD3E,IAAI,CAAC,EAAE,CAEX,CACR,CAAC,IACE,EAQN,aACE,SAAS,EAAC,YAAY,gBAEpB,QAAQ,CAAC,MAAM,IAAI,CAAC;4BAClB,CAAC,CAAC,uBAAuB;4BACzB,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,cAAc,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,qBAAqB,YAG9G,QAAQ;6BACN,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,GAAG,KAAK,IAAI,OAAO,CAAC,KAAK,GAAG,GAAG,CAAC;6BAC/D,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;4BACf,mFAAmF;4BACnF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;4BAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;4BAClD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO;gCAC3B,CAAC,CAAC,QAAQ,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY;gCAC/E,CAAC,CAAC,UAAU,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;4BAC1E,OAAO,CACL,aAAsB,SAAS,EAAC,eAAe,EAAC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,YACnH,KAAC,OAAO,IAAC,OAAO,EAAE,KAAK,YACrB;wCACE,8EAA8E;wCAC9E,0EAA0E;wCAC1E,8DAA8D;wCAC9D,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,WAAW,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAC9D,OAAO,CAAC,OAAO,gBACjB,KAAK,gBACL,OAAO,CAAC,KAAK,cACf,OAAO,CAAC,GAAG,EACrB,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,aAEhC,KAAC,CAAC,IAAC,SAAS,EAAC,YAAY,iBAAa,MAAM,EAAC,IAAI,EAAE,EAAE,GAAI,EACzD,eAAM,SAAS,EAAC,gBAAgB,YAC7B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,GAClE,IACA,GACD,IAnBH,OAAO,CAAC,GAAG,CAoBf,CACN,CAAC;wBACJ,CAAC,CAAC,GACD,EACJ,QAAQ,IAAI,KAAK,GAAG,IAAI,IAAI,QAAQ,IAAI,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CACpD,eAAM,SAAS,EAAC,iBAAiB,EAAC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAI,CAC1E,CAAC,CAAC,CAAC,IAAI,IACJ,EAIL,GAAG,GAAG,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CACzB,eACE,SAAS,EAAC,eAAe,iBACb,MAAM,EAClB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBACjB,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC;oBACzD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;oBAChE,OAAO,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;gBAC9E,CAAC,aAEA,QAAQ;yBACN,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;yBACpC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAChB,eAEE,SAAS,EAAC,mBAAmB,EAC7B,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,GAAG,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,GAAG,GAAG,GAAG,EAAE,IAFjH,OAAO,CAAC,GAAG,CAGhB,CACH,CAAC,EACJ,eACE,SAAS,EAAC,sBAAsB,EAChC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,GAAG,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,GACpG,EACF,eAAM,SAAS,EAAC,oBAAoB,EAAC,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC,GAAG,GAAG,GAAG,EAAE,GAAI,IACvF,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -12,7 +12,7 @@ import { type VideoCut } from 'cursedbelt-core/media/video-cuts';
|
|
|
12
12
|
* it uses ... It can then remove the parts of the video I don't want, save a new copy and I
|
|
13
13
|
* can view it when it's done."*
|
|
14
14
|
*
|
|
15
|
-
* So this
|
|
15
|
+
* So this page never touches a byte. It produces a `VideoCut[]`, hands it to `onSubmit`,
|
|
16
16
|
* and closes. Everything about what makes a list valid is `videoCuts.ts`, shared with the
|
|
17
17
|
* server that executes it, so the dialog and the encoder cannot disagree about what was
|
|
18
18
|
* asked for.
|
|
@@ -51,7 +51,28 @@ import { type VideoCut } from 'cursedbelt-core/media/video-cuts';
|
|
|
51
51
|
* ONE `InfoHint` — the fleet's standard ⓘ, which costs a 16px glyph at rest and opens on
|
|
52
52
|
* hover, focus or tap. That is the answer to *"tooltips or an info icon or something we
|
|
53
53
|
* standardize can make it clear without taking up space when out of use"*: the explanation
|
|
54
|
-
* exists, in one predictable place, and pays no permanent rent
|
|
54
|
+
* exists, in one predictable place, and pays no permanent rent on the page.
|
|
55
|
+
*
|
|
56
|
+
* ── A PAGE, with a zoomable timeline, down to the frame (owner, 2026-11-03) ───────────
|
|
57
|
+
* *"a full Trim page with a zoomable timeline, down to frame-by-frame selection."* It was a
|
|
58
|
+
* `size='lg'` modal with one fixed strip, so on a two-hour film a pixel was several seconds
|
|
59
|
+
* and the frame he wanted could not be marked at all. Now it is the shape of an NLE's trim
|
|
60
|
+
* view (YouTube Studio's editor, Resolve's cut page): the picture on top, the transport and a
|
|
61
|
+
* full-width timeline under it, the actions bottom-right.
|
|
62
|
+
* · It COVERS the viewport and has an address of its own when the app gives it one
|
|
63
|
+
* (`MediaGallery`'s `trimming`, `useGalleryDeepLink({ trim: true })`), so Back closes it
|
|
64
|
+
* and a reload keeps it. Underneath it is still the belt's dialog primitive — that is what
|
|
65
|
+
* traps focus, takes Escape and makes the gallery's own keys stand down (`openDialogs`).
|
|
66
|
+
* · The timeline zooms from the whole clip to one frame per 12 px (`CutTimeline`).
|
|
67
|
+
* · `,` and `.` step one frame, every split lands on a frame boundary, and the readout is
|
|
68
|
+
* `hh:mm:ss:ff`. The rate is the catalog's probe, else measured from the clip
|
|
69
|
+
* (`useFrameRate`), and an assumed rate is labeled as one.
|
|
70
|
+
*
|
|
71
|
+
* 🔴 Frame-accurate is a promise only because binary-server KEEPS it: its trim decodes and
|
|
72
|
+
* re-encodes every kept span (`trim=start:end` + `concat` into libx264 — `cutArgs`,
|
|
73
|
+
* `apps/binary-server/src/media/processing/ffmpegJobs.ts`), so a cut lands on the frame it
|
|
74
|
+
* names, not on the keyframe before it. The note under the timeline says so; a stream-copy
|
|
75
|
+
* trim would make that sentence false, and it must change with it.
|
|
55
76
|
*/
|
|
56
77
|
export interface VideoCutEditorProps {
|
|
57
78
|
/**
|
|
@@ -78,21 +99,26 @@ export interface VideoCutEditorProps {
|
|
|
78
99
|
/**
|
|
79
100
|
* The clip's length, when the app already knows it. The element's own metadata still wins
|
|
80
101
|
* once it loads: a duration the catalog rounded to the second would put every mark a
|
|
81
|
-
* fraction out, and the marks are the entire output of this
|
|
102
|
+
* fraction out, and the marks are the entire output of this page.
|
|
82
103
|
*/
|
|
83
104
|
durationSeconds?: number | null;
|
|
105
|
+
/**
|
|
106
|
+
* Frames per second from the catalog's probe, when it has one. Without it the page measures
|
|
107
|
+
* the clip itself (`useFrameRate`) and, until it can, says the rate it draws with is assumed.
|
|
108
|
+
*/
|
|
109
|
+
frameRate?: number | null;
|
|
84
110
|
open?: boolean;
|
|
85
111
|
onClose: () => void;
|
|
86
112
|
/**
|
|
87
|
-
* Send the marks. Resolving CLOSES the
|
|
113
|
+
* Send the marks. Resolving CLOSES the page; rejecting leaves it open with the marks
|
|
88
114
|
* intact, because the alternative is losing several minutes of careful scrubbing to one
|
|
89
115
|
* failed request.
|
|
90
116
|
*/
|
|
91
117
|
onSubmit: (cuts: VideoCut[]) => void | Promise<void>;
|
|
92
118
|
/** Wording for the confirm button — apps queue this differently. */
|
|
93
119
|
submitLabel?: string;
|
|
94
|
-
/** Shown under the
|
|
120
|
+
/** Shown under the timeline: what happens after Send, in the app's own words. */
|
|
95
121
|
note?: React.ReactNode;
|
|
96
122
|
}
|
|
97
|
-
export declare function VideoCutEditor({ src, hlsUrl, posterUrl, resolving, name, durationSeconds, open, onClose, onSubmit, submitLabel, note, ...passThrough }: VideoCutEditorProps): React.JSX.Element;
|
|
123
|
+
export declare function VideoCutEditor({ src, hlsUrl, posterUrl, resolving, name, durationSeconds, frameRate, open, onClose, onSubmit, submitLabel, note, ...passThrough }: VideoCutEditorProps): React.JSX.Element;
|
|
98
124
|
//# sourceMappingURL=VideoCutEditor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoCutEditor.d.ts","sourceRoot":"","sources":["../../../src/react/media/VideoCutEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAC9B,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoCutEditor.d.ts","sourceRoot":"","sources":["../../../src/react/media/VideoCutEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAC9B,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,OAAO,EAYL,KAAK,QAAQ,EACd,MAAM,kCAAkC,CAAC;AAO1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;;OAIG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,MAAM,EACN,SAAS,EACT,SAAiB,EACjB,IAAI,EACJ,eAAe,EACf,SAAS,EACT,IAAW,EACX,OAAO,EACP,QAAQ,EACR,WAAmC,EACnC,IAAI,EACJ,GAAG,WAAW,EACf,EAAE,mBAAmB,qBAmXrB"}
|