@hyperframes/studio 0.7.86 → 0.7.87
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-BL3qfGEQ.js → hyperframes-player-BFqB5AWJ.js} +1 -1
- package/dist/assets/{index-B5QGai9B.js → index-B1Vf4vCl.js} +1 -1
- package/dist/assets/index-BXkzp_OU.css +1 -0
- package/dist/assets/{index-CISVI7UC.js → index-BblzZ6Av.js} +221 -221
- package/dist/assets/{index-C0Vr8jjc.js → index-D_JU4EeH.js} +1 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.html +2 -2
- package/dist/index.js +6265 -4922
- package/dist/index.js.map +1 -1
- package/package.json +11 -8
- package/src/components/TimelineToolbar.tsx +14 -5
- package/src/components/nle/NLEContext.tsx +4 -1
- package/src/components/nle/TimelinePane.tsx +2 -0
- package/src/hooks/studioTestMode.test.ts +35 -0
- package/src/hooks/studioTestMode.ts +23 -0
- package/src/hooks/useGestureRecording.test.tsx +120 -0
- package/src/hooks/useGestureRecording.ts +49 -40
- package/src/hooks/useStudioTestHooks.test.tsx +161 -0
- package/src/hooks/useStudioTestHooks.ts +54 -7
- package/src/player/components/BeatStrip.tsx +21 -5
- package/src/player/components/CompositionThumbnail.test.ts +96 -2
- package/src/player/components/CompositionThumbnail.tsx +29 -16
- package/src/player/components/Timeline.test.ts +104 -0
- package/src/player/components/Timeline.tsx +141 -131
- package/src/player/components/Timeline.virtualization.test.tsx +400 -0
- package/src/player/components/TimelineCanvas.tsx +18 -10
- package/src/player/components/TimelineClip.tsx +4 -0
- package/src/player/components/TimelineClipDiamonds.test.tsx +85 -5
- package/src/player/components/TimelineClipDiamonds.tsx +53 -13
- package/src/player/components/TimelineLanes.test.tsx +15 -4
- package/src/player/components/TimelineLanes.tsx +81 -26
- package/src/player/components/TimelinePropertyLanes.test.tsx +16 -20
- package/src/player/components/TimelinePropertyLanes.tsx +1 -0
- package/src/player/components/TimelineRuler.tsx +21 -5
- package/src/player/components/TimelineTrackRow.tsx +46 -0
- package/src/player/components/TimelineTypes.ts +2 -0
- package/src/player/components/timelineClipDragGestureLifecycle.test.ts +97 -0
- package/src/player/components/timelineClipDragGestureLifecycle.ts +261 -0
- package/src/player/components/timelineDiamondTypes.ts +5 -0
- package/src/player/components/timelineDragDrop.ts +5 -5
- package/src/player/components/timelineEditing.test.ts +32 -0
- package/src/player/components/timelineEditing.ts +47 -5
- package/src/player/components/timelineFocusIdentity.test.ts +21 -0
- package/src/player/components/timelineFocusIdentity.ts +21 -0
- package/src/player/components/timelineLaneProps.ts +9 -0
- package/src/player/components/timelineLayout.test.ts +59 -0
- package/src/player/components/timelineLayout.ts +202 -176
- package/src/player/components/timelineRowVirtualizationFlag.test.ts +23 -0
- package/src/player/components/timelineRowVirtualizationFlag.ts +10 -0
- package/src/player/components/timelineRulerGeometry.ts +142 -0
- package/src/player/components/timelineViewModel.ts +44 -0
- package/src/player/components/timelineViewportGeometry.ts +51 -0
- package/src/player/components/timelineZoom.test.ts +42 -28
- package/src/player/components/timelineZoom.ts +42 -25
- package/src/player/components/useTimelineActiveClips.test.ts +82 -79
- package/src/player/components/useTimelineActiveClips.ts +49 -55
- package/src/player/components/useTimelineClipDrag.ts +40 -230
- package/src/player/components/useTimelineClipRenderWindow.ts +95 -0
- package/src/player/components/useTimelineGeometry.ts +4 -11
- package/src/player/components/useTimelinePlayhead.ts +30 -14
- package/src/player/components/useTimelineRangeSelection.ts +5 -4
- package/src/player/components/useTimelineRangeSelectionScrub.test.tsx +3 -2
- package/src/player/components/useTimelineRevealClip.test.tsx +139 -0
- package/src/player/components/useTimelineRevealClip.ts +171 -47
- package/src/player/components/useTimelineRowVirtualization.ts +140 -0
- package/src/player/components/useTimelineScrollViewport.test.tsx +202 -0
- package/src/player/components/useTimelineScrollViewport.ts +87 -9
- package/src/player/components/useTimelineSelectionLifecycle.ts +29 -0
- package/src/player/components/useTimelineShiftModifier.ts +20 -0
- package/src/player/components/useTimelineTicks.ts +22 -0
- package/src/player/components/useTimelineTrackLayout.test.ts +32 -26
- package/src/player/components/useTimelineTrackLayout.ts +35 -18
- package/src/player/components/useTimelineVirtualRows.test.tsx +174 -0
- package/src/player/components/useTimelineVirtualRows.ts +126 -0
- package/src/player/hooks/timelinePlayerSync.ts +20 -0
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +90 -51
- package/src/player/hooks/useTimelinePlayer.ts +8 -20
- package/src/player/lib/playbackScrub.ts +2 -2
- package/src/player/lib/timelineClipIndex.test.ts +169 -0
- package/src/player/lib/timelineClipIndex.ts +208 -0
- package/src/player/lib/timelineElementIndexes.test.ts +31 -0
- package/src/player/lib/timelineElementIndexes.ts +44 -0
- package/src/player/lib/timelinePerformanceDiagnostics.test.ts +100 -0
- package/src/player/lib/timelinePerformanceDiagnostics.ts +120 -0
- package/src/player/lib/timelinePerformanceFixture.ts +167 -0
- package/src/player/lib/timelineViewportBudgets.test.ts +60 -0
- package/src/player/lib/timelineViewportBudgets.ts +151 -0
- package/src/player/store/playerStore.test.ts +20 -3
- package/src/player/store/playerStore.ts +73 -45
- package/src/utils/studioHelpers.test.ts +48 -1
- package/src/utils/studioHelpers.ts +31 -34
- package/src/utils/studioUiPreferences.ts +3 -1
- package/dist/assets/index-D379YOKT.css +0 -1
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { useRef, useMemo, useCallback, useState,
|
|
1
|
+
import { useRef, useMemo, useCallback, useState, memo } from "react";
|
|
2
2
|
import { useMusicBeatAnalysis } from "../../hooks/useMusicBeatAnalysis";
|
|
3
|
-
import { isMusicTrack } from "../../utils/timelineInspector";
|
|
4
3
|
import { remapBeatAnalysisToComposition } from "../../utils/beatEditActions";
|
|
5
4
|
import { usePlayerStore, type TimelineElement } from "../store/playerStore";
|
|
6
5
|
import { useExpandedTimelineElements } from "../hooks/useExpandedTimelineElements";
|
|
7
|
-
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
8
6
|
import { defaultTimelineTheme } from "./timelineTheme";
|
|
9
7
|
import { useTimelineRangeSelection } from "./useTimelineRangeSelection";
|
|
10
8
|
import { useTimelinePlayhead } from "./useTimelinePlayhead";
|
|
11
|
-
import { useTimelineActiveClips } from "./useTimelineActiveClips";
|
|
12
9
|
import { useTimelineZoom } from "./useTimelineZoom";
|
|
13
10
|
import { useTimelineAssetDrop } from "./timelineDragDrop";
|
|
14
11
|
import { TimelineEmptyState } from "./TimelineEmptyState";
|
|
@@ -16,14 +13,12 @@ import { TimelineCanvas } from "./TimelineCanvas";
|
|
|
16
13
|
import { type KeyframeDiamondContextMenuState } from "./KeyframeDiamondContextMenu";
|
|
17
14
|
import { useTimelineClipDrag } from "./useTimelineClipDrag";
|
|
18
15
|
import { TimelineOverlays } from "./TimelineOverlays";
|
|
19
|
-
import { animationContributesLane } from "./TimelinePropertyLanes";
|
|
20
16
|
import { useTimelineEditPinning } from "./useTimelineEditPinning";
|
|
21
17
|
import { useTimelineStackingSync } from "./useTimelineStackingSync";
|
|
22
18
|
import { useTimelineGeometry } from "./useTimelineGeometry";
|
|
23
19
|
import { useAutoExpandKeyframedClips } from "./useAutoExpandKeyframedClips";
|
|
24
|
-
import { GUTTER, LABEL_COL_W, TRACKS_LEFT_PAD
|
|
20
|
+
import { GUTTER, LABEL_COL_W, TRACKS_LEFT_PAD } from "./timelineLayout";
|
|
25
21
|
import { useTimelineScrollViewport } from "./useTimelineScrollViewport";
|
|
26
|
-
import { STUDIO_PREVIEW_FPS } from "../lib/time";
|
|
27
22
|
import { useResolvedTimelineEditCallbacks } from "./useResolvedTimelineEditCallbacks";
|
|
28
23
|
import type { TimelineProps } from "./TimelineTypes";
|
|
29
24
|
import {
|
|
@@ -37,14 +32,25 @@ import { useTimelineGapHighlights } from "./useTimelineGapHighlights";
|
|
|
37
32
|
import { useStudioPlaybackContextOptional } from "../../contexts/StudioContext";
|
|
38
33
|
import { TimelineRazorGuide, useTimelineRazorInteraction } from "./TimelineRazorInteraction";
|
|
39
34
|
import { useTimelinePerformanceTelemetry } from "./useTimelinePerformanceTelemetry";
|
|
35
|
+
import {
|
|
36
|
+
getEffectiveTimelineDuration,
|
|
37
|
+
getTimelinePreviewElement,
|
|
38
|
+
hasKeyframedTimelineClips,
|
|
39
|
+
} from "./timelineViewModel";
|
|
40
|
+
import { useTimelineSelectionLifecycle } from "./useTimelineSelectionLifecycle";
|
|
41
|
+
import { useTimelineShiftModifier } from "./useTimelineShiftModifier";
|
|
42
|
+
import { useTimelineTicks } from "./useTimelineTicks";
|
|
43
|
+
import { getTimelineElementIndexes } from "../lib/timelineElementIndexes";
|
|
44
|
+
import { getTimelineElementIdentity } from "../lib/timelineElementHelpers";
|
|
45
|
+
import { useTimelineRowVirtualization } from "./useTimelineRowVirtualization";
|
|
46
|
+
import { useTimelineClipRenderWindow } from "./useTimelineClipRenderWindow";
|
|
47
|
+
import { useTimelineActiveClips } from "./useTimelineActiveClips";
|
|
40
48
|
|
|
41
|
-
// Re-export pure utilities so existing imports from "./Timeline" still resolve.
|
|
42
49
|
export {
|
|
43
|
-
generateTicks,
|
|
44
|
-
formatTimelineTickLabel,
|
|
45
50
|
shouldAutoScrollTimeline,
|
|
46
51
|
getTimelineScrollLeftForZoomTransition,
|
|
47
52
|
getTimelineScrollLeftForZoomAnchor,
|
|
53
|
+
getTimelinePlaybackFollowScrollLeft,
|
|
48
54
|
getTimelinePlayheadLeft,
|
|
49
55
|
getTimelineCanvasHeight,
|
|
50
56
|
shouldShowTimelineShortcutHint,
|
|
@@ -52,6 +58,12 @@ export {
|
|
|
52
58
|
shouldHandleTimelineDeleteKey,
|
|
53
59
|
getDefaultDroppedTrack,
|
|
54
60
|
} from "./timelineLayout";
|
|
61
|
+
export { formatTimelineTickLabel, generateTicks } from "./timelineRulerGeometry";
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
getTimelineScrollTopForGeometryChange,
|
|
65
|
+
getTimelineVisibleTimeRange,
|
|
66
|
+
} from "./timelineViewportGeometry";
|
|
55
67
|
|
|
56
68
|
export const Timeline = memo(function Timeline({
|
|
57
69
|
onSeek,
|
|
@@ -71,6 +83,7 @@ export const Timeline = memo(function Timeline({
|
|
|
71
83
|
onSplitElement: onSplitElementOverride,
|
|
72
84
|
onSelectElement,
|
|
73
85
|
theme: themeOverrides,
|
|
86
|
+
sessionEpoch = 0,
|
|
74
87
|
}: TimelineProps = {}) {
|
|
75
88
|
const {
|
|
76
89
|
onMoveElement,
|
|
@@ -102,7 +115,7 @@ export const Timeline = memo(function Timeline({
|
|
|
102
115
|
const rawElements = usePlayerStore((s) => s.elements);
|
|
103
116
|
const expandedElements = useExpandedTimelineElements();
|
|
104
117
|
const beatAnalysis = usePlayerStore((s) => s.beatAnalysis);
|
|
105
|
-
const musicElement = usePlayerStore((s) => s.elements.
|
|
118
|
+
const musicElement = usePlayerStore((s) => getTimelineElementIndexes(s.elements).musicElement);
|
|
106
119
|
const beatEdits = usePlayerStore((s) => s.beatEdits);
|
|
107
120
|
const adjustedBeatAnalysis = useMemo(
|
|
108
121
|
() => remapBeatAnalysisToComposition(beatAnalysis, musicElement, beatEdits),
|
|
@@ -113,17 +126,11 @@ export const Timeline = memo(function Timeline({
|
|
|
113
126
|
const timelineReady = usePlayerStore((s) => s.timelineReady);
|
|
114
127
|
const selectedElementId = usePlayerStore((s) => s.selectedElementId);
|
|
115
128
|
const selectedElementIds = usePlayerStore((s) => s.selectedElementIds);
|
|
129
|
+
const clipRevealRequest = usePlayerStore((s) => s.clipRevealRequest);
|
|
130
|
+
const focusedEaseSegment = usePlayerStore((s) => s.focusedEaseSegment);
|
|
116
131
|
const gsapAnimations = usePlayerStore((s) => s.gsapAnimations);
|
|
117
|
-
// Label mode = comp has keyframed clips (not just when expanded): keeps the layer
|
|
118
|
-
// disclosure + property column visible and reserves a GUTTER before 0s (Figma).
|
|
119
132
|
const hasKeyframedClips = useMemo(
|
|
120
|
-
() =>
|
|
121
|
-
Array.from(gsapAnimations.values()).some((list) =>
|
|
122
|
-
// Same lane-contribution predicate the layout uses: real keyframes OR a
|
|
123
|
-
// synthesizable flat tween. Checking animation.keyframes alone left a
|
|
124
|
-
// flat-tween-only comp without its reserved label column.
|
|
125
|
-
list.some((animation) => animationContributesLane(animation)),
|
|
126
|
-
),
|
|
133
|
+
() => hasKeyframedTimelineClips(gsapAnimations),
|
|
127
134
|
[gsapAnimations],
|
|
128
135
|
);
|
|
129
136
|
const labelMode = hasKeyframedClips;
|
|
@@ -135,28 +142,13 @@ export const Timeline = memo(function Timeline({
|
|
|
135
142
|
const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
|
|
136
143
|
const currentTime = usePlayerStore((s) => s.currentTime);
|
|
137
144
|
const { zoomMode, manualZoomPercent, setZoomMode, setManualZoomPercent } = useTimelineZoom();
|
|
138
|
-
|
|
139
145
|
const playheadRef = useRef<HTMLDivElement>(null);
|
|
140
146
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
141
147
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
142
148
|
const activeTool = usePlayerStore((s) => s.activeTool);
|
|
143
149
|
const [hoveredClip, setHoveredClip] = useState<string | null>(null);
|
|
144
150
|
const isDragging = useRef(false);
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
useMountEffect(() => {
|
|
148
|
-
const key = (e: KeyboardEvent) => e.key === "Shift" && setShiftHeld(e.type === "keydown");
|
|
149
|
-
const blur = () => setShiftHeld(false);
|
|
150
|
-
window.addEventListener("keydown", key);
|
|
151
|
-
window.addEventListener("keyup", key);
|
|
152
|
-
window.addEventListener("blur", blur);
|
|
153
|
-
return () => {
|
|
154
|
-
window.removeEventListener("keydown", key);
|
|
155
|
-
window.removeEventListener("keyup", key);
|
|
156
|
-
window.removeEventListener("blur", blur);
|
|
157
|
-
};
|
|
158
|
-
});
|
|
159
|
-
|
|
151
|
+
const shiftHeld = useTimelineShiftModifier();
|
|
160
152
|
const [showPopover, setShowPopover] = useState(false);
|
|
161
153
|
const [kfContextMenu, setKfContextMenu] = useState<KeyframeDiamondContextMenuState | null>(null);
|
|
162
154
|
const [clipContextMenu, setClipContextMenu] = useState<{
|
|
@@ -164,34 +156,37 @@ export const Timeline = memo(function Timeline({
|
|
|
164
156
|
y: number;
|
|
165
157
|
element: TimelineElement;
|
|
166
158
|
} | null>(null);
|
|
167
|
-
|
|
168
159
|
const setContainerRef = useCallback((el: HTMLDivElement | null) => {
|
|
169
160
|
containerRef.current = el;
|
|
170
161
|
}, []);
|
|
171
|
-
|
|
172
|
-
// Last horizontal scroll offset, restored across the post-edit iframe reload (pinned zoom).
|
|
173
162
|
const lastScrollLeftRef = useRef(0);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const result = Math.max(safeDur, ...rawElements.map((el) => el.start + el.duration));
|
|
179
|
-
return Number.isFinite(result) ? result : safeDur;
|
|
180
|
-
}, [rawElements, duration]);
|
|
181
|
-
|
|
163
|
+
const effectiveDuration = useMemo(
|
|
164
|
+
() => getEffectiveTimelineDuration(duration, rawElements),
|
|
165
|
+
[duration, rawElements],
|
|
166
|
+
);
|
|
182
167
|
const keyframeCache = usePlayerStore((s) => s.keyframeCache);
|
|
183
168
|
useAutoExpandKeyframedClips(gsapAnimations);
|
|
184
|
-
const {
|
|
185
|
-
|
|
169
|
+
const {
|
|
170
|
+
tracks,
|
|
171
|
+
trackStyles,
|
|
172
|
+
trackOrder,
|
|
173
|
+
trackOrderRef,
|
|
174
|
+
laneCounts,
|
|
175
|
+
rowGeometry,
|
|
176
|
+
rowGeometryRef,
|
|
177
|
+
} = useTimelineTrackLayout(
|
|
178
|
+
expandedElements,
|
|
179
|
+
gsapAnimations,
|
|
180
|
+
selectedElementId,
|
|
181
|
+
selectedElementIds,
|
|
182
|
+
);
|
|
186
183
|
const expandedElementsRef = useRef(expandedElements);
|
|
187
184
|
expandedElementsRef.current = expandedElements;
|
|
188
|
-
|
|
189
185
|
const ppsRef = useRef(100);
|
|
190
186
|
const durationRef = useRef(effectiveDuration);
|
|
191
187
|
durationRef.current = effectiveDuration;
|
|
192
188
|
// Declared before the fitPps derivation so the edit-pin wrappers can close over it.
|
|
193
189
|
const fitPpsRef = useRef(100);
|
|
194
|
-
|
|
195
190
|
const {
|
|
196
191
|
pinZoomBeforeEdit,
|
|
197
192
|
setRangeSelectionRef,
|
|
@@ -215,11 +210,9 @@ export const Timeline = memo(function Timeline({
|
|
|
215
210
|
onBlockDrop,
|
|
216
211
|
onCompositionDrop,
|
|
217
212
|
});
|
|
218
|
-
|
|
219
213
|
const { readClipZIndex, applyStackingPatches, zSyncEnabled } = useTimelineStackingSync({
|
|
220
214
|
expandedElementsRef,
|
|
221
215
|
});
|
|
222
|
-
|
|
223
216
|
const {
|
|
224
217
|
gapMenuModel,
|
|
225
218
|
gapHighlight,
|
|
@@ -249,7 +242,7 @@ export const Timeline = memo(function Timeline({
|
|
|
249
242
|
ppsRef,
|
|
250
243
|
durationRef,
|
|
251
244
|
trackOrderRef,
|
|
252
|
-
|
|
245
|
+
rowGeometryRef,
|
|
253
246
|
onMoveElement: pinnedOnMoveElement,
|
|
254
247
|
onMoveElements: pinnedOnMoveElements,
|
|
255
248
|
onResizeElement: pinnedOnResizeElement,
|
|
@@ -268,25 +261,41 @@ export const Timeline = memo(function Timeline({
|
|
|
268
261
|
ppsRef,
|
|
269
262
|
durationRef,
|
|
270
263
|
trackOrderRef,
|
|
271
|
-
|
|
264
|
+
rowGeometryRef,
|
|
272
265
|
contentOrigin,
|
|
273
266
|
onFileDrop: pinnedOnFileDrop,
|
|
274
267
|
onAssetDrop: pinnedOnAssetDrop,
|
|
275
268
|
onBlockDrop: pinnedOnBlockDrop,
|
|
276
269
|
onCompositionDrop: pinnedOnCompositionDrop,
|
|
277
270
|
});
|
|
278
|
-
|
|
279
|
-
const displayLayout = useTimelineDisplayLayout(draggedClip, trackOrder, rowHeights);
|
|
271
|
+
const displayLayout = useTimelineDisplayLayout(draggedClip, trackOrder, rowGeometry);
|
|
280
272
|
const { recordTimelineScroll } = useTimelinePerformanceTelemetry({
|
|
281
273
|
totalClipCount: expandedElements.length,
|
|
282
274
|
totalRowCount: displayLayout.displayTrackOrder.length,
|
|
283
275
|
zoomMode,
|
|
284
276
|
});
|
|
285
|
-
const {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
277
|
+
const { viewport, showShortcutHint, setScrollRef, syncScrollViewport } =
|
|
278
|
+
useTimelineScrollViewport(scrollRef, [
|
|
279
|
+
timelineReady,
|
|
280
|
+
expandedElements.length,
|
|
281
|
+
displayLayout.totalH,
|
|
282
|
+
]);
|
|
283
|
+
const rowWindow = useTimelineRowVirtualization({
|
|
284
|
+
scrollRef,
|
|
285
|
+
viewport,
|
|
286
|
+
rowGeometry: displayLayout.rowGeometry,
|
|
287
|
+
sessionEpoch,
|
|
288
|
+
elements: expandedElements,
|
|
289
|
+
selectedElementId,
|
|
290
|
+
revealElementId: clipRevealRequest?.elementId ?? null,
|
|
291
|
+
draggedRowKey: draggedClip?.started ? draggedClip.previewTrack : undefined,
|
|
292
|
+
resizingRowKey: resizingClip?.element.track,
|
|
293
|
+
clipContextMenuRowKey: clipContextMenu?.element.track,
|
|
294
|
+
keyframeContextMenuRowKey: kfContextMenu?.element.track,
|
|
295
|
+
lastScrollLeftRef,
|
|
296
|
+
syncScrollViewport,
|
|
297
|
+
});
|
|
298
|
+
const { enabled: rowVirtualizationActive, virtualRows, focusedElementId } = rowWindow;
|
|
290
299
|
const selectedKeyframes = usePlayerStore((s) => s.selectedKeyframes);
|
|
291
300
|
const toggleSelectedKeyframe = usePlayerStore((s) => s.toggleSelectedKeyframe);
|
|
292
301
|
const { onClickKeyframe, onSelectSegment, onShiftClickKeyframe, onContextMenuKeyframe } =
|
|
@@ -299,39 +308,53 @@ export const Timeline = memo(function Timeline({
|
|
|
299
308
|
setKfContextMenu,
|
|
300
309
|
toggleSelectedKeyframe,
|
|
301
310
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
zoomMode,
|
|
323
|
-
manualZoomPercent,
|
|
324
|
-
ppsRef,
|
|
325
|
-
fitPpsRef,
|
|
326
|
-
draggedClip,
|
|
327
|
-
resizingClip,
|
|
328
|
-
expandedElements,
|
|
329
|
-
isDragging,
|
|
330
|
-
scrollRef,
|
|
331
|
-
lastScrollLeftRef,
|
|
311
|
+
const { pps, fitPps, displayContentWidth, displayDuration, zoomModeRef, manualZoomPercentRef } =
|
|
312
|
+
useTimelineGeometry({
|
|
313
|
+
viewportWidth: viewport.clientWidth,
|
|
314
|
+
effectiveDuration,
|
|
315
|
+
zoomMode,
|
|
316
|
+
manualZoomPercent,
|
|
317
|
+
ppsRef,
|
|
318
|
+
fitPpsRef,
|
|
319
|
+
draggedClip,
|
|
320
|
+
resizingClip,
|
|
321
|
+
expandedElements,
|
|
322
|
+
isDragging,
|
|
323
|
+
scrollRef,
|
|
324
|
+
lastScrollLeftRef,
|
|
325
|
+
contentOrigin,
|
|
326
|
+
});
|
|
327
|
+
const { clipIndex, renderTimeRange, pinnedClipIdentities } = useTimelineClipRenderWindow({
|
|
328
|
+
tracks,
|
|
329
|
+
viewport,
|
|
330
|
+
pixelsPerSecond: pps,
|
|
332
331
|
contentOrigin,
|
|
332
|
+
duration: displayDuration,
|
|
333
|
+
selectedElementId: selectedElementId ?? undefined,
|
|
334
|
+
draggedElementId: draggedClip ? getTimelineElementIdentity(draggedClip.element) : undefined,
|
|
335
|
+
resizingElementId: resizingClip ? getTimelineElementIdentity(resizingClip.element) : undefined,
|
|
336
|
+
revealElementId: clipRevealRequest?.elementId,
|
|
337
|
+
focusedEaseElementId: focusedEaseSegment?.elementId,
|
|
338
|
+
clipContextMenuElementId: clipContextMenu
|
|
339
|
+
? getTimelineElementIdentity(clipContextMenu.element)
|
|
340
|
+
: undefined,
|
|
341
|
+
keyframeContextMenuElementId: kfContextMenu
|
|
342
|
+
? getTimelineElementIdentity(kfContextMenu.element)
|
|
343
|
+
: undefined,
|
|
344
|
+
focusedElementId,
|
|
345
|
+
scrollRef,
|
|
346
|
+
elements: expandedElements,
|
|
347
|
+
rowGeometry: displayLayout.rowGeometry,
|
|
348
|
+
allowHorizontalReveal: zoomMode === "manual",
|
|
349
|
+
rowVirtualizationActive,
|
|
350
|
+
sessionEpoch,
|
|
351
|
+
});
|
|
352
|
+
useTimelineActiveClips({
|
|
353
|
+
scrollRef,
|
|
354
|
+
currentTime,
|
|
355
|
+
clipStateVersion: renderTimeRange,
|
|
356
|
+
elementStateVersion: expandedElements,
|
|
333
357
|
});
|
|
334
|
-
|
|
335
358
|
const laneGapStrips = useTimelineGapHighlights({
|
|
336
359
|
gapHighlight,
|
|
337
360
|
tracks,
|
|
@@ -364,11 +387,6 @@ export const Timeline = memo(function Timeline({
|
|
|
364
387
|
onSeek,
|
|
365
388
|
contentOrigin,
|
|
366
389
|
});
|
|
367
|
-
useTimelineActiveClips({
|
|
368
|
-
scrollRef,
|
|
369
|
-
currentTime,
|
|
370
|
-
clipStateVersion,
|
|
371
|
-
});
|
|
372
390
|
const { razorGuideX, updateRazorGuide, clearRazorGuide, splitAllAtPointer } =
|
|
373
391
|
useTimelineRazorInteraction({
|
|
374
392
|
active: activeTool === "razor",
|
|
@@ -400,47 +418,25 @@ export const Timeline = memo(function Timeline({
|
|
|
400
418
|
setShowPopover,
|
|
401
419
|
elementsRef: expandedElementsRef,
|
|
402
420
|
trackOrderRef,
|
|
403
|
-
|
|
421
|
+
rowGeometryRef,
|
|
404
422
|
onSelectElement,
|
|
405
423
|
contentOrigin,
|
|
406
424
|
});
|
|
407
425
|
setRangeSelectionRef.current = setRangeSelection; // stable ref consumed by useTimelineClipDrag
|
|
408
426
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const prev = prevSelectedRef.current;
|
|
413
|
-
const curr = selectedElementRef.current;
|
|
414
|
-
prevSelectedRef.current = curr;
|
|
415
|
-
if (prev && !curr) {
|
|
416
|
-
setShowPopover(false);
|
|
417
|
-
setRangeSelection(null);
|
|
418
|
-
}
|
|
419
|
-
});
|
|
427
|
+
useTimelineSelectionLifecycle(expandedElements, selectedElementId, setShowPopover, () =>
|
|
428
|
+
setRangeSelection(null),
|
|
429
|
+
);
|
|
420
430
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
431
|
+
const { major, minor, majorTickInterval } = useTimelineTicks(
|
|
432
|
+
displayDuration,
|
|
433
|
+
pps,
|
|
434
|
+
timeDisplayMode,
|
|
435
|
+
rowVirtualizationActive ? renderTimeRange : undefined,
|
|
426
436
|
);
|
|
427
|
-
const majorTickInterval = major.length >= 2 ? major[1] - major[0] : effectiveDuration;
|
|
428
437
|
|
|
429
438
|
const getPreviewElement = useCallback(
|
|
430
|
-
(element: TimelineElement): TimelineElement =>
|
|
431
|
-
if (
|
|
432
|
-
resizingClip &&
|
|
433
|
-
(resizingClip.element.key ?? resizingClip.element.id) === (element.key ?? element.id)
|
|
434
|
-
) {
|
|
435
|
-
return {
|
|
436
|
-
...element,
|
|
437
|
-
start: resizingClip.previewStart,
|
|
438
|
-
duration: resizingClip.previewDuration,
|
|
439
|
-
playbackStart: resizingClip.previewPlaybackStart,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
return element;
|
|
443
|
-
},
|
|
439
|
+
(element: TimelineElement): TimelineElement => getTimelinePreviewElement(element, resizingClip),
|
|
444
440
|
[resizingClip],
|
|
445
441
|
);
|
|
446
442
|
|
|
@@ -460,6 +456,7 @@ export const Timeline = memo(function Timeline({
|
|
|
460
456
|
<div
|
|
461
457
|
ref={setContainerRef}
|
|
462
458
|
aria-label="Timeline"
|
|
459
|
+
data-timeline-element-count={expandedElements.length}
|
|
463
460
|
className={`relative border-t select-none h-full overflow-hidden ${isDragOver ? "ring-1 ring-inset ring-studio-accent/60" : ""} ${activeTool === "razor" ? "cursor-crosshair" : shiftHeld ? "cursor-crosshair" : "cursor-default"}`}
|
|
464
461
|
onMouseMove={updateRazorGuide}
|
|
465
462
|
onMouseLeave={clearRazorGuide}
|
|
@@ -471,12 +468,16 @@ export const Timeline = memo(function Timeline({
|
|
|
471
468
|
>
|
|
472
469
|
<div
|
|
473
470
|
ref={setScrollRef}
|
|
471
|
+
data-timeline-scroll-viewport
|
|
472
|
+
data-timeline-auto-scroll-left-inset={labelMode ? LABEL_COL_W : 0}
|
|
474
473
|
tabIndex={-1}
|
|
475
474
|
className={`${zoomMode === "fit" ? "overflow-x-hidden" : "overflow-x-auto"} overflow-y-auto h-full outline-none`}
|
|
476
475
|
onScroll={(e) => {
|
|
477
476
|
lastScrollLeftRef.current = e.currentTarget.scrollLeft; // restored across post-edit reload
|
|
478
477
|
recordTimelineScroll(e.currentTarget);
|
|
478
|
+
syncScrollViewport(e.currentTarget, true);
|
|
479
479
|
}}
|
|
480
|
+
{...rowWindow.timelineFocusProps}
|
|
480
481
|
onDragOver={handleAssetDragOver}
|
|
481
482
|
onDragLeave={() => clearDropPreview()}
|
|
482
483
|
onDrop={handleAssetDrop}
|
|
@@ -507,6 +508,12 @@ export const Timeline = memo(function Timeline({
|
|
|
507
508
|
theme={theme}
|
|
508
509
|
displayTrackOrder={displayLayout.displayTrackOrder}
|
|
509
510
|
rowHeights={displayLayout.displayRowHeights}
|
|
511
|
+
rowGeometry={displayLayout.rowGeometry}
|
|
512
|
+
virtualRows={virtualRows}
|
|
513
|
+
rowsVirtualized={rowVirtualizationActive}
|
|
514
|
+
clipIndex={clipIndex}
|
|
515
|
+
renderTimeRange={renderTimeRange}
|
|
516
|
+
pinnedClipIdentities={pinnedClipIdentities}
|
|
510
517
|
trackOrder={trackOrder}
|
|
511
518
|
tracks={tracks}
|
|
512
519
|
trackStyles={trackStyles}
|
|
@@ -520,7 +527,10 @@ export const Timeline = memo(function Timeline({
|
|
|
520
527
|
blockedClipRef={blockedClipRef}
|
|
521
528
|
suppressClickRef={suppressClickRef}
|
|
522
529
|
scrollRef={scrollRef}
|
|
523
|
-
|
|
530
|
+
// Windowing drops content to mount a row cheaply; unvirtualized it is pure cost.
|
|
531
|
+
renderClipContent={
|
|
532
|
+
rowVirtualizationActive && viewport.isScrolling ? undefined : renderClipContent
|
|
533
|
+
}
|
|
524
534
|
renderClipOverlay={renderClipOverlay}
|
|
525
535
|
playheadRef={playheadRef}
|
|
526
536
|
onDrillDown={onDrillDown}
|