118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
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";
|
|
import { BatchCommand } from "@/lib/commands";
|
|
import { DEFAULT_NEW_ELEMENT_DURATION } from "@/lib/timeline/creation";
|
|
import { TICKS_PER_SECOND } from "@/lib/wasm";
|
|
import { isTypableDOMElement } from "@/utils/browser";
|
|
import type { MediaType } from "@/lib/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();
|
|
invokeAction("paste-copied");
|
|
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]);
|
|
}
|