summaryrefslogtreecommitdiff
path: root/apps/web/src/core/managers/save-manager.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/core/managers/save-manager.ts')
-rw-r--r--apps/web/src/core/managers/save-manager.ts107
1 files changed, 107 insertions, 0 deletions
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<typeof setTimeout> | 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<void> {
+ 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<void> {
+ 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;
+ }
+}