verdere refactoring
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user