Unify file opening, importing, and pasting into a file ingest handler (#4548)

* Unify file and data ingest

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Timon
2026-09-18 22:47:02 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent f90bef159c
commit 7d1303f695
47 changed files with 855 additions and 980 deletions
+1 -21
View File
@@ -1,4 +1,3 @@
import { extractPixelData } from "/src/utility-functions/rasterization";
import { stripIndents } from "/src/utility-functions/strip-indents";
import type { EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
@@ -114,28 +113,9 @@ export async function triggerClipboardRead(editor: EditorWrapper) {
// Read an image from the clipboard and pass it to the editor to be loaded
const imageType = item.types.find((type) => type.startsWith("image/"));
// Import the actual SVG content if it's an SVG
if (imageType?.includes("svg")) {
const blob = await item.getType("text/plain");
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result === "string") editor.pasteSvg(undefined, reader.result);
};
reader.readAsText(blob);
return true;
}
// Import the bitmap image if it's an image
if (imageType) {
const blob = await item.getType(imageType);
const reader = new FileReader();
reader.onload = async () => {
if (reader.result instanceof ArrayBuffer) {
const imageData = await extractPixelData(new Blob([reader.result], { type: imageType }));
editor.pasteImage(undefined, new Uint8Array(imageData.data), imageData.width, imageData.height);
}
};
reader.readAsArrayBuffer(blob);
editor.ingestFile(undefined, imageType, new Uint8Array(await blob.arrayBuffer()));
return true;
}
+2 -18
View File
@@ -1,4 +1,3 @@
import { extractPixelData } from "/src/utility-functions/rasterization";
import type { EditorWrapper, FileFilter } from "/wrapper/pkg/graphite_wasm_wrapper";
export function downloadFileURL(filename: string, url: string) {
@@ -85,24 +84,9 @@ export async function pasteFile(item: DataTransferItem, editor: EditorWrapper, m
const file = item.getAsFile();
if (!file) return;
const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
if (file.type.startsWith("image/svg")) {
const svg = await file.text();
editor.pasteSvg(file.name, svg, mouse?.[0], mouse?.[1], insertParentId, insertIndex);
} else if (editor.rasterImageExtensions().includes(extension)) {
// Formats the editor decodes itself keep their original bytes instead of being rasterized by the browser
editor.pasteImageFile(file.name, await file.bytes(), mouse?.[0], mouse?.[1], insertParentId, insertIndex);
} else if (file.type.startsWith("image/")) {
const imageData = await extractPixelData(file);
editor.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, mouse?.[0], mouse?.[1], insertParentId, insertIndex);
} else {
// TODO: When we eventually have sub-documents, this should be changed to import the document as a node instead of opening it in a separate tab
editor.openFile(file.name, await file.bytes());
}
editor.ingestFile(file.name, file.type, await file.bytes(), mouse?.[0], mouse?.[1], insertParentId, insertIndex);
}
export function acceptStringFromFilters(filters: FileFilter[]): string {
const extensions = filters.flatMap((filter) => filter.extensions);
const imageMime = extensions.some((extension) => ["svg", "png", "jpg", "jpeg", "bmp", "gif", "webp", "avif", "tif", "tiff"].includes(extension)) ? ["image/*"] : [];
return [...imageMime, ...extensions.map((extension) => `.${extension}`)].join(",");
return filters.flatMap((filter) => [...filter.mimeTypes, ...filter.extensions.map((extension) => `.${extension}`)]).join(",");
}
+1 -1
View File
@@ -46,7 +46,7 @@ export async function loadDemoArtwork(editor: EditorWrapper) {
const filename = url.pathname.split("/").pop() || "Untitled.graphite";
const content = await response.bytes();
editor.openFile(filename, content);
editor.ingestPicked(filename, "", content, "Open");
history.replaceState("", "", `${window.location.pathname}${window.location.search}`);
} catch {
@@ -51,15 +51,6 @@ export async function rasterizeSVG(svg: string, width: number, height: number, m
return blob;
}
/// Convert an image source (e.g. PNG document) into pixel data, a width, and a height
export async function extractPixelData(imageData: ImageBitmapSource): Promise<ImageData> {
const canvasContext = await imageToCanvasContext(imageData);
const width = canvasContext.canvas.width;
const height = canvasContext.canvas.height;
return canvasContext.getImageData(0, 0, width, height);
}
export async function imageToCanvasContext(imageData: ImageBitmapSource): Promise<CanvasRenderingContext2D> {
// Special handling to rasterize an SVG file
let svgImageData;