mirror of
https://github.com/OFFIS-ESC/constellation-analyzer
synced 2026-01-26 23:43:40 +00:00
fix: preserve timeline states in document import/export
Fixed critical bug where importing a document would lose all timeline states except the current one. Also cleaned up unused legacy persistence functions. Changes: - Import now preserves complete document structure with all timeline states - Export already worked correctly, serializing all timeline states - Updated fileIO to return full ConstellationDocument instead of just graph - Removed unused legacy functions: hasSavedState, getLastSavedTimestamp, clearSavedState - Removed unused graphStore export/import (replaced by workspace-level system) - Updated type definitions to reflect removed functions The import process now correctly: 1. Accepts full ConstellationDocument from imported JSON 2. Preserves all timeline states and relationships 3. Loads complete timeline into timelineStore 4. Maintains document title and metadata 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d775cb8863
commit
aa2bd7e5d7
7 changed files with 29 additions and 82 deletions
|
|
@ -24,7 +24,7 @@ import type { Actor, Relation, NodeTypeConfig, EdgeTypeConfig, RelationData } fr
|
|||
* Read-only pass-through operations (no history):
|
||||
* - setNodes, setEdges (used for bulk updates during undo/redo/document loading)
|
||||
* - nodes, edges, nodeTypes, edgeTypes (state access)
|
||||
* - exportToFile, importFromFile, loadGraphState
|
||||
* - loadGraphState
|
||||
*
|
||||
* Usage:
|
||||
* const { addNode, updateNode, deleteNode, ... } = useGraphWithHistory();
|
||||
|
|
@ -278,10 +278,11 @@ export function useGraphWithHistory() {
|
|||
setEdges: graphStore.setEdges,
|
||||
setNodeTypes: graphStore.setNodeTypes,
|
||||
setEdgeTypes: graphStore.setEdgeTypes,
|
||||
exportToFile: graphStore.exportToFile,
|
||||
importFromFile: graphStore.importFromFile,
|
||||
loadGraphState: graphStore.loadGraphState,
|
||||
|
||||
// NOTE: exportToFile and importFromFile have been removed
|
||||
// Import/export is now handled by the workspace-level system (useWorkspaceStore)
|
||||
|
||||
// Expose flag for detecting restore operations
|
||||
isRestoringRef,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import type {
|
|||
GraphActions
|
||||
} from '../types';
|
||||
import { loadGraphState } from './persistence/loader';
|
||||
import { exportGraphToFile, selectFileForImport } from './persistence/fileIO';
|
||||
|
||||
/**
|
||||
* ⚠️ IMPORTANT: DO NOT USE THIS STORE DIRECTLY IN COMPONENTS ⚠️
|
||||
|
|
@ -187,33 +186,9 @@ export const useGraphStore = create<GraphStore & GraphActions>((set) => ({
|
|||
edgeTypes,
|
||||
}),
|
||||
|
||||
// File import/export operations
|
||||
exportToFile: () => {
|
||||
const state = useGraphStore.getState();
|
||||
exportGraphToFile(state.nodes, state.edges, state.nodeTypes, state.edgeTypes);
|
||||
},
|
||||
|
||||
importFromFile: (onError?: (error: string) => void) => {
|
||||
selectFileForImport(
|
||||
(data) => {
|
||||
// Load the imported data into the store
|
||||
set({
|
||||
nodes: data.nodes,
|
||||
edges: data.edges,
|
||||
nodeTypes: data.nodeTypes,
|
||||
edgeTypes: data.edgeTypes,
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
console.error('Import failed:', error);
|
||||
if (onError) {
|
||||
onError(error);
|
||||
} else {
|
||||
alert(`Failed to import file: ${error}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
// NOTE: exportToFile and importFromFile have been removed
|
||||
// Import/export is now handled by the workspace-level system
|
||||
// See: workspaceStore.importDocumentFromFile() and workspaceStore.exportDocument()
|
||||
|
||||
loadGraphState: (data) =>
|
||||
set({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Actor, Relation, NodeTypeConfig, EdgeTypeConfig } from '../../types';
|
||||
import type { ConstellationDocument } from './types';
|
||||
import { createDocument, serializeActors, serializeRelations } from './saver';
|
||||
import { validateDocument, deserializeGraphState } from './loader';
|
||||
import { validateDocument } from './loader';
|
||||
|
||||
/**
|
||||
* File I/O - Export and import ConstellationDocument to/from files
|
||||
|
|
@ -57,15 +57,11 @@ export function exportGraphToFile(
|
|||
|
||||
/**
|
||||
* Import graph state from a JSON file
|
||||
* Returns the full document with timeline preserved, not just the current graph state
|
||||
*/
|
||||
export function importGraphFromFile(
|
||||
file: File,
|
||||
onSuccess: (data: {
|
||||
nodes: Actor[];
|
||||
edges: Relation[];
|
||||
nodeTypes: NodeTypeConfig[];
|
||||
edgeTypes: EdgeTypeConfig[];
|
||||
}) => void,
|
||||
onSuccess: (document: ConstellationDocument) => void,
|
||||
onError: (error: string) => void
|
||||
): void {
|
||||
const reader = new FileReader();
|
||||
|
|
@ -80,14 +76,8 @@ export function importGraphFromFile(
|
|||
throw new Error('Invalid file format: File does not match expected Constellation Analyzer format');
|
||||
}
|
||||
|
||||
// Deserialize the graph state
|
||||
const graphState = deserializeGraphState(parsed as ConstellationDocument);
|
||||
|
||||
if (!graphState) {
|
||||
throw new Error('Failed to parse graph data from file');
|
||||
}
|
||||
|
||||
onSuccess(graphState);
|
||||
// Return the full document with timeline intact
|
||||
onSuccess(parsed as ConstellationDocument);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred while importing file';
|
||||
onError(errorMessage);
|
||||
|
|
@ -103,14 +93,10 @@ export function importGraphFromFile(
|
|||
|
||||
/**
|
||||
* Trigger file selection dialog for import
|
||||
* Returns the full document with timeline preserved
|
||||
*/
|
||||
export function selectFileForImport(
|
||||
onSuccess: (data: {
|
||||
nodes: Actor[];
|
||||
edges: Relation[];
|
||||
nodeTypes: NodeTypeConfig[];
|
||||
edgeTypes: EdgeTypeConfig[];
|
||||
}) => void,
|
||||
onSuccess: (document: ConstellationDocument) => void,
|
||||
onError: (error: string) => void
|
||||
): void {
|
||||
const input = document.createElement('input');
|
||||
|
|
|
|||
|
|
@ -184,12 +184,5 @@ export function loadGraphState(): {
|
|||
return deserializeGraphState(document);
|
||||
}
|
||||
|
||||
// Check if saved state exists
|
||||
export function hasSavedState(): boolean {
|
||||
return localStorage.getItem(STORAGE_KEYS.GRAPH_STATE) !== null;
|
||||
}
|
||||
|
||||
// Get last saved timestamp
|
||||
export function getLastSavedTimestamp(): string | null {
|
||||
return localStorage.getItem(STORAGE_KEYS.LAST_SAVED);
|
||||
}
|
||||
// NOTE: hasSavedState() and getLastSavedTimestamp() have been removed
|
||||
// They were part of the legacy single-document system and are no longer needed
|
||||
|
|
|
|||
|
|
@ -99,8 +99,6 @@ export function saveDocument(document: ConstellationDocument): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
// Clear saved state (legacy function)
|
||||
export function clearSavedState(): void {
|
||||
localStorage.removeItem(STORAGE_KEYS.GRAPH_STATE);
|
||||
localStorage.removeItem(STORAGE_KEYS.LAST_SAVED);
|
||||
}
|
||||
// NOTE: clearSavedState() has been removed
|
||||
// It was part of the legacy single-document system and is no longer needed
|
||||
// Workspace clearing is now handled by clearWorkspaceStorage() in workspace/persistence.ts
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { create } from 'zustand';
|
||||
import type { ConstellationDocument } from './persistence/types';
|
||||
import type { Workspace, WorkspaceActions, DocumentMetadata, WorkspaceSettings } from './workspace/types';
|
||||
import { createDocument as createDocumentHelper, serializeActors, serializeRelations } from './persistence/saver';
|
||||
import { createDocument as createDocumentHelper } from './persistence/saver';
|
||||
import { selectFileForImport, exportDocumentToFile } from './persistence/fileIO';
|
||||
import {
|
||||
generateWorkspaceId,
|
||||
|
|
@ -547,26 +547,19 @@ export const useWorkspaceStore = create<Workspace & WorkspaceActions>((set, get)
|
|||
importDocumentFromFile: async () => {
|
||||
return new Promise((resolve) => {
|
||||
selectFileForImport(
|
||||
(data) => {
|
||||
(importedDoc) => {
|
||||
const documentId = generateDocumentId();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Serialize actors and relations for storage
|
||||
const serializedNodes = serializeActors(data.nodes);
|
||||
const serializedEdges = serializeRelations(data.edges);
|
||||
|
||||
const importedDoc = createDocumentHelper(
|
||||
serializedNodes,
|
||||
serializedEdges,
|
||||
data.nodeTypes,
|
||||
data.edgeTypes
|
||||
);
|
||||
// Use the imported document as-is, preserving the complete timeline structure
|
||||
// Just update the IDs and metadata for the new workspace context
|
||||
importedDoc.metadata.documentId = documentId;
|
||||
importedDoc.metadata.title = 'Imported Analysis';
|
||||
importedDoc.metadata.title = importedDoc.metadata.title || 'Imported Analysis';
|
||||
importedDoc.metadata.updatedAt = now;
|
||||
|
||||
const metadata: DocumentMetadata = {
|
||||
id: documentId,
|
||||
title: 'Imported Analysis',
|
||||
title: importedDoc.metadata.title || 'Imported Analysis',
|
||||
isDirty: false,
|
||||
lastModified: now,
|
||||
};
|
||||
|
|
@ -575,6 +568,7 @@ export const useWorkspaceStore = create<Workspace & WorkspaceActions>((set, get)
|
|||
saveDocumentMetadata(documentId, metadata);
|
||||
|
||||
// Load the timeline from the imported document into timelineStore
|
||||
// This preserves all timeline states, not just the current one
|
||||
useTimelineStore.getState().loadTimeline(documentId, importedDoc.timeline as unknown as Timeline);
|
||||
|
||||
set((state) => {
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ export interface GraphActions {
|
|||
setEdges: (edges: Relation[]) => void;
|
||||
setNodeTypes: (nodeTypes: NodeTypeConfig[]) => void;
|
||||
setEdgeTypes: (edgeTypes: EdgeTypeConfig[]) => void;
|
||||
exportToFile: () => void;
|
||||
importFromFile: (onError?: (error: string) => void) => void;
|
||||
// NOTE: exportToFile and importFromFile have been removed
|
||||
// Import/export is now handled by the workspace-level system (workspaceStore)
|
||||
loadGraphState: (data: { nodes: Actor[]; edges: Relation[]; nodeTypes: NodeTypeConfig[]; edgeTypes: EdgeTypeConfig[] }) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue