From 3e89d2998588f8e35c524e523ba2ee853f0f6475 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 20 Apr 2026 11:31:17 +0200 Subject: refactor: restructure files to their domains, new preview overlay system, and dep graph --- apps/web/src/hooks/actions/use-action-handler.ts | 52 --- apps/web/src/hooks/actions/use-editor-actions.ts | 488 ----------------------- 2 files changed, 540 deletions(-) delete mode 100644 apps/web/src/hooks/actions/use-action-handler.ts delete mode 100644 apps/web/src/hooks/actions/use-editor-actions.ts (limited to 'apps/web/src/hooks/actions') diff --git a/apps/web/src/hooks/actions/use-action-handler.ts b/apps/web/src/hooks/actions/use-action-handler.ts deleted file mode 100644 index 3f6dbd21..00000000 --- a/apps/web/src/hooks/actions/use-action-handler.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useCallback, useEffect, useRef } from "react"; -import type { - TAction, - TActionFunc, - TActionHandlerOptions, - TArgOfAction, - TInvocationTrigger, -} from "@/lib/actions"; -import { bindAction, unbindAction } from "@/lib/actions"; - -export function useActionHandler( - action: A, - handler: TActionFunc, - isActive: TActionHandlerOptions, -) { - const handlerRef = useRef>(handler); - const isBoundRef = useRef(false); - - useEffect(() => { - handlerRef.current = handler; - }, [handler]); - - const stableHandler = useCallback( - (...parameters: [TArgOfAction, TInvocationTrigger?]) => { - ( - handlerRef.current as ( - ...handlerParameters: [TArgOfAction, TInvocationTrigger?] - ) => void - )(...parameters); - }, - [], - ) as TActionFunc; - - useEffect(() => { - const shouldBind = - isActive === undefined || - (typeof isActive === "boolean" ? isActive : isActive.current); - - if (shouldBind && !isBoundRef.current) { - bindAction(action, stableHandler); - isBoundRef.current = true; - } else if (!shouldBind && isBoundRef.current) { - unbindAction(action, stableHandler); - isBoundRef.current = false; - } - - return () => { - unbindAction(action, stableHandler); - isBoundRef.current = false; - }; - }, [action, stableHandler, isActive]); -} diff --git a/apps/web/src/hooks/actions/use-editor-actions.ts b/apps/web/src/hooks/actions/use-editor-actions.ts deleted file mode 100644 index 873fd33a..00000000 --- a/apps/web/src/hooks/actions/use-editor-actions.ts +++ /dev/null @@ -1,488 +0,0 @@ -"use client"; - -import { useEffect, useRef } from "react"; -import { useTimelineStore } from "@/stores/timeline-store"; -import { useActionHandler } from "@/hooks/actions/use-action-handler"; -import { useEditor } from "../use-editor"; -import { useElementSelection } from "../timeline/element/use-element-selection"; -import { TICKS_PER_SECOND } from "@/lib/wasm"; -import { useKeyframeSelection } from "../timeline/element/use-keyframe-selection"; -import { getElementsAtTime, hasMediaId } from "@/lib/timeline"; -import { cancelInteraction } from "@/lib/cancel-interaction"; -import { invokeAction } from "@/lib/actions"; -import { canToggleSourceAudio } from "@/lib/timeline/audio-separation"; -import { - activateScope, - clearActiveScope, - type ScopeEntry, -} from "@/lib/selection/scope"; - -export function useEditorActions() { - const editor = useEditor(); - const { selectedElements, setElementSelection } = useElementSelection(); - const { selectedKeyframes, clearKeyframeSelection } = useKeyframeSelection(); - const selectedMaskPointSelection = useEditor((e) => - e.selection.getSelectedMaskPointSelection(), - ); - const toggleSnapping = useTimelineStore((s) => s.toggleSnapping); - const rippleEditingEnabled = useTimelineStore((s) => s.rippleEditingEnabled); - const toggleRippleEditing = useTimelineStore((s) => s.toggleRippleEditing); - const hasTimelineSelectionRef = useRef(false); - const clearTimelineSelectionRef = useRef(() => {}); - const clearTimelineActiveSelectionRef = useRef(() => {}); - const timelineScopeRef = useRef(null); - const hasTimelineSelection = - selectedElements.length > 0 || - selectedKeyframes.length > 0 || - selectedMaskPointSelection !== null; - - hasTimelineSelectionRef.current = hasTimelineSelection; - clearTimelineSelectionRef.current = () => { - editor.selection.clearSelection(); - }; - clearTimelineActiveSelectionRef.current = () => { - editor.selection.clearMostSpecificSelection(); - }; - - if (!timelineScopeRef.current) { - timelineScopeRef.current = { - hasSelection: () => hasTimelineSelectionRef.current, - clear: () => { - clearTimelineSelectionRef.current(); - }, - clearActive: () => { - clearTimelineActiveSelectionRef.current(); - }, - }; - } - - useEffect(() => { - if (!hasTimelineSelection) { - return; - } - - const timelineScope = timelineScopeRef.current; - if (!timelineScope) { - return; - } - - return activateScope({ entry: timelineScope }); - }, [hasTimelineSelection]); - - useActionHandler( - "toggle-play", - () => { - editor.playback.toggle(); - }, - undefined, - ); - - useActionHandler( - "stop-playback", - () => { - if (editor.playback.getIsPlaying()) { - editor.playback.toggle(); - } - editor.playback.seek({ time: 0 }); - }, - undefined, - ); - - useActionHandler( - "seek-forward", - (args) => { - const seconds = args?.seconds ?? 1; - editor.playback.seek({ - time: Math.min( - editor.timeline.getTotalDuration(), - editor.playback.getCurrentTime() + seconds, - ), - }); - }, - undefined, - ); - - useActionHandler( - "seek-backward", - (args) => { - const seconds = args?.seconds ?? 1; - editor.playback.seek({ - time: Math.max(0, editor.playback.getCurrentTime() - seconds), - }); - }, - undefined, - ); - - useActionHandler( - "frame-step-forward", - () => { - const fps = editor.project.getActive().settings.fps; - const ticksPerFrame = Math.round( - (TICKS_PER_SECOND * fps.denominator) / fps.numerator, - ); - editor.playback.seek({ - time: Math.min( - editor.timeline.getTotalDuration(), - editor.playback.getCurrentTime() + ticksPerFrame, - ), - }); - }, - undefined, - ); - - useActionHandler( - "frame-step-backward", - () => { - const fps = editor.project.getActive().settings.fps; - const ticksPerFrame = Math.round( - (TICKS_PER_SECOND * fps.denominator) / fps.numerator, - ); - editor.playback.seek({ - time: Math.max(0, editor.playback.getCurrentTime() - ticksPerFrame), - }); - }, - undefined, - ); - - useActionHandler( - "jump-forward", - (args) => { - const seconds = args?.seconds ?? 5; - editor.playback.seek({ - time: Math.min( - editor.timeline.getTotalDuration(), - editor.playback.getCurrentTime() + seconds, - ), - }); - }, - undefined, - ); - - useActionHandler( - "jump-backward", - (args) => { - const seconds = args?.seconds ?? 5; - editor.playback.seek({ - time: Math.max(0, editor.playback.getCurrentTime() - seconds), - }); - }, - undefined, - ); - - useActionHandler( - "goto-start", - () => { - editor.playback.seek({ time: 0 }); - }, - undefined, - ); - - useActionHandler( - "goto-end", - () => { - editor.playback.seek({ time: editor.timeline.getTotalDuration() }); - }, - undefined, - ); - - useActionHandler( - "split", - () => { - const currentTime = editor.playback.getCurrentTime(); - const tracks = editor.scenes.getActiveScene().tracks; - const elementsToSplit = - selectedElements.length > 0 - ? selectedElements - : getElementsAtTime({ - tracks, - time: currentTime, - }); - - if (elementsToSplit.length === 0) return; - - editor.timeline.splitElements({ - elements: elementsToSplit, - splitTime: currentTime, - }); - }, - undefined, - ); - - useActionHandler( - "split-left", - () => { - const currentTime = editor.playback.getCurrentTime(); - const tracks = editor.scenes.getActiveScene().tracks; - const elementsToSplit = - selectedElements.length > 0 - ? selectedElements - : getElementsAtTime({ - tracks, - time: currentTime, - }); - - if (elementsToSplit.length === 0) return; - - const rightSideElements = editor.timeline.splitElements({ - elements: elementsToSplit, - splitTime: currentTime, - retainSide: "right", - }); - - if (rippleEditingEnabled && rightSideElements.length > 0) { - const firstRightElement = editor.timeline.getElementsWithTracks({ - elements: [rightSideElements[0]], - })[0]; - if (firstRightElement) { - editor.playback.seek({ time: firstRightElement.element.startTime }); - } - } - }, - undefined, - ); - - useActionHandler( - "split-right", - () => { - const currentTime = editor.playback.getCurrentTime(); - const tracks = editor.scenes.getActiveScene().tracks; - const elementsToSplit = - selectedElements.length > 0 - ? selectedElements - : getElementsAtTime({ - tracks, - time: currentTime, - }); - - if (elementsToSplit.length === 0) return; - - editor.timeline.splitElements({ - elements: elementsToSplit, - splitTime: currentTime, - retainSide: "left", - }); - }, - undefined, - ); - - useActionHandler( - "delete-selected", - () => { - switch (editor.selection.getActiveSelectionKind()) { - case "mask-points": - if (!selectedMaskPointSelection) { - return; - } - editor.timeline.deleteCustomMaskPoints({ - trackId: selectedMaskPointSelection.trackId, - elementId: selectedMaskPointSelection.elementId, - maskId: selectedMaskPointSelection.maskId, - pointIds: selectedMaskPointSelection.pointIds, - }); - return; - case "keyframes": - if (selectedKeyframes.length === 0) { - return; - } - editor.timeline.removeKeyframes({ keyframes: selectedKeyframes }); - clearKeyframeSelection(); - return; - case "elements": - if (selectedElements.length === 0) { - return; - } - editor.timeline.deleteElements({ - elements: selectedElements, - }); - return; - default: - return; - } - }, - undefined, - ); - - useActionHandler( - "toggle-source-audio", - () => { - if (selectedElements.length !== 1) { - return; - } - - const selectedElement = editor.timeline.getElementsWithTracks({ - elements: selectedElements, - })[0]; - if (!selectedElement) { - return; - } - - const mediaAsset = (() => { - const { element } = selectedElement; - if (!hasMediaId(element)) { - return null; - } - - return ( - editor.media - .getAssets() - .find((asset) => asset.id === element.mediaId) ?? null - ); - })(); - if (!canToggleSourceAudio(selectedElement.element, mediaAsset)) { - return; - } - - editor.timeline.toggleSourceAudioSeparation({ - trackId: selectedElement.track.id, - elementId: selectedElement.element.id, - }); - }, - undefined, - ); - - useActionHandler( - "select-all", - () => { - const scene = editor.scenes.getActiveScene(); - const allElements = [ - ...scene.tracks.overlay, - scene.tracks.main, - ...scene.tracks.audio, - ].flatMap((track) => - track.elements.map((element) => ({ - trackId: track.id, - elementId: element.id, - })), - ); - setElementSelection({ elements: allElements }); - }, - undefined, - ); - - useActionHandler( - "cancel-interaction", - () => { - if (!cancelInteraction()) { - invokeAction("deselect-all"); - } - }, - undefined, - ); - - useActionHandler( - "deselect-all", - () => { - if (!clearActiveScope()) { - editor.selection.clearMostSpecificSelection(); - } - }, - undefined, - ); - - useActionHandler( - "duplicate-selected", - () => { - editor.timeline.duplicateElements({ - elements: selectedElements, - }); - }, - undefined, - ); - - useActionHandler( - "toggle-elements-muted-selected", - () => { - editor.timeline.toggleElementsMuted({ elements: selectedElements }); - }, - undefined, - ); - - useActionHandler( - "toggle-elements-visibility-selected", - () => { - editor.timeline.toggleElementsVisibility({ elements: selectedElements }); - }, - undefined, - ); - - useActionHandler( - "toggle-bookmark", - () => { - editor.scenes.toggleBookmark({ time: editor.playback.getCurrentTime() }); - }, - undefined, - ); - - useActionHandler( - "copy-selected", - () => { - editor.clipboard.copy(); - }, - undefined, - ); - - useActionHandler( - "paste-copied", - () => { - editor.clipboard.paste(); - }, - undefined, - ); - - useActionHandler( - "toggle-snapping", - () => { - toggleSnapping(); - }, - undefined, - ); - - useActionHandler( - "toggle-ripple-editing", - () => { - toggleRippleEditing(); - }, - undefined, - ); - - useActionHandler( - "undo", - () => { - editor.command.undo(); - }, - undefined, - ); - - useActionHandler( - "redo", - () => { - editor.command.redo(); - }, - undefined, - ); - - // todo: potnetially unify these two actions: - useActionHandler( - "remove-media-asset", - (args) => { - if (!args) return; - editor.media.removeMediaAsset({ - projectId: args.projectId, - id: args.assetId, - }); - }, - undefined, - ); - - useActionHandler( - "remove-media-assets", - (args) => { - if (!args) return; - editor.media.removeMediaAssets({ - projectId: args.projectId, - ids: args.assetIds, - }); - }, - undefined, - ); -} -- cgit v1.3.1