mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
codebase overhaul (#697)
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./definitions";
|
||||
export * from "./types";
|
||||
export * from "./registry";
|
||||
@@ -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));
|
||||
};
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user