@hyperframes/studio 0.8.141 → 0.8.142
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/assets/{hyperframes-player-5Vx2AiUz.js → hyperframes-player-sAsRuV73.js} +1 -1
- package/dist/assets/{index-C0thd4eM.js → index-B-wffzRH.js} +126 -126
- package/dist/assets/{index-BAnh7cSQ.js → index-D3Vij9t1.js} +1 -1
- package/dist/assets/{index-P8fgj_Xa.js → index-D5q2_7Gw.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js +26 -15
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/TimelineToolbar.test.tsx +1 -1
- package/src/player/components/timelineLayout.test.ts +3 -3
- package/src/player/components/timelineZoomInput.test.ts +27 -6
- package/src/player/components/timelineZoomInput.ts +5 -1
- package/src/player/components/useTimelinePlayhead.test.tsx +90 -4
- package/src/player/components/useTimelinePlayhead.ts +18 -3
- package/src/player/components/useTimelineProviderState.tsx +1 -0
- package/src/player/lib/timelineViewportBudgets.test.ts +1 -1
- package/src/player/lib/timelineViewportBudgets.ts +1 -1
- package/src/utils/timelineAssetDrop.ts +1 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.142",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"dockview-react": "8.3.1",
|
|
61
61
|
"gsap": "^3.13.0",
|
|
62
62
|
"mediabunny": "^1.45.3",
|
|
63
|
-
"@hyperframes/
|
|
64
|
-
"@hyperframes/
|
|
65
|
-
"@hyperframes/
|
|
66
|
-
"@hyperframes/
|
|
67
|
-
"@hyperframes/
|
|
68
|
-
"@hyperframes/
|
|
63
|
+
"@hyperframes/player": "0.8.142",
|
|
64
|
+
"@hyperframes/studio-server": "0.8.142",
|
|
65
|
+
"@hyperframes/core": "0.8.142",
|
|
66
|
+
"@hyperframes/shader-transitions": "0.8.142",
|
|
67
|
+
"@hyperframes/sdk": "0.8.142",
|
|
68
|
+
"@hyperframes/parsers": "0.8.142"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"vite": "^6.4.2",
|
|
83
83
|
"vitest": "^4.1.11",
|
|
84
84
|
"zustand": "^5.0.0",
|
|
85
|
-
"@hyperframes/producer": "0.8.
|
|
85
|
+
"@hyperframes/producer": "0.8.142"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"react": "19",
|
|
@@ -271,7 +271,7 @@ describe("TimelineToolbar Fit", () => {
|
|
|
271
271
|
timelineFitPps: 10,
|
|
272
272
|
timelinePps: 10,
|
|
273
273
|
});
|
|
274
|
-
return registerTimelineZoomViewport({ scroll, contentOrigin: 32 });
|
|
274
|
+
return registerTimelineZoomViewport({ scroll, contentOrigin: 32, publishScroll: () => {} });
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
it("moves the slider and its readout with a zoom while it is previewed", () => {
|
|
@@ -27,13 +27,13 @@ import { resolveInsertRow } from "./timelineCollision";
|
|
|
27
27
|
import { getTimelineRenderTimeRange } from "./timelineViewportGeometry";
|
|
28
28
|
|
|
29
29
|
describe("horizontal timeline window", () => {
|
|
30
|
-
it("adds the shared
|
|
30
|
+
it("adds the shared quarter-viewport overscan on each side and clamps to duration", () => {
|
|
31
31
|
expect(getTimelineRenderTimeRange({ scrollLeft: 300, clientWidth: 500 }, 100, 200, 20)).toEqual(
|
|
32
|
-
{ start: 0, end:
|
|
32
|
+
{ start: 0, end: 7.25 },
|
|
33
33
|
);
|
|
34
34
|
expect(
|
|
35
35
|
getTimelineRenderTimeRange({ scrollLeft: 1_900, clientWidth: 500 }, 100, 200, 20),
|
|
36
|
-
).toEqual({ start:
|
|
36
|
+
).toEqual({ start: 15.75, end: 20 });
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it("generates globally aligned ticks directly inside the bounded window", () => {
|
|
@@ -49,6 +49,7 @@ afterEach(() => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
let unregisterViewport = () => {};
|
|
52
|
+
const publishScroll = vi.fn();
|
|
52
53
|
|
|
53
54
|
/** A 1080px timeline viewport with 32px of track headers, holding one scaled row. */
|
|
54
55
|
function viewport(scrollLeft = 0, scrollWidth = 20_000) {
|
|
@@ -60,7 +61,7 @@ function viewport(scrollLeft = 0, scrollWidth = 20_000) {
|
|
|
60
61
|
});
|
|
61
62
|
const row = scroll.appendChild(document.createElement("div"));
|
|
62
63
|
row.setAttribute("data-timeline-zoom-scale", "");
|
|
63
|
-
unregisterViewport = registerTimelineZoomViewport({ scroll, contentOrigin: 32 });
|
|
64
|
+
unregisterViewport = registerTimelineZoomViewport({ scroll, contentOrigin: 32, publishScroll });
|
|
64
65
|
return { scroll, row };
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -120,7 +121,7 @@ describe("requestTimelineZoom", () => {
|
|
|
120
121
|
it("lays a zoom-out about the left edge out at once, before it shows unmounted time", () => {
|
|
121
122
|
usePlayerStore.setState({ duration: 1000 });
|
|
122
123
|
viewport();
|
|
123
|
-
// Mounted to (1080 - 32 +
|
|
124
|
+
// Mounted to (1080 - 32 + 270) / 10 = 131.8 s; at 5 px/s the view reaches 209.6 s.
|
|
124
125
|
requestTimelineZoom(50, { time: 0, x: 32 });
|
|
125
126
|
vi.advanceTimersToNextFrame();
|
|
126
127
|
expect(usePlayerStore.getState().timelinePps).toBe(5);
|
|
@@ -134,14 +135,14 @@ describe("requestTimelineZoom", () => {
|
|
|
134
135
|
timelinePps: 100,
|
|
135
136
|
});
|
|
136
137
|
viewport(5000);
|
|
137
|
-
// Mounted
|
|
138
|
-
requestTimelineZoom(
|
|
138
|
+
// Mounted 46.98..63.18 s; at 70 px/s about 55.24 s the view shows 47.75..62.73 s.
|
|
139
|
+
requestTimelineZoom(700, { time: 55.24, x: 556 });
|
|
139
140
|
vi.advanceTimersToNextFrame();
|
|
140
141
|
expect(usePlayerStore.getState().timelinePps).toBe(100);
|
|
141
142
|
});
|
|
142
143
|
|
|
143
144
|
it("lays out a zoom-out before it shows past the window ruler ticks are drawn in", () => {
|
|
144
|
-
// 50 s of clips in content 1996 s wide: ticks are drawn to
|
|
145
|
+
// 50 s of clips in content 1996 s wide: ticks are drawn to 131.8 s, a view and a quarter in.
|
|
145
146
|
usePlayerStore.setState({ duration: 50 });
|
|
146
147
|
viewport();
|
|
147
148
|
requestTimelineZoom(60, { time: 0, x: 32 });
|
|
@@ -230,7 +231,7 @@ describe("requestTimelineZoom", () => {
|
|
|
230
231
|
const { scroll } = viewport();
|
|
231
232
|
requestTimelineZoom(150);
|
|
232
233
|
unregisterViewport();
|
|
233
|
-
unregisterViewport = registerTimelineZoomViewport({ scroll, contentOrigin: 32 });
|
|
234
|
+
unregisterViewport = registerTimelineZoomViewport({ scroll, contentOrigin: 32, publishScroll });
|
|
234
235
|
await Promise.resolve();
|
|
235
236
|
vi.advanceTimersByTime(200);
|
|
236
237
|
expect(usePlayerStore.getState().timelinePps).toBe(15);
|
|
@@ -240,6 +241,7 @@ describe("requestTimelineZoom", () => {
|
|
|
240
241
|
const unregisterOlder = registerTimelineZoomViewport({
|
|
241
242
|
scroll: document.createElement("div"),
|
|
242
243
|
contentOrigin: 32,
|
|
244
|
+
publishScroll,
|
|
243
245
|
});
|
|
244
246
|
viewport();
|
|
245
247
|
unregisterOlder();
|
|
@@ -258,6 +260,25 @@ describe("requestTimelineZoom", () => {
|
|
|
258
260
|
});
|
|
259
261
|
|
|
260
262
|
describe("zoomTimelineToRange", () => {
|
|
263
|
+
it("tells the timeline where a pan at the laid-out scale scrolled to", () => {
|
|
264
|
+
usePlayerStore.setState({
|
|
265
|
+
duration: 1000,
|
|
266
|
+
zoomMode: "manual",
|
|
267
|
+
manualZoomPercent: 1000,
|
|
268
|
+
timelineFitPps: 10,
|
|
269
|
+
timelinePps: 100,
|
|
270
|
+
});
|
|
271
|
+
const { scroll } = viewport(0);
|
|
272
|
+
const published: number[] = [];
|
|
273
|
+
publishScroll.mockImplementation((el: HTMLDivElement) => published.push(el.scrollLeft));
|
|
274
|
+
void zoomTimelineToRange(60, 70);
|
|
275
|
+
for (let i = 0; i < 40; i++) vi.advanceTimersToNextFrame();
|
|
276
|
+
publishScroll.mockReset();
|
|
277
|
+
expect(usePlayerStore.getState().timelinePps).toBe(100);
|
|
278
|
+
expect(scroll.scrollLeft).toBeGreaterThan(5000);
|
|
279
|
+
expect(published.at(-1)).toBe(scroll.scrollLeft);
|
|
280
|
+
});
|
|
281
|
+
|
|
261
282
|
it("fills the width with the range and puts its start at the left margin", () => {
|
|
262
283
|
viewport();
|
|
263
284
|
void zoomTimelineToRange(40, 90, { smooth: false });
|
|
@@ -21,6 +21,7 @@ export interface TimelineZoomAnchor {
|
|
|
21
21
|
export interface TimelineZoomViewport {
|
|
22
22
|
scroll: HTMLDivElement;
|
|
23
23
|
contentOrigin: number;
|
|
24
|
+
publishScroll: (scroll: HTMLDivElement) => void;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/** How an eased zoom ended: it reached its range, or a person's zoom or the caller stopped it. */
|
|
@@ -209,7 +210,10 @@ function commitPreview() {
|
|
|
209
210
|
);
|
|
210
211
|
}
|
|
211
212
|
preview = null;
|
|
212
|
-
if (Math.abs(view.scroll.scrollLeft - left) >= 0.5)
|
|
213
|
+
if (Math.abs(view.scroll.scrollLeft - left) >= 0.5) {
|
|
214
|
+
view.scroll.scrollLeft = left;
|
|
215
|
+
flushSync(() => view.publishScroll(view.scroll));
|
|
216
|
+
}
|
|
213
217
|
clearScaled(view.scroll);
|
|
214
218
|
emitPreview();
|
|
215
219
|
}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
|
|
3
|
-
import { act, useRef } from "react";
|
|
3
|
+
import { act, useLayoutEffect, useRef } from "react";
|
|
4
4
|
import { createRoot, type Root } from "react-dom/client";
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { liveTime, usePlayerStore, type ZoomMode } from "../store/playerStore";
|
|
7
7
|
import { useTimelinePlayhead } from "./useTimelinePlayhead";
|
|
8
|
+
import { useTimelineScrollViewport } from "./useTimelineScrollViewport";
|
|
9
|
+
import { useTimelineClipRenderWindow } from "./useTimelineClipRenderWindow";
|
|
10
|
+
import { requestTimelineZoom, settleTimelineZoom } from "./timelineZoomInput";
|
|
8
11
|
|
|
9
12
|
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
|
|
10
13
|
|
|
11
14
|
const ORIGIN = 32;
|
|
12
15
|
|
|
13
|
-
function scrollBox(scrollLeft: number) {
|
|
16
|
+
function scrollBox(scrollLeft: number, clientWidth = 800) {
|
|
14
17
|
const el = document.createElement("div");
|
|
15
18
|
let left = scrollLeft;
|
|
16
19
|
Object.defineProperties(el, {
|
|
17
|
-
clientWidth: { value:
|
|
20
|
+
clientWidth: { value: clientWidth },
|
|
18
21
|
scrollWidth: { value: 20_000 },
|
|
19
22
|
scrollLeft: { get: () => left, set: (v: number) => (left = v) },
|
|
20
23
|
});
|
|
@@ -27,9 +30,16 @@ interface HarnessProps {
|
|
|
27
30
|
scroll: HTMLDivElement;
|
|
28
31
|
dragging?: boolean;
|
|
29
32
|
zoomMode?: ZoomMode;
|
|
33
|
+
syncScrollViewport?: (scroll: HTMLDivElement) => void;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
function Harness({
|
|
36
|
+
function Harness({
|
|
37
|
+
pps: fixedPps,
|
|
38
|
+
scroll,
|
|
39
|
+
dragging = false,
|
|
40
|
+
zoomMode = "manual",
|
|
41
|
+
syncScrollViewport = () => {},
|
|
42
|
+
}: HarnessProps) {
|
|
33
43
|
const storePps = usePlayerStore((s) => s.timelinePps);
|
|
34
44
|
const pps = fixedPps ?? storePps;
|
|
35
45
|
const scrollRef = useRef(scroll);
|
|
@@ -37,6 +47,7 @@ function Harness({ pps: fixedPps, scroll, dragging = false, zoomMode = "manual"
|
|
|
37
47
|
useTimelinePlayhead({
|
|
38
48
|
playheadRef: { current: document.createElement("div") },
|
|
39
49
|
scrollRef,
|
|
50
|
+
syncScrollViewport,
|
|
40
51
|
ppsRef: { current: pps },
|
|
41
52
|
durationRef,
|
|
42
53
|
isDragging: { current: dragging },
|
|
@@ -238,6 +249,81 @@ describe("useTimelinePlayhead follow while paused", () => {
|
|
|
238
249
|
});
|
|
239
250
|
});
|
|
240
251
|
|
|
252
|
+
describe("useTimelinePlayhead committed viewport", () => {
|
|
253
|
+
function mountViewport(scrollLeft: number) {
|
|
254
|
+
vi.stubGlobal(
|
|
255
|
+
"ResizeObserver",
|
|
256
|
+
class {
|
|
257
|
+
observe() {}
|
|
258
|
+
disconnect() {}
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
usePlayerStore.setState({
|
|
262
|
+
zoomMode: "manual",
|
|
263
|
+
manualZoomPercent: 1000,
|
|
264
|
+
timelineFitPps: 10,
|
|
265
|
+
timelinePps: 100,
|
|
266
|
+
});
|
|
267
|
+
const scroll = scrollBox(scrollLeft, 1080);
|
|
268
|
+
const container = document.createElement("div");
|
|
269
|
+
function Probe() {
|
|
270
|
+
const pps = usePlayerStore((s) => s.timelinePps);
|
|
271
|
+
const { viewport, setScrollRef, syncScrollViewport } = useTimelineScrollViewport(
|
|
272
|
+
useRef(scroll),
|
|
273
|
+
[],
|
|
274
|
+
);
|
|
275
|
+
useLayoutEffect(() => setScrollRef(scroll), [setScrollRef]);
|
|
276
|
+
const { renderTimeRange } = useTimelineClipRenderWindow({
|
|
277
|
+
tracks: [],
|
|
278
|
+
viewport,
|
|
279
|
+
pixelsPerSecond: pps,
|
|
280
|
+
contentOrigin: ORIGIN,
|
|
281
|
+
duration: 100,
|
|
282
|
+
});
|
|
283
|
+
return (
|
|
284
|
+
<>
|
|
285
|
+
<Harness scroll={scroll} syncScrollViewport={syncScrollViewport} />
|
|
286
|
+
{renderTimeRange.start <= 59 && renderTimeRange.end >= 59 && <span data-clip="59" />}
|
|
287
|
+
<output>{viewport.scrollLeft}</output>
|
|
288
|
+
</>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const root = createRoot(container);
|
|
292
|
+
roots.push(root);
|
|
293
|
+
act(() => root.render(<Probe />));
|
|
294
|
+
return { scroll, container };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
298
|
+
|
|
299
|
+
it("keeps the visible 59-second clip mounted when a pointer zoom commits", () => {
|
|
300
|
+
const { scroll, container } = mountViewport(5000);
|
|
301
|
+
expect(container.querySelector('[data-clip="59"]')).not.toBeNull();
|
|
302
|
+
act(() => {
|
|
303
|
+
requestTimelineZoom(1090, { time: 55.24, x: 556 });
|
|
304
|
+
settleTimelineZoom();
|
|
305
|
+
});
|
|
306
|
+
expect(scroll.scrollLeft).toBeCloseTo(5497.16);
|
|
307
|
+
expect(container.querySelector('[data-clip="59"]')).not.toBeNull();
|
|
308
|
+
expect(Number(container.querySelector("output")?.textContent)).toBeCloseTo(5497.16);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("renders a pan at the laid-out scale before the browser paints", () => {
|
|
312
|
+
const { scroll, container } = mountViewport(0);
|
|
313
|
+
const actEnvironment = globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean };
|
|
314
|
+
const wasActEnvironment = actEnvironment.IS_REACT_ACT_ENVIRONMENT;
|
|
315
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = false;
|
|
316
|
+
try {
|
|
317
|
+
requestTimelineZoom(1000, { time: 60, x: ORIGIN });
|
|
318
|
+
settleTimelineZoom();
|
|
319
|
+
expect(scroll.scrollLeft).toBe(6000);
|
|
320
|
+
expect(container.querySelector("output")?.textContent).toBe("6000");
|
|
321
|
+
} finally {
|
|
322
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = wasActEnvironment;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
241
327
|
describe("useTimelinePlayhead wheel zoom", () => {
|
|
242
328
|
beforeEach(() => {
|
|
243
329
|
vi.useFakeTimers({
|
|
@@ -42,6 +42,7 @@ function revealPlayheadScrollLeft(
|
|
|
42
42
|
interface UseTimelinePlayheadInput {
|
|
43
43
|
playheadRef: React.RefObject<HTMLDivElement | null>;
|
|
44
44
|
scrollRef: React.RefObject<HTMLDivElement | null>;
|
|
45
|
+
syncScrollViewport: (scroll: HTMLDivElement) => void;
|
|
45
46
|
ppsRef: React.RefObject<number>;
|
|
46
47
|
durationRef: React.RefObject<number>;
|
|
47
48
|
isDragging: React.RefObject<boolean>;
|
|
@@ -61,6 +62,7 @@ interface UseTimelinePlayheadInput {
|
|
|
61
62
|
export function useTimelinePlayhead({
|
|
62
63
|
playheadRef,
|
|
63
64
|
scrollRef,
|
|
65
|
+
syncScrollViewport,
|
|
64
66
|
ppsRef,
|
|
65
67
|
durationRef,
|
|
66
68
|
isDragging,
|
|
@@ -100,6 +102,7 @@ export function useTimelinePlayhead({
|
|
|
100
102
|
const maxScrollLeft = Math.max(0, scroll.scrollWidth - scroll.clientWidth);
|
|
101
103
|
const left = anchor.time * pps + contentOrigin - anchor.x;
|
|
102
104
|
scroll.scrollLeft = Math.max(0, Math.min(maxScrollLeft, left));
|
|
105
|
+
syncScrollViewport(scroll);
|
|
103
106
|
return;
|
|
104
107
|
}
|
|
105
108
|
const zoomed = userZoomCount !== prevZoomCount;
|
|
@@ -121,7 +124,8 @@ export function useTimelinePlayhead({
|
|
|
121
124
|
scroll.scrollLeft = zoomed
|
|
122
125
|
? revealPlayheadScrollLeft(scroll, contentOrigin + time * pps, contentOrigin, anchored)
|
|
123
126
|
: anchored;
|
|
124
|
-
|
|
127
|
+
syncScrollViewport(scroll);
|
|
128
|
+
}, [pps, userZoomCount, scrollRef, durationRef, contentOrigin, syncScrollViewport]);
|
|
125
129
|
|
|
126
130
|
const syncPlayheadPosition = useCallback(
|
|
127
131
|
(time: number) => {
|
|
@@ -279,14 +283,25 @@ export function useTimelinePlayhead({
|
|
|
279
283
|
// A press meets the zoom it sees, not the one still waiting to be laid out.
|
|
280
284
|
scroll.addEventListener("pointerdown", settleTimelineZoom, { capture: true });
|
|
281
285
|
scroll.addEventListener("scroll", redrawTimelineZoomPreview, { passive: true });
|
|
282
|
-
const unregisterZoomViewport = registerTimelineZoomViewport({
|
|
286
|
+
const unregisterZoomViewport = registerTimelineZoomViewport({
|
|
287
|
+
scroll,
|
|
288
|
+
contentOrigin,
|
|
289
|
+
publishScroll: syncScrollViewport,
|
|
290
|
+
});
|
|
283
291
|
return () => {
|
|
284
292
|
scroll.removeEventListener("wheel", handlePinchWheel, { capture: true });
|
|
285
293
|
scroll.removeEventListener("pointerdown", settleTimelineZoom, { capture: true });
|
|
286
294
|
scroll.removeEventListener("scroll", redrawTimelineZoomPreview);
|
|
287
295
|
unregisterZoomViewport();
|
|
288
296
|
};
|
|
289
|
-
}, [
|
|
297
|
+
}, [
|
|
298
|
+
handlePinchWheel,
|
|
299
|
+
scrollRef,
|
|
300
|
+
timelineReady,
|
|
301
|
+
elementsLength,
|
|
302
|
+
contentOrigin,
|
|
303
|
+
syncScrollViewport,
|
|
304
|
+
]);
|
|
290
305
|
|
|
291
306
|
return { seekFromX, autoScrollDuringDrag, dragScrollRaf };
|
|
292
307
|
}
|
|
@@ -9,7 +9,7 @@ describe("timeline viewport budgets", () => {
|
|
|
9
9
|
expect(TIMELINE_VIEWPORT_BUDGETS).toMatchObject({
|
|
10
10
|
directScrollSafetyPx: 8_000_000,
|
|
11
11
|
rowOverscanPerSide: 2,
|
|
12
|
-
timeOverscanViewportRatio: 0.
|
|
12
|
+
timeOverscanViewportRatio: 0.25,
|
|
13
13
|
maxMountedRows: 64,
|
|
14
14
|
maxMountedClipRoots: 512,
|
|
15
15
|
maxMountedClipRootsPerRow: 128,
|
|
@@ -61,7 +61,7 @@ export const MAX_VISIBLE_THUMBNAIL_FRAMES = Math.ceil(3840 / (66 * (16 / 9))); /
|
|
|
61
61
|
export const TIMELINE_VIEWPORT_BUDGETS: Readonly<TimelineViewportBudgets> = Object.freeze({
|
|
62
62
|
directScrollSafetyPx: 8_000_000,
|
|
63
63
|
rowOverscanPerSide: 2,
|
|
64
|
-
timeOverscanViewportRatio: 0.
|
|
64
|
+
timeOverscanViewportRatio: 0.25,
|
|
65
65
|
maxMountedRows: 64,
|
|
66
66
|
maxMountedClipRoots: 512,
|
|
67
67
|
maxMountedClipRootsPerRow: 128,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { buildTimelineAssetId } from "@hyperframes/core/timeline-asset-id";
|
|
1
2
|
import { AUDIO_EXT, IMAGE_EXT, VIDEO_EXT } from "./mediaTypes";
|
|
2
3
|
import { roundToCenti } from "./rounding";
|
|
3
4
|
import { COMPOSITION_ROOT_OPEN_TAG_RE } from "./compositionPatterns";
|
|
@@ -16,21 +17,6 @@ export function getTimelineAssetKind(assetPath: string): TimelineAssetKind | nul
|
|
|
16
17
|
return null;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
export function buildTimelineAssetId(assetPath: string, existingIds: Iterable<string>): string {
|
|
20
|
-
const baseName = assetPath.split("/").pop() ?? "asset";
|
|
21
|
-
const normalized = baseName
|
|
22
|
-
.replace(/\.[^.]+$/, "")
|
|
23
|
-
.replace(/[^a-zA-Z0-9_-]+/g, "_")
|
|
24
|
-
.replace(/^_+|_+$/g, "")
|
|
25
|
-
.toLowerCase();
|
|
26
|
-
const baseId = normalized || "asset";
|
|
27
|
-
const ids = new Set(existingIds);
|
|
28
|
-
if (!ids.has(baseId)) return baseId;
|
|
29
|
-
let suffix = 2;
|
|
30
|
-
while (ids.has(`${baseId}_${suffix}`)) suffix += 1;
|
|
31
|
-
return `${baseId}_${suffix}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
20
|
export function resolveTimelineAssetSrc(targetPath: string, assetPath: string): string {
|
|
35
21
|
const targetDir = targetPath.includes("/")
|
|
36
22
|
? targetPath.slice(0, targetPath.lastIndexOf("/"))
|