@hyperframes/studio 0.7.54 → 0.7.56
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/{index-uBY329wb.js → index-BWnOxAiH.js} +1 -1
- package/dist/assets/{index-CMHYjEZ5.js → index-C4csZims.js} +1 -1
- package/dist/assets/index-CmVCjZjp.js +423 -0
- package/dist/assets/index-D-GyYi2d.css +1 -0
- package/dist/{chunk-SOTCF4DF.js → chunk-5QSIMBEJ.js} +2 -1
- package/dist/chunk-5QSIMBEJ.js.map +1 -0
- package/dist/{domEditingLayers-2ECJK24D.js → domEditingLayers-6LQGKPOI.js} +2 -2
- package/dist/index.d.ts +146 -144
- package/dist/index.html +2 -2
- package/dist/index.js +40582 -37803
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +106 -96
- package/src/components/EditorShell.tsx +253 -0
- package/src/components/StudioGlobalDragOverlay.tsx +1 -3
- package/src/components/StudioLeftSidebar.tsx +13 -3
- package/src/components/StudioRightPanel.tsx +8 -3
- package/src/components/TimelineToolbar.test.tsx +2 -2
- package/src/components/TimelineToolbar.tsx +179 -121
- package/src/components/editor/CanvasContextMenu.test.tsx +115 -0
- package/src/components/editor/CanvasContextMenu.tsx +198 -0
- package/src/components/editor/DomEditCropHandles.test.tsx +74 -3
- package/src/components/editor/DomEditCropHandles.tsx +64 -29
- package/src/components/editor/DomEditOverlay.test.ts +245 -279
- package/src/components/editor/DomEditOverlay.tsx +158 -126
- package/src/components/editor/DomEditOverlayHover.test.tsx +90 -0
- package/src/components/editor/DomEditRotateHandle.tsx +38 -18
- package/src/components/editor/DomEditSelectionChrome.test.tsx +80 -0
- package/src/components/editor/DomEditSelectionChrome.tsx +257 -0
- package/src/components/editor/LayersPanel.test.ts +66 -2
- package/src/components/editor/LayersPanel.tsx +43 -1
- package/src/components/editor/OffCanvasIndicators.test.tsx +34 -0
- package/src/components/editor/OffCanvasIndicators.tsx +49 -10
- package/src/components/editor/SnapGuideOverlay.tsx +28 -23
- package/src/components/editor/SnapToolbar.test.tsx +0 -1
- package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +214 -0
- package/src/components/editor/anchoredResizeReleaseShift.test.ts +208 -0
- package/src/components/editor/canvasContextMenuZOrder.test.ts +435 -0
- package/src/components/editor/canvasContextMenuZOrder.ts +352 -0
- package/src/components/editor/domEditNudge.test.ts +80 -0
- package/src/components/editor/domEditNudge.ts +44 -0
- package/src/components/editor/domEditOverlayGeometry.test.ts +167 -1
- package/src/components/editor/domEditOverlayGeometry.ts +280 -20
- package/src/components/editor/domEditOverlayGestures.ts +44 -41
- package/src/components/editor/domEditOverlayStartGesture.ts +62 -2
- package/src/components/editor/domEditResizeLocal.test.ts +131 -0
- package/src/components/editor/domEditResizeLocal.ts +125 -0
- package/src/components/editor/domEditingDom.ts +1 -1
- package/src/components/editor/manualEditsDomPatches.test.ts +47 -0
- package/src/components/editor/manualOffsetDrag.ts +55 -12
- package/src/components/editor/offCanvasIndicatorGeometry.ts +31 -10
- package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +25 -1
- package/src/components/editor/resizeDraft.ts +108 -0
- package/src/components/editor/snapEngine.test.ts +12 -78
- package/src/components/editor/snapEngine.ts +13 -53
- package/src/components/editor/useDomEditNudge.test.tsx +228 -0
- package/src/components/editor/useDomEditNudge.ts +254 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +115 -116
- package/src/components/editor/useDomEditOverlayRects.ts +9 -2
- package/src/components/nle/AssetPreviewOverlay.tsx +147 -0
- package/src/components/nle/NLEContext.test.ts +144 -0
- package/src/components/nle/NLEContext.tsx +328 -0
- package/src/components/nle/NLEPreview.tsx +1 -1
- package/src/components/nle/PreviewOverlays.tsx +240 -0
- package/src/components/nle/PreviewPane.tsx +163 -0
- package/src/components/nle/TimelinePane.test.ts +60 -0
- package/src/components/nle/TimelinePane.tsx +293 -0
- package/src/components/nle/TimelineResizeDivider.tsx +8 -2
- package/src/components/nle/useCompositionStack.test.tsx +44 -0
- package/src/components/nle/useCompositionStack.ts +17 -9
- package/src/components/nle/useTimelineEditCallbacks.ts +217 -0
- package/src/components/sidebar/AssetCard.tsx +326 -0
- package/src/components/sidebar/AssetContextMenu.tsx +15 -30
- package/src/components/sidebar/AssetsTab.test.ts +96 -0
- package/src/components/sidebar/AssetsTab.tsx +71 -206
- package/src/components/sidebar/AudioRow.tsx +39 -2
- package/src/components/sidebar/BlocksTab.tsx +8 -1
- package/src/components/sidebar/LeftSidebar.tsx +4 -1
- package/src/components/sidebar/assetHelpers.ts +29 -0
- package/src/contexts/DomEditContext.tsx +9 -0
- package/src/contexts/StudioContext.tsx +9 -8
- package/src/contexts/TimelineEditContext.tsx +3 -7
- package/src/hooks/deleteSelectedKeyframes.ts +35 -0
- package/src/hooks/domEditCommitTypes.ts +12 -1
- package/src/hooks/domSelectionTestHarness.ts +14 -0
- package/src/hooks/gestureTransaction.test.ts +311 -0
- package/src/hooks/gestureTransaction.ts +199 -0
- package/src/hooks/gsapDragCommit.test.ts +141 -41
- package/src/hooks/gsapDragCommit.ts +64 -74
- package/src/hooks/gsapDragStaticSetHelpers.ts +9 -21
- package/src/hooks/gsapResizeIntercept.test.ts +69 -0
- package/src/hooks/gsapResizeIntercept.ts +38 -10
- package/src/hooks/gsapRuntimeBridge.test.ts +49 -13
- package/src/hooks/gsapRuntimeBridge.ts +4 -4
- package/src/hooks/gsapScriptCommitTypes.ts +16 -5
- package/src/hooks/gsapShared.test.ts +19 -1
- package/src/hooks/gsapShared.ts +14 -0
- package/src/hooks/patchDocumentRootDuration.test.ts +71 -0
- package/src/hooks/timelineEditingGsap.ts +14 -0
- package/src/hooks/timelineEditingHelpers.test.ts +34 -1
- package/src/hooks/timelineEditingHelpers.ts +22 -48
- package/src/hooks/timelineMoveAdapter.test.ts +77 -0
- package/src/hooks/timelineMoveAdapter.ts +37 -0
- package/src/hooks/useAnimatedPropertyCommit.test.tsx +36 -8
- package/src/hooks/useAnimatedPropertyCommit.ts +31 -26
- package/src/hooks/useAppHotkeys.ts +16 -25
- package/src/hooks/useBlockHandlers.ts +55 -27
- package/src/hooks/useContextMenuDismiss.ts +31 -7
- package/src/hooks/useDomEditCommits.test.tsx +154 -1
- package/src/hooks/useDomEditCommits.ts +100 -3
- package/src/hooks/useDomEditPositionPatchCommit.ts +2 -0
- package/src/hooks/useDomEditSession.ts +2 -2
- package/src/hooks/useDomEditTextCommits.test.tsx +135 -0
- package/src/hooks/useDomEditTextCommits.ts +8 -3
- package/src/hooks/useDomEditWiring.ts +7 -9
- package/src/hooks/useDomGeometryCommits.ts +17 -2
- package/src/hooks/useDomSelection.test.ts +2 -36
- package/src/hooks/useDomSelection.ts +81 -51
- package/src/hooks/useDomSelectionSelectionGuards.test.ts +276 -0
- package/src/hooks/useElementLifecycleOps.test.tsx +258 -0
- package/src/hooks/useElementLifecycleOps.ts +40 -71
- package/src/hooks/useEnableKeyframes.test.ts +142 -1
- package/src/hooks/useEnableKeyframes.ts +73 -29
- package/src/hooks/useGestureCommit.test.tsx +130 -0
- package/src/hooks/useGestureCommit.ts +32 -8
- package/src/hooks/useGroupCommits.ts +7 -28
- package/src/hooks/useGsapAwareEditing.test.tsx +239 -0
- package/src/hooks/useGsapAwareEditing.ts +115 -20
- package/src/hooks/useGsapKeyframeOps.test.tsx +59 -0
- package/src/hooks/useGsapKeyframeOps.ts +38 -10
- package/src/hooks/useGsapScriptCommits.test.tsx +40 -1
- package/src/hooks/useGsapScriptCommits.ts +134 -50
- package/src/hooks/useGsapSelectionHandlers.ts +49 -16
- package/src/hooks/useMusicBeatAnalysis.ts +72 -36
- package/src/hooks/usePersistentEditHistory.test.ts +33 -0
- package/src/hooks/usePersistentEditHistory.ts +92 -58
- package/src/hooks/usePreviewPersistence.ts +25 -7
- package/src/hooks/useRazorSplit.history.test.tsx +303 -0
- package/src/hooks/useRazorSplit.test.ts +269 -0
- package/src/hooks/useRazorSplit.testHelpers.ts +64 -0
- package/src/hooks/useRazorSplit.ts +107 -36
- package/src/hooks/useRenderClipContent.ts +24 -6
- package/src/hooks/useStudioContextValue.ts +15 -5
- package/src/hooks/useStudioUrlState.ts +2 -4
- package/src/hooks/useTimelineEditing.test.tsx +20 -29
- package/src/hooks/useTimelineEditingTypes.ts +6 -0
- package/src/hooks/useTimelineGroupEditing.ts +18 -4
- package/src/index.ts +1 -1
- package/src/player/components/ImageThumbnail.test.tsx +173 -0
- package/src/player/components/ImageThumbnail.tsx +160 -0
- package/src/player/components/PlayheadIndicator.tsx +50 -5
- package/src/player/components/Timeline.test.ts +102 -19
- package/src/player/components/Timeline.tsx +194 -194
- package/src/player/components/TimelineCanvas.tsx +207 -521
- package/src/player/components/TimelineClip.tsx +4 -1
- package/src/player/components/TimelineClipDiamonds.tsx +9 -40
- package/src/player/components/TimelineEmptyState.tsx +3 -1
- package/src/player/components/TimelineLanes.tsx +487 -0
- package/src/player/components/TimelineOverlays.tsx +122 -0
- package/src/player/components/VideoThumbnail.test.tsx +152 -0
- package/src/player/components/VideoThumbnail.tsx +28 -7
- package/src/player/components/thumbnailUtils.test.ts +127 -0
- package/src/player/components/thumbnailUtils.ts +54 -0
- package/src/player/components/timelineCallbacks.ts +18 -18
- package/src/player/components/timelineClipChildren.tsx +39 -0
- package/src/player/components/timelineClipDragCommit.test.ts +943 -0
- package/src/player/components/timelineClipDragCommit.ts +401 -0
- package/src/player/components/timelineClipDragPreview.test.ts +144 -0
- package/src/player/components/timelineClipDragPreview.ts +323 -38
- package/src/player/components/timelineClipDragTypes.ts +65 -0
- package/src/player/components/timelineCollision.test.ts +477 -0
- package/src/player/components/timelineCollision.ts +263 -0
- package/src/player/components/timelineDragDrop.ts +69 -60
- package/src/player/components/timelineEditCapabilities.ts +52 -0
- package/src/player/components/timelineEditing.test.ts +1 -1
- package/src/player/components/timelineEditing.ts +76 -59
- package/src/player/components/timelineGroupEditing.test.ts +126 -0
- package/src/player/components/timelineGroupEditing.ts +133 -0
- package/src/player/components/timelineGroupResizeCommit.ts +54 -0
- package/src/player/components/timelineLayout.test.ts +95 -23
- package/src/player/components/timelineLayout.ts +120 -61
- package/src/player/components/timelineMarquee.test.ts +197 -0
- package/src/player/components/timelineMarquee.ts +112 -0
- package/src/player/components/timelineMultiDragPreview.test.ts +127 -0
- package/src/player/components/timelineMultiDragPreview.ts +106 -0
- package/src/player/components/timelineOptimisticRevision.ts +42 -0
- package/src/player/components/timelineSnapping.test.ts +134 -0
- package/src/player/components/timelineSnapping.ts +110 -0
- package/src/player/components/timelineStackingSync.test.ts +244 -0
- package/src/player/components/timelineStackingSync.ts +331 -0
- package/src/player/components/timelineTheme.ts +6 -1
- package/src/player/components/timelineZones.test.ts +425 -0
- package/src/player/components/timelineZones.ts +198 -0
- package/src/player/components/timelineZoom.test.ts +67 -0
- package/src/player/components/timelineZoom.ts +51 -0
- package/src/player/components/useResolvedTimelineEditCallbacks.ts +25 -3
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +241 -0
- package/src/player/components/useTimelineClipDrag.ts +380 -386
- package/src/player/components/useTimelineEditPinning.ts +121 -0
- package/src/player/components/useTimelineGeometry.ts +129 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -16
- package/src/player/components/useTimelineRangeSelection.ts +341 -50
- package/src/player/components/useTimelineStackingSync.test.tsx +67 -0
- package/src/player/components/useTimelineStackingSync.ts +83 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +0 -3
- package/src/player/hooks/usePlaybackKeyboard.ts +5 -0
- package/src/player/hooks/useTimelinePlayer.ts +48 -53
- package/src/player/hooks/useTimelinePlayerLoop.ts +85 -0
- package/src/player/hooks/useTimelineSyncCallbacks.test.ts +219 -0
- package/src/player/hooks/useTimelineSyncCallbacks.ts +104 -4
- package/src/player/index.ts +3 -2
- package/src/player/lib/time.test.ts +19 -19
- package/src/player/lib/time.ts +6 -2
- package/src/player/lib/timelineDOM.test.ts +25 -98
- package/src/player/lib/timelineDOM.ts +12 -81
- package/src/player/lib/timelineElementHelpers.ts +54 -5
- package/src/player/lib/timelineIframeHelpers.ts +2 -0
- package/src/player/store/playerStore.ts +62 -3
- package/src/styles/studio.css +27 -0
- package/src/utils/assetClickBehavior.test.ts +104 -0
- package/src/utils/assetClickBehavior.ts +75 -0
- package/src/utils/assetPreviewStore.ts +33 -0
- package/src/utils/blockInstaller.ts +10 -2
- package/src/utils/canvasNudgeGate.test.ts +31 -0
- package/src/utils/canvasNudgeGate.ts +32 -0
- package/src/utils/editHistory.test.ts +35 -0
- package/src/utils/gsapSoftReload.test.ts +119 -1
- package/src/utils/gsapSoftReload.ts +153 -0
- package/src/utils/mediaTypes.ts +3 -2
- package/src/utils/resizeDebug.ts +55 -0
- package/src/utils/rootDuration.test.ts +90 -1
- package/src/utils/rootDuration.ts +76 -13
- package/src/utils/sdkCutover.ts +4 -0
- package/src/utils/studioFileHistory.ts +37 -7
- package/src/utils/studioHelpers.ts +62 -0
- package/src/utils/studioPreviewHelpers.test.ts +65 -8
- package/src/utils/studioPreviewHelpers.ts +10 -0
- package/src/utils/studioTelemetry.ts +4 -1
- package/src/utils/studioUiPreferences.test.ts +38 -0
- package/src/utils/studioUiPreferences.ts +27 -0
- package/src/utils/studioUrlState.test.ts +0 -1
- package/src/utils/timelineAssetDrop.ts +62 -0
- package/src/utils/timelineDiscovery.ts +0 -17
- package/src/utils/timelineInspector.test.ts +121 -0
- package/src/utils/timelineInspector.ts +32 -1
- package/dist/assets/index-Dq7FEg0K.css +0 -1
- package/dist/assets/index-pRhCpGPz.js +0 -423
- package/dist/chunk-SOTCF4DF.js.map +0 -1
- package/src/components/StudioPreviewArea.tsx +0 -500
- package/src/components/nle/NLELayout.test.ts +0 -12
- package/src/components/nle/NLELayout.tsx +0 -591
- package/src/player/components/TimelineLayerGutter.tsx +0 -61
- package/src/player/components/TimelineSelectionOverlays.tsx +0 -55
- package/src/player/components/timelineMarqueeSelection.test.ts +0 -87
- package/src/player/components/timelineSnapTargets.test.ts +0 -144
- package/src/player/components/timelineSnapTargets.ts +0 -164
- package/src/player/components/useTimelineClipDrag.test.tsx +0 -506
- package/src/player/components/useTimelineClipGroupDrag.ts +0 -417
- package/src/player/components/useTimelineMarqueeSelection.test.tsx +0 -231
- package/src/player/components/useTimelineMarqueeSelection.ts +0 -276
- package/src/utils/timelineDiscovery.test.ts +0 -90
- /package/dist/{domEditingLayers-2ECJK24D.js.map → domEditingLayers-6LQGKPOI.js.map} +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, type RefObject } from "react";
|
|
2
|
+
import { usePlayerStore } from "../store/playerStore";
|
|
3
|
+
import type { TimelineDropCallbacks, TimelineEditCallbacks } from "./timelineCallbacks";
|
|
4
|
+
|
|
5
|
+
interface UseTimelineEditPinningInput {
|
|
6
|
+
ppsRef: RefObject<number>;
|
|
7
|
+
fitPpsRef: RefObject<number>;
|
|
8
|
+
onMoveElement: TimelineEditCallbacks["onMoveElement"];
|
|
9
|
+
onMoveElements: TimelineEditCallbacks["onMoveElements"];
|
|
10
|
+
onResizeElement: TimelineEditCallbacks["onResizeElement"];
|
|
11
|
+
onResizeElements: TimelineEditCallbacks["onResizeElements"];
|
|
12
|
+
onFileDrop: TimelineDropCallbacks["onFileDrop"];
|
|
13
|
+
onAssetDrop: TimelineDropCallbacks["onAssetDrop"];
|
|
14
|
+
onBlockDrop: TimelineDropCallbacks["onBlockDrop"];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Wrap every mutating timeline edit so the zoom pins to the current on-screen
|
|
18
|
+
// scale right before the edit commits — the reload the edit triggers then keeps
|
|
19
|
+
// the current scale instead of rescaling every clip (the blink-fix symptom).
|
|
20
|
+
// Each wrapper forwards its args unchanged and preserves the original's absence
|
|
21
|
+
// (an unset callback stays unset → the timeline's own fallbacks kick in).
|
|
22
|
+
export function useTimelineEditPinning({
|
|
23
|
+
ppsRef,
|
|
24
|
+
fitPpsRef,
|
|
25
|
+
onMoveElement,
|
|
26
|
+
onMoveElements,
|
|
27
|
+
onResizeElement,
|
|
28
|
+
onResizeElements,
|
|
29
|
+
onFileDrop,
|
|
30
|
+
onAssetDrop,
|
|
31
|
+
onBlockDrop,
|
|
32
|
+
}: UseTimelineEditPinningInput) {
|
|
33
|
+
const pinTimelineZoom = usePlayerStore((s) => s.pinTimelineZoom);
|
|
34
|
+
// Pin the timeline zoom to the current on-screen scale on the FIRST edit, so a
|
|
35
|
+
// duration change from that edit (drops/moves/deletes recompute the fit basis)
|
|
36
|
+
// stops rescaling every clip. `pinTimelineZoom` is a no-op once already pinned
|
|
37
|
+
// (or after a manual zoom), so the user's own zoom is never clobbered; Fit
|
|
38
|
+
// re-fits. Reads refs at call time for the latest scale.
|
|
39
|
+
const pinZoomBeforeEdit = useCallback(() => {
|
|
40
|
+
pinTimelineZoom(ppsRef.current, fitPpsRef.current);
|
|
41
|
+
}, [pinTimelineZoom, ppsRef, fitPpsRef]);
|
|
42
|
+
|
|
43
|
+
// Stable ref so useTimelineClipDrag can clear rangeSelection without circular dep
|
|
44
|
+
const setRangeSelectionRef = useRef<((sel: null) => void) | null>(null);
|
|
45
|
+
|
|
46
|
+
const pinnedOnMoveElement = useMemo(
|
|
47
|
+
() =>
|
|
48
|
+
onMoveElement &&
|
|
49
|
+
((...args: Parameters<typeof onMoveElement>) => {
|
|
50
|
+
pinZoomBeforeEdit();
|
|
51
|
+
return onMoveElement(...args);
|
|
52
|
+
}),
|
|
53
|
+
[onMoveElement, pinZoomBeforeEdit],
|
|
54
|
+
);
|
|
55
|
+
const pinnedOnMoveElements = useMemo(
|
|
56
|
+
() =>
|
|
57
|
+
onMoveElements &&
|
|
58
|
+
((...args: Parameters<typeof onMoveElements>) => {
|
|
59
|
+
pinZoomBeforeEdit();
|
|
60
|
+
return onMoveElements(...args);
|
|
61
|
+
}),
|
|
62
|
+
[onMoveElements, pinZoomBeforeEdit],
|
|
63
|
+
);
|
|
64
|
+
const pinnedOnResizeElement = useMemo(
|
|
65
|
+
() =>
|
|
66
|
+
onResizeElement &&
|
|
67
|
+
((...args: Parameters<typeof onResizeElement>) => {
|
|
68
|
+
pinZoomBeforeEdit();
|
|
69
|
+
return onResizeElement(...args);
|
|
70
|
+
}),
|
|
71
|
+
[onResizeElement, pinZoomBeforeEdit],
|
|
72
|
+
);
|
|
73
|
+
const pinnedOnResizeElements = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
onResizeElements &&
|
|
76
|
+
((...args: Parameters<typeof onResizeElements>) => {
|
|
77
|
+
pinZoomBeforeEdit();
|
|
78
|
+
return onResizeElements(...args);
|
|
79
|
+
}),
|
|
80
|
+
[onResizeElements, pinZoomBeforeEdit],
|
|
81
|
+
);
|
|
82
|
+
const pinnedOnFileDrop = useMemo(
|
|
83
|
+
() =>
|
|
84
|
+
onFileDrop &&
|
|
85
|
+
((...args: Parameters<typeof onFileDrop>) => {
|
|
86
|
+
pinZoomBeforeEdit();
|
|
87
|
+
return onFileDrop(...args);
|
|
88
|
+
}),
|
|
89
|
+
[onFileDrop, pinZoomBeforeEdit],
|
|
90
|
+
);
|
|
91
|
+
const pinnedOnAssetDrop = useMemo(
|
|
92
|
+
() =>
|
|
93
|
+
onAssetDrop &&
|
|
94
|
+
((...args: Parameters<typeof onAssetDrop>) => {
|
|
95
|
+
pinZoomBeforeEdit();
|
|
96
|
+
return onAssetDrop(...args);
|
|
97
|
+
}),
|
|
98
|
+
[onAssetDrop, pinZoomBeforeEdit],
|
|
99
|
+
);
|
|
100
|
+
const pinnedOnBlockDrop = useMemo(
|
|
101
|
+
() =>
|
|
102
|
+
onBlockDrop &&
|
|
103
|
+
((...args: Parameters<typeof onBlockDrop>) => {
|
|
104
|
+
pinZoomBeforeEdit();
|
|
105
|
+
return onBlockDrop(...args);
|
|
106
|
+
}),
|
|
107
|
+
[onBlockDrop, pinZoomBeforeEdit],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
pinZoomBeforeEdit,
|
|
112
|
+
setRangeSelectionRef,
|
|
113
|
+
pinnedOnMoveElement,
|
|
114
|
+
pinnedOnMoveElements,
|
|
115
|
+
pinnedOnResizeElement,
|
|
116
|
+
pinnedOnResizeElements,
|
|
117
|
+
pinnedOnFileDrop,
|
|
118
|
+
pinnedOnAssetDrop,
|
|
119
|
+
pinnedOnBlockDrop,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, type RefObject } from "react";
|
|
2
|
+
import { usePlayerStore, type TimelineElement, type ZoomMode } from "../store/playerStore";
|
|
3
|
+
import { getTimelinePixelsPerSecond } from "./timelineZoom";
|
|
4
|
+
import {
|
|
5
|
+
DRAG_EXTEND_MARGIN_PX,
|
|
6
|
+
getTimelineDisplayContentWidth,
|
|
7
|
+
getTimelineFitPps,
|
|
8
|
+
} from "./timelineLayout";
|
|
9
|
+
import type { DraggedClipState, ResizingClipState } from "./useTimelineClipDrag";
|
|
10
|
+
|
|
11
|
+
interface UseTimelineGeometryInput {
|
|
12
|
+
viewportWidth: number;
|
|
13
|
+
effectiveDuration: number;
|
|
14
|
+
zoomMode: ZoomMode;
|
|
15
|
+
manualZoomPercent: number;
|
|
16
|
+
ppsRef: RefObject<number>;
|
|
17
|
+
fitPpsRef: RefObject<number>;
|
|
18
|
+
draggedClip: DraggedClipState | null;
|
|
19
|
+
resizingClip: ResizingClipState | null;
|
|
20
|
+
expandedElements: TimelineElement[];
|
|
21
|
+
isDragging: RefObject<boolean>;
|
|
22
|
+
scrollRef: RefObject<HTMLDivElement | null>;
|
|
23
|
+
lastScrollLeftRef: RefObject<number>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Derive the timeline's horizontal geometry from the viewport, zoom, and any live
|
|
27
|
+
// drag/resize preview: the pixels-per-second scale, the rendered content width
|
|
28
|
+
// (CapCut-style over-extension while dragging/trimming), and the version key that
|
|
29
|
+
// re-triggers dependent effects after an edit re-derives the elements.
|
|
30
|
+
export function useTimelineGeometry({
|
|
31
|
+
viewportWidth,
|
|
32
|
+
effectiveDuration,
|
|
33
|
+
zoomMode,
|
|
34
|
+
manualZoomPercent,
|
|
35
|
+
ppsRef,
|
|
36
|
+
fitPpsRef,
|
|
37
|
+
draggedClip,
|
|
38
|
+
resizingClip,
|
|
39
|
+
expandedElements,
|
|
40
|
+
isDragging,
|
|
41
|
+
scrollRef,
|
|
42
|
+
lastScrollLeftRef,
|
|
43
|
+
}: UseTimelineGeometryInput) {
|
|
44
|
+
// Fit pps maps at least MIN_TIMELINE_EXTENT_S onto the viewport, so short
|
|
45
|
+
// comps show a 60s ruler with usable empty space (see getTimelineFitPps).
|
|
46
|
+
const fitPps = getTimelineFitPps(viewportWidth, effectiveDuration);
|
|
47
|
+
const pps = getTimelinePixelsPerSecond(fitPps, zoomMode, manualZoomPercent);
|
|
48
|
+
ppsRef.current = pps;
|
|
49
|
+
const trackContentWidth = Math.max(0, effectiveDuration * pps);
|
|
50
|
+
// Drag-to-extend: while a clip is dragged, keep the rendered extent a margin
|
|
51
|
+
// past the ghost's end. Holding the pointer in the right edge zone then keeps
|
|
52
|
+
// auto-scroll stepping (scrollWidth grows with the ghost), so the timeline
|
|
53
|
+
// extends at auto-scroll pace — placing a clip farther than the timeline
|
|
54
|
+
// currently shows. Growth is bounded per frame by AUTO_SCROLL_MAX_SPEED (no
|
|
55
|
+
// fling); leaving the edge zone stops it; the extra width collapses when the
|
|
56
|
+
// drag ends (the composition itself only grows on commit, content-driven).
|
|
57
|
+
const dragGhostEndPx = draggedClip?.started
|
|
58
|
+
? (draggedClip.previewStart + draggedClip.element.duration) * pps + DRAG_EXTEND_MARGIN_PX
|
|
59
|
+
: 0;
|
|
60
|
+
// Trim-to-extend: same mechanic for a right-edge RESIZE — the rendered extent
|
|
61
|
+
// tracks the trim preview's end so the edge auto-scroll zone always has room
|
|
62
|
+
// to keep stepping while the trim grows past the current timeline width.
|
|
63
|
+
const resizeGhostEndPx = resizingClip?.started
|
|
64
|
+
? (resizingClip.previewStart + resizingClip.previewDuration) * pps + DRAG_EXTEND_MARGIN_PX
|
|
65
|
+
: 0;
|
|
66
|
+
// The timeline canvas always fills at least the viewport width AND the
|
|
67
|
+
// MIN_TIMELINE_EXTENT_S floor: the ruler + empty track lanes keep going into
|
|
68
|
+
// the space instead of leaving dead black — CapCut-style. Only the RENDERED
|
|
69
|
+
// extent grows; clip positions/durations are untouched.
|
|
70
|
+
const displayContentWidth = getTimelineDisplayContentWidth({
|
|
71
|
+
trackContentWidth,
|
|
72
|
+
viewportWidth,
|
|
73
|
+
pps,
|
|
74
|
+
dragGhostEndPx,
|
|
75
|
+
resizeGhostEndPx,
|
|
76
|
+
});
|
|
77
|
+
const displayDuration = pps > 0 ? displayContentWidth / pps : effectiveDuration;
|
|
78
|
+
const clipStateVersion = useMemo(
|
|
79
|
+
() =>
|
|
80
|
+
expandedElements
|
|
81
|
+
.map((el) => `${el.key ?? el.id}:${el.start}:${el.duration}:${el.track}`)
|
|
82
|
+
.join("|"),
|
|
83
|
+
[expandedElements],
|
|
84
|
+
);
|
|
85
|
+
const zoomModeRef = useRef(zoomMode);
|
|
86
|
+
zoomModeRef.current = zoomMode;
|
|
87
|
+
const manualZoomPercentRef = useRef(manualZoomPercent);
|
|
88
|
+
manualZoomPercentRef.current = manualZoomPercent;
|
|
89
|
+
fitPpsRef.current = fitPps;
|
|
90
|
+
|
|
91
|
+
// Restore the horizontal scroll offset after an edit re-derives the elements
|
|
92
|
+
// (clipStateVersion changes) so the reload doesn't jump the view. Only in manual
|
|
93
|
+
// (pinned) mode — fit mode hides the x-scrollbar (scrollLeft is always 0) — and
|
|
94
|
+
// never mid-drag (auto-scroll owns the offset then). rAF waits for the new layout
|
|
95
|
+
// so the clamp reads the post-resync scrollWidth. zoomMode is a legitimate dep:
|
|
96
|
+
// re-running on a mode flip is a no-op thanks to the guard.
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (zoomMode !== "manual" || isDragging.current) return;
|
|
99
|
+
const el = scrollRef.current;
|
|
100
|
+
const target = lastScrollLeftRef.current;
|
|
101
|
+
if (!el || target <= 0) return;
|
|
102
|
+
const raf = requestAnimationFrame(() => {
|
|
103
|
+
const max = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
104
|
+
const next = Math.min(target, max);
|
|
105
|
+
if (Math.abs(el.scrollLeft - next) > 0.5) el.scrollLeft = next;
|
|
106
|
+
});
|
|
107
|
+
return () => cancelAnimationFrame(raf);
|
|
108
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
109
|
+
}, [clipStateVersion, zoomMode]);
|
|
110
|
+
// Publish the live scale so edit handlers OUTSIDE <Timeline> (the keyboard-delete
|
|
111
|
+
// path) can pin the zoom via pinTimelineZoomToCurrent without threading geometry.
|
|
112
|
+
// In a useEffect (not the render body) so React-18 concurrent replay — Suspense
|
|
113
|
+
// retry, transitions, StrictMode double-invoke — can't double-publish. The write is
|
|
114
|
+
// idempotent (same pps/fitPps → same fields), so this is behavior-preserving; the
|
|
115
|
+
// effect placement is just the strictly-correct shape.
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
usePlayerStore.getState().setTimelineScale(pps, fitPps);
|
|
118
|
+
}, [pps, fitPps]);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
pps,
|
|
122
|
+
fitPps,
|
|
123
|
+
displayContentWidth,
|
|
124
|
+
displayDuration,
|
|
125
|
+
clipStateVersion,
|
|
126
|
+
zoomModeRef,
|
|
127
|
+
manualZoomPercentRef,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef, useCallback, useEffect, useLayoutEffect } from "react";
|
|
2
|
-
import { liveTime,
|
|
2
|
+
import { liveTime, type ZoomMode } from "../store/playerStore";
|
|
3
3
|
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
4
4
|
import { getPinchTimelineZoomPercent } from "./timelineZoom";
|
|
5
5
|
import {
|
|
@@ -117,21 +117,9 @@ export function useTimelinePlayhead({
|
|
|
117
117
|
useMountEffect(() => {
|
|
118
118
|
const unsub = liveTime.subscribe((t) => {
|
|
119
119
|
if (!playheadRef.current || durationRef.current <= 0) return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
scroll &&
|
|
125
|
-
!isDragging.current &&
|
|
126
|
-
usePlayerStore.getState().isPlaying &&
|
|
127
|
-
shouldAutoScrollTimeline(zoomModeRef.current, scroll.scrollWidth, scroll.clientWidth)
|
|
128
|
-
) {
|
|
129
|
-
const edgeMargin = scroll.clientWidth * 0.12;
|
|
130
|
-
if (playheadX > scroll.scrollLeft + scroll.clientWidth - edgeMargin)
|
|
131
|
-
scroll.scrollLeft = playheadX - scroll.clientWidth * 0.15;
|
|
132
|
-
else if (playheadX < scroll.scrollLeft + GUTTER)
|
|
133
|
-
scroll.scrollLeft = Math.max(0, playheadX - GUTTER);
|
|
134
|
-
}
|
|
120
|
+
// Playback deliberately does NOT scroll the viewport to chase the playhead —
|
|
121
|
+
// the user's scroll position is theirs; the playhead may run off-screen.
|
|
122
|
+
playheadRef.current.style.left = `${getTimelinePlayheadLeft(t, ppsRef.current)}px`;
|
|
135
123
|
});
|
|
136
124
|
return unsub;
|
|
137
125
|
});
|