diff --git a/apps/desktop/src/routes/editor/ConfigSidebar.tsx b/apps/desktop/src/routes/editor/ConfigSidebar.tsx index 3663d04e78..3bade9cc9e 100644 --- a/apps/desktop/src/routes/editor/ConfigSidebar.tsx +++ b/apps/desktop/src/routes/editor/ConfigSidebar.tsx @@ -1913,26 +1913,25 @@ function ZoomSegmentPreview(props: { const start = createMemo(() => props.segment.start); - const segmentIndex = createMemo(() => { + const clipSegment = createMemo(() => { const st = start(); - const i = project.timeline?.segments.findIndex( - (s) => s.start <= st && s.end > st, - ); - if (i === undefined || i === -1) return 0; - return i; + return project.timeline?.segments.find((s) => s.start <= st && s.end > st); }); const relativeTime = createMemo(() => { const st = start(); - const segment = project.timeline?.segments[segmentIndex()]; + const segment = clipSegment(); if (!segment) return 0; return Math.max(0, st - segment.start); }); const video = document.createElement("video"); createEffect(() => { + // TODO: make this not hardcoded const path = convertFileSrc( - `${editorInstance.path}/content/segments/segment-${segmentIndex()}/display.mp4`, + `${editorInstance.path}/content/segments/segment-${ + clipSegment()?.recordingSegment ?? 0 + }/display.mp4`, ); video.src = path; video.preload = "auto"; diff --git a/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx b/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx index 8e0daff22c..e8fcca489d 100644 --- a/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx @@ -374,22 +374,11 @@ export function ClipTrack( createEventListener(e.currentTarget, "mouseup", (e) => { dispose(); - // // If there's only one segment, don't open the clip config panel - // // since there's nothing to configure - just let the normal click behavior happen - // const hasOnlyOneSegment = segments().length === 1; - - // if (hasOnlyOneSegment) { - // // Clear any existing selection (zoom, layout, etc.) when clicking on a clip - // // This ensures the sidebar updates properly - // setEditorState("timeline", "selection", null); - // } else { - // When there are multiple segments, show the clip configuration setEditorState("timeline", "selection", { type: "clip", index: i(), }); - // } props.handleUpdatePlayhead(e); }); }); diff --git a/apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx b/apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx index e6ec54e9f4..faa668e393 100644 --- a/apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx +++ b/apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx @@ -31,8 +31,14 @@ export function SceneTrack(props: { onDragStateChanged: (v: SceneSegmentDragState) => void; handleUpdatePlayhead: (e: MouseEvent) => void; }) { - const { project, setProject, projectHistory, setEditorState, editorState } = - useEditorContext(); + const { + project, + setProject, + projectHistory, + setEditorState, + editorState, + projectActions, + } = useEditorContext(); const { duration, secsPerPixel } = useTimelineContext(); @@ -200,13 +206,15 @@ export function SceneTrack(props: { {(segment, i) => { const { setTrackState } = useTrackContext(); - const sceneSegments = () => project.timeline!.sceneSegments!; + const sceneSegments = () => project.timeline?.sceneSegments ?? []; function createMouseDownDrag( setup: () => T, _update: (e: MouseEvent, v: T, initialMouseX: number) => void, ) { return (downEvent: MouseEvent) => { + if (editorState.timeline.interactMode !== "seek") return; + downEvent.stopPropagation(); const initial = setup(); @@ -298,6 +306,18 @@ export function SceneTrack(props: { onMouseLeave={() => { setHoveringSegment(false); }} + onMouseDown={(e) => { + e.stopPropagation(); + + if (editorState.timeline.interactMode === "split") { + const rect = e.currentTarget.getBoundingClientRect(); + const fraction = (e.clientX - rect.left) / rect.width; + + const splitTime = fraction * (segment.end - segment.start); + + projectActions.splitSceneSegment(i(), splitTime); + } + }} > void, ) { return (downEvent: MouseEvent) => { + if (editorState.timeline.interactMode !== "seek") return; + downEvent.stopPropagation(); const initial = setup(); @@ -420,6 +423,18 @@ export function ZoomTrack(props: { )} innerClass="ring-red-5" segment={segment} + onMouseDown={(e) => { + e.stopPropagation(); + + if (editorState.timeline.interactMode === "split") { + const rect = e.currentTarget.getBoundingClientRect(); + const fraction = (e.clientX - rect.left) / rect.width; + + const splitTime = fraction * (segment.end - segment.start); + + projectActions.splitZoomSegment(i(), splitTime); + } + }} > { + setProject( + "timeline", + "zoomSegments", + produce((segments) => { + const segment = segments[index]; + if (!segment) return; + + const newLengths = [segment.end - segment.start - time, time]; + + if (newLengths.some((l) => l < 1)) return; + + segments.splice(index + 1, 0, { + ...segment, + start: segment.start + time, + end: segment.end, + }); + segments[index].end = segment.start + time; + }), + ); + }, deleteZoomSegments: (segmentIndices: number[]) => { batch(() => { setProject( @@ -146,6 +166,27 @@ export const [EditorContextProvider, useEditorContext] = createContextProvider( setEditorState("timeline", "selection", null); }); }, + splitSceneSegment: (index: number, time: number) => { + setProject( + "timeline", + "sceneSegments", + produce((segments) => { + const segment = segments?.[index]; + if (!segment) return; + + const newLengths = [segment.end - segment.start - time, time]; + + if (newLengths.some((l) => l < 1)) return; + + segments.splice(index + 1, 0, { + ...segment, + start: segment.start + time, + end: segment.end, + }); + segments[index].end = segment.start + time; + }), + ); + }, deleteSceneSegment: (segmentIndex: number) => { batch(() => { setProject(