Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the document auto-save system initially restore the last-viewed tab before loading the rest #2194

Merged
merged 10 commits into from
Jan 25, 2025
1 change: 1 addition & 0 deletions editor/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ mod test {
document_is_auto_saved: true,
document_is_saved: true,
document_serialized_content: r#" [removed until test is reenabled] "#.into(),
to_front: false,
}
.into(),
InputPreprocessorMessage::BoundsOfViewports {
Expand Down
5 changes: 4 additions & 1 deletion editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ impl Dispatcher {
Message::NoOp => {}
Message::Init => {
// Load persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadAutoSaveDocuments);
queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
queue.add(FrontendMessage::TriggerLoadPreferences);

// Display the menu bar at the top of the window
Expand All @@ -153,6 +153,9 @@ impl Dispatcher {
node_descriptions: document_node_definitions::collect_node_descriptions(),
node_types: document_node_definitions::collect_node_types(),
});

// Finish loading persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
}
Message::Batched(messages) => {
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
Expand Down
7 changes: 6 additions & 1 deletion editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,18 @@ pub enum FrontendMessage {
document: String,
details: FrontendDocumentDetails,
},
TriggerLoadAutoSaveDocuments,
TriggerLoadFirstAutoSaveDocument,
TriggerLoadRestAutoSaveDocuments,
TriggerLoadPreferences,
TriggerOpenDocument,
TriggerPaste,
TriggerSavePreferences {
preferences: PreferencesMessageHandler,
},
TriggerSaveActiveDocument {
#[serde(rename = "documentId")]
document_id: DocumentId,
},
TriggerTextCommit,
TriggerTextCopy {
#[serde(rename = "copyText")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ pub struct DocumentMessageHandler {
/// 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.
#[serde(skip)]
layer_range_selection_reference: Option<LayerNodeIdentifier>,
/// 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.
#[serde(skip)]
pub is_loaded: bool,
}

impl Default for DocumentMessageHandler {
Expand Down Expand Up @@ -154,6 +157,7 @@ impl Default for DocumentMessageHandler {
saved_hash: None,
auto_saved_hash: None,
layer_range_selection_reference: None,
is_loaded: false,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/portfolio/portfolio_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub enum PortfolioMessage {
document_is_auto_saved: bool,
document_is_saved: bool,
document_serialized_content: String,
to_front: bool,
},
PasteIntoFolder {
clipboard: Clipboard,
Expand Down
45 changes: 28 additions & 17 deletions editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct PortfolioMessageData<'a> {
pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler,
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
document_ids: Vec<DocumentId>,
document_ids: VecDeque<DocumentId>,
active_panel: PanelType,
pub(crate) active_document_id: Option<DocumentId>,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
Expand Down Expand Up @@ -258,7 +258,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
} else if self.active_document_id.is_some() {
let document_id = if document_index == self.document_ids.len() {
// If we closed the last document take the one previous (same as last)
*self.document_ids.last().unwrap()
*self.document_ids.back().unwrap()
} else {
// Move to the next tab
self.document_ids[document_index]
Expand Down Expand Up @@ -349,7 +349,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
}

self.load_document(new_document, document_id, responses);
self.load_document(new_document, document_id, responses, false);
responses.add(PortfolioMessage::SelectDocument { document_id });
}
PortfolioMessage::NextDocument => {
if let Some(active_document_id) = self.active_document_id {
Expand All @@ -368,20 +369,24 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
document_name,
document_serialized_content,
} => {
let document_id = DocumentId(generate_uuid());
responses.add(PortfolioMessage::OpenDocumentFileWithId {
document_id: DocumentId(generate_uuid()),
document_id,
document_name,
document_is_auto_saved: false,
document_is_saved: true,
document_serialized_content,
to_front: false,
});
responses.add(PortfolioMessage::SelectDocument { document_id });
}
PortfolioMessage::OpenDocumentFileWithId {
document_id,
document_name,
document_is_auto_saved,
document_is_saved,
document_serialized_content,
to_front,
} => {
// TODO: Eventually remove this document upgrade code
// This big code block contains lots of hacky code for upgrading old documents to the new format
Expand Down Expand Up @@ -756,7 +761,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
document.set_auto_save_state(document_is_auto_saved);
document.set_save_state(document_is_saved);

self.load_document(document, document_id, responses);
self.load_document(document, document_id, responses, to_front);
}
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| {
Expand Down Expand Up @@ -896,6 +901,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
responses.add(MenuBarMessage::SendLayout);
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(FrontendMessage::UpdateActiveDocument { document_id });
responses.add(FrontendMessage::TriggerSaveActiveDocument { document_id });
responses.add(ToolMessage::InitTools);
responses.add(NodeGraphMessage::Init);
responses.add(OverlaysMessage::Draw);
Expand All @@ -909,6 +915,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
} else {
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}

let Some(document) = self.documents.get_mut(&document_id) else {
warn!("Tried to read non existant document");
return;
};
if !document.is_loaded {
document.is_loaded = true;
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(PropertiesPanelMessage::Clear);
}
}
PortfolioMessage::SubmitDocumentExport {
file_name,
Expand Down Expand Up @@ -1065,10 +1082,12 @@ impl PortfolioMessageHandler {
}
}

// TODO: Fix how this doesn't preserve tab order upon loading new document from *File > Open*
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>) {
let new_document = new_document;
self.document_ids.push(document_id);
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>, to_front: bool) {
if to_front {
self.document_ids.push_front(document_id);
} else {
self.document_ids.push_back(document_id);
}
new_document.update_layers_panel_control_bar_widgets(responses);

self.documents.insert(document_id, new_document);
Expand All @@ -1085,14 +1104,6 @@ impl PortfolioMessageHandler {
// 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
responses.add(DocumentMessage::GraphViewOverlay { open: false });
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(PortfolioMessage::SelectDocument { document_id });
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(ToolMessage::InitTools);
responses.add(NodeGraphMessage::Init);
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
responses.add(PropertiesPanelMessage::Clear);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}

/// Returns an iterator over the open documents in order.
Expand Down
101 changes: 93 additions & 8 deletions frontend/src/io-managers/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { createStore, del, get, set, update } from "idb-keyval";
import { get as getFromStore } from "svelte/store";

import { type Editor } from "@graphite/editor";
import { TriggerIndexedDbWriteDocument, TriggerIndexedDbRemoveDocument, TriggerSavePreferences, TriggerLoadAutoSaveDocuments, TriggerLoadPreferences } from "@graphite/messages";
import {
TriggerIndexedDbWriteDocument,
TriggerIndexedDbRemoveDocument,
TriggerSavePreferences,
TriggerLoadPreferences,
TriggerLoadFirstAutoSaveDocument,
TriggerLoadRestAutoSaveDocuments,
TriggerSaveActiveDocument,
} from "@graphite/messages";
import { type PortfolioState } from "@graphite/state-providers/portfolio";

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

async function storeDocumentOrder() {
const documentOrder = getFromStore(portfolio).documents.map((doc) => String(doc.id));

await set("documents_tab_order", documentOrder, graphiteStore);
}

async function storeCurrentDocumentId(documentId: string) {
await set("current_document_id", String(documentId), graphiteStore);
}

async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument) {
await update<Record<string, TriggerIndexedDbWriteDocument>>(
"documents",
Expand All @@ -28,6 +39,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
);

await storeDocumentOrder();
await storeCurrentDocumentId(autoSaveDocument.details.id);
}

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

const documentCount = getFromStore(portfolio).documents.length;
if (documentCount > 0) {
const documentIndex = getFromStore(portfolio).activeDocumentIndex;
const documentId = getFromStore(portfolio).documents[documentIndex].id;

await storeCurrentDocumentId(String(documentId));
} else {
await del("current_document_id", graphiteStore);
}

await storeDocumentOrder();
}

async function loadDocuments() {
async function loadFirstDocument() {
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
if (!previouslySavedDocuments || !documentOrder) return;

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

if (currentDocumentId) {
const doc = previouslySavedDocuments[currentDocumentId];
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), 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));
}
}
}

async function loadRestDocuments() {
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
const currentDocumentId = await get<string>("current_document_id", graphiteStore);
if (!previouslySavedDocuments || !documentOrder) return;

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

orderedSavedDocuments?.forEach(async (doc: TriggerIndexedDbWriteDocument) => {
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document);
});
if (currentDocumentId) {
const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.details.id === 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);
}
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);
}

editor.handle.selectDocument(BigInt(currentDocumentId));
} else {
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);
}

if (length > 0) {
const id = orderedSavedDocuments[length - 1].details.id;
editor.handle.selectDocument(BigInt(id));
}
}
}

// PREFERENCES
Expand Down Expand Up @@ -84,12 +157,24 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbRemoveDocument, async (removeAutoSaveDocument) => {
await removeDocument(removeAutoSaveDocument.documentId);
});
editor.subscriptions.subscribeJsMessage(TriggerLoadAutoSaveDocuments, async () => {
await loadDocuments();
editor.subscriptions.subscribeJsMessage(TriggerLoadFirstAutoSaveDocument, async () => {
await loadFirstDocument();
});
editor.subscriptions.subscribeJsMessage(TriggerLoadRestAutoSaveDocuments, async () => {
await loadRestDocuments();
});
editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => {
const documentId = String(triggerSaveActiveDocument.documentId);
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
if (!previouslySavedDocuments) return;
if (documentId in previouslySavedDocuments) {
await storeCurrentDocumentId(documentId);
}
});
}

export async function wipeDocuments() {
await del("documents_tab_order", graphiteStore);
await del("current_document_id", graphiteStore);
await del("documents", graphiteStore);
}
15 changes: 11 additions & 4 deletions frontend/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,8 @@ export class UpdateMouseCursor extends JsMessage {
readonly cursor!: MouseCursorIcon;
}

export class TriggerLoadAutoSaveDocuments extends JsMessage {}
export class TriggerLoadFirstAutoSaveDocument extends JsMessage {}
export class TriggerLoadRestAutoSaveDocuments extends JsMessage {}

export class TriggerLoadPreferences extends JsMessage {}

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

export class TriggerSaveActiveDocument extends JsMessage {
readonly documentId!: bigint;
}

export class DocumentChanged extends JsMessage {}

export type DataBuffer = {
Expand Down Expand Up @@ -1574,10 +1579,12 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerImport,
TriggerIndexedDbRemoveDocument,
TriggerIndexedDbWriteDocument,
TriggerLoadAutoSaveDocuments,
TriggerLoadFirstAutoSaveDocument,
TriggerLoadPreferences,
TriggerLoadRestAutoSaveDocuments,
TriggerOpenDocument,
TriggerPaste,
TriggerSaveActiveDocument,
TriggerSavePreferences,
TriggerTextCommit,
TriggerTextCopy,
Expand All @@ -1597,14 +1604,14 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateDocumentModeLayout,
UpdateDocumentRulers,
UpdateDocumentScrollbars,
UpdateExportReorderIndex,
UpdateEyedropperSamplingState,
UpdateGraphFadeArtwork,
UpdateGraphViewOverlay,
UpdateImportReorderIndex,
UpdateImportsExports,
UpdateInputHints,
UpdateInSelectedNetwork,
UpdateExportReorderIndex,
UpdateImportReorderIndex,
UpdateLayersPanelControlBarLayout,
UpdateLayerWidths,
UpdateMenuBarLayout,
Expand Down
Loading
Loading