"use strict"; /** Coordinate CMap persistence, dirty state, autosave and transitions. */ export class CmapStorageController { constructor({ repository, getEditor, getCurrentMap, getViewVisible, renderSvg, reloadMaps, status, translate, unsavedDialog, canEdit, onMapSaved }) { this.repository = repository; this.getEditor = getEditor; this.getCurrentMap = getCurrentMap; this.getViewVisible = getViewVisible; this.renderSvg = renderSvg; this.reloadMaps = reloadMaps; this.status = status; this.translate = translate; this.unsavedDialog = unsavedDialog; this.canEdit = canEdit; this.onMapSaved = onMapSaved; this.savedSnapshot = null; this.autosaveTimer = null; this.savePromise = null; this.pendingTransition = null; } currentSnapshot() { const editor = this.getEditor(); return editor ? JSON.stringify(editor.toDocument()) : null; } markSaved(snapshot = this.currentSnapshot()) { this.savedSnapshot = snapshot; } hasUnsavedChanges() { if (!this.getViewVisible()) return false; const current = this.currentSnapshot(); if (current === null || this.savedSnapshot === null || current === this.savedSnapshot) return false; const editor = this.getEditor(); if (editor && !editor.canUndo() && !editor.history.hasPendingCommit()) { this.savedSnapshot = current; return false; } return true; } cancelAutosave() { if (this.autosaveTimer !== null) { window.clearTimeout(this.autosaveTimer); this.autosaveTimer = null; } } scheduleAutosave() { this.cancelAutosave(); if (!this.canEdit() || !this.getCurrentMap() || !this.hasUnsavedChanges()) return; this.status(this.translate("autosave-pending", "Changes waiting to be saved")); this.autosaveTimer = window.setTimeout(() => { this.autosaveTimer = null; this.save({ automatic: true }).catch((error) => console.error(error)); }, 1500); } async requestTransition(action) { if (!this.hasUnsavedChanges()) { await action(); return true; } if (this.pendingTransition) return false; const transition = (async () => { const choice = await this.unsavedDialog.choose(); if (choice === "save" && !await this.save()) return false; if (choice === "discard") this.markSaved(); if (choice === "cancel") return false; await action(); return true; })(); this.pendingTransition = transition; try { return await transition; } finally { if (this.pendingTransition === transition) this.pendingTransition = null; } } hasPendingTransition() { return this.pendingTransition !== null; } hasSaveInProgress() { return this.savePromise !== null; } async waitForSave() { if (this.savePromise) await this.savePromise; } async save({ automatic = false, force = false, summary = null, snapshotVersion = false, historyMode = null } = {}) { const editor = this.getEditor(); if (!editor) return false; this.cancelAutosave(); if (automatic && !this.getCurrentMap()) return false; const effectiveHistoryMode = historyMode || (snapshotVersion ? "snapshot" : (automatic ? "autosave" : "manual")); if (this.savePromise) { const succeeded = await this.savePromise; if (!succeeded) return false; if (force || this.hasUnsavedChanges() || effectiveHistoryMode !== "autosave") { return this.save({ automatic, force, summary, snapshotVersion, historyMode }); } return true; } const snapshot = this.currentSnapshot(); if (snapshot === null) return false; if (!force && snapshot === this.savedSnapshot && effectiveHistoryMode === "autosave") return true; const model = editor.currentModel(); const renderedSvg = typeof this.renderSvg === "function" ? this.renderSvg() : ""; const storedMap = this.getCurrentMap(); let succeeded = false; this.status(this.translate("saving", "Saving…")); this.savePromise = (async () => { try { if (!storedMap) { const title = window.prompt(this.translate("concept-map-name", "Concept map name"), ""); if (!title || !title.trim()) return false; const created = await this.repository.create(title.trim(), model, null, { renderedSvg }); if (this.onMapSaved) this.onMapSaved(created); } else { const saved = await this.repository.save(storedMap, model, { summary: summary || this.translate("manual-save", "Manual save"), snapshot: snapshotVersion, saveKind: effectiveHistoryMode, renderedSvg }); if (this.onMapSaved) this.onMapSaved(saved); } this.markSaved(snapshot); await this.reloadMaps(); succeeded = true; return true; } catch (error) { this.status(error.message); return false; } })().finally(() => { this.savePromise = null; if (succeeded && this.hasUnsavedChanges()) this.scheduleAutosave(); }); return this.savePromise; } destroy() { this.cancelAutosave(); } }