summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/actions
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/lib/actions
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/lib/actions')
-rw-r--r--apps/web/src/lib/actions/definitions.ts162
-rw-r--r--apps/web/src/lib/actions/index.ts3
-rw-r--r--apps/web/src/lib/actions/registry.ts61
-rw-r--r--apps/web/src/lib/actions/types.ts42
4 files changed, 268 insertions, 0 deletions
diff --git a/apps/web/src/lib/actions/definitions.ts b/apps/web/src/lib/actions/definitions.ts
new file mode 100644
index 00000000..83144a25
--- /dev/null
+++ b/apps/web/src/lib/actions/definitions.ts
@@ -0,0 +1,162 @@
+import type { ShortcutKey } from "@/types/keybinding";
+
+export type TActionCategory =
+ | "playback"
+ | "navigation"
+ | "editing"
+ | "selection"
+ | "history"
+ | "timeline"
+ | "controls";
+
+export interface TActionDefinition {
+ description: string;
+ category: TActionCategory;
+ defaultShortcuts?: ShortcutKey[];
+ args?: Record<string, unknown>;
+}
+
+export const ACTIONS = {
+ "toggle-play": {
+ description: "Play/Pause",
+ category: "playback",
+ defaultShortcuts: ["space", "k"],
+ },
+ "stop-playback": {
+ description: "Stop playback",
+ category: "playback",
+ },
+ "seek-forward": {
+ description: "Seek forward 1 second",
+ category: "playback",
+ defaultShortcuts: ["l"],
+ args: { seconds: "number" },
+ },
+ "seek-backward": {
+ description: "Seek backward 1 second",
+ category: "playback",
+ defaultShortcuts: ["j"],
+ args: { seconds: "number" },
+ },
+ "frame-step-forward": {
+ description: "Frame step forward",
+ category: "navigation",
+ defaultShortcuts: ["right"],
+ },
+ "frame-step-backward": {
+ description: "Frame step backward",
+ category: "navigation",
+ defaultShortcuts: ["left"],
+ },
+ "jump-forward": {
+ description: "Jump forward 5 seconds",
+ category: "navigation",
+ defaultShortcuts: ["shift+right"],
+ args: { seconds: "number" },
+ },
+ "jump-backward": {
+ description: "Jump backward 5 seconds",
+ category: "navigation",
+ defaultShortcuts: ["shift+left"],
+ args: { seconds: "number" },
+ },
+ "goto-start": {
+ description: "Go to timeline start",
+ category: "navigation",
+ defaultShortcuts: ["home", "enter"],
+ },
+ "goto-end": {
+ description: "Go to timeline end",
+ category: "navigation",
+ defaultShortcuts: ["end"],
+ },
+ split: {
+ description: "Split elements at playhead",
+ category: "editing",
+ defaultShortcuts: ["s"],
+ },
+ "split-left": {
+ description: "Split and remove left",
+ category: "editing",
+ defaultShortcuts: ["q"],
+ },
+ "split-right": {
+ description: "Split and remove right",
+ category: "editing",
+ defaultShortcuts: ["w"],
+ },
+ "delete-selected": {
+ description: "Delete selected elements",
+ category: "editing",
+ defaultShortcuts: ["backspace", "delete"],
+ },
+ "copy-selected": {
+ description: "Copy selected elements",
+ category: "editing",
+ defaultShortcuts: ["ctrl+c"],
+ },
+ "paste-copied": {
+ description: "Paste elements at playhead",
+ category: "editing",
+ defaultShortcuts: ["ctrl+v"],
+ },
+ "toggle-snapping": {
+ description: "Toggle snapping",
+ category: "editing",
+ defaultShortcuts: ["n"],
+ },
+ "select-all": {
+ description: "Select all elements",
+ category: "selection",
+ defaultShortcuts: ["ctrl+a"],
+ },
+ "duplicate-selected": {
+ description: "Duplicate selected element",
+ category: "selection",
+ defaultShortcuts: ["ctrl+d"],
+ },
+ "toggle-elements-muted-selected": {
+ description: "Mute/unmute selected elements",
+ category: "selection",
+ },
+ "toggle-elements-visibility-selected": {
+ description: "Show/hide selected elements",
+ category: "selection",
+ },
+ "toggle-bookmark": {
+ description: "Toggle bookmark at playhead",
+ category: "timeline",
+ },
+ undo: {
+ description: "Undo",
+ category: "history",
+ defaultShortcuts: ["ctrl+z"],
+ },
+ redo: {
+ description: "Redo",
+ category: "history",
+ defaultShortcuts: ["ctrl+shift+z", "ctrl+y"],
+ },
+} as const satisfies Record<string, TActionDefinition>;
+
+export type TAction = keyof typeof ACTIONS;
+
+export function getActionDefinition(action: TAction): TActionDefinition {
+ return ACTIONS[action];
+}
+
+export function getDefaultShortcuts(): Record<ShortcutKey, TAction> {
+ const shortcuts: Record<string, TAction> = {};
+
+ for (const [action, def] of Object.entries(ACTIONS) as Array<
+ [TAction, TActionDefinition]
+ >) {
+ if (def.defaultShortcuts) {
+ for (const shortcut of def.defaultShortcuts) {
+ shortcuts[shortcut] = action;
+ }
+ }
+ }
+
+ return shortcuts as Record<ShortcutKey, TAction>;
+}
diff --git a/apps/web/src/lib/actions/index.ts b/apps/web/src/lib/actions/index.ts
new file mode 100644
index 00000000..801a846a
--- /dev/null
+++ b/apps/web/src/lib/actions/index.ts
@@ -0,0 +1,3 @@
+export * from "./definitions";
+export * from "./types";
+export * from "./registry";
diff --git a/apps/web/src/lib/actions/registry.ts b/apps/web/src/lib/actions/registry.ts
new file mode 100644
index 00000000..b9c84529
--- /dev/null
+++ b/apps/web/src/lib/actions/registry.ts
@@ -0,0 +1,61 @@
+import type {
+ TAction,
+ TActionFunc,
+ TActionWithArgs,
+ TActionWithOptionalArgs,
+ TActionArgsMap,
+ TArgOfAction,
+ TInvocationTrigger,
+} from "./types";
+
+type ActionHandler = (arg: unknown, trigger?: TInvocationTrigger) => void;
+const boundActions: Partial<Record<TAction, ActionHandler[]>> = {};
+
+export function bindAction<A extends TAction>(
+ action: A,
+ handler: TActionFunc<A>,
+) {
+ const handlers = boundActions[action];
+ const typedHandler = handler as ActionHandler;
+ if (handlers) {
+ handlers.push(typedHandler);
+ } else {
+ boundActions[action] = [typedHandler];
+ }
+}
+
+export function unbindAction<A extends TAction>(
+ action: A,
+ handler: TActionFunc<A>,
+) {
+ const handlers = boundActions[action];
+ if (!handlers) return;
+
+ const typedHandler = handler as ActionHandler;
+ boundActions[action] = handlers.filter((h) => h !== typedHandler);
+
+ if (boundActions[action]?.length === 0) {
+ delete boundActions[action];
+ }
+}
+
+type InvokeActionFunc = {
+ (
+ action: TActionWithOptionalArgs,
+ args?: undefined,
+ trigger?: TInvocationTrigger,
+ ): void;
+ <A extends TActionWithArgs>(
+ action: A,
+ args: TActionArgsMap[A],
+ trigger?: TInvocationTrigger,
+ ): void;
+};
+
+export const invokeAction: InvokeActionFunc = <A extends TAction>(
+ action: A,
+ args?: TArgOfAction<A>,
+ trigger?: TInvocationTrigger,
+) => {
+ boundActions[action]?.forEach((handler) => handler(args, trigger));
+};
diff --git a/apps/web/src/lib/actions/types.ts b/apps/web/src/lib/actions/types.ts
new file mode 100644
index 00000000..508bf10e
--- /dev/null
+++ b/apps/web/src/lib/actions/types.ts
@@ -0,0 +1,42 @@
+import type { MutableRefObject } from "react";
+import type { TAction } from "./definitions";
+
+export type { TAction };
+
+export type TActionArgsMap = {
+ "seek-forward": { seconds: number } | undefined;
+ "seek-backward": { seconds: number } | undefined;
+ "jump-forward": { seconds: number } | undefined;
+ "jump-backward": { seconds: number } | undefined;
+};
+
+type TKeysWithValueUndefined<T> = {
+ [K in keyof T]: undefined extends T[K] ? K : never;
+}[keyof T];
+
+export type TActionWithArgs = keyof TActionArgsMap;
+
+export type TActionWithOptionalArgs =
+ | TActionWithNoArgs
+ | TKeysWithValueUndefined<TActionArgsMap>;
+
+export type TActionWithNoArgs = Exclude<TAction, TActionWithArgs>;
+
+export type TArgOfAction<A extends TAction> = A extends TActionWithArgs
+ ? TActionArgsMap[A]
+ : undefined;
+
+export type TActionFunc<A extends TAction> = A extends TActionWithArgs
+ ? (arg: TArgOfAction<A>, trigger?: TInvocationTrigger) => void
+ : (_?: undefined, trigger?: TInvocationTrigger) => void;
+
+export type TInvocationTrigger = "keypress" | "mouseclick";
+
+export type TBoundActionList = {
+ [A in TAction]?: Array<TActionFunc<A>>;
+};
+
+export type TActionHandlerOptions =
+ | MutableRefObject<boolean>
+ | boolean
+ | undefined;