From 67701df4e9378b98d551a2dce6a1c80b1a65a2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 21 May 2026 18:25:39 -0400 Subject: [PATCH] fix(studio): add blocks at current playhead, extend root duration Blocks and components now start at the current playhead position instead of being appended after all existing content. If the new element extends beyond the root composition's data-duration, the root is automatically extended to fit. --- packages/studio/src/App.tsx | 3 ++ packages/studio/src/utils/blockInstaller.ts | 39 ++++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/studio/src/App.tsx b/packages/studio/src/App.tsx index 4f191dcb6..2e9f68a4a 100644 --- a/packages/studio/src/App.tsx +++ b/packages/studio/src/App.tsx @@ -193,6 +193,7 @@ export function StudioApp() { blockName, activeCompPath, previewIframe: previewIframeRef.current, + currentTime: usePlayerStore.getState().currentTime, timelineElements, readProjectFile: fileManager.readProjectFile, writeProjectFile: fileManager.writeProjectFile, @@ -237,6 +238,7 @@ export function StudioApp() { activeCompPath, placement, previewIframe: previewIframeRef.current, + currentTime: usePlayerStore.getState().currentTime, timelineElements, readProjectFile: fileManager.readProjectFile, writeProjectFile: fileManager.writeProjectFile, @@ -268,6 +270,7 @@ export function StudioApp() { activeCompPath, visualPosition: position, previewIframe: previewIframeRef.current, + currentTime: usePlayerStore.getState().currentTime, timelineElements, readProjectFile: fileManager.readProjectFile, writeProjectFile: fileManager.writeProjectFile, diff --git a/packages/studio/src/utils/blockInstaller.ts b/packages/studio/src/utils/blockInstaller.ts index fbcce82d4..c2ca2bbab 100644 --- a/packages/studio/src/utils/blockInstaller.ts +++ b/packages/studio/src/utils/blockInstaller.ts @@ -31,6 +31,7 @@ interface AddBlockOptions { placement?: { start: number; track: number }; visualPosition?: { left: number; top: number }; previewIframe?: HTMLIFrameElement | null; + currentTime?: number; timelineElements: TimelineElement[]; readProjectFile: (path: string) => Promise; writeProjectFile: (path: string, content: string) => Promise; @@ -120,20 +121,18 @@ export async function addBlockToProject( const isBlock = block.type === "hyperframes:block"; const hostDims = resolveTimelineAssetInitialGeometry(originalContent); + const currentTime = opts.currentTime ?? 0; const start = placement ? Number(formatTimelineAttributeNumber(placement.start)) - : isBlock - ? relevantElements.reduce( - (max, te) => Math.max(max, (te.start ?? 0) + (te.duration ?? 0)), - 0, - ) - : 0; - const duration = isBlock - ? (block as { duration: number }).duration - : relevantElements.reduce( - (max, te) => Math.max(max, (te.start ?? 0) + (te.duration ?? 0)), - 10, - ); + : Number(formatTimelineAttributeNumber(currentTime)); + const blockDuration = + "duration" in block ? (block as { duration: number }).duration : undefined; + const duration = + blockDuration ?? + relevantElements.reduce( + (max, te) => Math.max(max, (te.start ?? 0) + (te.duration ?? 0)), + 10, + ); const track = placement?.track ?? (isBlock @@ -163,7 +162,21 @@ export async function addBlockToProject( `>`, ].join("\n"); - const patchedContent = insertTimelineAssetIntoSource(originalContent, subCompHtml); + let patchedContent = insertTimelineAssetIntoSource(originalContent, subCompHtml); + + const newEnd = start + duration; + const rootDurMatch = patchedContent.match( + /(<[^>]*data-composition-id="[^"]*"[^>]*data-duration=")([^"]*)(")/, + ); + if (rootDurMatch) { + const rootDur = parseFloat(rootDurMatch[2]!); + if (newEnd > rootDur) { + patchedContent = patchedContent.replace( + rootDurMatch[0], + `${rootDurMatch[1]}${formatTimelineAttributeNumber(newEnd)}${rootDurMatch[3]}`, + ); + } + } await saveProjectFilesWithHistory({ projectId,