feat: [cont.] support inserting multiple images (#9875)

* feat: support inserting multiple images

* Initial

* handleAppOnDrop, onImageToolbarButtonClick, pasteFromClipboard

* Initial get history working

* insertMultipleImages -> insertImages

* Bug fixes, improvements

* Remove redundant branch

* Refactor addElementsFromMixedContentPaste

* History, drag & drop bug fixes

* Update snapshots

* Remove redundant try-catch

* Refactor pasteFromClipboard

* Plain paste check in mermaid paste

* Move comment

* processClipboardData -> insertClipboardContent

* Redundant variable

* Redundant variable

* Refactor insertImages

* createImagePlaceholder -> newImagePlaceholder

* Get rid of unneeded NEVER schedule, filter out failed images

* Trigger CI

* Position placeholders before initializing

* Don't mutate scene with positionElementsOnGrid, captureUpdate: CaptureUpdateAction.IMMEDIATELY

* Comment

* Move positionOnGrid out of file

* Rename file

* Get rid of generic

* Initial tests

* More asserts, test paste

* Test image tool

* De-duplicate

* Stricter assert, move rest of logic outside of waitFor

* Modify history tests

* De-duplicate update snapshots

* Trigger CI

* Fix package build

* Make setupImageTest more explicit

* Re-introduce generic to use latest placeholder versions

* newElementWith instead of mutateElement to delete failed placeholder

* Insert failed images separately with CaptureUpdateAction.NEVER

* Refactor

* Don't re-order elements

* WIP

* Get rid of 'never' for failed

* refactor type check

* align max file size constant

* make grid padding scale to zoom

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Omar Brikaa
2025-09-01 18:31:24 +03:00
committed by GitHub
parent ae89608985
commit 3bdaafe4b5
15 changed files with 895 additions and 502 deletions
+41 -8
View File
@@ -18,6 +18,8 @@ import { CanvasError, ImageSceneDataError } from "../errors";
import { calculateScrollCenter } from "../scene";
import { decodeSvgBase64Payload } from "../scene/export";
import { isClipboardEvent } from "../clipboard";
import { base64ToString, stringToBase64, toByteString } from "./encode";
import { nativeFileSystemSupported } from "./filesystem";
import { isValidExcalidrawData, isValidLibrary } from "./json";
@@ -389,23 +391,54 @@ export const ImageURLToFile = async (
throw new Error("Error: unsupported file type", { cause: "UNSUPPORTED" });
};
export const getFileFromEvent = async (
event: React.DragEvent<HTMLDivElement>,
export const getFilesFromEvent = async (
event: React.DragEvent<HTMLDivElement> | ClipboardEvent,
) => {
const file = event.dataTransfer.files.item(0);
const fileHandle = await getFileHandle(event);
let fileList: FileList | undefined = undefined;
let items: DataTransferItemList | undefined = undefined;
return { file: file ? await normalizeFile(file) : null, fileHandle };
if (isClipboardEvent(event)) {
fileList = event.clipboardData?.files;
items = event.clipboardData?.items;
} else {
const dragEvent = event as React.DragEvent<HTMLDivElement>;
fileList = dragEvent.dataTransfer?.files;
items = dragEvent.dataTransfer?.items;
}
const files: (File | null)[] = Array.from(fileList || []);
return await Promise.all(
files.map(async (file, idx) => {
const dataTransferItem = items?.[idx];
const fileHandle = dataTransferItem
? getFileHandle(dataTransferItem)
: null;
return file
? {
file: await normalizeFile(file),
fileHandle: await fileHandle,
}
: {
file: null,
fileHandle: null,
};
}),
);
};
export const getFileHandle = async (
event: React.DragEvent<HTMLDivElement>,
event: DragEvent | React.DragEvent | DataTransferItem,
): Promise<FileSystemHandle | null> => {
if (nativeFileSystemSupported) {
try {
const item = event.dataTransfer.items[0];
const dataTransferItem =
event instanceof DataTransferItem
? event
: (event as DragEvent).dataTransfer?.items?.[0];
const handle: FileSystemHandle | null =
(await (item as any).getAsFileSystemHandle()) || null;
(await (dataTransferItem as any).getAsFileSystemHandle()) || null;
return handle;
} catch (error: any) {