summaryrefslogtreecommitdiff
path: root/apps/web/src/hooks
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/hooks')
-rw-r--r--apps/web/src/hooks/actions/use-editor-actions.ts43
-rw-r--r--apps/web/src/hooks/use-editor.ts117
-rw-r--r--apps/web/src/hooks/use-keybindings.ts8
-rw-r--r--apps/web/src/hooks/use-paste-media.ts11
4 files changed, 80 insertions, 99 deletions
diff --git a/apps/web/src/hooks/actions/use-editor-actions.ts b/apps/web/src/hooks/actions/use-editor-actions.ts
index be04a7d3..1c4735bb 100644
--- a/apps/web/src/hooks/actions/use-editor-actions.ts
+++ b/apps/web/src/hooks/actions/use-editor-actions.ts
@@ -7,10 +7,7 @@ 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 { getElementsAtTime, hasMediaId } from "@/lib/timeline";
import { cancelInteraction } from "@/lib/cancel-interaction";
import { invokeAction } from "@/lib/actions";
import { canToggleSourceAudio } from "@/lib/timeline/audio-separation";
@@ -24,8 +21,6 @@ export function useEditorActions() {
const editor = useEditor();
const { selectedElements, setElementSelection } = useElementSelection();
const { selectedKeyframes, clearKeyframeSelection } = useKeyframeSelection();
- const clipboard = useTimelineStore((s) => s.clipboard);
- const setClipboard = useTimelineStore((s) => s.setClipboard);
const toggleSnapping = useTimelineStore((s) => s.toggleSnapping);
const rippleEditingEnabled = useTimelineStore((s) => s.rippleEditingEnabled);
const toggleRippleEditing = useTimelineStore((s) => s.toggleRippleEditing);
@@ -111,7 +106,9 @@ export function useEditorActions() {
"frame-step-forward",
() => {
const fps = editor.project.getActive().settings.fps;
- const ticksPerFrame = Math.round(TICKS_PER_SECOND * fps.denominator / fps.numerator);
+ const ticksPerFrame = Math.round(
+ (TICKS_PER_SECOND * fps.denominator) / fps.numerator,
+ );
editor.playback.seek({
time: Math.min(
editor.timeline.getTotalDuration(),
@@ -126,7 +123,9 @@ export function useEditorActions() {
"frame-step-backward",
() => {
const fps = editor.project.getActive().settings.fps;
- const ticksPerFrame = Math.round(TICKS_PER_SECOND * fps.denominator / fps.numerator);
+ const ticksPerFrame = Math.round(
+ (TICKS_PER_SECOND * fps.denominator) / fps.numerator,
+ );
editor.playback.seek({
time: Math.max(0, editor.playback.getCurrentTime() - ticksPerFrame),
});
@@ -294,8 +293,9 @@ export function useEditorActions() {
}
return (
- editor.media.getAssets().find((asset) => asset.id === element.mediaId) ??
- null
+ editor.media
+ .getAssets()
+ .find((asset) => asset.id === element.mediaId) ?? null
);
})();
if (!canToggleSourceAudio(selectedElement.element, mediaAsset)) {
@@ -387,21 +387,7 @@ export function useEditorActions() {
useActionHandler(
"copy-selected",
() => {
- if (selectedElements.length === 0) return;
-
- const results = editor.timeline.getElementsWithTracks({
- elements: selectedElements,
- });
- const items = results.map(({ track, element }) => {
- const { id: _, ...elementWithoutId } = element;
- return {
- trackId: track.id,
- trackType: track.type,
- element: elementWithoutId,
- };
- });
-
- setClipboard({ items });
+ editor.clipboard.copy();
},
undefined,
);
@@ -409,12 +395,7 @@ export function useEditorActions() {
useActionHandler(
"paste-copied",
() => {
- if (!clipboard?.items.length) return;
-
- editor.timeline.pasteAtTime({
- time: editor.playback.getCurrentTime(),
- clipboardItems: clipboard.items,
- });
+ editor.clipboard.paste();
},
undefined,
);
diff --git a/apps/web/src/hooks/use-editor.ts b/apps/web/src/hooks/use-editor.ts
index bafa6cf7..1a5ca8ae 100644
--- a/apps/web/src/hooks/use-editor.ts
+++ b/apps/web/src/hooks/use-editor.ts
@@ -1,58 +1,59 @@
-import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
-import { EditorCore } from "@/core";
-
-function isShallowEqual(a: unknown, b: unknown): boolean {
- if (Object.is(a, b)) return true;
- if (!Array.isArray(a) || !Array.isArray(b)) return false;
- if (a.length !== b.length) return false;
- return a.every((item, i) => Object.is(item, b[i]));
-}
-
-const subscribeNone = () => () => {};
-
-export function useEditor(): EditorCore;
-export function useEditor<T>(selector: (editor: EditorCore) => T): T;
-export function useEditor<T>(
- selector?: (editor: EditorCore) => T,
-): EditorCore | T {
- const editor = useMemo(() => EditorCore.getInstance(), []);
- const selectorRef = useRef(selector);
- selectorRef.current = selector;
-
- const snapshotCacheRef = useRef<unknown>(undefined);
-
- const subscribeAll = useCallback(
- (onChange: () => void) => {
- const unsubscribers = [
- editor.playback.subscribe(onChange),
- editor.timeline.subscribe(onChange),
- editor.scenes.subscribe(onChange),
- editor.project.subscribe(onChange),
- editor.media.subscribe(onChange),
- editor.renderer.subscribe(onChange),
- editor.selection.subscribe(onChange),
- ];
- return () => {
- unsubscribers.forEach((unsubscribe) => {
- unsubscribe();
- });
- };
- },
- [editor],
- );
-
- const getSnapshot = useCallback(() => {
- const next = selectorRef.current ? selectorRef.current(editor) : editor;
- if (isShallowEqual(snapshotCacheRef.current, next)) {
- return snapshotCacheRef.current;
- }
- snapshotCacheRef.current = next;
- return next;
- }, [editor]);
-
- return useSyncExternalStore(
- selector ? subscribeAll : subscribeNone,
- getSnapshot,
- getSnapshot,
- ) as EditorCore | T;
-}
+import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
+import { EditorCore } from "@/core";
+
+function isShallowEqual(a: unknown, b: unknown): boolean {
+ if (Object.is(a, b)) return true;
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
+ if (a.length !== b.length) return false;
+ return a.every((item, i) => Object.is(item, b[i]));
+}
+
+const subscribeNone = () => () => {};
+
+export function useEditor(): EditorCore;
+export function useEditor<T>(selector: (editor: EditorCore) => T): T;
+export function useEditor<T>(
+ selector?: (editor: EditorCore) => T,
+): EditorCore | T {
+ const editor = useMemo(() => EditorCore.getInstance(), []);
+ const selectorRef = useRef(selector);
+ selectorRef.current = selector;
+
+ const snapshotCacheRef = useRef<unknown>(undefined);
+
+ const subscribeAll = useCallback(
+ (onChange: () => void) => {
+ const unsubscribers = [
+ editor.playback.subscribe(onChange),
+ editor.timeline.subscribe(onChange),
+ editor.scenes.subscribe(onChange),
+ editor.project.subscribe(onChange),
+ editor.media.subscribe(onChange),
+ editor.renderer.subscribe(onChange),
+ editor.selection.subscribe(onChange),
+ editor.clipboard.subscribe(onChange),
+ ];
+ return () => {
+ unsubscribers.forEach((unsubscribe) => {
+ unsubscribe();
+ });
+ };
+ },
+ [editor],
+ );
+
+ const getSnapshot = useCallback(() => {
+ const next = selectorRef.current ? selectorRef.current(editor) : editor;
+ if (isShallowEqual(snapshotCacheRef.current, next)) {
+ return snapshotCacheRef.current;
+ }
+ snapshotCacheRef.current = next;
+ return next;
+ }, [editor]);
+
+ return useSyncExternalStore(
+ selector ? subscribeAll : subscribeNone,
+ getSnapshot,
+ getSnapshot,
+ ) as EditorCore | T;
+}
diff --git a/apps/web/src/hooks/use-keybindings.ts b/apps/web/src/hooks/use-keybindings.ts
index 69a4e847..3f96c340 100644
--- a/apps/web/src/hooks/use-keybindings.ts
+++ b/apps/web/src/hooks/use-keybindings.ts
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { invokeAction } from "@/lib/actions";
+import { useEditor } from "@/hooks/use-editor";
import { useKeybindingsStore } from "@/stores/keybindings-store";
-import { useTimelineStore } from "@/stores/timeline-store";
import { isTypableDOMElement } from "@/utils/browser";
/**
@@ -10,6 +10,7 @@ import { isTypableDOMElement } from "@/utils/browser";
* the appropriate actions based on keybindings
*/
export function useKeybindingsListener() {
+ const editor = useEditor();
const {
keybindings,
getKeybindingString,
@@ -17,7 +18,6 @@ export function useKeybindingsListener() {
isLoadingProject,
isRecording,
} = useKeybindingsStore();
- const clipboard = useTimelineStore((state) => state.clipboard);
useEffect(() => {
const eventOptions: AddEventListenerOptions = { capture: true };
@@ -140,7 +140,7 @@ export function useKeybindingsListener() {
if (isTextInput) return;
if (boundAction === "paste-copied") {
- if (!clipboard?.items.length) return;
+ if (!editor.clipboard.hasEntry()) return;
ev.preventDefault();
invokeAction("paste-copied", undefined, "keypress");
return;
@@ -177,6 +177,6 @@ export function useKeybindingsListener() {
overlayDepth,
isLoadingProject,
isRecording,
- clipboard,
+ editor,
]);
}
diff --git a/apps/web/src/hooks/use-paste-media.ts b/apps/web/src/hooks/use-paste-media.ts
index ca821d6b..95020a04 100644
--- a/apps/web/src/hooks/use-paste-media.ts
+++ b/apps/web/src/hooks/use-paste-media.ts
@@ -2,7 +2,6 @@ import { useEffect } from "react";
import { useEditor } from "@/hooks/use-editor";
import { processMediaAssets } from "@/lib/media/processing";
import { showMediaUploadToast } from "@/lib/media/upload-toast";
-import { invokeAction } from "@/lib/actions";
import { buildElementFromMedia } from "@/lib/timeline/element-utils";
import { AddMediaAssetCommand } from "@/lib/commands/media";
import { InsertElementCommand } from "@/lib/commands/timeline";
@@ -52,7 +51,7 @@ export function usePasteMedia() {
});
if (files.length === 0) {
event.preventDefault();
- invokeAction("paste-copied");
+ editor.clipboard.paste();
return;
}
@@ -74,10 +73,10 @@ export function usePasteMedia() {
asset,
);
const assetId = addMediaCmd.getAssetId();
- const duration =
- asset.duration != null
- ? Math.round(asset.duration * TICKS_PER_SECOND)
- : DEFAULT_NEW_ELEMENT_DURATION;
+ const duration =
+ asset.duration != null
+ ? Math.round(asset.duration * TICKS_PER_SECOND)
+ : DEFAULT_NEW_ELEMENT_DURATION;
const trackType = asset.type === "audio" ? "audio" : "video";
const element = buildElementFromMedia({