# Uploading from disk Once you have `File`s or handles, write them with `importStream` / `importFiles` — see [streaming](./streaming.md). The other direction: [Downloading to disk](./downloading.md). ```typescript import { createOPFS } from 'opfs-worker'; const fs = createOPFS({ root: '/my-app' }); ``` ## File / folder picker (``) A plain `` gives you files. Add `webkitdirectory` and the picker selects a whole folder — each file then carries its relative path in `webkitRelativePath`: ```html ``` ```typescript const input = document.querySelector('#files')!; input.addEventListener('change', async () => { if (!input.files?.length) return; const entries = [...input.files].map( (file) => [`/uploads/${file.webkitRelativePath || file.name}`, file] as [string, File] ); await fs.importFiles(entries, { onProgress: ({ path, totalBytesWritten, totalBytes }) => { console.log(`${path}: ${totalBytesWritten}/${totalBytes}`); }, }); }); ``` ## File System Access API (`showOpenFilePicker` / `showDirectoryPicker`) Pass handles straight in — we call `getFile()` and walk directories for you: ```typescript // One or more files const handles = await window.showOpenFilePicker({ multiple: true }); for (const handle of handles) { await fs.importStream(`/uploads/${handle.name}`, handle); } // or in one shot: await fs.importFiles(handles, { prefix: '/uploads' }); // Whole directory const dir = await window.showDirectoryPicker(); await fs.importFiles(dir, { prefix: '/uploads' }); // → /uploads/readme.txt, /uploads/src/app.ts, … ``` Safari and Firefox have limited File System Access support — keep the `` path as a fallback. ## Paste from clipboard `paste` gives you the same `DataTransfer` as drag-and-drop (`event.clipboardData`). Copied files from the OS, or a screenshot as an image file, land in `files` / `items` — then `importFiles` as usual: ```typescript window.addEventListener('paste', async (e) => { const data = e.clipboardData; if (!data?.files.length) return; // Don't steal Cmd/Ctrl+V from text fields if ((e.target as HTMLElement)?.matches('input, textarea, [contenteditable="true"]')) { return; } e.preventDefault(); const entries = [...data.files].map( (file) => [`/uploads/${file.name}`, file] as [string, File] ); await fs.importFiles(entries); }); ``` For folder trees, reuse the `webkitGetAsEntry` walker from drag-and-drop (`clipboardData` is a `DataTransfer`). ## Drag and drop Dropped folders need the non-standard (but widely supported) `webkitGetAsEntry` API, walked recursively: ```typescript async function collect(entry: FileSystemEntry, prefix = ''): Promise<[string, File][]> { if (entry.isFile) { const file = await new Promise((res, rej) => (entry as FileSystemFileEntry).file(res, rej) ); return [[`${prefix}${entry.name}`, file]]; } const reader = (entry as FileSystemDirectoryEntry).createReader(); const children: FileSystemEntry[] = []; // readEntries returns partial batches — keep calling until empty for (;;) { const batch = await new Promise((res, rej) => reader.readEntries(res, rej) ); if (batch.length === 0) break; children.push(...batch); } const nested = await Promise.all( children.map((c) => collect(c, `${prefix}${entry.name}/`)) ); return nested.flat(); } const dropZone = document.querySelector('#drop')!; // Required — without this, drop never fires dropZone.addEventListener('dragover', (e) => e.preventDefault()); dropZone.addEventListener('drop', async (e) => { e.preventDefault(); const entries = [...e.dataTransfer!.items] .map((item) => item.webkitGetAsEntry()) .filter((entry): entry is FileSystemEntry => entry !== null); const files = ( await Promise.all(entries.map((entry) => collect(entry, '/uploads/'))) ).flat(); await fs.importFiles(files, { onProgress: ({ path, index, count, bytesWritten, bytesTotal }) => { console.log(`[${index + 1}/${count}] ${path}: ${bytesWritten}/${bytesTotal}`); }, }); }); ``` The [live demo](https://kachurun.github.io/opfs-worker/) does drop + paste this way — see `demo/components/FileBrowser/`.