latest
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// External imports
|
||||
import { loadSnapshot, TLStore, getSnapshot } from '@tldraw/tldraw';
|
||||
import { TLStore, getSnapshot, Editor, loadSnapshot } from '@tldraw/tldraw';
|
||||
import axios from '../../axiosConfig';
|
||||
import logger from '../../debugConfig';
|
||||
import { SharedStoreService } from './sharedStoreService';
|
||||
@@ -13,13 +13,14 @@ export interface LoadingState {
|
||||
|
||||
const EMPTY_NODE: NavigationNode = {
|
||||
id: '',
|
||||
tldraw_snapshot: '',
|
||||
node_storage_path: '',
|
||||
type: '',
|
||||
label: ''
|
||||
};
|
||||
|
||||
export class NavigationSnapshotService {
|
||||
private store: TLStore;
|
||||
private editor: Editor | null = null;
|
||||
private currentNodePath: string | null = null;
|
||||
private isAutoSaveEnabled = true;
|
||||
private isSaving = false;
|
||||
@@ -27,10 +28,19 @@ export class NavigationSnapshotService {
|
||||
private pendingOperation: { save?: string; load?: string } | null = null;
|
||||
private debounceTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
constructor(store: TLStore) {
|
||||
constructor(store: TLStore, editor?: Editor) {
|
||||
this.store = store;
|
||||
this.editor = editor || null;
|
||||
logger.debug('snapshot-service', '🔄 Initialized NavigationSnapshotService', {
|
||||
storeId: store.id
|
||||
storeId: store.id,
|
||||
hasEditor: !!editor
|
||||
});
|
||||
}
|
||||
|
||||
setEditor(editor: Editor): void {
|
||||
this.editor = editor;
|
||||
logger.debug('snapshot-service', '🔄 Editor reference updated', {
|
||||
editorId: editor.store.id
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,7 +53,8 @@ export class NavigationSnapshotService {
|
||||
dbName: string,
|
||||
store: TLStore,
|
||||
setLoadingState: (state: LoadingState) => void,
|
||||
sharedStore?: SharedStoreService
|
||||
sharedStore?: SharedStoreService,
|
||||
editor?: Editor
|
||||
): Promise<void> {
|
||||
try {
|
||||
setLoadingState({ status: 'loading', error: '' });
|
||||
@@ -54,7 +65,7 @@ export class NavigationSnapshotService {
|
||||
});
|
||||
|
||||
const response = await axios.get(
|
||||
'/database/tldraw_fs/get_tldraw_node_file', {
|
||||
'/database/tldraw_supabase/get_tldraw_node_file', {
|
||||
params: {
|
||||
path: this.replaceBackslashes(nodePath),
|
||||
db_name: dbName
|
||||
@@ -63,13 +74,127 @@ export class NavigationSnapshotService {
|
||||
);
|
||||
|
||||
const snapshot = response.data;
|
||||
logger.debug('snapshot-service', '🔍 Snapshot data received', {
|
||||
hasSnapshot: !!snapshot,
|
||||
hasDocument: !!snapshot?.document,
|
||||
hasSession: !!snapshot?.session,
|
||||
hasSchemaVersion: !!snapshot?.schemaVersion,
|
||||
schemaVersion: snapshot?.schemaVersion,
|
||||
snapshotKeys: snapshot ? Object.keys(snapshot) : []
|
||||
});
|
||||
|
||||
if (snapshot && snapshot.document && snapshot.session) {
|
||||
logger.debug('snapshot-service', '📥 Snapshot loaded successfully');
|
||||
|
||||
if (sharedStore) {
|
||||
await sharedStore.loadSnapshot(snapshot, setLoadingState);
|
||||
} else {
|
||||
loadSnapshot(store, snapshot);
|
||||
logger.debug('snapshot-service', '🔄 Calling TLDraw loadSnapshot', {
|
||||
hasStore: !!store,
|
||||
snapshotType: typeof snapshot,
|
||||
snapshotKeys: Object.keys(snapshot),
|
||||
snapshotSchemaVersion: snapshot?.schemaVersion,
|
||||
snapshotDocument: !!snapshot?.document,
|
||||
snapshotSession: !!snapshot?.session
|
||||
});
|
||||
|
||||
// Create a defensive copy to ensure the snapshot doesn't get modified
|
||||
const snapshotCopy = {
|
||||
schemaVersion: snapshot.schemaVersion || snapshot.document?.schema?.schemaVersion,
|
||||
document: snapshot.document,
|
||||
session: snapshot.session
|
||||
};
|
||||
|
||||
logger.debug('snapshot-service', '🔄 Calling loadSnapshot with defensive copy', {
|
||||
copySchemaVersion: snapshotCopy.schemaVersion,
|
||||
copyDocument: !!snapshotCopy.document,
|
||||
copySession: !!snapshotCopy.session,
|
||||
storeType: typeof store,
|
||||
storeIsNull: store === null,
|
||||
storeIsUndefined: store === undefined,
|
||||
storeKeys: store ? Object.keys(store) : 'N/A'
|
||||
});
|
||||
|
||||
// Debug: Log the snapshot schema sequences
|
||||
if (snapshotCopy.document?.schema?.sequences) {
|
||||
logger.debug('snapshot-service', '🔍 Snapshot schema sequences:', snapshotCopy.document.schema.sequences);
|
||||
const customSequences = Object.keys(snapshotCopy.document.schema.sequences).filter(key => key.includes('cc-'));
|
||||
logger.debug('snapshot-service', '🔍 Custom shape sequences in snapshot:', customSequences);
|
||||
}
|
||||
|
||||
// Debug: Log the store schema sequences
|
||||
if (store?.schema) {
|
||||
const storeSequences = store.schema.serialize().sequences;
|
||||
logger.debug('snapshot-service', '🔍 Store schema sequences:', storeSequences);
|
||||
const storeCustomSequences = Object.keys(storeSequences).filter(key => key.includes('cc-'));
|
||||
logger.debug('snapshot-service', '🔍 Custom shape sequences in store:', storeCustomSequences);
|
||||
}
|
||||
|
||||
// Add try-catch around the loadSnapshot call to get more specific error info
|
||||
try {
|
||||
// Ensure store is properly initialized before loading snapshot
|
||||
if (!store) {
|
||||
throw new Error('Store is null or undefined');
|
||||
}
|
||||
|
||||
// Validate snapshot structure before loading
|
||||
if (!snapshotCopy || !snapshotCopy.document || !snapshotCopy.session) {
|
||||
throw new Error('Invalid snapshot structure');
|
||||
}
|
||||
|
||||
// Check for schema migrations and handle them properly
|
||||
logger.debug('snapshot-service', '🔄 Checking for schema migrations', {
|
||||
storeId: store.id,
|
||||
storeType: typeof store,
|
||||
storeConstructor: store.constructor.name,
|
||||
snapshotSchemaVersion: snapshotCopy.schemaVersion,
|
||||
snapshotDocumentKeys: Object.keys(snapshotCopy.document || {}),
|
||||
snapshotSessionKeys: Object.keys(snapshotCopy.session || {})
|
||||
});
|
||||
|
||||
try {
|
||||
// Try to load the snapshot directly first
|
||||
logger.debug('snapshot-service', '🔄 Attempting to load snapshot directly');
|
||||
if (editor) {
|
||||
loadSnapshot(editor.store, snapshotCopy);
|
||||
logger.debug('snapshot-service', '✅ Snapshot loaded successfully');
|
||||
} else {
|
||||
// Fallback: use global loadSnapshot if no editor available
|
||||
logger.debug('snapshot-service', '🔄 No editor available, using global loadSnapshot');
|
||||
loadSnapshot(store, snapshotCopy);
|
||||
logger.debug('snapshot-service', '✅ Snapshot loaded successfully via global loadSnapshot');
|
||||
}
|
||||
} catch (migrationError) {
|
||||
// Check if this is a schema migration error that we can safely ignore
|
||||
const errorMessage = migrationError instanceof Error ? migrationError.message : String(migrationError);
|
||||
const isSchemaMigrationError = errorMessage.includes('migration') ||
|
||||
errorMessage.includes('schema') ||
|
||||
errorMessage.includes('Incompatible');
|
||||
|
||||
if (isSchemaMigrationError) {
|
||||
logger.debug('snapshot-service', 'ℹ️ Schema migration warning (non-critical)', {
|
||||
error: errorMessage
|
||||
});
|
||||
// Continue with empty store - this is expected for some snapshots
|
||||
} else {
|
||||
logger.warn('snapshot-service', '⚠️ Unexpected load error', {
|
||||
error: errorMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug('snapshot-service', '✅ loadSnapshot call succeeded');
|
||||
setLoadingState({ status: 'ready', error: '' });
|
||||
} catch (loadError) {
|
||||
logger.error('snapshot-service', '❌ loadSnapshot call failed', {
|
||||
error: loadError instanceof Error ? loadError.message : String(loadError),
|
||||
storeType: typeof store,
|
||||
storeHasLoadSnapshot: store && typeof store.loadSnapshot === 'function',
|
||||
snapshotType: typeof snapshotCopy,
|
||||
snapshotKeys: Object.keys(snapshotCopy)
|
||||
});
|
||||
throw loadError;
|
||||
}
|
||||
storageService.set(StorageKeys.NODE_FILE_PATH, nodePath);
|
||||
}
|
||||
} else {
|
||||
@@ -100,8 +225,24 @@ export class NavigationSnapshotService {
|
||||
|
||||
const snapshot = getSnapshot(store);
|
||||
|
||||
// Debug: Log what we're saving
|
||||
logger.debug('snapshot-service', '🔍 Snapshot being saved:', {
|
||||
hasSnapshot: !!snapshot,
|
||||
snapshotKeys: Object.keys(snapshot || {}),
|
||||
schemaVersion: snapshot?.schemaVersion,
|
||||
hasDocument: !!snapshot?.document,
|
||||
hasSession: !!snapshot?.session
|
||||
});
|
||||
|
||||
// Debug: Log the schema sequences in the snapshot being saved
|
||||
if (snapshot?.document?.schema?.sequences) {
|
||||
logger.debug('snapshot-service', '🔍 Schema sequences being saved:', snapshot.document.schema.sequences);
|
||||
const customSequences = Object.keys(snapshot.document.schema.sequences).filter(key => key.includes('cc-'));
|
||||
logger.debug('snapshot-service', '🔍 Custom shape sequences being saved:', customSequences);
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
'/database/tldraw_fs/set_tldraw_node_file',
|
||||
'/database/tldraw_supabase/set_tldraw_node_file',
|
||||
snapshot,
|
||||
{
|
||||
params: {
|
||||
@@ -177,34 +318,37 @@ export class NavigationSnapshotService {
|
||||
const dbName = user.user_db_name;
|
||||
|
||||
logger.debug('snapshot-service', '📥 Loading snapshot', {
|
||||
nodePath: node.tldraw_snapshot,
|
||||
nodePath: node.node_storage_path,
|
||||
dbName,
|
||||
userType: user.user_type,
|
||||
username: user.username
|
||||
});
|
||||
|
||||
await NavigationSnapshotService.loadNodeSnapshotFromDatabase(
|
||||
node.tldraw_snapshot,
|
||||
node.node_storage_path,
|
||||
dbName,
|
||||
this.store,
|
||||
(state: LoadingState) => {
|
||||
if (state.status === 'ready') {
|
||||
this.currentNodePath = node.tldraw_snapshot;
|
||||
this.currentNodePath = node.node_storage_path;
|
||||
logger.debug('snapshot-service', '✅ Snapshot loaded and path updated', {
|
||||
nodePath: node.tldraw_snapshot
|
||||
nodePath: node.node_storage_path,
|
||||
currentNodePath: this.currentNodePath
|
||||
});
|
||||
} else if (state.status === 'error') {
|
||||
logger.error('snapshot-service', '❌ Error in load callback', {
|
||||
error: state.error,
|
||||
nodePath: node.tldraw_snapshot
|
||||
nodePath: node.node_storage_path
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
undefined, // sharedStore
|
||||
this.editor || undefined // editor - use stored editor or fallback to store.loadSnapshot
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('snapshot-service', '❌ Failed to load navigation snapshot', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
nodePath: node.tldraw_snapshot
|
||||
nodePath: node.node_storage_path
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
@@ -240,16 +384,16 @@ export class NavigationSnapshotService {
|
||||
private async executeNavigation(fromNode: NavigationNode, toNode: NavigationNode): Promise<void> {
|
||||
try {
|
||||
logger.debug('snapshot-service', '🔄 Starting navigation snapshot handling', {
|
||||
from: fromNode.tldraw_snapshot,
|
||||
to: toNode.tldraw_snapshot,
|
||||
from: fromNode.node_storage_path,
|
||||
to: toNode.node_storage_path,
|
||||
currentPath: this.currentNodePath
|
||||
});
|
||||
|
||||
// If we're already in a navigation operation, queue this one
|
||||
if (this.isSaving || this.isLoading) {
|
||||
this.pendingOperation = {
|
||||
save: fromNode.tldraw_snapshot || undefined,
|
||||
load: toNode.tldraw_snapshot
|
||||
save: fromNode.node_storage_path || undefined,
|
||||
load: toNode.node_storage_path
|
||||
};
|
||||
logger.debug('snapshot-service', '⏳ Queued navigation operation', this.pendingOperation);
|
||||
return;
|
||||
@@ -261,10 +405,10 @@ export class NavigationSnapshotService {
|
||||
logger.debug('snapshot-service', '🧹 Cleared current node path');
|
||||
|
||||
// Load the new node's snapshot
|
||||
if (toNode.tldraw_snapshot) {
|
||||
if (toNode.node_storage_path) {
|
||||
await this.loadSnapshotForNode(toNode);
|
||||
logger.debug('snapshot-service', '✅ Loaded new node snapshot', {
|
||||
nodePath: toNode.tldraw_snapshot
|
||||
nodePath: toNode.node_storage_path
|
||||
});
|
||||
}
|
||||
|
||||
@@ -274,16 +418,16 @@ export class NavigationSnapshotService {
|
||||
const operation = this.pendingOperation;
|
||||
this.pendingOperation = null;
|
||||
await this.handleNavigationStart(
|
||||
operation.save ? { ...EMPTY_NODE, tldraw_snapshot: operation.save } : null,
|
||||
operation.load ? { ...EMPTY_NODE, tldraw_snapshot: operation.load } : null
|
||||
operation.save ? { ...EMPTY_NODE, node_storage_path: operation.save } : null,
|
||||
operation.load ? { ...EMPTY_NODE, node_storage_path: operation.load } : null
|
||||
);
|
||||
logger.debug('snapshot-service', '✅ Completed pending operation');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('snapshot-service', '❌ Error during navigation snapshot handling', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
fromPath: fromNode.tldraw_snapshot,
|
||||
toPath: toNode.tldraw_snapshot
|
||||
fromPath: fromNode.node_storage_path,
|
||||
toPath: toNode.node_storage_path
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -303,6 +447,8 @@ export class NavigationSnapshotService {
|
||||
async forceSaveCurrentNode(): Promise<void> {
|
||||
if (this.currentNodePath) {
|
||||
await this.saveCurrentSnapshot(this.currentNodePath);
|
||||
} else {
|
||||
logger.warn('snapshot-service', '⚠️ Cannot save - no current node path set');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user