summaryrefslogtreecommitdiff
path: root/apps/web/src/media/use-paste-media.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/media/use-paste-media.ts
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/media/use-paste-media.ts')
-rw-r--r--apps/web/src/media/use-paste-media.ts116
1 files changed, 116 insertions, 0 deletions
diff --git a/apps/web/src/media/use-paste-media.ts b/apps/web/src/media/use-paste-media.ts
new file mode 100644
index 00000000..3055920e
--- /dev/null
+++ b/apps/web/src/media/use-paste-media.ts
@@ -0,0 +1,116 @@
+import { useEffect } from "react";
+import { useEditor } from "@/editor/use-editor";
+import { processMediaAssets } from "@/media/processing";
+import { showMediaUploadToast } from "@/media/upload-toast";
+import { buildElementFromMedia } from "@/timeline/element-utils";
+import { AddMediaAssetCommand } from "@/commands/media";
+import { InsertElementCommand } from "@/commands/timeline";
+import { BatchCommand } from "@/commands";
+import { DEFAULT_NEW_ELEMENT_DURATION } from "@/timeline/creation";
+import { TICKS_PER_SECOND } from "@/wasm";
+import { isTypableDOMElement } from "@/utils/browser";
+import type { MediaType } from "@/media/types";
+
+const MEDIA_MIME_PREFIXES: MediaType[] = ["image", "video", "audio"];
+
+function isMediaMimeType({ type }: { type: string }): boolean {
+ return MEDIA_MIME_PREFIXES.some((prefix) => type.startsWith(`${prefix}/`));
+}
+
+function extractMediaFilesFromClipboard({
+ clipboardData,
+}: {
+ clipboardData: DataTransfer | null;
+}): File[] {
+ if (!clipboardData?.items) return [];
+
+ const files: File[] = [];
+ for (const item of clipboardData.items) {
+ if (item.kind !== "file") continue;
+ if (!isMediaMimeType({ type: item.type })) continue;
+
+ const file = item.getAsFile();
+ if (file) files.push(file);
+ }
+ return files;
+}
+
+export function usePasteMedia() {
+ const editor = useEditor();
+
+ useEffect(() => {
+ const handlePaste = async (event: ClipboardEvent) => {
+ const activeElement = document.activeElement as HTMLElement;
+
+ if (activeElement && isTypableDOMElement({ element: activeElement })) {
+ return;
+ }
+
+ const files = extractMediaFilesFromClipboard({
+ clipboardData: event.clipboardData,
+ });
+ if (files.length === 0) {
+ event.preventDefault();
+ editor.clipboard.paste();
+ return;
+ }
+
+ event.preventDefault();
+
+ const activeProject = editor.project.getActive();
+ if (!activeProject) return;
+
+ try {
+ await showMediaUploadToast({
+ filesCount: files.length,
+ promise: async () => {
+ const processedAssets = await processMediaAssets({ files });
+ const startTime = editor.playback.getCurrentTime();
+
+ for (const asset of processedAssets) {
+ const addMediaCmd = new AddMediaAssetCommand(
+ activeProject.metadata.id,
+ asset,
+ );
+ const assetId = addMediaCmd.getAssetId();
+ 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({
+ mediaId: assetId,
+ mediaType: asset.type,
+ name: asset.name,
+ duration,
+ startTime,
+ buffer:
+ asset.type === "audio"
+ ? new AudioBuffer({ length: 1, sampleRate: 44100 })
+ : undefined,
+ });
+
+ const insertCmd = new InsertElementCommand({
+ element,
+ placement: { mode: "auto", trackType },
+ });
+ const batchCmd = new BatchCommand([addMediaCmd, insertCmd]);
+ editor.command.execute({ command: batchCmd });
+ }
+
+ return {
+ uploadedCount: processedAssets.length,
+ assetNames: processedAssets.map((asset) => asset.name),
+ };
+ },
+ });
+ } catch (error) {
+ console.error("Failed to paste media:", error);
+ }
+ };
+
+ window.addEventListener("paste", handlePaste);
+ return () => window.removeEventListener("paste", handlePaste);
+ }, [editor]);
+}