From 7bf0984698e43b9aa2dd879f65951318ed410390 Mon Sep 17 00:00:00 2001 From: Maze <167211895+mazeincoding@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:20:04 +0100 Subject: codebase overhaul (#697) --- apps/web/src/core/managers/save-manager.ts | 107 +++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 apps/web/src/core/managers/save-manager.ts (limited to 'apps/web/src/core/managers/save-manager.ts') diff --git a/apps/web/src/core/managers/save-manager.ts b/apps/web/src/core/managers/save-manager.ts new file mode 100644 index 00000000..d5881b6b --- /dev/null +++ b/apps/web/src/core/managers/save-manager.ts @@ -0,0 +1,107 @@ +import type { EditorCore } from "@/core"; + +type SaveManagerOptions = { + debounceMs?: number; +}; + +export class SaveManager { + private debounceMs: number; + private isPaused = false; + private isSaving = false; + private hasPendingSave = false; + private saveTimer: ReturnType | null = null; + private unsubscribeHandlers: Array<() => void> = []; + + constructor( + private editor: EditorCore, + { debounceMs = 800 }: SaveManagerOptions = {}, + ) { + this.debounceMs = debounceMs; + } + + start(): void { + if (this.unsubscribeHandlers.length > 0) return; + + this.unsubscribeHandlers = [ + this.editor.scenes.subscribe(() => { + this.markDirty(); + }), + this.editor.timeline.subscribe(() => { + this.markDirty(); + }), + ]; + } + + stop(): void { + for (const unsubscribe of this.unsubscribeHandlers) { + unsubscribe(); + } + this.unsubscribeHandlers = []; + this.clearTimer(); + } + + pause(): void { + this.isPaused = true; + } + + resume(): void { + this.isPaused = false; + if (this.hasPendingSave) { + this.queueSave(); + } + } + + markDirty({ force = false }: { force?: boolean } = {}): void { + if (this.isPaused && !force) return; + this.hasPendingSave = true; + this.queueSave(); + } + + async flush(): Promise { + this.hasPendingSave = true; + await this.saveNow(); + } + + getIsDirty(): boolean { + return this.hasPendingSave || this.isSaving; + } + + private queueSave(): void { + if (this.isSaving) return; + if (this.saveTimer) { + clearTimeout(this.saveTimer); + } + this.saveTimer = setTimeout(() => { + void this.saveNow(); + }, this.debounceMs); + } + + private async saveNow(): Promise { + if (this.isSaving) return; + if (!this.hasPendingSave) return; + + const activeProject = this.editor.project.getActive(); + if (!activeProject) return; + if (this.editor.project.getIsLoading()) return; + if (this.editor.project.getMigrationState().isMigrating) return; + + this.isSaving = true; + this.hasPendingSave = false; + this.clearTimer(); + + try { + await this.editor.project.saveCurrentProject(); + } finally { + this.isSaving = false; + if (this.hasPendingSave) { + this.queueSave(); + } + } + } + + private clearTimer(): void { + if (!this.saveTimer) return; + clearTimeout(this.saveTimer); + this.saveTimer = null; + } +} -- cgit v1.3.1