summaryrefslogtreecommitdiff
path: root/apps/web/src/hooks/actions
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/hooks/actions
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/hooks/actions')
-rw-r--r--apps/web/src/hooks/actions/use-action-handler.ts52
-rw-r--r--apps/web/src/hooks/actions/use-editor-actions.ts488
2 files changed, 0 insertions, 540 deletions
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<A extends TAction>(
- action: A,
- handler: TActionFunc<A>,
- isActive: TActionHandlerOptions,
-) {
- const handlerRef = useRef<TActionFunc<A>>(handler);
- const isBoundRef = useRef(false);
-
- useEffect(() => {
- handlerRef.current = handler;
- }, [handler]);
-
- const stableHandler = useCallback(
- (...parameters: [TArgOfAction<A>, TInvocationTrigger?]) => {
- (
- handlerRef.current as (
- ...handlerParameters: [TArgOfAction<A>, TInvocationTrigger?]
- ) => void
- )(...parameters);
- },
- [],
- ) as TActionFunc<A>;
-
- 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<ScopeEntry | null>(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,
- );
-}