@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
|
@@ -1,591 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useState,
|
|
3
|
-
useCallback,
|
|
4
|
-
useRef,
|
|
5
|
-
useEffect,
|
|
6
|
-
useSyncExternalStore,
|
|
7
|
-
memo,
|
|
8
|
-
type ReactNode,
|
|
9
|
-
} from "react";
|
|
10
|
-
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
11
|
-
import { useTimelinePlayer, PlayerControls, Timeline, usePlayerStore } from "../../player";
|
|
12
|
-
import type { TimelineElement } from "../../player";
|
|
13
|
-
import type { BlockedTimelineEditIntent } from "../../player/components/timelineEditing";
|
|
14
|
-
import { NLEPreview } from "./NLEPreview";
|
|
15
|
-
import { CompositionBreadcrumb } from "./CompositionBreadcrumb";
|
|
16
|
-
import { TimelineResizeDivider, MIN_TIMELINE_H, MIN_PREVIEW_H } from "./TimelineResizeDivider";
|
|
17
|
-
import { usePreviewBlockDrop } from "./usePreviewBlockDrop";
|
|
18
|
-
import { useCompositionStack } from "./useCompositionStack";
|
|
19
|
-
import { useTimelineEditContext } from "../../contexts/TimelineEditContext";
|
|
20
|
-
import { setCompositionSourceMap } from "../editor/domEditingDom";
|
|
21
|
-
import { trackStudioExpandedClipEdit } from "../../telemetry/events";
|
|
22
|
-
import {
|
|
23
|
-
TIMELINE_TOGGLE_SHORTCUT_LABEL,
|
|
24
|
-
getTimelineToggleTitle,
|
|
25
|
-
} from "../../utils/timelineDiscovery";
|
|
26
|
-
import { ensureMotionPathPluginLoaded } from "../../utils/gsapSoftReload";
|
|
27
|
-
import { readStudioUiPreferences, writeStudioUiPreferences } from "../../utils/studioUiPreferences";
|
|
28
|
-
|
|
29
|
-
interface NLELayoutProps {
|
|
30
|
-
projectId: string;
|
|
31
|
-
portrait?: boolean;
|
|
32
|
-
/** Slot for overlays rendered on top of the preview (cursors, highlights, etc.) */
|
|
33
|
-
previewOverlay?: ReactNode;
|
|
34
|
-
/** Slot rendered above the timeline tracks (toolbar with split, delete, zoom) */
|
|
35
|
-
timelineToolbar?: ReactNode;
|
|
36
|
-
/** Slot rendered below the timeline tracks */
|
|
37
|
-
timelineFooter?: ReactNode;
|
|
38
|
-
/** Increment to force the preview to reload (e.g., after file writes) */
|
|
39
|
-
refreshKey?: number;
|
|
40
|
-
/** Navigate to a specific composition path (e.g., "compositions/intro.html") */
|
|
41
|
-
activeCompositionPath?: string | null;
|
|
42
|
-
/** Callback to expose the iframe ref (for element picker, etc.) */
|
|
43
|
-
onIframeRef?: (iframe: HTMLIFrameElement | null) => void;
|
|
44
|
-
/** Callback when the viewed composition changes (drill-down/back) */
|
|
45
|
-
onCompositionChange?: (compositionPath: string | null) => void;
|
|
46
|
-
/** Custom clip content renderer for timeline (thumbnails, waveforms, etc.) */
|
|
47
|
-
renderClipContent?: (
|
|
48
|
-
element: TimelineElement,
|
|
49
|
-
style: { clip: string; label: string },
|
|
50
|
-
) => ReactNode;
|
|
51
|
-
onFileDrop?: (
|
|
52
|
-
files: File[],
|
|
53
|
-
placement?: Pick<TimelineElement, "start" | "track">,
|
|
54
|
-
) => Promise<void> | void;
|
|
55
|
-
onDeleteElement?: (element: TimelineElement) => Promise<void> | void;
|
|
56
|
-
onAssetDrop?: (
|
|
57
|
-
assetPath: string,
|
|
58
|
-
placement: Pick<TimelineElement, "start" | "track">,
|
|
59
|
-
) => Promise<void> | void;
|
|
60
|
-
onBlockDrop?: (
|
|
61
|
-
blockName: string,
|
|
62
|
-
placement: Pick<TimelineElement, "start" | "track">,
|
|
63
|
-
) => Promise<void> | void;
|
|
64
|
-
onPreviewBlockDrop?: (
|
|
65
|
-
blockName: string,
|
|
66
|
-
position: { left: number; top: number },
|
|
67
|
-
) => Promise<void> | void;
|
|
68
|
-
onBlockedEditAttempt?: (element: TimelineElement, intent: BlockedTimelineEditIntent) => void;
|
|
69
|
-
onSelectTimelineElement?: (element: TimelineElement | null) => void;
|
|
70
|
-
/** Exposes the compIdToSrc map for parent components (e.g., useRenderClipContent) */
|
|
71
|
-
onCompIdToSrcChange?: (map: Map<string, string>) => void;
|
|
72
|
-
/** Whether the timeline panel is visible (default: true) */
|
|
73
|
-
timelineVisible?: boolean;
|
|
74
|
-
/** Callback to toggle timeline visibility */
|
|
75
|
-
onToggleTimeline?: () => void;
|
|
76
|
-
/** Notifies parent when composition loading state changes */
|
|
77
|
-
onCompositionLoadingChange?: (loading: boolean) => void;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const DEFAULT_TIMELINE_H = 220;
|
|
81
|
-
|
|
82
|
-
function subscribeFullscreen(cb: () => void) {
|
|
83
|
-
document.addEventListener("fullscreenchange", cb);
|
|
84
|
-
return () => document.removeEventListener("fullscreenchange", cb);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function getFullscreenElement() {
|
|
88
|
-
return document.fullscreenElement;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function shouldDisableTimelineWhileCompositionLoading(compositionLoading: boolean): boolean {
|
|
92
|
-
return compositionLoading;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// fallow-ignore-next-line complexity
|
|
96
|
-
export const NLELayout = memo(function NLELayout({
|
|
97
|
-
projectId,
|
|
98
|
-
portrait,
|
|
99
|
-
previewOverlay,
|
|
100
|
-
timelineToolbar,
|
|
101
|
-
timelineFooter,
|
|
102
|
-
refreshKey,
|
|
103
|
-
activeCompositionPath,
|
|
104
|
-
onIframeRef,
|
|
105
|
-
onCompositionChange,
|
|
106
|
-
renderClipContent,
|
|
107
|
-
onFileDrop,
|
|
108
|
-
onDeleteElement,
|
|
109
|
-
onAssetDrop,
|
|
110
|
-
onBlockDrop,
|
|
111
|
-
onPreviewBlockDrop,
|
|
112
|
-
onBlockedEditAttempt,
|
|
113
|
-
onSelectTimelineElement,
|
|
114
|
-
onCompIdToSrcChange,
|
|
115
|
-
timelineVisible,
|
|
116
|
-
onToggleTimeline,
|
|
117
|
-
onCompositionLoadingChange: onCompositionLoadingChangeParent,
|
|
118
|
-
}: NLELayoutProps) {
|
|
119
|
-
const {
|
|
120
|
-
iframeRef,
|
|
121
|
-
togglePlay,
|
|
122
|
-
seek,
|
|
123
|
-
onIframeLoad: baseOnIframeLoad,
|
|
124
|
-
refreshPlayer,
|
|
125
|
-
} = useTimelinePlayer();
|
|
126
|
-
|
|
127
|
-
// Reset timeline state when the project changes. Done in an effect, not during
|
|
128
|
-
// render: reset() updates the player store, and updating another store/component
|
|
129
|
-
// mid-render triggers React's "Cannot update a component while rendering a
|
|
130
|
-
// different component" warning. The effect runs right after commit, so the new
|
|
131
|
-
// project's first frame may briefly show prior timeline state before it clears.
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
usePlayerStore.getState().reset();
|
|
134
|
-
}, [projectId]);
|
|
135
|
-
|
|
136
|
-
const stageRefForDrop = useRef<HTMLDivElement | null>(null);
|
|
137
|
-
const handleStageRef = useCallback((ref: React.RefObject<HTMLDivElement | null>) => {
|
|
138
|
-
stageRefForDrop.current = ref.current;
|
|
139
|
-
}, []);
|
|
140
|
-
|
|
141
|
-
// Authored composition size measured from the loaded preview — drives drop
|
|
142
|
-
// coordinate mapping so blocks land where the user pointed on any comp size.
|
|
143
|
-
const [previewCompositionSize, setPreviewCompositionSize] = useState<{
|
|
144
|
-
width: number;
|
|
145
|
-
height: number;
|
|
146
|
-
} | null>(null);
|
|
147
|
-
|
|
148
|
-
const {
|
|
149
|
-
isDragOver: previewDragOver,
|
|
150
|
-
handleDragEnter: handlePreviewDragEnter,
|
|
151
|
-
handleDragOver: handlePreviewDragOver,
|
|
152
|
-
handleDragLeave: handlePreviewDragLeave,
|
|
153
|
-
handleDrop: handlePreviewDrop,
|
|
154
|
-
} = usePreviewBlockDrop({
|
|
155
|
-
portrait,
|
|
156
|
-
compositionSize: previewCompositionSize,
|
|
157
|
-
stageRef: stageRefForDrop as React.RefObject<HTMLDivElement | null>,
|
|
158
|
-
onBlockDrop: onPreviewBlockDrop,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Lightweight reload: change iframe src instead of destroying the Player.
|
|
162
|
-
// refreshPlayer() saves the seek position and appends a cache-busting _t
|
|
163
|
-
// param — the Player instance stays alive so the adapter is available for
|
|
164
|
-
// saveSeekPosition() to read the current time before the reload.
|
|
165
|
-
const prevRefreshKeyRef = useRef(refreshKey);
|
|
166
|
-
useEffect(() => {
|
|
167
|
-
if (refreshKey === prevRefreshKeyRef.current) return;
|
|
168
|
-
prevRefreshKeyRef.current = refreshKey;
|
|
169
|
-
refreshPlayer();
|
|
170
|
-
}, [refreshKey, refreshPlayer]);
|
|
171
|
-
|
|
172
|
-
const onIframeLoad = useCallback(() => {
|
|
173
|
-
baseOnIframeLoad();
|
|
174
|
-
// Pre-load + register MotionPathPlugin once so adding a motion path in the
|
|
175
|
-
// studio doesn't take the async plugin-load flash path on the first soft
|
|
176
|
-
// reload (the comp may not ship the plugin until it actually uses one).
|
|
177
|
-
ensureMotionPathPluginLoaded(iframeRef.current);
|
|
178
|
-
onIframeRef?.(iframeRef.current);
|
|
179
|
-
}, [baseOnIframeLoad, iframeRef, onIframeRef]);
|
|
180
|
-
|
|
181
|
-
const {
|
|
182
|
-
compositionStack,
|
|
183
|
-
updateCompositionStack,
|
|
184
|
-
handleNavigateComposition,
|
|
185
|
-
handleDrillDown: drillDown,
|
|
186
|
-
masterSeekRef,
|
|
187
|
-
compIdToSrc,
|
|
188
|
-
setCompIdToSrc,
|
|
189
|
-
} = useCompositionStack({
|
|
190
|
-
projectId,
|
|
191
|
-
activeCompositionPath,
|
|
192
|
-
onCompositionChange,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Wrap handleDrillDown to also scan the iframe DOM for data-composition-src
|
|
196
|
-
const iframeRef_ = iframeRef;
|
|
197
|
-
const handleDrillDown = useCallback(
|
|
198
|
-
(element: TimelineElement) => {
|
|
199
|
-
if (!element.compositionSrc) return;
|
|
200
|
-
usePlayerStore.getState().setSelectedElementId(null);
|
|
201
|
-
// Check compIdToSrc map first; then scan iframe DOM; then fall through to drillDown
|
|
202
|
-
const compId = element.id;
|
|
203
|
-
let resolvedPath = compIdToSrc.get(compId);
|
|
204
|
-
if (!resolvedPath) {
|
|
205
|
-
try {
|
|
206
|
-
const doc = iframeRef_.current?.contentDocument;
|
|
207
|
-
if (doc) {
|
|
208
|
-
const host = doc.querySelector(
|
|
209
|
-
`[data-composition-id="${CSS.escape(compId)}"][data-composition-src]`,
|
|
210
|
-
);
|
|
211
|
-
if (host) {
|
|
212
|
-
resolvedPath = host.getAttribute("data-composition-src") || undefined;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
} catch {
|
|
216
|
-
/* cross-origin */
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
// Delegate with the resolved compositionSrc (may be same as original)
|
|
220
|
-
drillDown({
|
|
221
|
-
id: compId,
|
|
222
|
-
compositionSrc: resolvedPath ?? element.compositionSrc,
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
[compIdToSrc, drillDown, iframeRef_],
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
// Move/resize/split come from the timeline edit context, not props — the
|
|
229
|
-
// wrappers below intercept expanded clips and must call the *real* handlers.
|
|
230
|
-
// (Delete is a direct prop; it stays that way.)
|
|
231
|
-
const { onMoveElement, onResizeElement, onSplitElement } = useTimelineEditContext();
|
|
232
|
-
|
|
233
|
-
// An expanded sub-comp child reaches the normal edit handlers in its own
|
|
234
|
-
// local coordinates: addressed by its real DOM id, with timeline time rebased
|
|
235
|
-
// onto the sub-comp it lives in. The handlers then save + reloadPreview exactly
|
|
236
|
-
// as they do for top-level clips — no separate live-DOM path.
|
|
237
|
-
const toLocalElement = useCallback(
|
|
238
|
-
(element: TimelineElement, basis: number): TimelineElement => ({
|
|
239
|
-
...element,
|
|
240
|
-
id: element.domId ?? element.id,
|
|
241
|
-
start: element.start - basis,
|
|
242
|
-
}),
|
|
243
|
-
[],
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
const handleMoveElement = useCallback(
|
|
247
|
-
(element: TimelineElement, updates: Pick<TimelineElement, "start" | "track">) => {
|
|
248
|
-
const basis = element.expandedParentStart;
|
|
249
|
-
if (basis === undefined) return onMoveElement?.(element, updates);
|
|
250
|
-
trackStudioExpandedClipEdit({ action: "move" });
|
|
251
|
-
onMoveElement?.(toLocalElement(element, basis), {
|
|
252
|
-
...updates,
|
|
253
|
-
start: Math.max(0, updates.start - basis),
|
|
254
|
-
});
|
|
255
|
-
},
|
|
256
|
-
[onMoveElement, toLocalElement],
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
const handleResizeElement = useCallback(
|
|
260
|
-
(
|
|
261
|
-
element: TimelineElement,
|
|
262
|
-
updates: Pick<TimelineElement, "start" | "duration" | "playbackStart">,
|
|
263
|
-
) => {
|
|
264
|
-
const basis = element.expandedParentStart;
|
|
265
|
-
if (basis === undefined) return onResizeElement?.(element, updates);
|
|
266
|
-
trackStudioExpandedClipEdit({ action: "resize" });
|
|
267
|
-
onResizeElement?.(toLocalElement(element, basis), {
|
|
268
|
-
...updates,
|
|
269
|
-
start: Math.max(0, updates.start - basis),
|
|
270
|
-
});
|
|
271
|
-
},
|
|
272
|
-
[onResizeElement, toLocalElement],
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
const handleDeleteElement = useCallback(
|
|
276
|
-
(element: TimelineElement) => {
|
|
277
|
-
const basis = element.expandedParentStart;
|
|
278
|
-
if (basis === undefined) return onDeleteElement?.(element);
|
|
279
|
-
trackStudioExpandedClipEdit({ action: "delete" });
|
|
280
|
-
return onDeleteElement?.(toLocalElement(element, basis));
|
|
281
|
-
},
|
|
282
|
-
[onDeleteElement, toLocalElement],
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
const handleSplitElement = useCallback(
|
|
286
|
-
(element: TimelineElement, splitTime: number) => {
|
|
287
|
-
const basis = element.expandedParentStart;
|
|
288
|
-
if (basis === undefined) return onSplitElement?.(element, splitTime);
|
|
289
|
-
trackStudioExpandedClipEdit({ action: "split" });
|
|
290
|
-
return onSplitElement?.(toLocalElement(element, basis), Math.max(0, splitTime - basis));
|
|
291
|
-
},
|
|
292
|
-
[onSplitElement, toLocalElement],
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
// Composition ID → file path map from raw index.html
|
|
296
|
-
const compIdToSrcRef = useRef(compIdToSrc);
|
|
297
|
-
compIdToSrcRef.current = compIdToSrc;
|
|
298
|
-
|
|
299
|
-
useMountEffect(() => {
|
|
300
|
-
fetch(`/api/projects/${projectId}/files/index.html`)
|
|
301
|
-
.then((r) => {
|
|
302
|
-
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
303
|
-
return r.json();
|
|
304
|
-
})
|
|
305
|
-
.then((data: { content?: string }) => {
|
|
306
|
-
const html = data.content || "";
|
|
307
|
-
const map = new Map<string, string>();
|
|
308
|
-
const re =
|
|
309
|
-
/data-composition-id=["']([^"']+)["'][^>]*data-composition-src=["']([^"']+)["']|data-composition-src=["']([^"']+)["'][^>]*data-composition-id=["']([^"']+)["']/g;
|
|
310
|
-
let match;
|
|
311
|
-
while ((match = re.exec(html)) !== null) {
|
|
312
|
-
const id = match[1] || match[4];
|
|
313
|
-
const src = match[2] || match[3];
|
|
314
|
-
if (id && src) map.set(id, src);
|
|
315
|
-
}
|
|
316
|
-
setCompIdToSrc(map);
|
|
317
|
-
// Let DOM source-resolution recover a subcomposition element's source file
|
|
318
|
-
// (the runtime drops the linkage when inlining — see getSourceFileForElement).
|
|
319
|
-
setCompositionSourceMap(map);
|
|
320
|
-
onCompIdToSrcChange?.(map);
|
|
321
|
-
})
|
|
322
|
-
.catch((err: unknown) => {
|
|
323
|
-
// Non-fatal: drill-down still works via the iframe DOM scan; without
|
|
324
|
-
// the map only source-file resolution for sub-comps degrades.
|
|
325
|
-
console.warn("[studio] Couldn't load composition source map from index.html:", err);
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// Patch elements with compositionSrc whenever elements or compIdToSrc change.
|
|
330
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
331
|
-
useEffect(() => {
|
|
332
|
-
if (compIdToSrc.size === 0) return;
|
|
333
|
-
const patchElements = (elements: TimelineElement[]): TimelineElement[] | null => {
|
|
334
|
-
const map = compIdToSrcRef.current;
|
|
335
|
-
if (map.size === 0) return null;
|
|
336
|
-
let patched = false;
|
|
337
|
-
const updated = elements.map((el) => {
|
|
338
|
-
if (el.compositionSrc) return el;
|
|
339
|
-
const src = map.get(el.id) ?? map.get(el.id.replace(/-(host|comp|layer)$/, ""));
|
|
340
|
-
if (src) {
|
|
341
|
-
patched = true;
|
|
342
|
-
return { ...el, compositionSrc: src };
|
|
343
|
-
}
|
|
344
|
-
return el;
|
|
345
|
-
});
|
|
346
|
-
return patched ? updated : null;
|
|
347
|
-
};
|
|
348
|
-
const patched = patchElements(usePlayerStore.getState().elements);
|
|
349
|
-
if (patched) usePlayerStore.getState().setElements(patched);
|
|
350
|
-
let patching = false;
|
|
351
|
-
return usePlayerStore.subscribe((state, prev) => {
|
|
352
|
-
if (patching) return;
|
|
353
|
-
if (state.elements === prev.elements || state.elements.length === 0) return;
|
|
354
|
-
if (state.elements.every((el) => el.compositionSrc)) return;
|
|
355
|
-
patching = true;
|
|
356
|
-
const result = patchElements(state.elements);
|
|
357
|
-
if (result) state.setElements(result);
|
|
358
|
-
patching = false;
|
|
359
|
-
});
|
|
360
|
-
}, [compIdToSrc]);
|
|
361
|
-
|
|
362
|
-
// Resizable timeline height — persisted alongside zoom/pan so the user's
|
|
363
|
-
// workspace layout survives reloads.
|
|
364
|
-
const [timelineH, setTimelineH] = useState(() => {
|
|
365
|
-
const stored = readStudioUiPreferences().timelineHeight;
|
|
366
|
-
return stored !== undefined && stored >= MIN_TIMELINE_H ? stored : DEFAULT_TIMELINE_H;
|
|
367
|
-
});
|
|
368
|
-
const persistTimelineH = useCallback((height: number) => {
|
|
369
|
-
writeStudioUiPreferences({ timelineHeight: Math.round(height) });
|
|
370
|
-
}, []);
|
|
371
|
-
// A height persisted on a tall window can exceed this window's container and
|
|
372
|
-
// collapse the flex-1 preview to 0px — clamp once the container is measurable
|
|
373
|
-
// (the drag/keyboard paths already clamp; the restore path must too).
|
|
374
|
-
useEffect(() => {
|
|
375
|
-
const containerH = containerRef.current?.getBoundingClientRect().height;
|
|
376
|
-
if (!containerH) return;
|
|
377
|
-
const max = containerH - MIN_PREVIEW_H;
|
|
378
|
-
setTimelineH((prev) => (prev > max ? Math.max(MIN_TIMELINE_H, max) : prev));
|
|
379
|
-
}, []);
|
|
380
|
-
const hasLoadedOnceRef = useRef(false);
|
|
381
|
-
const [compositionLoading, setCompositionLoadingRaw] = useState(true);
|
|
382
|
-
const setCompositionLoading = useCallback((loading: boolean) => {
|
|
383
|
-
if (!loading) hasLoadedOnceRef.current = true;
|
|
384
|
-
if (loading && hasLoadedOnceRef.current) return;
|
|
385
|
-
setCompositionLoadingRaw(loading);
|
|
386
|
-
}, []);
|
|
387
|
-
const timelineDisabled = shouldDisableTimelineWhileCompositionLoading(compositionLoading);
|
|
388
|
-
|
|
389
|
-
useEffect(() => {
|
|
390
|
-
onCompositionLoadingChangeParent?.(compositionLoading);
|
|
391
|
-
}, [compositionLoading, onCompositionLoadingChangeParent]);
|
|
392
|
-
|
|
393
|
-
const fullscreenElement = useSyncExternalStore(subscribeFullscreen, getFullscreenElement);
|
|
394
|
-
const isTimelineVisible = timelineVisible ?? true;
|
|
395
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
396
|
-
const isFullscreen = fullscreenElement === containerRef.current && fullscreenElement != null;
|
|
397
|
-
|
|
398
|
-
const toggleFullscreen = useCallback(() => {
|
|
399
|
-
if (!containerRef.current) return;
|
|
400
|
-
if (document.fullscreenElement) {
|
|
401
|
-
void document.exitFullscreen();
|
|
402
|
-
} else {
|
|
403
|
-
void containerRef.current.requestFullscreen();
|
|
404
|
-
}
|
|
405
|
-
}, []);
|
|
406
|
-
|
|
407
|
-
const currentLevel = compositionStack[compositionStack.length - 1];
|
|
408
|
-
const directUrl = compositionStack.length > 1 ? currentLevel.previewUrl : undefined;
|
|
409
|
-
|
|
410
|
-
const onIframeRefStable = useRef(onIframeRef);
|
|
411
|
-
onIframeRefStable.current = onIframeRef;
|
|
412
|
-
useEffect(() => {
|
|
413
|
-
onIframeRefStable.current?.(iframeRef.current);
|
|
414
|
-
}, [compositionStack.length, refreshKey, iframeRef]);
|
|
415
|
-
|
|
416
|
-
// Keyboard: Escape to pop composition level
|
|
417
|
-
const handleKeyDown = useCallback(
|
|
418
|
-
(e: React.KeyboardEvent) => {
|
|
419
|
-
if (e.key === "Escape" && compositionStack.length > 1) {
|
|
420
|
-
updateCompositionStack((prev) => prev.slice(0, -1));
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
424
|
-
[compositionStack.length],
|
|
425
|
-
);
|
|
426
|
-
|
|
427
|
-
// Suppress TS unused-var warning for masterSeekRef (used inside useCompositionStack)
|
|
428
|
-
void masterSeekRef;
|
|
429
|
-
|
|
430
|
-
return (
|
|
431
|
-
<div
|
|
432
|
-
ref={containerRef}
|
|
433
|
-
className="flex flex-col h-full min-h-0 bg-neutral-950"
|
|
434
|
-
onKeyDown={handleKeyDown}
|
|
435
|
-
tabIndex={-1}
|
|
436
|
-
data-studio-fullscreen-target=""
|
|
437
|
-
>
|
|
438
|
-
{/* Preview + player controls */}
|
|
439
|
-
<div className="flex-1 min-h-0 flex flex-col">
|
|
440
|
-
<div
|
|
441
|
-
className="flex-1 min-h-0 relative overflow-hidden"
|
|
442
|
-
data-preview-pan-surface="true"
|
|
443
|
-
onPointerDown={(e) => {
|
|
444
|
-
const el = iframeRef.current?.parentElement ?? iframeRef.current;
|
|
445
|
-
if (!el) return;
|
|
446
|
-
const rect = el.getBoundingClientRect();
|
|
447
|
-
const inside =
|
|
448
|
-
e.clientX >= rect.left &&
|
|
449
|
-
e.clientX <= rect.right &&
|
|
450
|
-
e.clientY >= rect.top &&
|
|
451
|
-
e.clientY <= rect.bottom;
|
|
452
|
-
if (!inside) onSelectTimelineElement?.(null);
|
|
453
|
-
}}
|
|
454
|
-
onDragEnter={handlePreviewDragEnter}
|
|
455
|
-
onDragOver={handlePreviewDragOver}
|
|
456
|
-
onDragLeave={handlePreviewDragLeave}
|
|
457
|
-
onDrop={handlePreviewDrop}
|
|
458
|
-
>
|
|
459
|
-
<div className="absolute inset-0 overflow-hidden">
|
|
460
|
-
<NLEPreview
|
|
461
|
-
projectId={projectId}
|
|
462
|
-
iframeRef={iframeRef}
|
|
463
|
-
onIframeLoad={onIframeLoad}
|
|
464
|
-
onCompositionLoadingChange={setCompositionLoading}
|
|
465
|
-
portrait={portrait}
|
|
466
|
-
directUrl={directUrl}
|
|
467
|
-
suppressLoadingOverlay={hasLoadedOnceRef.current}
|
|
468
|
-
onStageRef={handleStageRef}
|
|
469
|
-
onCompositionSizeChange={setPreviewCompositionSize}
|
|
470
|
-
/>
|
|
471
|
-
{previewDragOver && (
|
|
472
|
-
<div className="absolute inset-2 z-40 rounded-lg border-2 border-dashed border-studio-accent/50 bg-studio-accent/[0.04] pointer-events-none" />
|
|
473
|
-
)}
|
|
474
|
-
</div>
|
|
475
|
-
{!isFullscreen && previewOverlay}
|
|
476
|
-
</div>
|
|
477
|
-
<div className="bg-neutral-950 border-t border-neutral-800/50 flex-shrink-0">
|
|
478
|
-
{!isFullscreen && compositionStack.length > 1 && (
|
|
479
|
-
<CompositionBreadcrumb
|
|
480
|
-
stack={compositionStack}
|
|
481
|
-
onNavigate={handleNavigateComposition}
|
|
482
|
-
/>
|
|
483
|
-
)}
|
|
484
|
-
<PlayerControls
|
|
485
|
-
onTogglePlay={togglePlay}
|
|
486
|
-
onSeek={seek}
|
|
487
|
-
disabled={timelineDisabled}
|
|
488
|
-
isFullscreen={isFullscreen}
|
|
489
|
-
onToggleFullscreen={toggleFullscreen}
|
|
490
|
-
/>
|
|
491
|
-
</div>
|
|
492
|
-
</div>
|
|
493
|
-
|
|
494
|
-
{!isFullscreen && isTimelineVisible ? (
|
|
495
|
-
<>
|
|
496
|
-
<TimelineResizeDivider
|
|
497
|
-
timelineH={timelineH}
|
|
498
|
-
setTimelineH={setTimelineH}
|
|
499
|
-
persistTimelineH={persistTimelineH}
|
|
500
|
-
containerRef={containerRef}
|
|
501
|
-
disabled={timelineDisabled}
|
|
502
|
-
/>
|
|
503
|
-
|
|
504
|
-
{/* Timeline section */}
|
|
505
|
-
<div
|
|
506
|
-
className="relative flex flex-col flex-shrink-0"
|
|
507
|
-
style={{ height: timelineH }}
|
|
508
|
-
aria-disabled={timelineDisabled || undefined}
|
|
509
|
-
>
|
|
510
|
-
<div
|
|
511
|
-
className="flex flex-col flex-1 min-h-0 overflow-hidden bg-neutral-950"
|
|
512
|
-
onDoubleClick={(e) => {
|
|
513
|
-
if ((e.target as HTMLElement).closest("[data-clip]")) return;
|
|
514
|
-
if (timelineDisabled) return;
|
|
515
|
-
if (compositionStack.length > 1) {
|
|
516
|
-
updateCompositionStack((prev) => prev.slice(0, -1));
|
|
517
|
-
}
|
|
518
|
-
}}
|
|
519
|
-
>
|
|
520
|
-
<div className="flex-shrink-0">{timelineToolbar}</div>
|
|
521
|
-
<Timeline
|
|
522
|
-
onSeek={seek}
|
|
523
|
-
onDrillDown={handleDrillDown}
|
|
524
|
-
renderClipContent={renderClipContent}
|
|
525
|
-
onFileDrop={onFileDrop}
|
|
526
|
-
onDeleteElement={handleDeleteElement}
|
|
527
|
-
onAssetDrop={onAssetDrop}
|
|
528
|
-
onBlockDrop={onBlockDrop}
|
|
529
|
-
onMoveElement={handleMoveElement}
|
|
530
|
-
onResizeElement={handleResizeElement}
|
|
531
|
-
onBlockedEditAttempt={onBlockedEditAttempt}
|
|
532
|
-
onSplitElement={handleSplitElement}
|
|
533
|
-
onSelectElement={onSelectTimelineElement}
|
|
534
|
-
/>
|
|
535
|
-
</div>
|
|
536
|
-
{timelineFooter && <div className="flex-shrink-0">{timelineFooter}</div>}
|
|
537
|
-
{timelineDisabled && (
|
|
538
|
-
<div
|
|
539
|
-
className="absolute inset-0 z-30 cursor-not-allowed bg-black/18 flex items-center justify-center"
|
|
540
|
-
data-testid="timeline-loading-disabled-overlay"
|
|
541
|
-
role="status"
|
|
542
|
-
onPointerDown={(event) => event.preventDefault()}
|
|
543
|
-
onDragOver={(event) => event.preventDefault()}
|
|
544
|
-
onDrop={(event) => event.preventDefault()}
|
|
545
|
-
>
|
|
546
|
-
<span className="rounded-md bg-neutral-900/90 px-2.5 py-1 text-[11px] text-neutral-400">
|
|
547
|
-
Loading composition…
|
|
548
|
-
</span>
|
|
549
|
-
</div>
|
|
550
|
-
)}
|
|
551
|
-
</div>
|
|
552
|
-
</>
|
|
553
|
-
) : !isFullscreen && onToggleTimeline ? (
|
|
554
|
-
<div className="flex-shrink-0 border-t border-neutral-800/50 bg-neutral-950/96">
|
|
555
|
-
<div className="flex h-10 items-center justify-between px-3">
|
|
556
|
-
<div className="text-[10px] font-medium uppercase tracking-[0.16em] text-neutral-500">
|
|
557
|
-
Timeline
|
|
558
|
-
</div>
|
|
559
|
-
<button
|
|
560
|
-
type="button"
|
|
561
|
-
onClick={onToggleTimeline}
|
|
562
|
-
className="flex h-7 items-center gap-1.5 rounded-md border border-neutral-800 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-700 hover:bg-neutral-900 hover:text-neutral-100"
|
|
563
|
-
title={getTimelineToggleTitle(false)}
|
|
564
|
-
aria-label="Show timeline editor"
|
|
565
|
-
>
|
|
566
|
-
<svg
|
|
567
|
-
width="13"
|
|
568
|
-
height="13"
|
|
569
|
-
viewBox="0 0 24 24"
|
|
570
|
-
fill="none"
|
|
571
|
-
stroke="currentColor"
|
|
572
|
-
strokeWidth="1.7"
|
|
573
|
-
strokeLinecap="round"
|
|
574
|
-
strokeLinejoin="round"
|
|
575
|
-
aria-hidden="true"
|
|
576
|
-
>
|
|
577
|
-
<rect x="3" y="13" width="18" height="8" rx="1" />
|
|
578
|
-
<path d="M7 9h10" />
|
|
579
|
-
<path d="M8 5h8" />
|
|
580
|
-
</svg>
|
|
581
|
-
<span>Show</span>
|
|
582
|
-
<span className="hidden rounded bg-white/5 px-1 py-0.5 font-mono text-[9px] text-neutral-500 sm:inline">
|
|
583
|
-
{TIMELINE_TOGGLE_SHORTCUT_LABEL}
|
|
584
|
-
</span>
|
|
585
|
-
</button>
|
|
586
|
-
</div>
|
|
587
|
-
</div>
|
|
588
|
-
) : null}
|
|
589
|
-
</div>
|
|
590
|
-
);
|
|
591
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Eye, EyeSlash } from "@phosphor-icons/react";
|
|
2
|
-
import { Music } from "../../icons/SystemIcons";
|
|
3
|
-
import type { TimelineTheme } from "./timelineTheme";
|
|
4
|
-
import { GUTTER } from "./timelineLayout";
|
|
5
|
-
|
|
6
|
-
interface TimelineLayerGutterProps {
|
|
7
|
-
isAudio: boolean;
|
|
8
|
-
isTrackHidden: boolean;
|
|
9
|
-
rowTrack: number;
|
|
10
|
-
theme: TimelineTheme;
|
|
11
|
-
onToggleHidden: () => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function TimelineLayerGutter({
|
|
15
|
-
isAudio,
|
|
16
|
-
isTrackHidden,
|
|
17
|
-
rowTrack,
|
|
18
|
-
theme,
|
|
19
|
-
onToggleHidden,
|
|
20
|
-
}: TimelineLayerGutterProps) {
|
|
21
|
-
return (
|
|
22
|
-
<div
|
|
23
|
-
className="sticky left-0 z-[12] flex-shrink-0 flex flex-col items-center justify-center gap-0.5"
|
|
24
|
-
style={{
|
|
25
|
-
width: GUTTER,
|
|
26
|
-
background: theme.gutterBackground,
|
|
27
|
-
borderRight: `1px solid ${theme.gutterBorder}`,
|
|
28
|
-
}}
|
|
29
|
-
>
|
|
30
|
-
{isAudio && (
|
|
31
|
-
<Music
|
|
32
|
-
size={12}
|
|
33
|
-
weight="fill"
|
|
34
|
-
aria-hidden="true"
|
|
35
|
-
style={{ color: theme.textSecondary, opacity: 0.7 }}
|
|
36
|
-
/>
|
|
37
|
-
)}
|
|
38
|
-
<button
|
|
39
|
-
type="button"
|
|
40
|
-
aria-label={isTrackHidden ? `Show track ${rowTrack}` : `Hide track ${rowTrack}`}
|
|
41
|
-
title={isTrackHidden ? `Show track ${rowTrack}` : `Hide track ${rowTrack}`}
|
|
42
|
-
className={`flex h-6 w-6 items-center justify-center rounded border-0 bg-transparent p-0 transition-colors focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-[-1px] focus-visible:outline-[#3CE6AC] ${
|
|
43
|
-
isTrackHidden ? "text-[#3CE6AC] hover:text-white" : "text-white/35 hover:text-white/75"
|
|
44
|
-
}`}
|
|
45
|
-
onPointerDown={(e) => {
|
|
46
|
-
e.stopPropagation();
|
|
47
|
-
}}
|
|
48
|
-
onClick={(e) => {
|
|
49
|
-
e.stopPropagation();
|
|
50
|
-
onToggleHidden();
|
|
51
|
-
}}
|
|
52
|
-
>
|
|
53
|
-
{isTrackHidden ? (
|
|
54
|
-
<EyeSlash size={14} weight="bold" aria-hidden="true" />
|
|
55
|
-
) : (
|
|
56
|
-
<Eye size={14} weight="bold" aria-hidden="true" />
|
|
57
|
-
)}
|
|
58
|
-
</button>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|