From 281ec63d7e0dfe70534790deb790c5bbd6701502 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 30 Jul 2025 10:25:35 -0400 Subject: Fixed BUG Editor cursor does not stop at the end of an audio file Get duration of content on the timeline to make sure the playhead moves until the end of the timeline content when the content is less than 10 seconds. Originally it would go all the way to the 10 seconds when timeline content was less than that. I checked how clipchamp does it and they have a min 10 sec timeline but video timeline plays at length of content, so we are doing it like the pros. timeline is jumpy though will probably work on that. --- apps/web/src/hooks/use-editor-actions.ts | 318 +++++++++++++++++++------------ 1 file changed, 193 insertions(+), 125 deletions(-) (limited to 'apps/web/src/hooks') diff --git a/apps/web/src/hooks/use-editor-actions.ts b/apps/web/src/hooks/use-editor-actions.ts index b2fad55f..bfe9a7b9 100644 --- a/apps/web/src/hooks/use-editor-actions.ts +++ b/apps/web/src/hooks/use-editor-actions.ts @@ -26,136 +26,204 @@ export function useEditorActions() { const { activeProject } = useProjectStore(); // Playback actions - useActionHandler("toggle-play", () => { - toggle(); - }, undefined); - - useActionHandler("stop-playback", () => { - if (isPlaying) { + useActionHandler( + "toggle-play", + () => { toggle(); - } - seek(0); - }, undefined); - - useActionHandler("seek-forward", (args) => { - const seconds = args?.seconds ?? 1; - seek(Math.min(duration, currentTime + seconds)); - }, undefined); - - useActionHandler("seek-backward", (args) => { - const seconds = args?.seconds ?? 1; - seek(Math.max(0, currentTime - seconds)); - }, undefined); - - useActionHandler("frame-step-forward", () => { - const projectFps = activeProject?.fps || 30; - seek(Math.min(duration, currentTime + 1 / projectFps)); - }, undefined); - - useActionHandler("frame-step-backward", () => { - const projectFps = activeProject?.fps || 30; - seek(Math.max(0, currentTime - 1 / projectFps)); - }, undefined); - - useActionHandler("jump-forward", (args) => { - const seconds = args?.seconds ?? 5; - seek(Math.min(duration, currentTime + seconds)); - }, undefined); - - useActionHandler("jump-backward", (args) => { - const seconds = args?.seconds ?? 5; - seek(Math.max(0, currentTime - seconds)); - }, undefined); - - useActionHandler("goto-start", () => { - seek(0); - }, undefined); - - useActionHandler("goto-end", () => { - seek(duration); - }, undefined); + }, + undefined + ); + + useActionHandler( + "stop-playback", + () => { + if (isPlaying) { + toggle(); + } + seek(0); + }, + undefined + ); + + useActionHandler( + "seek-forward", + (args) => { + const seconds = args?.seconds ?? 1; + seek(Math.min(duration, currentTime + seconds)); + }, + undefined + ); + + useActionHandler( + "seek-backward", + (args) => { + const seconds = args?.seconds ?? 1; + seek(Math.max(0, currentTime - seconds)); + }, + undefined + ); + + useActionHandler( + "frame-step-forward", + () => { + const projectFps = activeProject?.fps || 30; + seek(Math.min(duration, currentTime + 1 / projectFps)); + }, + undefined + ); + + useActionHandler( + "frame-step-backward", + () => { + const projectFps = activeProject?.fps || 30; + seek(Math.max(0, currentTime - 1 / projectFps)); + }, + undefined + ); + + useActionHandler( + "jump-forward", + (args) => { + const seconds = args?.seconds ?? 5; + seek(Math.min(duration, currentTime + seconds)); + }, + undefined + ); + + useActionHandler( + "jump-backward", + (args) => { + const seconds = args?.seconds ?? 5; + seek(Math.max(0, currentTime - seconds)); + }, + undefined + ); + + useActionHandler( + "goto-start", + () => { + seek(0); + }, + undefined + ); + + useActionHandler( + "goto-end", + () => { + seek(duration); + }, + undefined + ); // Timeline editing actions - useActionHandler("split-element", () => { - if (selectedElements.length !== 1) { - toast.error("Select exactly one element to split"); - return; - } - - const { trackId, elementId } = selectedElements[0]; - const track = tracks.find((t: any) => t.id === trackId); - const element = track?.elements.find((el: any) => el.id === elementId); - - if (element) { - const effectiveStart = element.startTime; - const effectiveEnd = - element.startTime + - (element.duration - element.trimStart - element.trimEnd); - - if (currentTime > effectiveStart && currentTime < effectiveEnd) { - splitElement(trackId, elementId, currentTime); - } else { - toast.error("Playhead must be within selected element"); + useActionHandler( + "split-element", + () => { + if (selectedElements.length !== 1) { + toast.error("Select exactly one element to split"); + return; + } + + const { trackId, elementId } = selectedElements[0]; + const track = tracks.find((t: any) => t.id === trackId); + const element = track?.elements.find((el: any) => el.id === elementId); + + if (element) { + const effectiveStart = element.startTime; + const effectiveEnd = + element.startTime + + (element.duration - element.trimStart - element.trimEnd); + + if (currentTime > effectiveStart && currentTime < effectiveEnd) { + splitElement(trackId, elementId, currentTime); + } else { + toast.error("Playhead must be within selected element"); + } } - } - }, undefined); - - useActionHandler("delete-selected", () => { - if (selectedElements.length === 0) { - return; - } - selectedElements.forEach( - ({ trackId, elementId }: { trackId: string; elementId: string }) => { - removeElementFromTrack(trackId, elementId); + }, + undefined + ); + + useActionHandler( + "delete-selected", + () => { + if (selectedElements.length === 0) { + return; + } + selectedElements.forEach( + ({ trackId, elementId }: { trackId: string; elementId: string }) => { + removeElementFromTrack(trackId, elementId); + } + ); + clearSelectedElements(); + }, + undefined + ); + + useActionHandler( + "select-all", + () => { + const allElements = tracks.flatMap((track: any) => + track.elements.map((element: any) => ({ + trackId: track.id, + elementId: element.id, + })) + ); + setSelectedElements(allElements); + }, + undefined + ); + + useActionHandler( + "duplicate-selected", + () => { + if (selectedElements.length !== 1) { + toast.error("Select exactly one element to duplicate"); + return; } - ); - clearSelectedElements(); - }, undefined); - - useActionHandler("select-all", () => { - const allElements = tracks.flatMap((track: any) => - track.elements.map((element: any) => ({ - trackId: track.id, - elementId: element.id, - })) - ); - setSelectedElements(allElements); - }, undefined); - - useActionHandler("duplicate-selected", () => { - if (selectedElements.length !== 1) { - toast.error("Select exactly one element to duplicate"); - return; - } - - const { trackId, elementId } = selectedElements[0]; - const track = tracks.find((t: any) => t.id === trackId); - const element = track?.elements.find((el: any) => el.id === elementId); - - if (element) { - const newStartTime = - element.startTime + - (element.duration - element.trimStart - element.trimEnd) + - 0.1; - const { id, ...elementWithoutId } = element; - - addElementToTrack(trackId, { - ...elementWithoutId, - startTime: newStartTime, - }); - } - }, undefined); - - useActionHandler("toggle-snapping", () => { - toggleSnapping(); - }, undefined); - // History actions - useActionHandler("undo", () => { - undo(); - }, undefined); + const { trackId, elementId } = selectedElements[0]; + const track = tracks.find((t: any) => t.id === trackId); + const element = track?.elements.find((el: any) => el.id === elementId); + + if (element) { + const newStartTime = + element.startTime + + (element.duration - element.trimStart - element.trimEnd) + + 0.1; + const { id, ...elementWithoutId } = element; + + addElementToTrack(trackId, { + ...elementWithoutId, + startTime: newStartTime, + }); + } + }, + undefined + ); + + useActionHandler( + "toggle-snapping", + () => { + toggleSnapping(); + }, + undefined + ); - useActionHandler("redo", () => { - redo(); - }, undefined); + // History actions + useActionHandler( + "undo", + () => { + undo(); + }, + undefined + ); + + useActionHandler( + "redo", + () => { + redo(); + }, + undefined + ); } -- cgit v1.3.1