Attach dropped files in every composer (#1750)
* fix(app): attach dropped files in composer Composer parents had to expose attachment callbacks, so New Workspace only wired image drops and JSON files never became file attachments. Centralize dropped-file ingestion in Composer so all composer surfaces share the same image and file path. * fix(app): handle desktop dropped attachment paths
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { droppedItemsToPickedFiles } from "./drop";
|
||||
|
||||
describe("composer dropped attachments", () => {
|
||||
it("turns non-image browser files into picked files and leaves raster images for image handling", async () => {
|
||||
const jsonFile = new File([JSON.stringify({ ok: true })], "config.json", {
|
||||
type: "application/json",
|
||||
});
|
||||
const imageFile = new File([new Uint8Array([0])], "screen.png", { type: "image/png" });
|
||||
|
||||
const files = await droppedItemsToPickedFiles([
|
||||
{ kind: "web-file", file: jsonFile },
|
||||
{ kind: "web-file", file: imageFile },
|
||||
]);
|
||||
|
||||
expect(files).toEqual([
|
||||
{
|
||||
fileName: "config.json",
|
||||
mimeType: "application/json",
|
||||
bytes: new Uint8Array(await jsonFile.arrayBuffer()),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("turns non-image desktop paths into picked files and leaves raster images for image handling", async () => {
|
||||
const windowsPath = "C:\\Users\\alice\\config.json";
|
||||
const posixPath = "/Users/alice/notes/readme.txt";
|
||||
const imagePath = "C:\\Users\\alice\\screen.png";
|
||||
const bytesByPath = new Map([
|
||||
[windowsPath, new Uint8Array([1, 2, 3])],
|
||||
[posixPath, new Uint8Array([4, 5])],
|
||||
]);
|
||||
const readPaths: string[] = [];
|
||||
|
||||
const files = await droppedItemsToPickedFiles(
|
||||
[
|
||||
{ kind: "desktop-path", path: windowsPath },
|
||||
{ kind: "desktop-path", path: imagePath },
|
||||
{ kind: "desktop-path", path: posixPath },
|
||||
],
|
||||
{
|
||||
readDesktopFileBytes: async (path) => {
|
||||
readPaths.push(path);
|
||||
const bytes = bytesByPath.get(path);
|
||||
if (!bytes) {
|
||||
throw new Error(`Unexpected desktop read: ${path}`);
|
||||
}
|
||||
return bytes;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(readPaths).toEqual([windowsPath, posixPath]);
|
||||
expect(files).toEqual([
|
||||
{
|
||||
fileName: "config.json",
|
||||
mimeType: "application/octet-stream",
|
||||
bytes: new Uint8Array([1, 2, 3]),
|
||||
},
|
||||
{
|
||||
fileName: "readme.txt",
|
||||
mimeType: "application/octet-stream",
|
||||
bytes: new Uint8Array([4, 5]),
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
getMimeTypeFromPath,
|
||||
isRasterImageFile,
|
||||
isRasterImagePath,
|
||||
} from "@/attachments/file-types";
|
||||
import { readDesktopFileBytes, type PickedFile } from "@/attachments/picked-file";
|
||||
import type { DroppedItem } from "@/hooks/use-file-drop-zone";
|
||||
|
||||
interface DroppedAttachmentsRuntime {
|
||||
readDesktopFileBytes(path: string): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
const defaultRuntime: DroppedAttachmentsRuntime = {
|
||||
readDesktopFileBytes,
|
||||
};
|
||||
|
||||
function fileNameFromPath(path: string): string {
|
||||
const segments = path.split(/[/\\]/);
|
||||
for (let index = segments.length - 1; index >= 0; index -= 1) {
|
||||
const segment = segments[index];
|
||||
if (segment) {
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export async function droppedItemsToPickedFiles(
|
||||
items: DroppedItem[],
|
||||
runtime: DroppedAttachmentsRuntime = defaultRuntime,
|
||||
): Promise<PickedFile[]> {
|
||||
const files: PickedFile[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
if (item.kind === "web-file") {
|
||||
if (isRasterImageFile(item.file)) {
|
||||
continue;
|
||||
}
|
||||
files.push({
|
||||
fileName: item.file.name,
|
||||
mimeType: item.file.type || getMimeTypeFromPath(item.file.name),
|
||||
bytes: new Uint8Array(await item.file.arrayBuffer()),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isRasterImagePath(item.path)) {
|
||||
continue;
|
||||
}
|
||||
files.push({
|
||||
fileName: fileNameFromPath(item.path),
|
||||
mimeType: getMimeTypeFromPath(item.path),
|
||||
bytes: await runtime.readDesktopFileBytes(item.path),
|
||||
});
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
Reference in New Issue
Block a user