import type { PickerAdapter, PickerNode } from '../../types/filePicker' import { pickerFormat } from './validation' /** Construct metadata only: no FileReader, arrayBuffer or upload. */ export function localFilesAdapter( files: File[], relativePaths?: (string | undefined)[], ): PickerAdapter { const nodes = new Map() for (const [index, file] of files.entries()) { const relative = relativePaths?.[index] || file.webkitRelativePath || file.name const parts = relative.split('/') let parent: string | null = null for (let i = 0; i < parts.length - 1; i++) { const path = parts.slice(0, i + 1).join('/') if (!nodes.has(path)) nodes.set(path, { kind: 'folder', name: parts[i]!, path, parentPath: parent, fileCount: 0, bytes: 0, formats: {}, }) parent = path } // Plain file picks can have equal names; preserve both File objects. let path = relative for (let n = 2; nodes.has(path); n++) path = `${relative} (${n})` nodes.set(path, { kind: 'file', name: file.name, path, parentPath: parent, bytes: file.size, modifiedAt: new Date(file.lastModified).toISOString(), format: pickerFormat(file.name), file, }) } const children = new Map() for (const node of nodes.values()) { const parent = node.parentPath ?? '' const siblings = children.get(parent) if (siblings) siblings.push(node) else children.set(parent, [node]) if (node.kind !== 'file') continue // Each file contributes once to its ancestors, rather than scanning all // files again for every folder in the selection. for (let slash = node.path.indexOf('/'); slash >= 0; slash = node.path.indexOf('/', slash + 1)) { const folder = nodes.get(node.path.slice(0, slash)) if (folder?.kind !== 'folder') continue folder.fileCount = (folder.fileCount ?? 0) + 1 folder.bytes = (folder.bytes ?? 0) + (node.bytes ?? 0) folder.formats![node.format] = (folder.formats![node.format] ?? 0) + 1 } } const list = (path: string) => [...(children.get(path) ?? [])] return { listRoot: async () => list(''), listChildren: async (path) => list(path), search: async (query, scope) => [...nodes.values()].filter((node) => { const inScope = scope.recursive ? !scope.path || node.path.startsWith(`${scope.path}/`) : (node.parentPath ?? '') === scope.path return inScope && node.name.toLowerCase().includes(query.toLowerCase()) }), } }