mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 22:18:12 +08:00
Clean up autosave persistence (#3115)
* Set auto save state to false on document rename * Update open document list on transaction commit and aboard * Use current network to compute hash Was using the last element in undo Before artworks where not auto saved when the had no undo history * Refactor persistence
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { FrontendDocumentDetails } from "@graphite/messages";
|
||||
import type { OpenDocument } from "@graphite/messages";
|
||||
import type { DialogState } from "@graphite/state-providers/dialog";
|
||||
import type { PortfolioState } from "@graphite/state-providers/portfolio";
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
|
||||
|
||||
$: documentTabLabels = $portfolio.documents.map((doc: FrontendDocumentDetails) => {
|
||||
$: documentTabLabels = $portfolio.documents.map((doc: OpenDocument) => {
|
||||
const name = doc.displayName;
|
||||
|
||||
if (!editor.handle.inDevelopmentMode()) return { name };
|
||||
|
||||
@@ -283,7 +283,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
|
||||
async function onBeforeUnload(e: BeforeUnloadEvent) {
|
||||
const activeDocument = get(portfolio).documents[get(portfolio).activeDocumentIndex];
|
||||
if (activeDocument && !activeDocument.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);
|
||||
if (activeDocument && !activeDocument.details.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);
|
||||
|
||||
// Skip the message if the editor crashed, since work is already lost
|
||||
if (await editor.handle.hasCrashed()) return;
|
||||
@@ -291,7 +291,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
// Skip the message during development, since it's annoying when testing
|
||||
if (await editor.handle.inDevelopmentMode()) return;
|
||||
|
||||
const allDocumentsSaved = get(portfolio).documents.reduce((acc, doc) => acc && doc.isSaved, true);
|
||||
const allDocumentsSaved = get(portfolio).documents.reduce((acc, doc) => acc && doc.details.isSaved, true);
|
||||
if (!allDocumentsSaved) {
|
||||
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
|
||||
e.preventDefault();
|
||||
|
||||
@@ -3,8 +3,8 @@ import { get as getFromStore } from "svelte/store";
|
||||
|
||||
import { type Editor } from "@graphite/editor";
|
||||
import {
|
||||
TriggerIndexedDbWriteDocument,
|
||||
TriggerIndexedDbRemoveDocument,
|
||||
TriggerPersistenceWriteDocument,
|
||||
TriggerPersistenceRemoveDocument,
|
||||
TriggerSavePreferences,
|
||||
TriggerLoadPreferences,
|
||||
TriggerLoadFirstAutoSaveDocument,
|
||||
@@ -27,23 +27,23 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
||||
await set("current_document_id", String(documentId), graphiteStore);
|
||||
}
|
||||
|
||||
async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument) {
|
||||
await update<Record<string, TriggerIndexedDbWriteDocument>>(
|
||||
async function storeDocument(autoSaveDocument: TriggerPersistenceWriteDocument) {
|
||||
await update<Record<string, TriggerPersistenceWriteDocument>>(
|
||||
"documents",
|
||||
(old) => {
|
||||
const documents = old || {};
|
||||
documents[autoSaveDocument.details.id] = autoSaveDocument;
|
||||
documents[autoSaveDocument.documentId] = autoSaveDocument;
|
||||
return documents;
|
||||
},
|
||||
graphiteStore,
|
||||
);
|
||||
|
||||
await storeDocumentOrder();
|
||||
await storeCurrentDocumentId(autoSaveDocument.details.id);
|
||||
await storeCurrentDocumentId(autoSaveDocument.documentId);
|
||||
}
|
||||
|
||||
async function removeDocument(id: string) {
|
||||
await update<Record<string, TriggerIndexedDbWriteDocument>>(
|
||||
await update<Record<string, TriggerPersistenceWriteDocument>>(
|
||||
"documents",
|
||||
(old) => {
|
||||
const documents = old || {};
|
||||
@@ -77,7 +77,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
||||
}
|
||||
|
||||
async function loadFirstDocument() {
|
||||
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
|
||||
const previouslySavedDocuments = await get<Record<string, TriggerPersistenceWriteDocument>>("documents", graphiteStore);
|
||||
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
|
||||
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
|
||||
if (!previouslySavedDocuments || !documentOrder) return;
|
||||
@@ -86,20 +86,20 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
||||
|
||||
if (currentDocumentId && currentDocumentId in previouslySavedDocuments) {
|
||||
const doc = previouslySavedDocuments[currentDocumentId];
|
||||
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document, false);
|
||||
editor.handle.openAutoSavedDocument(BigInt(doc.documentId), doc.details.name, doc.details.isSaved, doc.document, false);
|
||||
editor.handle.selectDocument(BigInt(currentDocumentId));
|
||||
} else {
|
||||
const len = orderedSavedDocuments.length;
|
||||
if (len > 0) {
|
||||
const doc = orderedSavedDocuments[len - 1];
|
||||
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document, false);
|
||||
editor.handle.selectDocument(BigInt(doc.details.id));
|
||||
editor.handle.openAutoSavedDocument(BigInt(doc.documentId), doc.details.name, doc.details.isSaved, doc.document, false);
|
||||
editor.handle.selectDocument(BigInt(doc.documentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRestDocuments() {
|
||||
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
|
||||
const previouslySavedDocuments = await get<Record<string, TriggerPersistenceWriteDocument>>("documents", graphiteStore);
|
||||
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
|
||||
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
|
||||
if (!previouslySavedDocuments || !documentOrder) return;
|
||||
@@ -107,19 +107,19 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
||||
const orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : []));
|
||||
|
||||
if (currentDocumentId) {
|
||||
const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.details.id === currentDocumentId);
|
||||
const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.documentId === currentDocumentId);
|
||||
const beforeCurrentIndex = currentIndex - 1;
|
||||
const afterCurrentIndex = currentIndex + 1;
|
||||
|
||||
for (let i = beforeCurrentIndex; i >= 0; i--) {
|
||||
const { document, details } = orderedSavedDocuments[i];
|
||||
const { id, name, isSaved } = details;
|
||||
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true);
|
||||
const { documentId, document, details } = orderedSavedDocuments[i];
|
||||
const { name, isSaved } = details;
|
||||
editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, true);
|
||||
}
|
||||
for (let i = afterCurrentIndex; i < orderedSavedDocuments.length; i++) {
|
||||
const { document, details } = orderedSavedDocuments[i];
|
||||
const { id, name, isSaved } = details;
|
||||
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, false);
|
||||
const { documentId, document, details } = orderedSavedDocuments[i];
|
||||
const { name, isSaved } = details;
|
||||
editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, false);
|
||||
}
|
||||
|
||||
editor.handle.selectDocument(BigInt(currentDocumentId));
|
||||
@@ -127,13 +127,13 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
||||
const length = orderedSavedDocuments.length;
|
||||
|
||||
for (let i = length - 2; i >= 0; i--) {
|
||||
const { document, details } = orderedSavedDocuments[i];
|
||||
const { id, name, isSaved } = details;
|
||||
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true);
|
||||
const { documentId, document, details } = orderedSavedDocuments[i];
|
||||
const { name, isSaved } = details;
|
||||
editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, true);
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
const id = orderedSavedDocuments[length - 1].details.id;
|
||||
const id = orderedSavedDocuments[length - 1].documentId;
|
||||
editor.handle.selectDocument(BigInt(id));
|
||||
}
|
||||
}
|
||||
@@ -161,10 +161,10 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
||||
editor.subscriptions.subscribeJsMessage(TriggerLoadPreferences, async () => {
|
||||
await loadPreferences();
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbWriteDocument, async (autoSaveDocument) => {
|
||||
editor.subscriptions.subscribeJsMessage(TriggerPersistenceWriteDocument, async (autoSaveDocument) => {
|
||||
await storeDocument(autoSaveDocument);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbRemoveDocument, async (removeAutoSaveDocument) => {
|
||||
editor.subscriptions.subscribeJsMessage(TriggerPersistenceRemoveDocument, async (removeAutoSaveDocument) => {
|
||||
await removeDocument(removeAutoSaveDocument.documentId);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerLoadFirstAutoSaveDocument, async () => {
|
||||
@@ -175,7 +175,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => {
|
||||
const documentId = String(triggerSaveActiveDocument.documentId);
|
||||
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
|
||||
const previouslySavedDocuments = await get<Record<string, TriggerPersistenceWriteDocument>>("documents", graphiteStore);
|
||||
if (!previouslySavedDocuments) return;
|
||||
if (documentId in previouslySavedDocuments) {
|
||||
await storeCurrentDocumentId(documentId);
|
||||
|
||||
+21
-23
@@ -129,37 +129,36 @@ export class UpdateNodeGraphSelection extends JsMessage {
|
||||
}
|
||||
|
||||
export class UpdateOpenDocumentsList extends JsMessage {
|
||||
@Type(() => FrontendDocumentDetails)
|
||||
readonly openDocuments!: FrontendDocumentDetails[];
|
||||
@Type(() => OpenDocument)
|
||||
readonly openDocuments!: OpenDocument[];
|
||||
}
|
||||
|
||||
export class UpdateWirePathInProgress extends JsMessage {
|
||||
readonly wirePath!: WirePath | undefined;
|
||||
}
|
||||
|
||||
// Allows the auto save system to use a string for the id rather than a BigInt.
|
||||
// IndexedDb does not allow for BigInts as primary keys.
|
||||
// TypeScript does not allow subclasses to change the type of class variables in subclasses.
|
||||
// It is an abstract class to point out that it should not be instantiated directly.
|
||||
export abstract class DocumentDetails {
|
||||
export class OpenDocument {
|
||||
readonly id!: bigint;
|
||||
@Type(() => DocumentDetails)
|
||||
readonly details!: DocumentDetails;
|
||||
|
||||
get displayName(): string {
|
||||
return this.details.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
export class DocumentDetails {
|
||||
readonly name!: string;
|
||||
|
||||
readonly isAutoSaved!: boolean;
|
||||
|
||||
readonly isSaved!: boolean;
|
||||
|
||||
// This field must be provided by the subclass implementation
|
||||
// readonly id!: bigint | string;
|
||||
|
||||
get displayName(): string {
|
||||
return `${this.name}${this.isSaved ? "" : "*"}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class FrontendDocumentDetails extends DocumentDetails {
|
||||
readonly id!: bigint;
|
||||
}
|
||||
|
||||
export class Box {
|
||||
readonly startX!: number;
|
||||
|
||||
@@ -277,21 +276,20 @@ export class WireUpdate {
|
||||
readonly wirePathUpdate!: WirePath | undefined;
|
||||
}
|
||||
|
||||
export class IndexedDbDocumentDetails extends DocumentDetails {
|
||||
export class TriggerPersistenceWriteDocument extends JsMessage {
|
||||
// Use a string since IndexedDB can not use BigInts for keys
|
||||
@Transform(({ value }: { value: bigint }) => value.toString())
|
||||
id!: string;
|
||||
}
|
||||
documentId!: string;
|
||||
|
||||
export class TriggerIndexedDbWriteDocument extends JsMessage {
|
||||
document!: string;
|
||||
|
||||
@Type(() => IndexedDbDocumentDetails)
|
||||
details!: IndexedDbDocumentDetails;
|
||||
@Type(() => DocumentDetails)
|
||||
details!: DocumentDetails;
|
||||
|
||||
version!: string;
|
||||
}
|
||||
|
||||
export class TriggerIndexedDbRemoveDocument extends JsMessage {
|
||||
export class TriggerPersistenceRemoveDocument extends JsMessage {
|
||||
// Use a string since IndexedDB can not use BigInts for keys
|
||||
@Transform(({ value }: { value: bigint }) => value.toString())
|
||||
documentId!: string;
|
||||
@@ -1643,8 +1641,8 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
TriggerFetchAndOpenDocument,
|
||||
TriggerFontLoad,
|
||||
TriggerImport,
|
||||
TriggerIndexedDbRemoveDocument,
|
||||
TriggerIndexedDbWriteDocument,
|
||||
TriggerPersistenceRemoveDocument,
|
||||
TriggerPersistenceWriteDocument,
|
||||
TriggerLoadFirstAutoSaveDocument,
|
||||
TriggerLoadPreferences,
|
||||
TriggerLoadRestAutoSaveDocuments,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
import { type Editor } from "@graphite/editor";
|
||||
import type { OpenDocument } from "@graphite/messages";
|
||||
import {
|
||||
type FrontendDocumentDetails,
|
||||
TriggerFetchAndOpenDocument,
|
||||
TriggerSaveDocument,
|
||||
TriggerExportImage,
|
||||
@@ -21,7 +21,7 @@ import { extractPixelData, rasterizeSVG } from "@graphite/utility-functions/rast
|
||||
export function createPortfolioState(editor: Editor) {
|
||||
const { subscribe, update } = writable({
|
||||
unsaved: false,
|
||||
documents: [] as FrontendDocumentDetails[],
|
||||
documents: [] as OpenDocument[],
|
||||
activeDocumentIndex: 0,
|
||||
dataPanelOpen: false,
|
||||
propertiesPanelOpen: true,
|
||||
|
||||
Reference in New Issue
Block a user