@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
|
@@ -20,6 +20,14 @@ interface UseRazorSplitOptions {
|
|
|
20
20
|
recordEdit: (input: RecordEditInput) => Promise<void>;
|
|
21
21
|
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
22
22
|
reloadPreview: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Resync the in-memory SDK session after the server-side split write (the
|
|
25
|
+
* split-element / split-gsap endpoints write the file directly, so the SDK's
|
|
26
|
+
* linkedom doc is now stale). This reload is read-only; the split endpoint owns
|
|
27
|
+
* the final on-disk bytes and history baseline. Every other server-side-write
|
|
28
|
+
* timeline path (move / resize / delete / drop / visibility) also resyncs.
|
|
29
|
+
*/
|
|
30
|
+
forceReloadSdkSession?: () => void;
|
|
23
31
|
isRecordingRef?: React.RefObject<boolean>;
|
|
24
32
|
}
|
|
25
33
|
|
|
@@ -101,6 +109,62 @@ async function splitGsapAnimations(
|
|
|
101
109
|
};
|
|
102
110
|
}
|
|
103
111
|
|
|
112
|
+
function getOriginalContent(originals: ReadonlyMap<string, string>, path: string): string {
|
|
113
|
+
const original = originals.get(path);
|
|
114
|
+
if (original === undefined) {
|
|
115
|
+
throw new Error(`Missing original contents for ${path}`);
|
|
116
|
+
}
|
|
117
|
+
return original;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function restoreFilesToOriginal(
|
|
121
|
+
originals: ReadonlyMap<string, string>,
|
|
122
|
+
paths: Iterable<string>,
|
|
123
|
+
writeProjectFile: (path: string, content: string) => Promise<void>,
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
for (const path of paths) {
|
|
126
|
+
await writeProjectFile(path, getOriginalContent(originals, path));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function readOriginalFiles(
|
|
131
|
+
pid: string,
|
|
132
|
+
elements: TimelineElement[],
|
|
133
|
+
activeCompPath: string | null,
|
|
134
|
+
): Promise<Map<string, string>> {
|
|
135
|
+
const originals = new Map<string, string>();
|
|
136
|
+
for (const element of elements) {
|
|
137
|
+
const path = element.sourceFile || activeCompPath || "index.html";
|
|
138
|
+
if (!originals.has(path)) {
|
|
139
|
+
originals.set(path, await readFileContent(pid, path));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return originals;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function splitElementsAtTime(
|
|
146
|
+
pid: string,
|
|
147
|
+
elements: TimelineElement[],
|
|
148
|
+
splitTime: number,
|
|
149
|
+
activeCompPath: string | null,
|
|
150
|
+
originals: ReadonlyMap<string, string>,
|
|
151
|
+
snapshots: Map<string, { before: string; after: string }>,
|
|
152
|
+
writeProjectFile: (path: string, content: string) => Promise<void>,
|
|
153
|
+
): Promise<number> {
|
|
154
|
+
let count = 0;
|
|
155
|
+
for (const element of elements) {
|
|
156
|
+
const result = await executeSplit(pid, element, splitTime, activeCompPath, writeProjectFile);
|
|
157
|
+
if (!result.changed) continue;
|
|
158
|
+
snapshots.set(result.targetPath, {
|
|
159
|
+
before: getOriginalContent(originals, result.targetPath),
|
|
160
|
+
after: result.patchedContent,
|
|
161
|
+
});
|
|
162
|
+
await writeProjectFile(result.targetPath, result.patchedContent);
|
|
163
|
+
count++;
|
|
164
|
+
}
|
|
165
|
+
return count;
|
|
166
|
+
}
|
|
167
|
+
|
|
104
168
|
// fallow-ignore-next-line complexity
|
|
105
169
|
async function executeSplit(
|
|
106
170
|
pid: string,
|
|
@@ -122,13 +186,23 @@ async function executeSplit(
|
|
|
122
186
|
const originalContent = await readFileContent(pid, targetPath);
|
|
123
187
|
const newId = generateSplitId(collectHtmlIds(originalContent), element.domId || "clip");
|
|
124
188
|
|
|
189
|
+
// An expanded sub-comp child arrives in MASTER-timeline coordinates — both its
|
|
190
|
+
// `start` and the incoming `splitTime` are offset by the host's master start
|
|
191
|
+
// (expandedParentStart) — but its `sourceFile` is the sub-comp, whose clips are
|
|
192
|
+
// authored in LOCAL time. Rebase both onto local time before the server patches
|
|
193
|
+
// the file, exactly as TimelinePane.handleSplitElement does for non-razor edits.
|
|
194
|
+
// Root-level clips (no expandedParentStart) are already local, so pass through.
|
|
195
|
+
const basis = element.expandedParentStart;
|
|
196
|
+
const localSplitTime = basis === undefined ? splitTime : Math.max(0, splitTime - basis);
|
|
197
|
+
const localElementStart = basis === undefined ? element.start : element.start - basis;
|
|
198
|
+
|
|
125
199
|
const splitResult = await splitHtmlElement(
|
|
126
200
|
pid,
|
|
127
201
|
targetPath,
|
|
128
202
|
patchTarget,
|
|
129
|
-
|
|
203
|
+
localSplitTime,
|
|
130
204
|
newId,
|
|
131
|
-
|
|
205
|
+
localElementStart,
|
|
132
206
|
element.duration,
|
|
133
207
|
);
|
|
134
208
|
if (!splitResult.ok) throw new Error("Failed to split clip.");
|
|
@@ -147,8 +221,8 @@ async function executeSplit(
|
|
|
147
221
|
targetPath,
|
|
148
222
|
element.domId,
|
|
149
223
|
newId,
|
|
150
|
-
|
|
151
|
-
|
|
224
|
+
localSplitTime,
|
|
225
|
+
localElementStart,
|
|
152
226
|
element.duration,
|
|
153
227
|
);
|
|
154
228
|
if (gsapResult.content) patchedContent = gsapResult.content;
|
|
@@ -172,6 +246,7 @@ export function useRazorSplit({
|
|
|
172
246
|
recordEdit,
|
|
173
247
|
domEditSaveTimestampRef,
|
|
174
248
|
reloadPreview,
|
|
249
|
+
forceReloadSdkSession,
|
|
175
250
|
isRecordingRef,
|
|
176
251
|
}: UseRazorSplitOptions) {
|
|
177
252
|
const projectIdRef = useRef(projectId);
|
|
@@ -208,6 +283,9 @@ export function useRazorSplit({
|
|
|
208
283
|
recordEdit,
|
|
209
284
|
});
|
|
210
285
|
|
|
286
|
+
// Server writes bypass the SDK session, so reopen it before refreshing
|
|
287
|
+
// the preview. The split response already owns the final persisted bytes.
|
|
288
|
+
forceReloadSdkSession?.();
|
|
211
289
|
reloadPreview();
|
|
212
290
|
trackStudioRazorSplit({ mode: "single", count: 1 });
|
|
213
291
|
showToast(`Split ${getTimelineElementLabel(element)} at ${splitTime.toFixed(2)}s`, "info");
|
|
@@ -229,6 +307,7 @@ export function useRazorSplit({
|
|
|
229
307
|
writeProjectFile,
|
|
230
308
|
domEditSaveTimestampRef,
|
|
231
309
|
reloadPreview,
|
|
310
|
+
forceReloadSdkSession,
|
|
232
311
|
isRecordingRef,
|
|
233
312
|
],
|
|
234
313
|
);
|
|
@@ -247,51 +326,42 @@ export function useRazorSplit({
|
|
|
247
326
|
const splittable = selectSplittableElements(elements, splitTime);
|
|
248
327
|
if (splittable.length === 0) return;
|
|
249
328
|
|
|
329
|
+
let originals = new Map<string, string>();
|
|
330
|
+
const finalSnapshots = new Map<string, { before: string; after: string }>();
|
|
250
331
|
try {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
for (const element of splittable) {
|
|
263
|
-
const result = await executeSplit(
|
|
264
|
-
pid,
|
|
265
|
-
element,
|
|
266
|
-
splitTime,
|
|
267
|
-
activeCompPath,
|
|
268
|
-
writeProjectFile,
|
|
269
|
-
);
|
|
270
|
-
if (result.changed) {
|
|
271
|
-
finalContent.set(result.targetPath, result.patchedContent);
|
|
272
|
-
await writeProjectFile(result.targetPath, result.patchedContent);
|
|
273
|
-
splitCount++;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
332
|
+
originals = await readOriginalFiles(pid, splittable, activeCompPath);
|
|
333
|
+
const splitCount = await splitElementsAtTime(
|
|
334
|
+
pid,
|
|
335
|
+
splittable,
|
|
336
|
+
splitTime,
|
|
337
|
+
activeCompPath,
|
|
338
|
+
originals,
|
|
339
|
+
finalSnapshots,
|
|
340
|
+
writeProjectFile,
|
|
341
|
+
);
|
|
277
342
|
if (splitCount === 0) return;
|
|
278
343
|
|
|
279
344
|
domEditSaveTimestampRef.current = Date.now();
|
|
280
345
|
await recordEdit({
|
|
281
346
|
label: `Split ${splitCount} clips at ${splitTime.toFixed(2)}s`,
|
|
282
347
|
kind: "timeline",
|
|
283
|
-
files: Object.fromEntries(
|
|
284
|
-
[...finalContent].map(([path, after]) => [
|
|
285
|
-
path,
|
|
286
|
-
{ before: originals.get(path) ?? "", after },
|
|
287
|
-
]),
|
|
288
|
-
),
|
|
348
|
+
files: Object.fromEntries(finalSnapshots),
|
|
289
349
|
});
|
|
290
350
|
|
|
351
|
+
// Resync the stale SDK doc after the batched server write (see the
|
|
352
|
+
// single-split path above for why this precedes the reload).
|
|
353
|
+
forceReloadSdkSession?.();
|
|
291
354
|
reloadPreview();
|
|
292
355
|
trackStudioRazorSplit({ mode: "all", count: splitCount });
|
|
293
356
|
showToast(`Split ${splitCount} clips at ${splitTime.toFixed(2)}s`, "info");
|
|
294
357
|
} catch (error) {
|
|
358
|
+
// Best-effort rollback — a failing restore write must not swallow the
|
|
359
|
+
// original error's toast, which is what tells the user the split failed.
|
|
360
|
+
try {
|
|
361
|
+
await restoreFilesToOriginal(originals, finalSnapshots.keys(), writeProjectFile);
|
|
362
|
+
} catch {
|
|
363
|
+
/* leave disk as-is; the original failure is reported below */
|
|
364
|
+
}
|
|
295
365
|
const message = error instanceof Error ? error.message : "Failed to split clips";
|
|
296
366
|
showToast(message, "error");
|
|
297
367
|
}
|
|
@@ -303,6 +373,7 @@ export function useRazorSplit({
|
|
|
303
373
|
writeProjectFile,
|
|
304
374
|
domEditSaveTimestampRef,
|
|
305
375
|
reloadPreview,
|
|
376
|
+
forceReloadSdkSession,
|
|
306
377
|
isRecordingRef,
|
|
307
378
|
],
|
|
308
379
|
);
|
|
@@ -3,6 +3,8 @@ import { createElement } from "react";
|
|
|
3
3
|
import { CompositionThumbnail, VideoThumbnail } from "../player";
|
|
4
4
|
import type { TimelineElement } from "../player";
|
|
5
5
|
import { AudioWaveform } from "../player/components/AudioWaveform";
|
|
6
|
+
import { ImageThumbnail } from "../player/components/ImageThumbnail";
|
|
7
|
+
import { encodePreviewPath, resolveMediaPreviewUrl } from "../player/components/thumbnailUtils";
|
|
6
8
|
|
|
7
9
|
export function normalizeCompositionSrc(
|
|
8
10
|
compSrc: string,
|
|
@@ -51,8 +53,16 @@ function trimFractions(el: TimelineElement): { start?: number; end?: number } {
|
|
|
51
53
|
*/
|
|
52
54
|
function renderAudioClip(el: TimelineElement, pid: string, labelColor: string): ReactNode {
|
|
53
55
|
const srcRelative = resolvePreviewRelative(el.src, pid);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
// Encode each path segment (spaces, parens, U+202F, unicode) so the URL matches
|
|
57
|
+
// what the assets panel loads — a raw segment 404s. resolvePreviewRelative
|
|
58
|
+
// returns the DECODED path, so it must be re-encoded here.
|
|
59
|
+
const encodedRelative = srcRelative ? encodePreviewPath(srcRelative) : null;
|
|
60
|
+
const audioUrl = encodedRelative
|
|
61
|
+
? `/api/projects/${pid}/preview/${encodedRelative}`
|
|
62
|
+
: (el.src ?? "");
|
|
63
|
+
const waveformUrl = encodedRelative
|
|
64
|
+
? `/api/projects/${pid}/waveform/${encodedRelative}`
|
|
65
|
+
: undefined;
|
|
56
66
|
const { start, end } = trimFractions(el);
|
|
57
67
|
return createElement(AudioWaveform, {
|
|
58
68
|
audioUrl,
|
|
@@ -100,7 +110,7 @@ export function useRenderClipContent({
|
|
|
100
110
|
// instead of capturing the master at a time when the comp is fading in.
|
|
101
111
|
if (compSrc) {
|
|
102
112
|
return createElement(CompositionThumbnail, {
|
|
103
|
-
previewUrl: `/api/projects/${pid}/preview/comp/${compSrc}`,
|
|
113
|
+
previewUrl: `/api/projects/${pid}/preview/comp/${encodePreviewPath(compSrc)}`,
|
|
104
114
|
label: "",
|
|
105
115
|
labelColor: style.label,
|
|
106
116
|
|
|
@@ -138,9 +148,17 @@ export function useRenderClipContent({
|
|
|
138
148
|
!/(backdrop|background|overlay|scrim|mask)/i.test(el.id);
|
|
139
149
|
|
|
140
150
|
if ((el.tag === "video" || el.tag === "img") && el.src) {
|
|
141
|
-
const mediaSrc = el.src
|
|
142
|
-
|
|
143
|
-
|
|
151
|
+
const mediaSrc = resolveMediaPreviewUrl(el.src, pid);
|
|
152
|
+
// Still images can't be decoded by VideoThumbnail's <video> extractor
|
|
153
|
+
// (the error event fires and the shimmer never resolves) — render the
|
|
154
|
+
// image itself as the strip.
|
|
155
|
+
if (el.tag === "img") {
|
|
156
|
+
return createElement(ImageThumbnail, {
|
|
157
|
+
imageSrc: mediaSrc,
|
|
158
|
+
label: "",
|
|
159
|
+
labelColor: style.label,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
144
162
|
return createElement(VideoThumbnail, {
|
|
145
163
|
videoSrc: mediaSrc,
|
|
146
164
|
label: "",
|
|
@@ -2,6 +2,8 @@ import { useCallback, useMemo, useRef, useState, type DragEvent } from "react";
|
|
|
2
2
|
import { STUDIO_INSPECTOR_PANELS_ENABLED } from "../components/editor/manualEditingAvailability";
|
|
3
3
|
import type { StudioContextValue } from "../contexts/StudioContext";
|
|
4
4
|
import type { RightInspectorPanes } from "../utils/studioHelpers";
|
|
5
|
+
import type { TimelineFileDropHandler } from "./useTimelineEditingTypes";
|
|
6
|
+
import { usePlayerStore } from "../player";
|
|
5
7
|
|
|
6
8
|
interface StudioContextInput {
|
|
7
9
|
projectId: string;
|
|
@@ -34,8 +36,6 @@ interface StudioContextInput {
|
|
|
34
36
|
waitForPendingDomEditSaves: () => Promise<void>;
|
|
35
37
|
handlePreviewIframeRef: (iframe: HTMLIFrameElement | null) => void;
|
|
36
38
|
refreshPreviewDocumentVersion: () => void;
|
|
37
|
-
timelineVisible: boolean;
|
|
38
|
-
toggleTimelineVisibility: () => void;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// fallow-ignore-next-line complexity
|
|
@@ -61,8 +61,6 @@ export function buildStudioContextValue(input: StudioContextInput): StudioContex
|
|
|
61
61
|
waitForPendingDomEditSaves: input.waitForPendingDomEditSaves,
|
|
62
62
|
handlePreviewIframeRef: input.handlePreviewIframeRef,
|
|
63
63
|
refreshPreviewDocumentVersion: input.refreshPreviewDocumentVersion,
|
|
64
|
-
timelineVisible: input.timelineVisible,
|
|
65
|
-
toggleTimelineVisibility: input.toggleTimelineVisibility,
|
|
66
64
|
};
|
|
67
65
|
}
|
|
68
66
|
|
|
@@ -108,7 +106,7 @@ export function useInspectorState(
|
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
// fallow-ignore-next-line complexity
|
|
111
|
-
|
|
109
|
+
function useDragOverlay(onImportFiles: (files: FileList) => void) {
|
|
112
110
|
const [active, setActive] = useState(false);
|
|
113
111
|
const counterRef = useRef(0);
|
|
114
112
|
const onDragOver = useCallback((e: DragEvent) => {
|
|
@@ -137,3 +135,15 @@ export function useDragOverlay(onImportFiles: (files: FileList) => void) {
|
|
|
137
135
|
);
|
|
138
136
|
return { active, onDragOver, onDragEnter, onDragLeave, onDrop };
|
|
139
137
|
}
|
|
138
|
+
|
|
139
|
+
/** Global OS file drop: imports and places at the playhead position. */
|
|
140
|
+
export function useGlobalFileDrop(handleTimelineFileDrop: TimelineFileDropHandler) {
|
|
141
|
+
const onDrop = useCallback(
|
|
142
|
+
(files: FileList) => {
|
|
143
|
+
const start = usePlayerStore.getState().currentTime;
|
|
144
|
+
void handleTimelineFileDrop(Array.from(files), { start, track: 0 });
|
|
145
|
+
},
|
|
146
|
+
[handleTimelineFileDrop],
|
|
147
|
+
);
|
|
148
|
+
return useDragOverlay(onDrop);
|
|
149
|
+
}
|
|
@@ -20,7 +20,6 @@ interface UseStudioUrlStateParams {
|
|
|
20
20
|
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
21
21
|
rightPanelTab: RightPanelTab;
|
|
22
22
|
rightCollapsed: boolean;
|
|
23
|
-
timelineVisible: boolean;
|
|
24
23
|
activeCompPathHydrated: boolean;
|
|
25
24
|
domEditSelection: DomEditSelection | null;
|
|
26
25
|
buildDomSelectionFromTarget: (
|
|
@@ -66,7 +65,6 @@ export function useStudioUrlState({
|
|
|
66
65
|
previewIframeRef,
|
|
67
66
|
rightPanelTab,
|
|
68
67
|
rightCollapsed,
|
|
69
|
-
timelineVisible,
|
|
70
68
|
activeCompPathHydrated,
|
|
71
69
|
domEditSelection,
|
|
72
70
|
buildDomSelectionFromTarget,
|
|
@@ -91,12 +89,12 @@ export function useStudioUrlState({
|
|
|
91
89
|
currentTime: stableTimeRef.current,
|
|
92
90
|
rightPanelTab,
|
|
93
91
|
rightCollapsed,
|
|
94
|
-
timelineVisible,
|
|
92
|
+
timelineVisible: null,
|
|
95
93
|
selection: hydratedSelectionRef.current
|
|
96
94
|
? toPersistedSelection(domEditSelection)
|
|
97
95
|
: pendingSelectionRef.current,
|
|
98
96
|
}),
|
|
99
|
-
[activeCompPath, domEditSelection, rightCollapsed, rightPanelTab
|
|
97
|
+
[activeCompPath, domEditSelection, rightCollapsed, rightPanelTab],
|
|
100
98
|
);
|
|
101
99
|
|
|
102
100
|
// Resolve a URL selection to a live element and apply it. Shared by the initial
|
|
@@ -159,7 +159,7 @@ type TimelineRecordEdit = NonNullable<
|
|
|
159
159
|
function renderTimelineEditingHookWithLifecycle(input: {
|
|
160
160
|
timelineElements: TimelineElement[];
|
|
161
161
|
iframe: HTMLIFrameElement;
|
|
162
|
-
|
|
162
|
+
commitDomEditPatchBatches: ReturnType<typeof vi.fn<(...args: unknown[]) => Promise<void>>>;
|
|
163
163
|
}): {
|
|
164
164
|
move: ReturnType<typeof useTimelineEditing>["handleTimelineElementMove"];
|
|
165
165
|
unmount: () => void;
|
|
@@ -176,7 +176,7 @@ function renderTimelineEditingHookWithLifecycle(input: {
|
|
|
176
176
|
projectIdRef: { current: "p1" },
|
|
177
177
|
reloadPreview: vi.fn(),
|
|
178
178
|
clearDomSelection: vi.fn(),
|
|
179
|
-
|
|
179
|
+
commitDomEditPatchBatches: input.commitDomEditPatchBatches,
|
|
180
180
|
});
|
|
181
181
|
const commitRef = useRef(lifecycle.handleDomZIndexReorderCommit);
|
|
182
182
|
commitRef.current = lifecycle.handleDomZIndexReorderCommit;
|
|
@@ -481,11 +481,11 @@ describe("useTimelineEditing timeline z-index reorder", () => {
|
|
|
481
481
|
]);
|
|
482
482
|
const front = timelineElement({ id: "front", track: 0, zIndex: 0 });
|
|
483
483
|
const back = timelineElement({ id: "back", track: 1, zIndex: 0 });
|
|
484
|
-
const
|
|
484
|
+
const commitDomEditPatchBatches = vi.fn<(...args: unknown[]) => Promise<void>>(async () => {});
|
|
485
485
|
const { move, unmount } = renderTimelineEditingHookWithLifecycle({
|
|
486
486
|
timelineElements: [front, back],
|
|
487
487
|
iframe,
|
|
488
|
-
|
|
488
|
+
commitDomEditPatchBatches,
|
|
489
489
|
});
|
|
490
490
|
|
|
491
491
|
await act(async () => {
|
|
@@ -501,8 +501,11 @@ describe("useTimelineEditing timeline z-index reorder", () => {
|
|
|
501
501
|
await flushAsyncWork();
|
|
502
502
|
});
|
|
503
503
|
|
|
504
|
-
expect(
|
|
505
|
-
|
|
504
|
+
expect(commitDomEditPatchBatches).toHaveBeenCalled();
|
|
505
|
+
const batch = commitDomEditPatchBatches.mock.calls[0]![0] as Array<{
|
|
506
|
+
patches: Array<{ operations: unknown[] }>;
|
|
507
|
+
}>;
|
|
508
|
+
expect(batch[0]?.patches[0]?.operations).toEqual([
|
|
506
509
|
{ type: "inline-style", property: "z-index", value: "2" },
|
|
507
510
|
{ type: "inline-style", property: "position", value: "relative" },
|
|
508
511
|
]);
|
|
@@ -522,14 +525,13 @@ describe("useTimelineEditing timeline z-index reorder", () => {
|
|
|
522
525
|
{ ...back, hasExplicitZIndex: false },
|
|
523
526
|
]);
|
|
524
527
|
const saveError = new Error("save failed");
|
|
525
|
-
const
|
|
528
|
+
const commitDomEditPatchBatches = vi
|
|
526
529
|
.fn<(...args: unknown[]) => Promise<void>>()
|
|
527
|
-
.mockResolvedValueOnce(undefined)
|
|
528
530
|
.mockRejectedValueOnce(saveError);
|
|
529
531
|
const { move, unmount } = renderTimelineEditingHookWithLifecycle({
|
|
530
532
|
timelineElements: [front, back],
|
|
531
533
|
iframe,
|
|
532
|
-
|
|
534
|
+
commitDomEditPatchBatches,
|
|
533
535
|
});
|
|
534
536
|
const doc = iframe.contentDocument;
|
|
535
537
|
if (!doc) throw new Error("Expected iframe document");
|
|
@@ -576,29 +578,24 @@ describe("useTimelineEditing timeline z-index reorder", () => {
|
|
|
576
578
|
unmount();
|
|
577
579
|
});
|
|
578
580
|
|
|
579
|
-
it("waits for
|
|
581
|
+
it("waits for the lifecycle z-index batch before resolving a reorder", async () => {
|
|
580
582
|
const iframe = createPreviewIframe([
|
|
581
583
|
{ id: "front", track: 0, style: "position: relative; z-index: 1" },
|
|
582
584
|
{ id: "back", track: 1, style: "position: relative; z-index: 0" },
|
|
583
585
|
]);
|
|
584
586
|
const front = timelineElement({ id: "front", track: 0, zIndex: 1 });
|
|
585
587
|
const back = timelineElement({ id: "back", track: 1, zIndex: 0 });
|
|
586
|
-
let
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
releaseFirst = resolve;
|
|
588
|
+
let releaseBatch!: () => void;
|
|
589
|
+
const batchSave = new Promise<void>((resolve) => {
|
|
590
|
+
releaseBatch = resolve;
|
|
590
591
|
});
|
|
591
|
-
const
|
|
592
|
-
releaseSecond = resolve;
|
|
593
|
-
});
|
|
594
|
-
const commitPositionPatchToHtml = vi
|
|
592
|
+
const commitDomEditPatchBatches = vi
|
|
595
593
|
.fn<(...args: unknown[]) => Promise<void>>()
|
|
596
|
-
.mockReturnValueOnce(
|
|
597
|
-
.mockReturnValueOnce(secondSave);
|
|
594
|
+
.mockReturnValueOnce(batchSave);
|
|
598
595
|
const { move, unmount } = renderTimelineEditingHookWithLifecycle({
|
|
599
596
|
timelineElements: [front, back],
|
|
600
597
|
iframe,
|
|
601
|
-
|
|
598
|
+
commitDomEditPatchBatches,
|
|
602
599
|
});
|
|
603
600
|
let settled = false;
|
|
604
601
|
|
|
@@ -621,17 +618,11 @@ describe("useTimelineEditing timeline z-index reorder", () => {
|
|
|
621
618
|
await flushAsyncWork();
|
|
622
619
|
});
|
|
623
620
|
|
|
624
|
-
expect(
|
|
625
|
-
expect(settled).toBe(false);
|
|
626
|
-
|
|
627
|
-
await act(async () => {
|
|
628
|
-
releaseFirst();
|
|
629
|
-
await flushAsyncWork();
|
|
630
|
-
});
|
|
621
|
+
expect(commitDomEditPatchBatches).toHaveBeenCalledTimes(1);
|
|
631
622
|
expect(settled).toBe(false);
|
|
632
623
|
|
|
633
624
|
await act(async () => {
|
|
634
|
-
|
|
625
|
+
releaseBatch();
|
|
635
626
|
await movePromise;
|
|
636
627
|
await flushAsyncWork();
|
|
637
628
|
});
|
|
@@ -22,6 +22,7 @@ export type TimelineZIndexReorderCommit = (
|
|
|
22
22
|
sourceFile: string;
|
|
23
23
|
key?: string;
|
|
24
24
|
}>,
|
|
25
|
+
coalesceKey?: string,
|
|
25
26
|
) => Promise<void>;
|
|
26
27
|
|
|
27
28
|
export interface UseTimelineEditingOptions {
|
|
@@ -43,3 +44,8 @@ export interface UseTimelineEditingOptions {
|
|
|
43
44
|
forceReloadSdkSession?: () => void;
|
|
44
45
|
handleDomZIndexReorderCommitRef?: MutableRefObject<TimelineZIndexReorderCommit | null>;
|
|
45
46
|
}
|
|
47
|
+
|
|
48
|
+
export type TimelineFileDropHandler = (
|
|
49
|
+
files: File[],
|
|
50
|
+
placement?: { start: number; track: number },
|
|
51
|
+
) => Promise<void>;
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
export interface TimelineGroupMoveChange {
|
|
22
22
|
element: TimelineElement;
|
|
23
23
|
start: number;
|
|
24
|
+
track?: number;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export interface TimelineGroupResizeChange {
|
|
@@ -152,9 +153,13 @@ export function useTimelineGroupEditing({
|
|
|
152
153
|
(changes: TimelineGroupMoveChange[], options?: TimelineGroupCommitOptions) => {
|
|
153
154
|
if (changes.length === 0) return Promise.resolve();
|
|
154
155
|
for (const change of changes) {
|
|
155
|
-
|
|
156
|
+
const attrs: Array<[string, string]> = [
|
|
156
157
|
["data-start", formatTimelineAttributeNumber(change.start)],
|
|
157
|
-
]
|
|
158
|
+
];
|
|
159
|
+
if (change.track != null) {
|
|
160
|
+
attrs.push(["data-track-index", formatTimelineAttributeNumber(change.track)]);
|
|
161
|
+
}
|
|
162
|
+
patchIframeDomTiming(previewIframeRef.current, change.element, attrs);
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
const maxEnd = Math.max(...changes.map((change) => change.start + change.element.duration));
|
|
@@ -169,7 +174,10 @@ export function useTimelineGroupEditing({
|
|
|
169
174
|
: null,
|
|
170
175
|
);
|
|
171
176
|
const canUseSdk =
|
|
172
|
-
!needsExtension &&
|
|
177
|
+
!needsExtension &&
|
|
178
|
+
sharedPath !== null &&
|
|
179
|
+
changes.every((change) => change.track == null) &&
|
|
180
|
+
sdkChanges.every((change) => change !== null);
|
|
173
181
|
if (canUseSdk) {
|
|
174
182
|
const handled = await sdkTimingBatchPersist(
|
|
175
183
|
sdkChanges.filter((change): change is NonNullable<typeof change> => change !== null),
|
|
@@ -194,7 +202,13 @@ export function useTimelineGroupEditing({
|
|
|
194
202
|
changes.map((change) => ({
|
|
195
203
|
element: change.element,
|
|
196
204
|
buildPatches: (original, target) =>
|
|
197
|
-
buildTimelineMoveTimingPatch(
|
|
205
|
+
buildTimelineMoveTimingPatch(
|
|
206
|
+
original,
|
|
207
|
+
target,
|
|
208
|
+
change.start,
|
|
209
|
+
change.element.duration,
|
|
210
|
+
change.track,
|
|
211
|
+
),
|
|
198
212
|
})),
|
|
199
213
|
coalesceKey,
|
|
200
214
|
);
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// NLE Layout
|
|
2
|
-
export {
|
|
2
|
+
export { EditorShell } from "./components/EditorShell";
|
|
3
3
|
export { NLEPreview } from "./components/nle/NLEPreview";
|
|
4
4
|
export { CompositionBreadcrumb } from "./components/nle/CompositionBreadcrumb";
|
|
5
5
|
export type { CompositionLevel } from "./components/nle/CompositionBreadcrumb";
|