Skip to content

Commit 083dfa5

Browse files
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
1 parent b5ebe78 commit 083dfa5

9 files changed

Lines changed: 93 additions & 83 deletions

File tree

editor/src/messages/frontend/frontend_message.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
1+
use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
22
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
33
use crate::messages::layout::utility_types::widget_prelude::*;
44
use crate::messages::portfolio::document::node_graph::utility_types::{
@@ -90,13 +90,15 @@ pub enum FrontendMessage {
9090
font: Font,
9191
},
9292
TriggerImport,
93-
TriggerIndexedDbRemoveDocument {
93+
TriggerPersistenceRemoveDocument {
9494
#[serde(rename = "documentId")]
9595
document_id: DocumentId,
9696
},
97-
TriggerIndexedDbWriteDocument {
97+
TriggerPersistenceWriteDocument {
98+
#[serde(rename = "documentId")]
99+
document_id: DocumentId,
98100
document: String,
99-
details: FrontendDocumentDetails,
101+
details: DocumentDetails,
100102
},
101103
TriggerLoadFirstAutoSaveDocument,
102104
TriggerLoadRestAutoSaveDocuments,
@@ -308,7 +310,7 @@ pub enum FrontendMessage {
308310
},
309311
UpdateOpenDocumentsList {
310312
#[serde(rename = "openDocuments")]
311-
open_documents: Vec<FrontendDocumentDetails>,
313+
open_documents: Vec<OpenDocument>,
312314
},
313315
UpdatePropertiesPanelLayout {
314316
#[serde(rename = "layoutTarget")]

editor/src/messages/frontend/utility_types.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
22
use crate::messages::prelude::*;
33

44
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
5-
pub struct FrontendDocumentDetails {
6-
#[serde(rename = "isAutoSaved")]
7-
pub is_auto_saved: bool,
5+
pub struct OpenDocument {
6+
pub id: DocumentId,
7+
pub details: DocumentDetails,
8+
}
9+
10+
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
11+
pub struct DocumentDetails {
12+
pub name: String,
813
#[serde(rename = "isSaved")]
914
pub is_saved: bool,
10-
pub name: String,
11-
pub id: DocumentId,
15+
#[serde(rename = "isAutoSaved")]
16+
pub is_auto_saved: bool,
1217
}
1318

1419
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
952952

953953
self.path = None;
954954
self.set_save_state(false);
955+
self.set_auto_save_state(false);
955956

956957
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
957958
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
@@ -1301,6 +1302,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
13011302
}
13021303
self.network_interface.finish_transaction();
13031304
self.document_redo_history.clear();
1305+
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
13041306
}
13051307
DocumentMessage::AbortTransaction => {
13061308
responses.add(DocumentMessage::RepeatedAbortTransaction { undo_count: 1 });
@@ -1316,6 +1318,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
13161318

13171319
self.network_interface.finish_transaction();
13181320
responses.add(OverlaysMessage::Draw);
1321+
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
13191322
}
13201323
DocumentMessage::ToggleLayerExpansion { id, recursive } => {
13211324
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
@@ -1975,16 +1978,16 @@ impl DocumentMessageHandler {
19751978
Some(previous_network)
19761979
}
19771980

1978-
pub fn current_hash(&self) -> Option<u64> {
1979-
self.document_undo_history.iter().last().map(|network| network.document_network().current_hash())
1981+
pub fn current_hash(&self) -> u64 {
1982+
self.network_interface.document_network().current_hash()
19801983
}
19811984

19821985
pub fn is_auto_saved(&self) -> bool {
1983-
self.current_hash() == self.auto_saved_hash
1986+
Some(self.current_hash()) == self.auto_saved_hash
19841987
}
19851988

19861989
pub fn is_saved(&self) -> bool {
1987-
self.current_hash() == self.saved_hash
1990+
Some(self.current_hash()) == self.saved_hash
19881991
}
19891992

19901993
pub fn is_graph_overlay_open(&self) -> bool {
@@ -1993,15 +1996,15 @@ impl DocumentMessageHandler {
19931996

19941997
pub fn set_auto_save_state(&mut self, is_saved: bool) {
19951998
if is_saved {
1996-
self.auto_saved_hash = self.current_hash();
1999+
self.auto_saved_hash = Some(self.current_hash());
19972000
} else {
19982001
self.auto_saved_hash = None;
19992002
}
20002003
}
20012004

20022005
pub fn set_save_state(&mut self, is_saved: bool) {
20032006
if is_saved {
2004-
self.saved_hash = self.current_hash();
2007+
self.saved_hash = Some(self.current_hash());
20052008
} else {
20062009
self.saved_hash = None;
20072010
}

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION}
66
use crate::messages::animation::TimingInformation;
77
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
88
use crate::messages::dialog::simple_dialogs;
9-
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
9+
use crate::messages::frontend::utility_types::{DocumentDetails, OpenDocument};
1010
use crate::messages::layout::utility_types::widget_prelude::*;
1111
use crate::messages::portfolio::document::DocumentMessageContext;
1212
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
@@ -187,13 +187,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
187187
}
188188
PortfolioMessage::AutoSaveDocument { document_id } => {
189189
let document = self.documents.get(&document_id).unwrap();
190-
responses.add(FrontendMessage::TriggerIndexedDbWriteDocument {
190+
responses.add(FrontendMessage::TriggerPersistenceWriteDocument {
191+
document_id,
191192
document: document.serialize_document(),
192-
details: FrontendDocumentDetails {
193-
is_auto_saved: document.is_auto_saved(),
194-
is_saved: document.is_saved(),
195-
id: document_id,
193+
details: DocumentDetails {
196194
name: document.name.clone(),
195+
is_saved: document.is_saved(),
196+
is_auto_saved: document.is_auto_saved(),
197197
},
198198
})
199199
}
@@ -216,7 +216,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
216216
}
217217

218218
for document_id in &self.document_ids {
219-
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id: *document_id });
219+
responses.add(FrontendMessage::TriggerPersistenceRemoveDocument { document_id: *document_id });
220220
}
221221

222222
responses.add(PortfolioMessage::DestroyAllDocuments);
@@ -242,7 +242,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
242242

243243
// Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed)
244244
responses.add(PortfolioMessage::DeleteDocument { document_id });
245-
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id });
245+
responses.add(FrontendMessage::TriggerPersistenceRemoveDocument { document_id });
246246

247247
// Send the new list of document tab names
248248
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
@@ -1044,11 +1044,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
10441044
.document_ids
10451045
.iter()
10461046
.filter_map(|id| {
1047-
self.documents.get(id).map(|document| FrontendDocumentDetails {
1048-
is_auto_saved: document.is_auto_saved(),
1049-
is_saved: document.is_saved(),
1047+
self.documents.get(id).map(|document| OpenDocument {
10501048
id: *id,
1051-
name: document.name.clone(),
1049+
details: DocumentDetails {
1050+
is_auto_saved: document.is_auto_saved(),
1051+
is_saved: document.is_saved(),
1052+
name: document.name.clone(),
1053+
},
10521054
})
10531055
})
10541056
.collect::<Vec<_>>();

frontend/src/components/window/workspace/Workspace.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { getContext } from "svelte";
33
44
import type { Editor } from "@graphite/editor";
5-
import type { FrontendDocumentDetails } from "@graphite/messages";
5+
import type { OpenDocument } from "@graphite/messages";
66
import type { DialogState } from "@graphite/state-providers/dialog";
77
import type { PortfolioState } from "@graphite/state-providers/portfolio";
88
@@ -29,7 +29,7 @@
2929
3030
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
3131
32-
$: documentTabLabels = $portfolio.documents.map((doc: FrontendDocumentDetails) => {
32+
$: documentTabLabels = $portfolio.documents.map((doc: OpenDocument) => {
3333
const name = doc.displayName;
3434
3535
if (!editor.handle.inDevelopmentMode()) return { name };

frontend/src/io-managers/input.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,15 +283,15 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
283283

284284
async function onBeforeUnload(e: BeforeUnloadEvent) {
285285
const activeDocument = get(portfolio).documents[get(portfolio).activeDocumentIndex];
286-
if (activeDocument && !activeDocument.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);
286+
if (activeDocument && !activeDocument.details.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);
287287

288288
// Skip the message if the editor crashed, since work is already lost
289289
if (await editor.handle.hasCrashed()) return;
290290

291291
// Skip the message during development, since it's annoying when testing
292292
if (await editor.handle.inDevelopmentMode()) return;
293293

294-
const allDocumentsSaved = get(portfolio).documents.reduce((acc, doc) => acc && doc.isSaved, true);
294+
const allDocumentsSaved = get(portfolio).documents.reduce((acc, doc) => acc && doc.details.isSaved, true);
295295
if (!allDocumentsSaved) {
296296
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
297297
e.preventDefault();

frontend/src/io-managers/persistence.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { get as getFromStore } from "svelte/store";
33

44
import { type Editor } from "@graphite/editor";
55
import {
6-
TriggerIndexedDbWriteDocument,
7-
TriggerIndexedDbRemoveDocument,
6+
TriggerPersistenceWriteDocument,
7+
TriggerPersistenceRemoveDocument,
88
TriggerSavePreferences,
99
TriggerLoadPreferences,
1010
TriggerLoadFirstAutoSaveDocument,
@@ -27,23 +27,23 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
2727
await set("current_document_id", String(documentId), graphiteStore);
2828
}
2929

30-
async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument) {
31-
await update<Record<string, TriggerIndexedDbWriteDocument>>(
30+
async function storeDocument(autoSaveDocument: TriggerPersistenceWriteDocument) {
31+
await update<Record<string, TriggerPersistenceWriteDocument>>(
3232
"documents",
3333
(old) => {
3434
const documents = old || {};
35-
documents[autoSaveDocument.details.id] = autoSaveDocument;
35+
documents[autoSaveDocument.documentId] = autoSaveDocument;
3636
return documents;
3737
},
3838
graphiteStore,
3939
);
4040

4141
await storeDocumentOrder();
42-
await storeCurrentDocumentId(autoSaveDocument.details.id);
42+
await storeCurrentDocumentId(autoSaveDocument.documentId);
4343
}
4444

4545
async function removeDocument(id: string) {
46-
await update<Record<string, TriggerIndexedDbWriteDocument>>(
46+
await update<Record<string, TriggerPersistenceWriteDocument>>(
4747
"documents",
4848
(old) => {
4949
const documents = old || {};
@@ -77,7 +77,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
7777
}
7878

7979
async function loadFirstDocument() {
80-
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
80+
const previouslySavedDocuments = await get<Record<string, TriggerPersistenceWriteDocument>>("documents", graphiteStore);
8181
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
8282
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
8383
if (!previouslySavedDocuments || !documentOrder) return;
@@ -86,54 +86,54 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
8686

8787
if (currentDocumentId && currentDocumentId in previouslySavedDocuments) {
8888
const doc = previouslySavedDocuments[currentDocumentId];
89-
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document, false);
89+
editor.handle.openAutoSavedDocument(BigInt(doc.documentId), doc.details.name, doc.details.isSaved, doc.document, false);
9090
editor.handle.selectDocument(BigInt(currentDocumentId));
9191
} else {
9292
const len = orderedSavedDocuments.length;
9393
if (len > 0) {
9494
const doc = orderedSavedDocuments[len - 1];
95-
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document, false);
96-
editor.handle.selectDocument(BigInt(doc.details.id));
95+
editor.handle.openAutoSavedDocument(BigInt(doc.documentId), doc.details.name, doc.details.isSaved, doc.document, false);
96+
editor.handle.selectDocument(BigInt(doc.documentId));
9797
}
9898
}
9999
}
100100

101101
async function loadRestDocuments() {
102-
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
102+
const previouslySavedDocuments = await get<Record<string, TriggerPersistenceWriteDocument>>("documents", graphiteStore);
103103
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
104104
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
105105
if (!previouslySavedDocuments || !documentOrder) return;
106106

107107
const orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : []));
108108

109109
if (currentDocumentId) {
110-
const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.details.id === currentDocumentId);
110+
const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.documentId === currentDocumentId);
111111
const beforeCurrentIndex = currentIndex - 1;
112112
const afterCurrentIndex = currentIndex + 1;
113113

114114
for (let i = beforeCurrentIndex; i >= 0; i--) {
115-
const { document, details } = orderedSavedDocuments[i];
116-
const { id, name, isSaved } = details;
117-
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true);
115+
const { documentId, document, details } = orderedSavedDocuments[i];
116+
const { name, isSaved } = details;
117+
editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, true);
118118
}
119119
for (let i = afterCurrentIndex; i < orderedSavedDocuments.length; i++) {
120-
const { document, details } = orderedSavedDocuments[i];
121-
const { id, name, isSaved } = details;
122-
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, false);
120+
const { documentId, document, details } = orderedSavedDocuments[i];
121+
const { name, isSaved } = details;
122+
editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, false);
123123
}
124124

125125
editor.handle.selectDocument(BigInt(currentDocumentId));
126126
} else {
127127
const length = orderedSavedDocuments.length;
128128

129129
for (let i = length - 2; i >= 0; i--) {
130-
const { document, details } = orderedSavedDocuments[i];
131-
const { id, name, isSaved } = details;
132-
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true);
130+
const { documentId, document, details } = orderedSavedDocuments[i];
131+
const { name, isSaved } = details;
132+
editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, true);
133133
}
134134

135135
if (length > 0) {
136-
const id = orderedSavedDocuments[length - 1].details.id;
136+
const id = orderedSavedDocuments[length - 1].documentId;
137137
editor.handle.selectDocument(BigInt(id));
138138
}
139139
}
@@ -161,10 +161,10 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
161161
editor.subscriptions.subscribeJsMessage(TriggerLoadPreferences, async () => {
162162
await loadPreferences();
163163
});
164-
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbWriteDocument, async (autoSaveDocument) => {
164+
editor.subscriptions.subscribeJsMessage(TriggerPersistenceWriteDocument, async (autoSaveDocument) => {
165165
await storeDocument(autoSaveDocument);
166166
});
167-
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbRemoveDocument, async (removeAutoSaveDocument) => {
167+
editor.subscriptions.subscribeJsMessage(TriggerPersistenceRemoveDocument, async (removeAutoSaveDocument) => {
168168
await removeDocument(removeAutoSaveDocument.documentId);
169169
});
170170
editor.subscriptions.subscribeJsMessage(TriggerLoadFirstAutoSaveDocument, async () => {
@@ -175,7 +175,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
175175
});
176176
editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => {
177177
const documentId = String(triggerSaveActiveDocument.documentId);
178-
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
178+
const previouslySavedDocuments = await get<Record<string, TriggerPersistenceWriteDocument>>("documents", graphiteStore);
179179
if (!previouslySavedDocuments) return;
180180
if (documentId in previouslySavedDocuments) {
181181
await storeCurrentDocumentId(documentId);

0 commit comments

Comments
 (0)