summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline/timeline-action-handlers.tsx
diff options
context:
space:
mode:
authorSimon Orzel <simonorzel@gmail.com>2025-07-17 01:59:39 +0200
committerSimon Orzel <simonorzel@gmail.com>2025-07-17 01:59:39 +0200
commit9fa9f406fe2722cec1e0d65d9b3868acd1e99f87 (patch)
tree8d6eebabf99941c970a36dc271da9e9d63128f42 /apps/web/src/components/editor/timeline/timeline-action-handlers.tsx
parent0d2894feda369d518f650ac2c832edb0bb8b9254 (diff)
downloadopencut-9fa9f406fe2722cec1e0d65d9b3868acd1e99f87.tar.gz
feat: splits working code
Diffstat (limited to 'apps/web/src/components/editor/timeline/timeline-action-handlers.tsx')
-rw-r--r--apps/web/src/components/editor/timeline/timeline-action-handlers.tsx162
1 files changed, 162 insertions, 0 deletions
diff --git a/apps/web/src/components/editor/timeline/timeline-action-handlers.tsx b/apps/web/src/components/editor/timeline/timeline-action-handlers.tsx
new file mode 100644
index 00000000..ca12d71a
--- /dev/null
+++ b/apps/web/src/components/editor/timeline/timeline-action-handlers.tsx
@@ -0,0 +1,162 @@
+import { toast } from "sonner";
+import { useTimelineStore } from "@/stores/timeline-store";
+import { usePlaybackStore } from "@/stores/playback-store";
+import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
+
+export function useTimelineActionHandlers() {
+ const {
+ tracks,
+ addTrack,
+ addElementToTrack,
+ removeElementFromTrack,
+ selectedElements,
+ clearSelectedElements,
+ splitElement,
+ splitAndKeepLeft,
+ splitAndKeepRight,
+ separateAudio,
+ } = useTimelineStore();
+ const { currentTime } = usePlaybackStore();
+
+ // Action handlers for toolbar
+ const handleSplitSelected = () => {
+ if (selectedElements.length === 0) {
+ toast.error("No elements selected");
+ return;
+ }
+ let splitCount = 0;
+ selectedElements.forEach(({ trackId, elementId }) => {
+ const track = tracks.find((t) => t.id === trackId);
+ const element = track?.elements.find((c) => c.id === elementId);
+ if (element && track) {
+ const effectiveStart = element.startTime;
+ const effectiveEnd =
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd);
+
+ if (currentTime > effectiveStart && currentTime < effectiveEnd) {
+ const newElementId = splitElement(trackId, elementId, currentTime);
+ if (newElementId) splitCount++;
+ }
+ }
+ });
+ if (splitCount === 0) {
+ toast.error("Playhead must be within selected elements to split");
+ }
+ };
+
+ const handleDuplicateSelected = () => {
+ if (selectedElements.length === 0) {
+ toast.error("No elements selected");
+ return;
+ }
+ const canDuplicate = selectedElements.length === 1;
+ if (!canDuplicate) return;
+
+ const newSelections: { trackId: string; elementId: string }[] = [];
+
+ selectedElements.forEach(({ trackId, elementId }) => {
+ const track = tracks.find((t) => t.id === trackId);
+ const element = track?.elements.find((el) => el.id === elementId);
+
+ if (element) {
+ const newStartTime =
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd) +
+ 0.1;
+
+ // Create element without id (will be generated by store)
+ const { id, ...elementWithoutId } = element;
+
+ addElementToTrack(trackId, {
+ ...elementWithoutId,
+ startTime: newStartTime,
+ });
+
+ // We can't predict the new id, so just clear selection for now
+ // TODO: addElementToTrack could return the new element id
+ }
+ });
+
+ clearSelectedElements();
+ };
+
+ const handleFreezeSelected = () => {
+ toast.info("Freeze frame functionality coming soon!");
+ };
+
+ const handleSplitAndKeepLeft = () => {
+ if (selectedElements.length !== 1) {
+ toast.error("Select exactly one element");
+ return;
+ }
+ const { trackId, elementId } = selectedElements[0];
+ const track = tracks.find((t) => t.id === trackId);
+ const element = track?.elements.find((c) => c.id === elementId);
+ if (!element) return;
+ const effectiveStart = element.startTime;
+ const effectiveEnd =
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd);
+ if (currentTime <= effectiveStart || currentTime >= effectiveEnd) {
+ toast.error("Playhead must be within selected element");
+ return;
+ }
+ splitAndKeepLeft(trackId, elementId, currentTime);
+ };
+
+ const handleSplitAndKeepRight = () => {
+ if (selectedElements.length !== 1) {
+ toast.error("Select exactly one element");
+ return;
+ }
+ const { trackId, elementId } = selectedElements[0];
+ const track = tracks.find((t) => t.id === trackId);
+ const element = track?.elements.find((c) => c.id === elementId);
+ if (!element) return;
+ const effectiveStart = element.startTime;
+ const effectiveEnd =
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd);
+ if (currentTime <= effectiveStart || currentTime >= effectiveEnd) {
+ toast.error("Playhead must be within selected element");
+ return;
+ }
+ splitAndKeepRight(trackId, elementId, currentTime);
+ };
+
+ const handleSeparateAudio = () => {
+ if (selectedElements.length !== 1) {
+ toast.error("Select exactly one media element to separate audio");
+ return;
+ }
+ const { trackId, elementId } = selectedElements[0];
+ const track = tracks.find((t) => t.id === trackId);
+ if (!track || track.type !== "media") {
+ toast.error("Select a media element to separate audio");
+ return;
+ }
+ separateAudio(trackId, elementId);
+ };
+
+ const handleDeleteSelected = () => {
+ if (selectedElements.length === 0) {
+ toast.error("No elements selected");
+ return;
+ }
+ selectedElements.forEach(({ trackId, elementId }) => {
+ removeElementFromTrack(trackId, elementId);
+ });
+ clearSelectedElements();
+ };
+
+ return {
+ handleSplitSelected,
+ handleDuplicateSelected,
+ handleFreezeSelected,
+ handleSplitAndKeepLeft,
+ handleSplitAndKeepRight,
+ handleSeparateAudio,
+ handleDeleteSelected,
+ };
+}