Skip to content

Commit 9954e49

Browse files
mTvare6Keavon
andauthored
Make the document auto-save system initially restore the last-viewed tab before loading the rest (#2194)
* Fixes last tab being opened instead of last active tab Fixes https://discord.com/channels/731730685944922173/881073965047636018/937518022548131891 * Defers node initialisation to SelectDocument message instead of load_document * Fix warning regarding attempt to load closed document * Defer loading resources and running nodes to selection time * Make last active tab load before others * Load last active saved document instead of last autosaved doc * Fix failing tests * Code review --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent de36d49 commit 9954e49

File tree

9 files changed

+153
-32
lines changed

9 files changed

+153
-32
lines changed

editor/src/application.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ mod test {
7575
document_is_auto_saved: true,
7676
document_is_saved: true,
7777
document_serialized_content: r#" [removed until test is reenabled] "#.into(),
78+
to_front: false,
7879
}
7980
.into(),
8081
InputPreprocessorMessage::BoundsOfViewports {

editor/src/dispatcher.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ impl Dispatcher {
141141
Message::NoOp => {}
142142
Message::Init => {
143143
// Load persistent data from the browser database
144-
queue.add(FrontendMessage::TriggerLoadAutoSaveDocuments);
144+
queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
145145
queue.add(FrontendMessage::TriggerLoadPreferences);
146146

147147
// Display the menu bar at the top of the window
@@ -153,6 +153,9 @@ impl Dispatcher {
153153
node_descriptions: document_node_definitions::collect_node_descriptions(),
154154
node_types: document_node_definitions::collect_node_types(),
155155
});
156+
157+
// Finish loading persistent data from the browser database
158+
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
156159
}
157160
Message::Batched(messages) => {
158161
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));

editor/src/messages/frontend/frontend_message.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,18 @@ pub enum FrontendMessage {
8585
document: String,
8686
details: FrontendDocumentDetails,
8787
},
88-
TriggerLoadAutoSaveDocuments,
88+
TriggerLoadFirstAutoSaveDocument,
89+
TriggerLoadRestAutoSaveDocuments,
8990
TriggerLoadPreferences,
9091
TriggerOpenDocument,
9192
TriggerPaste,
9293
TriggerSavePreferences {
9394
preferences: PreferencesMessageHandler,
9495
},
96+
TriggerSaveActiveDocument {
97+
#[serde(rename = "documentId")]
98+
document_id: DocumentId,
99+
},
95100
TriggerTextCommit,
96101
TriggerTextCopy {
97102
#[serde(rename = "copyText")]

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ pub struct DocumentMessageHandler {
117117
/// If the user clicks or Ctrl-clicks one layer, it becomes the start of the range selection and then Shift-clicking another layer selects all layers between the start and end.
118118
#[serde(skip)]
119119
layer_range_selection_reference: Option<LayerNodeIdentifier>,
120+
/// Whether or not the editor has executed the network to render the document yet. If this is opened as an inactive tab, it won't be loaded initially because the active tab is prioritized.
121+
#[serde(skip)]
122+
pub is_loaded: bool,
120123
}
121124

122125
impl Default for DocumentMessageHandler {
@@ -154,6 +157,7 @@ impl Default for DocumentMessageHandler {
154157
saved_hash: None,
155158
auto_saved_hash: None,
156159
layer_range_selection_reference: None,
160+
is_loaded: false,
157161
}
158162
}
159163
}

editor/src/messages/portfolio/portfolio_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub enum PortfolioMessage {
7878
document_is_auto_saved: bool,
7979
document_is_saved: bool,
8080
document_serialized_content: String,
81+
to_front: bool,
8182
},
8283
PasteIntoFolder {
8384
clipboard: Clipboard,

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub struct PortfolioMessageData<'a> {
3232
pub struct PortfolioMessageHandler {
3333
menu_bar_message_handler: MenuBarMessageHandler,
3434
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
35-
document_ids: Vec<DocumentId>,
35+
document_ids: VecDeque<DocumentId>,
3636
active_panel: PanelType,
3737
pub(crate) active_document_id: Option<DocumentId>,
3838
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
@@ -258,7 +258,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
258258
} else if self.active_document_id.is_some() {
259259
let document_id = if document_index == self.document_ids.len() {
260260
// If we closed the last document take the one previous (same as last)
261-
*self.document_ids.last().unwrap()
261+
*self.document_ids.back().unwrap()
262262
} else {
263263
// Move to the next tab
264264
self.document_ids[document_index]
@@ -349,7 +349,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
349349
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
350350
}
351351

352-
self.load_document(new_document, document_id, responses);
352+
self.load_document(new_document, document_id, responses, false);
353+
responses.add(PortfolioMessage::SelectDocument { document_id });
353354
}
354355
PortfolioMessage::NextDocument => {
355356
if let Some(active_document_id) = self.active_document_id {
@@ -368,20 +369,24 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
368369
document_name,
369370
document_serialized_content,
370371
} => {
372+
let document_id = DocumentId(generate_uuid());
371373
responses.add(PortfolioMessage::OpenDocumentFileWithId {
372-
document_id: DocumentId(generate_uuid()),
374+
document_id,
373375
document_name,
374376
document_is_auto_saved: false,
375377
document_is_saved: true,
376378
document_serialized_content,
379+
to_front: false,
377380
});
381+
responses.add(PortfolioMessage::SelectDocument { document_id });
378382
}
379383
PortfolioMessage::OpenDocumentFileWithId {
380384
document_id,
381385
document_name,
382386
document_is_auto_saved,
383387
document_is_saved,
384388
document_serialized_content,
389+
to_front,
385390
} => {
386391
// TODO: Eventually remove this document upgrade code
387392
// This big code block contains lots of hacky code for upgrading old documents to the new format
@@ -756,7 +761,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
756761
document.set_auto_save_state(document_is_auto_saved);
757762
document.set_save_state(document_is_saved);
758763

759-
self.load_document(document, document_id, responses);
764+
self.load_document(document, document_id, responses, to_front);
760765
}
761766
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
762767
let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| {
@@ -896,6 +901,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
896901
responses.add(MenuBarMessage::SendLayout);
897902
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
898903
responses.add(FrontendMessage::UpdateActiveDocument { document_id });
904+
responses.add(FrontendMessage::TriggerSaveActiveDocument { document_id });
899905
responses.add(ToolMessage::InitTools);
900906
responses.add(NodeGraphMessage::Init);
901907
responses.add(OverlaysMessage::Draw);
@@ -909,6 +915,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
909915
} else {
910916
responses.add(PortfolioMessage::UpdateDocumentWidgets);
911917
}
918+
919+
let Some(document) = self.documents.get_mut(&document_id) else {
920+
warn!("Tried to read non existant document");
921+
return;
922+
};
923+
if !document.is_loaded {
924+
document.is_loaded = true;
925+
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
926+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
927+
responses.add(PropertiesPanelMessage::Clear);
928+
}
912929
}
913930
PortfolioMessage::SubmitDocumentExport {
914931
file_name,
@@ -1065,10 +1082,12 @@ impl PortfolioMessageHandler {
10651082
}
10661083
}
10671084

1068-
// TODO: Fix how this doesn't preserve tab order upon loading new document from *File > Open*
1069-
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>) {
1070-
let new_document = new_document;
1071-
self.document_ids.push(document_id);
1085+
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>, to_front: bool) {
1086+
if to_front {
1087+
self.document_ids.push_front(document_id);
1088+
} else {
1089+
self.document_ids.push_back(document_id);
1090+
}
10721091
new_document.update_layers_panel_control_bar_widgets(responses);
10731092

10741093
self.documents.insert(document_id, new_document);
@@ -1085,14 +1104,6 @@ impl PortfolioMessageHandler {
10851104
// TODO: Remove this and find a way to fix the issue where creating a new document when the node graph is open causes the transform in the new document to be incorrect
10861105
responses.add(DocumentMessage::GraphViewOverlay { open: false });
10871106
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
1088-
responses.add(PortfolioMessage::SelectDocument { document_id });
1089-
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
1090-
responses.add(PortfolioMessage::UpdateDocumentWidgets);
1091-
responses.add(ToolMessage::InitTools);
1092-
responses.add(NodeGraphMessage::Init);
1093-
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
1094-
responses.add(PropertiesPanelMessage::Clear);
1095-
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
10961107
}
10971108

10981109
/// Returns an iterator over the open documents in order.

frontend/src/io-managers/persistence.ts

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@ import { createStore, del, get, set, update } from "idb-keyval";
22
import { get as getFromStore } from "svelte/store";
33

44
import { type Editor } from "@graphite/editor";
5-
import { TriggerIndexedDbWriteDocument, TriggerIndexedDbRemoveDocument, TriggerSavePreferences, TriggerLoadAutoSaveDocuments, TriggerLoadPreferences } from "@graphite/messages";
5+
import {
6+
TriggerIndexedDbWriteDocument,
7+
TriggerIndexedDbRemoveDocument,
8+
TriggerSavePreferences,
9+
TriggerLoadPreferences,
10+
TriggerLoadFirstAutoSaveDocument,
11+
TriggerLoadRestAutoSaveDocuments,
12+
TriggerSaveActiveDocument,
13+
} from "@graphite/messages";
614
import { type PortfolioState } from "@graphite/state-providers/portfolio";
715

816
const graphiteStore = createStore("graphite", "store");
@@ -12,10 +20,13 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
1220

1321
async function storeDocumentOrder() {
1422
const documentOrder = getFromStore(portfolio).documents.map((doc) => String(doc.id));
15-
1623
await set("documents_tab_order", documentOrder, graphiteStore);
1724
}
1825

26+
async function storeCurrentDocumentId(documentId: string) {
27+
await set("current_document_id", String(documentId), graphiteStore);
28+
}
29+
1930
async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument) {
2031
await update<Record<string, TriggerIndexedDbWriteDocument>>(
2132
"documents",
@@ -28,6 +39,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
2839
);
2940

3041
await storeDocumentOrder();
42+
await storeCurrentDocumentId(autoSaveDocument.details.id);
3143
}
3244

3345
async function removeDocument(id: string) {
@@ -41,19 +53,80 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
4153
graphiteStore,
4254
);
4355

56+
const documentCount = getFromStore(portfolio).documents.length;
57+
if (documentCount > 0) {
58+
const documentIndex = getFromStore(portfolio).activeDocumentIndex;
59+
const documentId = getFromStore(portfolio).documents[documentIndex].id;
60+
61+
await storeCurrentDocumentId(String(documentId));
62+
} else {
63+
await del("current_document_id", graphiteStore);
64+
}
65+
4466
await storeDocumentOrder();
4567
}
4668

47-
async function loadDocuments() {
69+
async function loadFirstDocument() {
70+
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
71+
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
72+
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
73+
if (!previouslySavedDocuments || !documentOrder) return;
74+
75+
const orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : []));
76+
77+
if (currentDocumentId) {
78+
const doc = previouslySavedDocuments[currentDocumentId];
79+
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document, false);
80+
editor.handle.selectDocument(BigInt(currentDocumentId));
81+
} else {
82+
const len = orderedSavedDocuments.length;
83+
if (len > 0) {
84+
const doc = orderedSavedDocuments[len - 1];
85+
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document, false);
86+
editor.handle.selectDocument(BigInt(doc.details.id));
87+
}
88+
}
89+
}
90+
91+
async function loadRestDocuments() {
4892
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
4993
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
94+
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
5095
if (!previouslySavedDocuments || !documentOrder) return;
5196

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

54-
orderedSavedDocuments?.forEach(async (doc: TriggerIndexedDbWriteDocument) => {
55-
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document);
56-
});
99+
if (currentDocumentId) {
100+
const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.details.id === currentDocumentId);
101+
const beforeCurrentIndex = currentIndex - 1;
102+
const afterCurrentIndex = currentIndex + 1;
103+
104+
for (let i = beforeCurrentIndex; i >= 0; i--) {
105+
const { document, details } = orderedSavedDocuments[i];
106+
const { id, name, isSaved } = details;
107+
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true);
108+
}
109+
for (let i = afterCurrentIndex; i < orderedSavedDocuments.length; i++) {
110+
const { document, details } = orderedSavedDocuments[i];
111+
const { id, name, isSaved } = details;
112+
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, false);
113+
}
114+
115+
editor.handle.selectDocument(BigInt(currentDocumentId));
116+
} else {
117+
const length = orderedSavedDocuments.length;
118+
119+
for (let i = length - 2; i >= 0; i--) {
120+
const { document, details } = orderedSavedDocuments[i];
121+
const { id, name, isSaved } = details;
122+
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true);
123+
}
124+
125+
if (length > 0) {
126+
const id = orderedSavedDocuments[length - 1].details.id;
127+
editor.handle.selectDocument(BigInt(id));
128+
}
129+
}
57130
}
58131

59132
// PREFERENCES
@@ -84,12 +157,24 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
84157
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbRemoveDocument, async (removeAutoSaveDocument) => {
85158
await removeDocument(removeAutoSaveDocument.documentId);
86159
});
87-
editor.subscriptions.subscribeJsMessage(TriggerLoadAutoSaveDocuments, async () => {
88-
await loadDocuments();
160+
editor.subscriptions.subscribeJsMessage(TriggerLoadFirstAutoSaveDocument, async () => {
161+
await loadFirstDocument();
162+
});
163+
editor.subscriptions.subscribeJsMessage(TriggerLoadRestAutoSaveDocuments, async () => {
164+
await loadRestDocuments();
165+
});
166+
editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => {
167+
const documentId = String(triggerSaveActiveDocument.documentId);
168+
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
169+
if (!previouslySavedDocuments) return;
170+
if (documentId in previouslySavedDocuments) {
171+
await storeCurrentDocumentId(documentId);
172+
}
89173
});
90174
}
91175

92176
export async function wipeDocuments() {
93177
await del("documents_tab_order", graphiteStore);
178+
await del("current_document_id", graphiteStore);
94179
await del("documents", graphiteStore);
95180
}

frontend/src/messages.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,8 @@ export class UpdateMouseCursor extends JsMessage {
768768
readonly cursor!: MouseCursorIcon;
769769
}
770770

771-
export class TriggerLoadAutoSaveDocuments extends JsMessage {}
771+
export class TriggerLoadFirstAutoSaveDocument extends JsMessage {}
772+
export class TriggerLoadRestAutoSaveDocuments extends JsMessage {}
772773

773774
export class TriggerLoadPreferences extends JsMessage {}
774775

@@ -807,6 +808,10 @@ export class TriggerSavePreferences extends JsMessage {
807808
readonly preferences!: Record<string, unknown>;
808809
}
809810

811+
export class TriggerSaveActiveDocument extends JsMessage {
812+
readonly documentId!: bigint;
813+
}
814+
810815
export class DocumentChanged extends JsMessage {}
811816

812817
export type DataBuffer = {
@@ -1574,10 +1579,12 @@ export const messageMakers: Record<string, MessageMaker> = {
15741579
TriggerImport,
15751580
TriggerIndexedDbRemoveDocument,
15761581
TriggerIndexedDbWriteDocument,
1577-
TriggerLoadAutoSaveDocuments,
1582+
TriggerLoadFirstAutoSaveDocument,
15781583
TriggerLoadPreferences,
1584+
TriggerLoadRestAutoSaveDocuments,
15791585
TriggerOpenDocument,
15801586
TriggerPaste,
1587+
TriggerSaveActiveDocument,
15811588
TriggerSavePreferences,
15821589
TriggerTextCommit,
15831590
TriggerTextCopy,
@@ -1597,14 +1604,14 @@ export const messageMakers: Record<string, MessageMaker> = {
15971604
UpdateDocumentModeLayout,
15981605
UpdateDocumentRulers,
15991606
UpdateDocumentScrollbars,
1607+
UpdateExportReorderIndex,
16001608
UpdateEyedropperSamplingState,
16011609
UpdateGraphFadeArtwork,
16021610
UpdateGraphViewOverlay,
1611+
UpdateImportReorderIndex,
16031612
UpdateImportsExports,
16041613
UpdateInputHints,
16051614
UpdateInSelectedNetwork,
1606-
UpdateExportReorderIndex,
1607-
UpdateImportReorderIndex,
16081615
UpdateLayersPanelControlBarLayout,
16091616
UpdateLayerWidths,
16101617
UpdateMenuBarLayout,

0 commit comments

Comments
 (0)