Files
webmail/lib/file-drop/choice.ts
T
Linus Rath e211ae905e feat: offer attach or upload when files are dragged onto any screen
Files dragged into the window from outside now bring up a full-window
overlay with large drop zones: attach to a new message and upload to
Files, with the open composer first, "Upload to <folder>" on the Files
screen, and add to calendar, add to contacts, open or import for .ics,
.vcf and .eml files. At most four zones, the one under the pointer
highlights, Esc or dragging out cancels, a drop beside the zones does
nothing. Drags that start in the app never show it. A file pasted
outside a text field gets the same choices as a keyboard dialog.

The desktop app used to turn any file dropped on the window into a new
draft (lib/desktop/window-files.ts). That handling moved to the
overlay, so the "files on the window" tests in os-window.test.tsx went
with it: the same cases are now in components/file-drop/__tests__.
2026-11-04 05:37:35 +01:00

36 lines
1.1 KiB
TypeScript

/**
* The overlay's choices without a drag: for files pasted outside a text field, and for a
* file opened with the desktop app ("Open with" in Explorer, Finder or a file manager).
* The host shows them as a dialog that works from the keyboard. Requests wait for the
* host to mount, and the host waits for a sign-in.
*/
import type { FileDropZoneId } from './zones';
export interface FileChoiceRequest {
files: File[];
/** Where the focus starts, if that choice is there. */
preferred?: FileDropZoneId;
}
type Listener = (request: FileChoiceRequest) => void;
let listener: Listener | null = null;
const waiting: FileChoiceRequest[] = [];
export function requestFileChoice(request: FileChoiceRequest): void {
if (request.files.length === 0) return;
if (listener) listener(request);
else waiting.push(request);
}
/** One host at a time. What came before it mounted is handed over at once. */
export function listenForFileChoices(next: Listener): () => void {
listener = next;
for (const request of waiting.splice(0)) next(request);
return () => {
if (listener === next) listener = null;
};
}