Skip to content

Commit 45c168d

Browse files
authored
Allow splitting zoom and scene segments (#1222)
* allow splitting zoom segments * split scene track
1 parent 82c3a34 commit 45c168d

File tree

5 files changed

+88
-24
lines changed

5 files changed

+88
-24
lines changed

apps/desktop/src/routes/editor/ConfigSidebar.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,26 +1913,25 @@ function ZoomSegmentPreview(props: {
19131913

19141914
const start = createMemo(() => props.segment.start);
19151915

1916-
const segmentIndex = createMemo(() => {
1916+
const clipSegment = createMemo(() => {
19171917
const st = start();
1918-
const i = project.timeline?.segments.findIndex(
1919-
(s) => s.start <= st && s.end > st,
1920-
);
1921-
if (i === undefined || i === -1) return 0;
1922-
return i;
1918+
return project.timeline?.segments.find((s) => s.start <= st && s.end > st);
19231919
});
19241920

19251921
const relativeTime = createMemo(() => {
19261922
const st = start();
1927-
const segment = project.timeline?.segments[segmentIndex()];
1923+
const segment = clipSegment();
19281924
if (!segment) return 0;
19291925
return Math.max(0, st - segment.start);
19301926
});
19311927

19321928
const video = document.createElement("video");
19331929
createEffect(() => {
1930+
// TODO: make this not hardcoded
19341931
const path = convertFileSrc(
1935-
`${editorInstance.path}/content/segments/segment-${segmentIndex()}/display.mp4`,
1932+
`${editorInstance.path}/content/segments/segment-${
1933+
clipSegment()?.recordingSegment ?? 0
1934+
}/display.mp4`,
19361935
);
19371936
video.src = path;
19381937
video.preload = "auto";

apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -374,22 +374,11 @@ export function ClipTrack(
374374
createEventListener(e.currentTarget, "mouseup", (e) => {
375375
dispose();
376376

377-
// // If there's only one segment, don't open the clip config panel
378-
// // since there's nothing to configure - just let the normal click behavior happen
379-
// const hasOnlyOneSegment = segments().length === 1;
380-
381-
// if (hasOnlyOneSegment) {
382-
// // Clear any existing selection (zoom, layout, etc.) when clicking on a clip
383-
// // This ensures the sidebar updates properly
384-
// setEditorState("timeline", "selection", null);
385-
// } else {
386-
// When there are multiple segments, show the clip configuration
387377
setEditorState("timeline", "selection", {
388378
type: "clip",
389379
index: i(),
390380
});
391381

392-
// }
393382
props.handleUpdatePlayhead(e);
394383
});
395384
});

apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,14 @@ export function SceneTrack(props: {
3131
onDragStateChanged: (v: SceneSegmentDragState) => void;
3232
handleUpdatePlayhead: (e: MouseEvent) => void;
3333
}) {
34-
const { project, setProject, projectHistory, setEditorState, editorState } =
35-
useEditorContext();
34+
const {
35+
project,
36+
setProject,
37+
projectHistory,
38+
setEditorState,
39+
editorState,
40+
projectActions,
41+
} = useEditorContext();
3642

3743
const { duration, secsPerPixel } = useTimelineContext();
3844

@@ -200,13 +206,15 @@ export function SceneTrack(props: {
200206
{(segment, i) => {
201207
const { setTrackState } = useTrackContext();
202208

203-
const sceneSegments = () => project.timeline!.sceneSegments!;
209+
const sceneSegments = () => project.timeline?.sceneSegments ?? [];
204210

205211
function createMouseDownDrag<T>(
206212
setup: () => T,
207213
_update: (e: MouseEvent, v: T, initialMouseX: number) => void,
208214
) {
209215
return (downEvent: MouseEvent) => {
216+
if (editorState.timeline.interactMode !== "seek") return;
217+
210218
downEvent.stopPropagation();
211219

212220
const initial = setup();
@@ -298,6 +306,18 @@ export function SceneTrack(props: {
298306
onMouseLeave={() => {
299307
setHoveringSegment(false);
300308
}}
309+
onMouseDown={(e) => {
310+
e.stopPropagation();
311+
312+
if (editorState.timeline.interactMode === "split") {
313+
const rect = e.currentTarget.getBoundingClientRect();
314+
const fraction = (e.clientX - rect.left) / rect.width;
315+
316+
const splitTime = fraction * (segment.end - segment.start);
317+
318+
projectActions.splitSceneSegment(i(), splitTime);
319+
}
320+
}}
301321
>
302322
<SegmentHandle
303323
position="start"

apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function ZoomTrack(props: {
4141
setEditorState,
4242
editorState,
4343
totalDuration,
44+
projectActions,
4445
} = useEditorContext();
4546

4647
const { duration, secsPerPixel } = useTimelineContext();
@@ -291,6 +292,8 @@ export function ZoomTrack(props: {
291292
_update: (e: MouseEvent, v: T, initialMouseX: number) => void,
292293
) {
293294
return (downEvent: MouseEvent) => {
295+
if (editorState.timeline.interactMode !== "seek") return;
296+
294297
downEvent.stopPropagation();
295298

296299
const initial = setup();
@@ -420,6 +423,18 @@ export function ZoomTrack(props: {
420423
)}
421424
innerClass="ring-red-5"
422425
segment={segment}
426+
onMouseDown={(e) => {
427+
e.stopPropagation();
428+
429+
if (editorState.timeline.interactMode === "split") {
430+
const rect = e.currentTarget.getBoundingClientRect();
431+
const fraction = (e.clientX - rect.left) / rect.width;
432+
433+
const splitTime = fraction * (segment.end - segment.start);
434+
435+
projectActions.splitZoomSegment(i(), splitTime);
436+
}
437+
}}
423438
>
424439
<SegmentHandle
425440
position="start"

apps/desktop/src/routes/editor/context.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,9 @@ export const [EditorContextProvider, useEditorContext] = createContextProvider(
9595
const segment = segments[currentSegmentIndex];
9696

9797
segments.splice(currentSegmentIndex + 1, 0, {
98+
...segment,
9899
start: segment.start + searchTime,
99100
end: segment.end,
100-
timescale: 1,
101-
recordingSegment: segment.recordingSegment,
102101
});
103102
segments[currentSegmentIndex].end = segment.start + searchTime;
104103
}),
@@ -128,6 +127,27 @@ export const [EditorContextProvider, useEditorContext] = createContextProvider(
128127
setEditorState("timeline", "selection", null);
129128
});
130129
},
130+
splitZoomSegment: (index: number, time: number) => {
131+
setProject(
132+
"timeline",
133+
"zoomSegments",
134+
produce((segments) => {
135+
const segment = segments[index];
136+
if (!segment) return;
137+
138+
const newLengths = [segment.end - segment.start - time, time];
139+
140+
if (newLengths.some((l) => l < 1)) return;
141+
142+
segments.splice(index + 1, 0, {
143+
...segment,
144+
start: segment.start + time,
145+
end: segment.end,
146+
});
147+
segments[index].end = segment.start + time;
148+
}),
149+
);
150+
},
131151
deleteZoomSegments: (segmentIndices: number[]) => {
132152
batch(() => {
133153
setProject(
@@ -146,6 +166,27 @@ export const [EditorContextProvider, useEditorContext] = createContextProvider(
146166
setEditorState("timeline", "selection", null);
147167
});
148168
},
169+
splitSceneSegment: (index: number, time: number) => {
170+
setProject(
171+
"timeline",
172+
"sceneSegments",
173+
produce((segments) => {
174+
const segment = segments?.[index];
175+
if (!segment) return;
176+
177+
const newLengths = [segment.end - segment.start - time, time];
178+
179+
if (newLengths.some((l) => l < 1)) return;
180+
181+
segments.splice(index + 1, 0, {
182+
...segment,
183+
start: segment.start + time,
184+
end: segment.end,
185+
});
186+
segments[index].end = segment.start + time;
187+
}),
188+
);
189+
},
149190
deleteSceneSegment: (segmentIndex: number) => {
150191
batch(() => {
151192
setProject(

0 commit comments

Comments
 (0)