Implement IndexedDB document auto-save (#422)

* removed all use of document indicies

* -add u64 support for wasm bridge

* fixed rust formating

* Cleaned up FrontendDocumentState in js-messages

* Tiny tweaks from code review

* - moved more of closeDocumentWithConfirmation to rust
- updated serde_wasm_bindgen to add feature flag

* working initial auto save impl

* auto save is a lifetime file

* - cargo fmt
- fixc error message
- move document version constant

* code review round 1

* generate seed for uuid in js when wasm is initialized

* Resolve PR feedback

* Further address PR feedback

* Fix failing test

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: otdavies <oliver@psyfer.io>
This commit is contained in:
mfish33
2021-12-27 01:56:47 -08:00
committed by Keavon Chambers
co-authored by Keavon Chambers otdavies
parent d5c5560c55
commit bfdd0dc7e2
17 changed files with 255 additions and 30 deletions
+77
View File
@@ -0,0 +1,77 @@
import { AutoSaveDocument, RemoveAutoSaveDocument } from "@/dispatcher/js-messages";
import { DocumentsState } from "@/state/documents";
import { EditorState } from "@/state/wasm-loader";
const GRAPHITE_INDEXED_DB_NAME = "graphite-indexed-db";
const GRAPHITE_INDEXED_DB_VERSION = 1;
const GRAPHITE_AUTO_SAVE_STORE = "auto-save-documents";
const GRAPHITE_AUTO_SAVE_ORDER_KEY = "auto-save-documents-order";
const databaseConnection: Promise<IDBDatabase> = new Promise((resolve) => {
const dbOpenRequest = indexedDB.open(GRAPHITE_INDEXED_DB_NAME, GRAPHITE_INDEXED_DB_VERSION);
dbOpenRequest.onupgradeneeded = () => {
const db = dbOpenRequest.result;
if (!db.objectStoreNames.contains(GRAPHITE_AUTO_SAVE_STORE)) {
db.createObjectStore(GRAPHITE_AUTO_SAVE_STORE, { keyPath: "details.id" });
}
};
dbOpenRequest.onerror = () => {
// eslint-disable-next-line no-console
console.error("Graphite IndexedDb error:", dbOpenRequest.error);
};
dbOpenRequest.onsuccess = () => {
resolve(dbOpenRequest.result);
};
});
export function createAutoSaveManager(editor: EditorState, documents: DocumentsState) {
const openAutoSavedDocuments = async (): Promise<void> => {
const db = await databaseConnection;
const transaction = db.transaction(GRAPHITE_AUTO_SAVE_STORE, "readonly");
const request = transaction.objectStore(GRAPHITE_AUTO_SAVE_STORE).getAll();
return new Promise((resolve) => {
request.onsuccess = () => {
const previouslySavedDocuments: AutoSaveDocument[] = request.result;
const documentOrder: string[] = JSON.parse(window.localStorage.getItem(GRAPHITE_AUTO_SAVE_ORDER_KEY) || "[]");
const orderedSavedDocuments = documentOrder.map((id) => previouslySavedDocuments.find((autoSave) => autoSave.details.id === id)).filter((x) => x !== undefined) as AutoSaveDocument[];
orderedSavedDocuments.forEach((doc: AutoSaveDocument) => {
editor.instance.open_auto_saved_document(BigInt(doc.details.id), doc.details.name, doc.details.is_saved, doc.document);
});
resolve(undefined);
};
});
};
const storeDocumentOrder = () => {
// Make sure to store as string since JSON does not play nice with BigInt
const documentOrder = documents.state.documents.map((doc) => doc.id.toString());
window.localStorage.setItem(GRAPHITE_AUTO_SAVE_ORDER_KEY, JSON.stringify(documentOrder));
};
editor.dispatcher.subscribeJsMessage(AutoSaveDocument, async (autoSaveDocument) => {
const db = await databaseConnection;
const transaction = db.transaction(GRAPHITE_AUTO_SAVE_STORE, "readwrite");
transaction.objectStore(GRAPHITE_AUTO_SAVE_STORE).put(autoSaveDocument);
storeDocumentOrder();
});
editor.dispatcher.subscribeJsMessage(RemoveAutoSaveDocument, async (removeAutoSaveDocument) => {
const db = await databaseConnection;
const transaction = db.transaction(GRAPHITE_AUTO_SAVE_STORE, "readwrite");
transaction.objectStore(GRAPHITE_AUTO_SAVE_STORE).delete(removeAutoSaveDocument.document_id);
storeDocumentOrder();
});
// On creation
openAutoSavedDocuments();
return {
openAutoSavedDocuments,
};
}
+3
View File
@@ -169,6 +169,9 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
};
const onBeforeUnload = (e: BeforeUnloadEvent) => {
const activeDocument = document.state.documents[document.state.activeDocumentIndex];
if (!activeDocument.is_saved) editor.instance.trigger_auto_save(activeDocument.id);
// Skip the message if the editor crashed, since work is already lost
if (editor.instance.has_crashed()) return;