diff options
Diffstat (limited to 'apps/web/src/hooks')
| -rw-r--r-- | apps/web/src/hooks/actions/use-editor-actions.ts | 43 | ||||
| -rw-r--r-- | apps/web/src/hooks/use-editor.ts | 117 | ||||
| -rw-r--r-- | apps/web/src/hooks/use-keybindings.ts | 8 | ||||
| -rw-r--r-- | apps/web/src/hooks/use-paste-media.ts | 11 |
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({ |
