import type { MutableRefObject } from "react"; import type { EditHistoryKind } from "./editHistory"; import { createStudioSaveHttpError } from "./studioSaveDiagnostics"; export interface RecordEditInput { label: string; kind: EditHistoryKind; coalesceKey?: string; coalesceMs?: number; files: Record; } export interface DomEditCommitBaseParams { activeCompPath: string | null; showToast: (message: string, tone?: "error" | "info") => void; writeProjectFile: (path: string, content: string) => Promise; domEditSaveTimestampRef: MutableRefObject; editHistory: { recordEdit: (entry: RecordEditInput) => Promise }; projectIdRef: MutableRefObject; reloadPreview: () => void; clearDomSelection: () => void; } interface SaveProjectFilesWithHistoryInput { projectId: string; label: string; kind: EditHistoryKind; coalesceKey?: string; coalesceMs?: number; files: Record; readFile: (path: string) => Promise; writeFile: (path: string, content: string) => Promise; recordEdit: (entry: RecordEditInput) => Promise; } export async function readProjectFileContent(pid: string, path: string): Promise { const response = await fetch(`/api/projects/${pid}/files/${encodeURIComponent(path)}`); if (!response.ok) { throw await createStudioSaveHttpError(response, `Failed to read ${path}`); } const data = (await response.json()) as { content?: string }; if (typeof data.content !== "string") { throw new Error(`Missing file contents for ${path}`); } return data.content; } export async function saveProjectFilesWithHistory({ label, kind, coalesceKey, coalesceMs, files, readFile, writeFile, recordEdit, }: SaveProjectFilesWithHistoryInput): Promise { const snapshots: Record = {}; for (const [path, after] of Object.entries(files)) { const before = await readFile(path); if (before !== after) { snapshots[path] = { before, after }; } } const changedPaths = Object.keys(snapshots); if (changedPaths.length === 0) return []; const writtenPaths: string[] = []; try { for (const path of changedPaths) { await writeFile(path, snapshots[path].after); writtenPaths.push(path); } await recordEdit({ label, kind, coalesceKey, coalesceMs, files: snapshots }); } catch (error) { try { for (const path of writtenPaths.reverse()) { await writeFile(path, snapshots[path].before); } } catch (rollbackError) { throw new AggregateError( [error, rollbackError], "Failed to save project files and rollback did not complete", ); } throw error; } return changedPaths; }