// Project utility files retain their directory structure in OPFS. export async function writeProjectUtilityFiles(directory: string, files: Array<{ filename: string; contentsBase64: string }>): Promise { if (!directory.trim() || /[\\/]/.test(directory) || [".", ".."].includes(directory)) throw new Error("Choose a project folder name"); const root = await navigator.storage.getDirectory(); const projects = await root.getDirectoryHandle("freecad-project-utility", { create: true }); const destination = await projects.getDirectoryHandle(directory, { create: true }); for (const file of files) { const parts = file.filename.split("/"); if (parts.some(part => !part || part === "." || part === ".." || part.includes("\\"))) throw new Error("Invalid project file path"); let parent = destination; for (const part of parts.slice(0, -1)) parent = await parent.getDirectoryHandle(part, { create: true }); const handle = await parent.getFileHandle(parts[parts.length - 1], { create: true }); const writer = await handle.createWritable(); try { await writer.write(Uint8Array.from(atob(file.contentsBase64), char => char.charCodeAt(0))); await writer.close(); } catch (error) { await writer.abort().catch(() => undefined); throw error; } } } export async function readProjectUtilityFiles(directory: string): Promise> { if (!directory.trim() || /[\\/]/.test(directory) || [".", ".."].includes(directory)) throw new Error("Choose a project folder name"); const root = await navigator.storage.getDirectory(); const projects = await root.getDirectoryHandle("freecad-project-utility"); const source = await projects.getDirectoryHandle(directory); const files: Array<{ filename: string; contentsBase64: string }> = []; async function visit(handle: FileSystemDirectoryHandle, prefix: string): Promise { // The iterable DOM declaration is not available in every TypeScript DOM lib. const iterable = handle as FileSystemDirectoryHandle & { entries(): AsyncIterableIterator<[string, FileSystemHandle]>; }; for await (const [name, entry] of iterable.entries()) { if (entry.kind === "directory") await visit(entry as FileSystemDirectoryHandle, prefix + name + "/"); else files.push({ filename: prefix + name, contentsBase64: await encodeProjectFile(await (entry as FileSystemFileHandle).getFile()) }); } } await visit(source, ""); return files; } export async function encodeProjectFile(file: Blob): Promise { const bytes = new Uint8Array(await file.arrayBuffer()); let binary = ""; for (let offset = 0; offset < bytes.length; offset += 8192) binary += String.fromCharCode(...bytes.subarray(offset, offset + 8192)); return btoa(binary); }