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:
Timon 2025-09-02 13:27:38 +00:00 committed by GitHub
parent b5ebe78f5e
commit 083dfa5f49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 93 additions and 83 deletions

View File

@ -1,4 +1,4 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform; use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{ use crate::messages::portfolio::document::node_graph::utility_types::{
@ -90,13 +90,15 @@ pub enum FrontendMessage {
font: Font, font: Font,
}, },
TriggerImport, TriggerImport,
TriggerIndexedDbRemoveDocument { TriggerPersistenceRemoveDocument {
#[serde(rename = "documentId")] #[serde(rename = "documentId")]
document_id: DocumentId, document_id: DocumentId,
}, },
TriggerIndexedDbWriteDocument { TriggerPersistenceWriteDocument {
#[serde(rename = "documentId")]
document_id: DocumentId,
document: String, document: String,
details: FrontendDocumentDetails, details: DocumentDetails,
}, },
TriggerLoadFirstAutoSaveDocument, TriggerLoadFirstAutoSaveDocument,
TriggerLoadRestAutoSaveDocuments, TriggerLoadRestAutoSaveDocuments,
@ -308,7 +310,7 @@ pub enum FrontendMessage {
}, },
UpdateOpenDocumentsList { UpdateOpenDocumentsList {
#[serde(rename = "openDocuments")] #[serde(rename = "openDocuments")]
open_documents: Vec<FrontendDocumentDetails>, open_documents: Vec<OpenDocument>,
}, },
UpdatePropertiesPanelLayout { UpdatePropertiesPanelLayout {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]

View File

@ -2,13 +2,18 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendDocumentDetails { pub struct OpenDocument {
#[serde(rename = "isAutoSaved")] pub id: DocumentId,
pub is_auto_saved: bool, pub details: DocumentDetails,
}
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct DocumentDetails {
pub name: String,
#[serde(rename = "isSaved")] #[serde(rename = "isSaved")]
pub is_saved: bool, pub is_saved: bool,
pub name: String, #[serde(rename = "isAutoSaved")]
pub id: DocumentId, pub is_auto_saved: bool,
} }
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]

View File

@ -952,6 +952,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
self.path = None; self.path = None;
self.set_save_state(false); self.set_save_state(false);
self.set_auto_save_state(false);
responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::UpdateNewNodeGraph); responses.add(NodeGraphMessage::UpdateNewNodeGraph);
@ -1301,6 +1302,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
} }
self.network_interface.finish_transaction(); self.network_interface.finish_transaction();
self.document_redo_history.clear(); self.document_redo_history.clear();
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
} }
DocumentMessage::AbortTransaction => { DocumentMessage::AbortTransaction => {
responses.add(DocumentMessage::RepeatedAbortTransaction { undo_count: 1 }); responses.add(DocumentMessage::RepeatedAbortTransaction { undo_count: 1 });
@ -1316,6 +1318,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
self.network_interface.finish_transaction(); self.network_interface.finish_transaction();
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
} }
DocumentMessage::ToggleLayerExpansion { id, recursive } => { DocumentMessage::ToggleLayerExpansion { id, recursive } => {
let layer = LayerNodeIdentifier::new(id, &self.network_interface); let layer = LayerNodeIdentifier::new(id, &self.network_interface);
@ -1975,16 +1978,16 @@ impl DocumentMessageHandler {
Some(previous_network) Some(previous_network)
} }
pub fn current_hash(&self) -> Option<u64> { pub fn current_hash(&self) -> u64 {
self.document_undo_history.iter().last().map(|network| network.document_network().current_hash()) self.network_interface.document_network().current_hash()
} }
pub fn is_auto_saved(&self) -> bool { pub fn is_auto_saved(&self) -> bool {
self.current_hash() == self.auto_saved_hash Some(self.current_hash()) == self.auto_saved_hash
} }
pub fn is_saved(&self) -> bool { pub fn is_saved(&self) -> bool {
self.current_hash() == self.saved_hash Some(self.current_hash()) == self.saved_hash
} }
pub fn is_graph_overlay_open(&self) -> bool { pub fn is_graph_overlay_open(&self) -> bool {
@ -1993,7 +1996,7 @@ impl DocumentMessageHandler {
pub fn set_auto_save_state(&mut self, is_saved: bool) { pub fn set_auto_save_state(&mut self, is_saved: bool) {
if is_saved { if is_saved {
self.auto_saved_hash = self.current_hash(); self.auto_saved_hash = Some(self.current_hash());
} else { } else {
self.auto_saved_hash = None; self.auto_saved_hash = None;
} }
@ -2001,7 +2004,7 @@ impl DocumentMessageHandler {
pub fn set_save_state(&mut self, is_saved: bool) { pub fn set_save_state(&mut self, is_saved: bool) {
if is_saved { if is_saved {
self.saved_hash = self.current_hash(); self.saved_hash = Some(self.current_hash());
} else { } else {
self.saved_hash = None; self.saved_hash = None;
} }

View File

@ -6,7 +6,7 @@ use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION}
use crate::messages::animation::TimingInformation; use crate::messages::animation::TimingInformation;
use crate::messages::debug::utility_types::MessageLoggingVerbosity; use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs; use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails; use crate::messages::frontend::utility_types::{DocumentDetails, OpenDocument};
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
@ -187,13 +187,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
} }
PortfolioMessage::AutoSaveDocument { document_id } => { PortfolioMessage::AutoSaveDocument { document_id } => {
let document = self.documents.get(&document_id).unwrap(); let document = self.documents.get(&document_id).unwrap();
responses.add(FrontendMessage::TriggerIndexedDbWriteDocument { responses.add(FrontendMessage::TriggerPersistenceWriteDocument {
document_id,
document: document.serialize_document(), document: document.serialize_document(),
details: FrontendDocumentDetails { details: DocumentDetails {
is_auto_saved: document.is_auto_saved(),
is_saved: document.is_saved(),
id: document_id,
name: document.name.clone(), name: document.name.clone(),
is_saved: document.is_saved(),
is_auto_saved: document.is_auto_saved(),
}, },
}) })
} }
@ -216,7 +216,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
} }
for document_id in &self.document_ids { for document_id in &self.document_ids {
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id: *document_id }); responses.add(FrontendMessage::TriggerPersistenceRemoveDocument { document_id: *document_id });
} }
responses.add(PortfolioMessage::DestroyAllDocuments); responses.add(PortfolioMessage::DestroyAllDocuments);
@ -242,7 +242,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed) // Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed)
responses.add(PortfolioMessage::DeleteDocument { document_id }); responses.add(PortfolioMessage::DeleteDocument { document_id });
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id }); responses.add(FrontendMessage::TriggerPersistenceRemoveDocument { document_id });
// Send the new list of document tab names // Send the new list of document tab names
responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(PortfolioMessage::UpdateOpenDocumentsList);
@ -1044,11 +1044,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
.document_ids .document_ids
.iter() .iter()
.filter_map(|id| { .filter_map(|id| {
self.documents.get(id).map(|document| FrontendDocumentDetails { self.documents.get(id).map(|document| OpenDocument {
is_auto_saved: document.is_auto_saved(),
is_saved: document.is_saved(),
id: *id, id: *id,
name: document.name.clone(), details: DocumentDetails {
is_auto_saved: document.is_auto_saved(),
is_saved: document.is_saved(),
name: document.name.clone(),
},
}) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -2,7 +2,7 @@
import { getContext } from "svelte"; import { getContext } from "svelte";
import type { Editor } from "@graphite/editor"; 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 { DialogState } from "@graphite/state-providers/dialog";
import type { PortfolioState } from "@graphite/state-providers/portfolio"; import type { PortfolioState } from "@graphite/state-providers/portfolio";
@ -29,7 +29,7 @@
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex); $: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
$: documentTabLabels = $portfolio.documents.map((doc: FrontendDocumentDetails) => { $: documentTabLabels = $portfolio.documents.map((doc: OpenDocument) => {
const name = doc.displayName; const name = doc.displayName;
if (!editor.handle.inDevelopmentMode()) return { name }; if (!editor.handle.inDevelopmentMode()) return { name };

View File

@ -283,7 +283,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
async function onBeforeUnload(e: BeforeUnloadEvent) { async function onBeforeUnload(e: BeforeUnloadEvent) {
const activeDocument = get(portfolio).documents[get(portfolio).activeDocumentIndex]; 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 // Skip the message if the editor crashed, since work is already lost
if (await editor.handle.hasCrashed()) return; 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 // Skip the message during development, since it's annoying when testing
if (await editor.handle.inDevelopmentMode()) return; 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) { if (!allDocumentsSaved) {
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?"; e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
e.preventDefault(); e.preventDefault();

View File

@ -3,8 +3,8 @@ import { get as getFromStore } from "svelte/store";
import { type Editor } from "@graphite/editor"; import { type Editor } from "@graphite/editor";
import { import {
TriggerIndexedDbWriteDocument, TriggerPersistenceWriteDocument,
TriggerIndexedDbRemoveDocument, TriggerPersistenceRemoveDocument,
TriggerSavePreferences, TriggerSavePreferences,
TriggerLoadPreferences, TriggerLoadPreferences,
TriggerLoadFirstAutoSaveDocument, TriggerLoadFirstAutoSaveDocument,
@ -27,23 +27,23 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
await set("current_document_id", String(documentId), graphiteStore); await set("current_document_id", String(documentId), graphiteStore);
} }
async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument) { async function storeDocument(autoSaveDocument: TriggerPersistenceWriteDocument) {
await update<Record<string, TriggerIndexedDbWriteDocument>>( await update<Record<string, TriggerPersistenceWriteDocument>>(
"documents", "documents",
(old) => { (old) => {
const documents = old || {}; const documents = old || {};
documents[autoSaveDocument.details.id] = autoSaveDocument; documents[autoSaveDocument.documentId] = autoSaveDocument;
return documents; return documents;
}, },
graphiteStore, graphiteStore,
); );
await storeDocumentOrder(); await storeDocumentOrder();
await storeCurrentDocumentId(autoSaveDocument.details.id); await storeCurrentDocumentId(autoSaveDocument.documentId);
} }
async function removeDocument(id: string) { async function removeDocument(id: string) {
await update<Record<string, TriggerIndexedDbWriteDocument>>( await update<Record<string, TriggerPersistenceWriteDocument>>(
"documents", "documents",
(old) => { (old) => {
const documents = old || {}; const documents = old || {};
@ -77,7 +77,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
} }
async function loadFirstDocument() { 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 documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
const currentDocumentId = await get<string>("current_document_id", graphiteStore); const currentDocumentId = await get<string>("current_document_id", graphiteStore);
if (!previouslySavedDocuments || !documentOrder) return; if (!previouslySavedDocuments || !documentOrder) return;
@ -86,20 +86,20 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
if (currentDocumentId && currentDocumentId in previouslySavedDocuments) { if (currentDocumentId && currentDocumentId in previouslySavedDocuments) {
const doc = previouslySavedDocuments[currentDocumentId]; 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)); editor.handle.selectDocument(BigInt(currentDocumentId));
} else { } else {
const len = orderedSavedDocuments.length; const len = orderedSavedDocuments.length;
if (len > 0) { if (len > 0) {
const doc = orderedSavedDocuments[len - 1]; const doc = orderedSavedDocuments[len - 1];
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(doc.details.id)); editor.handle.selectDocument(BigInt(doc.documentId));
} }
} }
} }
async function loadRestDocuments() { 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 documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
const currentDocumentId = await get<string>("current_document_id", graphiteStore); const currentDocumentId = await get<string>("current_document_id", graphiteStore);
if (!previouslySavedDocuments || !documentOrder) return; if (!previouslySavedDocuments || !documentOrder) return;
@ -107,19 +107,19 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
const orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : [])); const orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : []));
if (currentDocumentId) { if (currentDocumentId) {
const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.details.id === currentDocumentId); const currentIndex = orderedSavedDocuments.findIndex((doc) => doc.documentId === currentDocumentId);
const beforeCurrentIndex = currentIndex - 1; const beforeCurrentIndex = currentIndex - 1;
const afterCurrentIndex = currentIndex + 1; const afterCurrentIndex = currentIndex + 1;
for (let i = beforeCurrentIndex; i >= 0; i--) { for (let i = beforeCurrentIndex; i >= 0; i--) {
const { document, details } = orderedSavedDocuments[i]; const { documentId, document, details } = orderedSavedDocuments[i];
const { id, name, isSaved } = details; const { name, isSaved } = details;
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true); editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, true);
} }
for (let i = afterCurrentIndex; i < orderedSavedDocuments.length; i++) { for (let i = afterCurrentIndex; i < orderedSavedDocuments.length; i++) {
const { document, details } = orderedSavedDocuments[i]; const { documentId, document, details } = orderedSavedDocuments[i];
const { id, name, isSaved } = details; const { name, isSaved } = details;
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, false); editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, false);
} }
editor.handle.selectDocument(BigInt(currentDocumentId)); editor.handle.selectDocument(BigInt(currentDocumentId));
@ -127,13 +127,13 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
const length = orderedSavedDocuments.length; const length = orderedSavedDocuments.length;
for (let i = length - 2; i >= 0; i--) { for (let i = length - 2; i >= 0; i--) {
const { document, details } = orderedSavedDocuments[i]; const { documentId, document, details } = orderedSavedDocuments[i];
const { id, name, isSaved } = details; const { name, isSaved } = details;
editor.handle.openAutoSavedDocument(BigInt(id), name, isSaved, document, true); editor.handle.openAutoSavedDocument(BigInt(documentId), name, isSaved, document, true);
} }
if (length > 0) { if (length > 0) {
const id = orderedSavedDocuments[length - 1].details.id; const id = orderedSavedDocuments[length - 1].documentId;
editor.handle.selectDocument(BigInt(id)); editor.handle.selectDocument(BigInt(id));
} }
} }
@ -161,10 +161,10 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
editor.subscriptions.subscribeJsMessage(TriggerLoadPreferences, async () => { editor.subscriptions.subscribeJsMessage(TriggerLoadPreferences, async () => {
await loadPreferences(); await loadPreferences();
}); });
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbWriteDocument, async (autoSaveDocument) => { editor.subscriptions.subscribeJsMessage(TriggerPersistenceWriteDocument, async (autoSaveDocument) => {
await storeDocument(autoSaveDocument); await storeDocument(autoSaveDocument);
}); });
editor.subscriptions.subscribeJsMessage(TriggerIndexedDbRemoveDocument, async (removeAutoSaveDocument) => { editor.subscriptions.subscribeJsMessage(TriggerPersistenceRemoveDocument, async (removeAutoSaveDocument) => {
await removeDocument(removeAutoSaveDocument.documentId); await removeDocument(removeAutoSaveDocument.documentId);
}); });
editor.subscriptions.subscribeJsMessage(TriggerLoadFirstAutoSaveDocument, async () => { editor.subscriptions.subscribeJsMessage(TriggerLoadFirstAutoSaveDocument, async () => {
@ -175,7 +175,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
}); });
editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => { editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => {
const documentId = String(triggerSaveActiveDocument.documentId); 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 (!previouslySavedDocuments) return;
if (documentId in previouslySavedDocuments) { if (documentId in previouslySavedDocuments) {
await storeCurrentDocumentId(documentId); await storeCurrentDocumentId(documentId);

View File

@ -129,37 +129,36 @@ export class UpdateNodeGraphSelection extends JsMessage {
} }
export class UpdateOpenDocumentsList extends JsMessage { export class UpdateOpenDocumentsList extends JsMessage {
@Type(() => FrontendDocumentDetails) @Type(() => OpenDocument)
readonly openDocuments!: FrontendDocumentDetails[]; readonly openDocuments!: OpenDocument[];
} }
export class UpdateWirePathInProgress extends JsMessage { export class UpdateWirePathInProgress extends JsMessage {
readonly wirePath!: WirePath | undefined; readonly wirePath!: WirePath | undefined;
} }
// Allows the auto save system to use a string for the id rather than a BigInt. export class OpenDocument {
// IndexedDb does not allow for BigInts as primary keys. readonly id!: bigint;
// TypeScript does not allow subclasses to change the type of class variables in subclasses. @Type(() => DocumentDetails)
// It is an abstract class to point out that it should not be instantiated directly. readonly details!: DocumentDetails;
export abstract class DocumentDetails {
get displayName(): string {
return this.details.displayName;
}
}
export class DocumentDetails {
readonly name!: string; readonly name!: string;
readonly isAutoSaved!: boolean; readonly isAutoSaved!: boolean;
readonly isSaved!: boolean; readonly isSaved!: boolean;
// This field must be provided by the subclass implementation
// readonly id!: bigint | string;
get displayName(): string { get displayName(): string {
return `${this.name}${this.isSaved ? "" : "*"}`; return `${this.name}${this.isSaved ? "" : "*"}`;
} }
} }
export class FrontendDocumentDetails extends DocumentDetails {
readonly id!: bigint;
}
export class Box { export class Box {
readonly startX!: number; readonly startX!: number;
@ -277,21 +276,20 @@ export class WireUpdate {
readonly wirePathUpdate!: WirePath | undefined; 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()) @Transform(({ value }: { value: bigint }) => value.toString())
id!: string; documentId!: string;
}
export class TriggerIndexedDbWriteDocument extends JsMessage {
document!: string; document!: string;
@Type(() => IndexedDbDocumentDetails) @Type(() => DocumentDetails)
details!: IndexedDbDocumentDetails; details!: DocumentDetails;
version!: string; version!: string;
} }
export class TriggerIndexedDbRemoveDocument extends JsMessage { export class TriggerPersistenceRemoveDocument extends JsMessage {
// Use a string since IndexedDB can not use BigInts for keys // Use a string since IndexedDB can not use BigInts for keys
@Transform(({ value }: { value: bigint }) => value.toString()) @Transform(({ value }: { value: bigint }) => value.toString())
documentId!: string; documentId!: string;
@ -1643,8 +1641,8 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerFetchAndOpenDocument, TriggerFetchAndOpenDocument,
TriggerFontLoad, TriggerFontLoad,
TriggerImport, TriggerImport,
TriggerIndexedDbRemoveDocument, TriggerPersistenceRemoveDocument,
TriggerIndexedDbWriteDocument, TriggerPersistenceWriteDocument,
TriggerLoadFirstAutoSaveDocument, TriggerLoadFirstAutoSaveDocument,
TriggerLoadPreferences, TriggerLoadPreferences,
TriggerLoadRestAutoSaveDocuments, TriggerLoadRestAutoSaveDocuments,

View File

@ -1,8 +1,8 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor"; import { type Editor } from "@graphite/editor";
import type { OpenDocument } from "@graphite/messages";
import { import {
type FrontendDocumentDetails,
TriggerFetchAndOpenDocument, TriggerFetchAndOpenDocument,
TriggerSaveDocument, TriggerSaveDocument,
TriggerExportImage, TriggerExportImage,
@ -21,7 +21,7 @@ import { extractPixelData, rasterizeSVG } from "@graphite/utility-functions/rast
export function createPortfolioState(editor: Editor) { export function createPortfolioState(editor: Editor) {
const { subscribe, update } = writable({ const { subscribe, update } = writable({
unsaved: false, unsaved: false,
documents: [] as FrontendDocumentDetails[], documents: [] as OpenDocument[],
activeDocumentIndex: 0, activeDocumentIndex: 0,
dataPanelOpen: false, dataPanelOpen: false,
propertiesPanelOpen: true, propertiesPanelOpen: true,