Overview
OpenCut uses an actions system to handle user-triggered operations. Actions are defined in @/lib/actions/definitions.ts and handlers are registered using these hooks.
useEditorActions()
A top-level hook that registers all core editor action handlers for playback, timeline operations, and editing commands.
Signature
function useEditorActions(): void
Usage
This hook is typically called once at the application level to set up all standard editor actions:
import { useEditorActions } from '@/hooks/actions/use-editor-actions';
function EditorProvider() {
useEditorActions();
return <Editor />;
}
Registered actions
The hook registers handlers for these action categories:
Playback controls:
toggle-play - Toggle play/pause
stop-playback - Stop and reset to start
seek-forward - Seek forward by seconds
seek-backward - Seek backward by seconds
frame-step-forward - Step forward one frame
frame-step-backward - Step backward one frame
jump-forward - Jump forward 5 seconds
jump-backward - Jump backward 5 seconds
goto-start - Go to timeline start
goto-end - Go to timeline end
Timeline editing:
split - Split elements at playhead
split-left - Split and keep right side
split-right - Split and keep left side
delete-selected - Delete selected elements
duplicate-selected - Duplicate selected elements
Selection:
select-all - Select all timeline elements
copy-selected - Copy selected to clipboard
paste-copied - Paste from clipboard
Element properties:
toggle-elements-muted-selected - Toggle mute
toggle-elements-visibility-selected - Toggle visibility
Other:
toggle-bookmark - Add/remove bookmark at playhead
toggle-snapping - Toggle timeline snapping
undo - Undo last command
redo - Redo last undone command
useActionHandler()
A hook for registering a handler for a specific action.
Signature
function useActionHandler<A extends TAction>(
action: A,
handler: TActionFunc<A>,
isActive: TActionHandlerOptions
): void
Parameters
The action identifier (e.g., "toggle-play", "split").
The function to execute when the action is invoked. Receives optional arguments and trigger type.(arg?: TArgOfAction<A>, trigger?: TInvocationTrigger) => void
isActive
boolean | MutableRefObject<boolean> | undefined
required
Controls whether the handler is active:
undefined - Always active
boolean - Static active state
MutableRefObject<boolean> - Dynamic active state via ref
Examples
Basic action handler
import { useActionHandler } from '@/hooks/actions/use-action-handler';
import { useEditor } from '@/hooks/use-editor';
function MyComponent() {
const editor = useEditor();
useActionHandler(
"toggle-play",
() => {
editor.playback.toggle();
},
undefined
);
return <div>Press Space to play/pause</div>;
}
Action with arguments
import { useActionHandler } from '@/hooks/actions/use-action-handler';
import { useEditor } from '@/hooks/use-editor';
function SeekControls() {
const editor = useEditor();
useActionHandler(
"seek-forward",
(args) => {
const seconds = args?.seconds ?? 1;
editor.playback.seek({
time: Math.min(
editor.timeline.getTotalDuration(),
editor.playback.getCurrentTime() + seconds
)
});
},
undefined
);
return null;
}
Conditional handler
import { useActionHandler } from '@/hooks/actions/use-action-handler';
import { useEditor } from '@/hooks/use-editor';
import { useState } from 'react';
function ConditionalActions() {
const editor = useEditor();
const [isEditMode, setIsEditMode] = useState(false);
// Only handle delete when in edit mode
useActionHandler(
"delete-selected",
() => {
const selected = editor.selection.getSelected();
editor.timeline.deleteElements({ elements: selected });
},
isEditMode
);
return (
<button onClick={() => setIsEditMode(!isEditMode)}>
{isEditMode ? 'Exit edit mode' : 'Enter edit mode'}
</button>
);
}
Complex handler with state
import { useActionHandler } from '@/hooks/actions/use-action-handler';
import { useEditor } from '@/hooks/use-editor';
import { useElementSelection } from '@/hooks/timeline/element/use-element-selection';
import { getElementsAtTime } from '@/lib/timeline';
function SplitHandler() {
const editor = useEditor();
const { selectedElements } = useElementSelection();
useActionHandler(
"split",
() => {
const currentTime = editor.playback.getCurrentTime();
const elementsToSplit =
selectedElements.length > 0
? selectedElements
: getElementsAtTime({
tracks: editor.timeline.getTracks(),
time: currentTime,
});
if (elementsToSplit.length === 0) return;
editor.timeline.splitElements({
elements: elementsToSplit,
splitTime: currentTime,
});
},
undefined
);
return null;
}
Action invocation
To trigger actions from UI components, use invokeAction() instead of calling editor methods directly:
import { invokeAction } from '@/lib/actions';
// Good - uses action system with UX feedback
const handleSplit = () => invokeAction("split");
// Avoid - bypasses action system
const handleSplit = () => editor.timeline.splitElements({ ... });
The action system provides user feedback (toasts, validation) that direct editor method calls bypass. Use invokeAction() for user-triggered operations.
Types
// Action identifier
type TAction = "toggle-play" | "split" | "delete-selected" | ...;
// Action function signature
type TActionFunc<A extends TAction> = A extends TActionWithArgs
? (arg: TArgOfAction<A>, trigger?: TInvocationTrigger) => void
: (_?: undefined, trigger?: TInvocationTrigger) => void;
// Invocation trigger (how the action was invoked)
type TInvocationTrigger = "keypress" | "mouseclick";
// Handler options
type TActionHandlerOptions =
| MutableRefObject<boolean>
| boolean
| undefined;
See also