mirror of
https://github.com/OFFIS-ESC/constellation-analyzer
synced 2026-01-27 07:43:41 +00:00
Implements a comprehensive label system and completely redesigns all filtering (labels, actor types, relation types) to use intuitive positive filtering where empty selection shows all items. Label System Features: - Create, edit, delete labels with names, colors, and scope (actors/relations/both) - Inline editing with click-to-edit UI for quick modifications - Quick-add label forms in config modals - Autocomplete label selector with inline label creation - Label badges rendered on nodes and edges (no overflow limits) - Full undo/redo support for label operations - Label validation and cleanup when labels are deleted - Labels stored per-document in workspace system Filter System Redesign: - Changed from negative to positive filtering for all filter types - Empty selection = show all items (intuitive default) - Selected items = show only those items (positive filter) - Consistent behavior across actor types, relation types, and labels - Clear visual feedback with selection counts and helper text - Auto-zoom viewport adjustment works for all filter types including labels Label Cleanup & Validation: - When label deleted, automatically removed from all nodes/edges across all timeline states - Label references validated during node/edge updates - Unknown label IDs filtered out to maintain data integrity UI Improvements: - All labels rendered without overflow limits (removed +N more indicators) - Filter checkboxes start unchecked (select to filter, not hide) - Helper text explains current filter state - Selection counts displayed in filter section headers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import type { ConstellationDocument, SerializedActor, SerializedRelation } from './types';
|
|
import type { Actor, Relation, NodeTypeConfig, EdgeTypeConfig, LabelConfig } from '../../types';
|
|
import { STORAGE_KEYS, SCHEMA_VERSION, APP_NAME } from './constants';
|
|
|
|
/**
|
|
* Saver - Handles serialization and saving to localStorage
|
|
*/
|
|
|
|
// Serialize actors for storage (strip React Flow internals)
|
|
export function serializeActors(actors: Actor[]): SerializedActor[] {
|
|
return actors.map(actor => ({
|
|
id: actor.id,
|
|
type: actor.type || 'custom', // Default to 'custom' if undefined
|
|
position: actor.position,
|
|
data: actor.data,
|
|
}));
|
|
}
|
|
|
|
// Serialize relations for storage (strip React Flow internals)
|
|
export function serializeRelations(relations: Relation[]): SerializedRelation[] {
|
|
return relations.map(relation => ({
|
|
id: relation.id,
|
|
source: relation.source,
|
|
target: relation.target,
|
|
type: relation.type,
|
|
data: relation.data,
|
|
sourceHandle: relation.sourceHandle,
|
|
targetHandle: relation.targetHandle,
|
|
}));
|
|
}
|
|
|
|
// Generate unique state ID
|
|
function generateStateId(): string {
|
|
return `state_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
// Create a complete document from graph data
|
|
// Creates a document with a single initial timeline state containing the provided graph
|
|
export function createDocument(
|
|
nodes: SerializedActor[],
|
|
edges: SerializedRelation[],
|
|
nodeTypes: NodeTypeConfig[],
|
|
edgeTypes: EdgeTypeConfig[],
|
|
labels?: LabelConfig[],
|
|
existingDocument?: ConstellationDocument
|
|
): ConstellationDocument {
|
|
const now = new Date().toISOString();
|
|
const rootStateId = generateStateId();
|
|
|
|
// Create the initial timeline state with the provided graph (nodes and edges only)
|
|
const initialState = {
|
|
id: rootStateId,
|
|
label: 'Initial State',
|
|
parentStateId: undefined,
|
|
graph: {
|
|
nodes,
|
|
edges,
|
|
},
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
|
|
// Create document with global types, labels, and timeline containing the initial state
|
|
return {
|
|
metadata: {
|
|
version: SCHEMA_VERSION,
|
|
appName: APP_NAME,
|
|
createdAt: existingDocument?.metadata?.createdAt || now,
|
|
updatedAt: now,
|
|
lastSavedBy: 'browser',
|
|
},
|
|
nodeTypes,
|
|
edgeTypes,
|
|
labels: labels || [],
|
|
timeline: {
|
|
states: {
|
|
[rootStateId]: initialState,
|
|
},
|
|
currentStateId: rootStateId,
|
|
rootStateId: rootStateId,
|
|
},
|
|
};
|
|
}
|
|
|
|
// Save document to localStorage (legacy function for old single-document system)
|
|
// NOTE: This is only used for migration purposes. Workspace documents are saved
|
|
// via workspace/persistence.ts
|
|
export function saveDocument(document: ConstellationDocument): boolean {
|
|
try {
|
|
const json = JSON.stringify(document);
|
|
localStorage.setItem(STORAGE_KEYS.GRAPH_STATE, json);
|
|
localStorage.setItem(STORAGE_KEYS.LAST_SAVED, document.metadata.updatedAt);
|
|
return true;
|
|
} catch (error) {
|
|
if (error instanceof Error && error.name === 'QuotaExceededError') {
|
|
console.error('Storage quota exceeded');
|
|
} else {
|
|
console.error('Failed to save document:', error);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 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
|