refactoring: graph state
This commit is contained in:
@@ -48,6 +48,20 @@ This doc summarizes recent improvements and suggested next steps for readability
|
|||||||
- **config/renderingLogic**: uses context nodes/edges directly (no casts); `setVarInContext(src: NodeLike)`.
|
- **config/renderingLogic**: uses context nodes/edges directly (no casts); `setVarInContext(src: NodeLike)`.
|
||||||
- **useRenderingNodeState**: casts to `NodeLike[]` / `EdgeLike[]` when calling `buildSourceSignatures` (types from renderingSignatures).
|
- **useRenderingNodeState**: casts to `NodeLike[]` / `EdgeLike[]` when calling `buildSourceSignatures` (types from renderingSignatures).
|
||||||
|
|
||||||
|
### 9. Graph state, connection state, and node display state
|
||||||
|
|
||||||
|
- **lib/graph/state.ts**: Central state contracts and flow doc.
|
||||||
|
- **GraphState** – in-memory nodes + edges (useGraphStateWithHistory, persisted by useCanvasGraph).
|
||||||
|
- **ConnectionPathState** – type describing the slice that drives edge status and path animation (trigger/updating/paused/error sets).
|
||||||
|
- **NodeDisplayStatus** – `'initial' | 'loading' | 'success' | 'error'` for node UI; **getNodeDisplayStatus({ loading, error, hasContent })**.
|
||||||
|
- **StoredGraphState** – shape for save/load (version + nodes + edges).
|
||||||
|
- **FlowContext**: Module doc splits value into (1) Graph state, (2) Connection path state, (3) UI state. Same flat props, clearer sections.
|
||||||
|
- **useGraphStateWithHistory**: JSDoc explains history (past/future), setNodes vs setNodesSilent, setStateImmediate.
|
||||||
|
- **projectGraphStorage**: Uses **StoredGraphState** from state.ts; re-exports type. Doc references state flow.
|
||||||
|
- **useRenderingNodeState**: Returns **displayStatus** (for NodeStatusIndicator/empty/error UI) and **lifecycle** (updating/error/paused for useSyncConnectionStatus). Hook calls useSyncConnectionStatus(id, state.lifecycle). Type includes RenderingNodeLifecycle.
|
||||||
|
- **RenderingNode**: Uses **state.displayStatus** for NodeStatusIndicator instead of computing status locally.
|
||||||
|
- **nodeLifecycle** and **connectionStatus**: Docs reference state.ts for overall state flow.
|
||||||
|
|
||||||
## Design patterns in use
|
## Design patterns in use
|
||||||
|
|
||||||
| Pattern | Where |
|
| Pattern | Where |
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Per-project graph persistence (localStorage).
|
* Per-project graph persistence (localStorage).
|
||||||
* Used by CanvasPage to load/save and by ProjectsTablePage for export.
|
* Saves and loads StoredGraphState (version + nodes + edges). Used by useCanvasGraph and export.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { StoredGraphState } from '@/lib/graph/state'
|
||||||
|
|
||||||
|
export type { StoredGraphState }
|
||||||
export const PROJECT_FILE_EXT = '.zui.json'
|
export const PROJECT_FILE_EXT = '.zui.json'
|
||||||
export const PROJECT_VERSION = 1
|
export const PROJECT_VERSION = 1
|
||||||
|
|
||||||
@@ -12,22 +15,20 @@ export function getGraphStorageKey(projectId: string): string {
|
|||||||
return `${GRAPH_KEY_PREFIX}${projectId}`
|
return `${GRAPH_KEY_PREFIX}${projectId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GraphState = { version: number; nodes: unknown[]; edges: unknown[] }
|
export function loadGraphFromStorage(projectId: string): StoredGraphState | null {
|
||||||
|
|
||||||
export function loadGraphFromStorage(projectId: string): GraphState | null {
|
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(getGraphStorageKey(projectId))
|
const raw = localStorage.getItem(getGraphStorageKey(projectId))
|
||||||
if (!raw) return null
|
if (!raw) return null
|
||||||
const data = JSON.parse(raw) as unknown
|
const data = JSON.parse(raw) as unknown
|
||||||
if (!data || typeof data !== 'object' || !Array.isArray((data as GraphState).nodes) || !Array.isArray((data as GraphState).edges))
|
if (!data || typeof data !== 'object' || !Array.isArray((data as StoredGraphState).nodes) || !Array.isArray((data as StoredGraphState).edges))
|
||||||
return null
|
return null
|
||||||
return data as GraphState
|
return data as StoredGraphState
|
||||||
} catch {
|
} catch {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveGraphToStorage(projectId: string, state: GraphState): void {
|
export function saveGraphToStorage(projectId: string, state: StoredGraphState): void {
|
||||||
localStorage.setItem(getGraphStorageKey(projectId), JSON.stringify(state))
|
localStorage.setItem(getGraphStorageKey(projectId), JSON.stringify(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,14 +62,6 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
|
|||||||
? { width, height }
|
? { width, height }
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const status = state.loading
|
|
||||||
? 'loading'
|
|
||||||
: state.error
|
|
||||||
? 'error'
|
|
||||||
: state.renderedContent
|
|
||||||
? 'success'
|
|
||||||
: 'initial'
|
|
||||||
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [rawEditorHeight, rawEditorContainerRef] = useResizeHeight(180, [viewMode])
|
const [rawEditorHeight, rawEditorContainerRef] = useResizeHeight(180, [viewMode])
|
||||||
|
|
||||||
@@ -138,7 +130,7 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeStatusIndicator status={status} variant="overlay" width={dimensions?.width} height={dimensions?.height}>
|
<NodeStatusIndicator status={state.displayStatus} variant="overlay" width={dimensions?.width} height={dimensions?.height}>
|
||||||
<BaseNode
|
<BaseNode
|
||||||
className="min-w-96 min-h-[320px]"
|
className="min-w-96 min-h-[320px]"
|
||||||
dimensions={dimensions}
|
dimensions={dimensions}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
stripTemplateSyntax,
|
stripTemplateSyntax,
|
||||||
} from '@/lib/graph/rendering'
|
} from '@/lib/graph/rendering'
|
||||||
import { buildSourceSignatures, type NodeLike, type EdgeLike } from '@/lib/graph/renderingSignatures'
|
import { buildSourceSignatures, type NodeLike, type EdgeLike } from '@/lib/graph/renderingSignatures'
|
||||||
|
import { getNodeDisplayStatus, type NodeDisplayStatus } from '@/lib/graph/state'
|
||||||
import type { ConfigTypeId, SourceRenderingLogicContext } from '@/lib/graph/rendering'
|
import type { ConfigTypeId, SourceRenderingLogicContext } from '@/lib/graph/rendering'
|
||||||
|
|
||||||
export type RenderingNodeData = {
|
export type RenderingNodeData = {
|
||||||
@@ -38,7 +39,19 @@ const DEFAULT_VIEWPORT_WIDTH = 1200
|
|||||||
const DEFAULT_VIEWPORT_HEIGHT = 800
|
const DEFAULT_VIEWPORT_HEIGHT = 800
|
||||||
const RENDER_DEBOUNCE_MS = 250
|
const RENDER_DEBOUNCE_MS = 250
|
||||||
|
|
||||||
|
/** Lifecycle state to pass to useSyncConnectionStatus so edge status (updating/paused/error) stays in sync. */
|
||||||
|
export type RenderingNodeLifecycle = {
|
||||||
|
updating: boolean
|
||||||
|
error: boolean
|
||||||
|
paused: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type RenderingNodeState = {
|
export type RenderingNodeState = {
|
||||||
|
/** For NodeStatusIndicator and empty/error UI. Derived from loading, error, renderedContent. */
|
||||||
|
displayStatus: NodeDisplayStatus
|
||||||
|
/** Pass to useSyncConnectionStatus so edges show correct status (blue/yellow/red). */
|
||||||
|
lifecycle: RenderingNodeLifecycle
|
||||||
|
|
||||||
// Connection & run control
|
// Connection & run control
|
||||||
incomingIds: string[]
|
incomingIds: string[]
|
||||||
effectiveUpdateMode: 'auto' | 'manual'
|
effectiveUpdateMode: 'auto' | 'manual'
|
||||||
@@ -183,7 +196,11 @@ export function useRenderingNodeState(
|
|||||||
incomingIds.length > 0 &&
|
incomingIds.length > 0 &&
|
||||||
sourceSignature !== lastRunSourceSignature
|
sourceSignature !== lastRunSourceSignature
|
||||||
|
|
||||||
useSyncConnectionStatus(id, { updating: loading, error: error != null, paused: hasPendingInputs })
|
const lifecycle = useMemo<RenderingNodeLifecycle>(
|
||||||
|
() => ({ updating: loading, error: error != null, paused: hasPendingInputs }),
|
||||||
|
[loading, error, hasPendingInputs]
|
||||||
|
)
|
||||||
|
useSyncConnectionStatus(id, lifecycle)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (incomingIds.length === 0) {
|
if (incomingIds.length === 0) {
|
||||||
@@ -472,7 +489,14 @@ export function useRenderingNodeState(
|
|||||||
const sourceData = useMemo(() => (srcNode?.data as Record<string, unknown>) ?? {}, [srcNode?.data])
|
const sourceData = useMemo(() => (srcNode?.data as Record<string, unknown>) ?? {}, [srcNode?.data])
|
||||||
const sourceNodeType = (srcNode?.type as string) ?? null
|
const sourceNodeType = (srcNode?.type as string) ?? null
|
||||||
|
|
||||||
|
const displayStatus = useMemo<NodeDisplayStatus>(
|
||||||
|
() => getNodeDisplayStatus({ loading, error, hasContent: !!renderedContent }),
|
||||||
|
[loading, error, renderedContent]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
displayStatus,
|
||||||
|
lifecycle,
|
||||||
incomingIds,
|
incomingIds,
|
||||||
effectiveUpdateMode,
|
effectiveUpdateMode,
|
||||||
runTrigger,
|
runTrigger,
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { useCallback, useRef, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
|
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory graph state. History is a past/future stack of GraphState snapshots.
|
||||||
|
* setNodes/setEdges push current state to past; setNodesSilent/setEdgesSilent update without history.
|
||||||
|
* setStateImmediate replaces state and clears history (e.g. load example, import).
|
||||||
|
*/
|
||||||
export type GraphState = { nodes: AppNode[]; edges: AppEdge[] }
|
export type GraphState = { nodes: AppNode[]; edges: AppEdge[] }
|
||||||
|
|
||||||
function cloneState(state: GraphState): GraphState {
|
function cloneState(state: GraphState): GraphState {
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
* Connection status: visual state of an edge (color/class).
|
* Connection status: visual state of an edge (color/class).
|
||||||
* Priority when multiple apply: error > paused > updating > default.
|
* Priority when multiple apply: error > paused > updating > default.
|
||||||
*
|
*
|
||||||
* Status is derived from node lifecycle state: nodes report updating / paused / error
|
* State flow: nodes report lifecycle via useSyncConnectionStatus() (nodeLifecycle.ts) → FlowContext
|
||||||
* via useSyncConnectionStatus() in nodeLifecycle.ts, which updates FlowContext sets.
|
* holds the sets → edges pass those sets into getConnectionStatus() here. See lib/graph/state.ts.
|
||||||
* Edges read those sets here to pick the single status per edge.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type ConnectionStatus = 'default' | 'updating' | 'paused' | 'error'
|
export type ConnectionStatus = 'default' | 'updating' | 'paused' | 'error'
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* FlowContext provides graph state, connection path state, and UI state to nodes and edges.
|
||||||
|
*
|
||||||
|
* ## 1. Graph state (nodes, edges)
|
||||||
|
* - nodes, setNodes, edges, setEdges – owned by useCanvasGraph (history + persistence).
|
||||||
|
*
|
||||||
|
* ## 2. Connection path state (edge visuals and path animation)
|
||||||
|
* - Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus() in nodeLifecycle.
|
||||||
|
* - Context holds: connectionPathUpdatingNodeIds, connectionPathTriggerNodeIds, connectionPathNodeIds,
|
||||||
|
* connectionPathPausedSegmentNodeIds, connectionPathActiveSegmentNodeIds, connectionPathPausedNodeIds,
|
||||||
|
* connectionPathErrorNodeIds, plus add/remove/start/end callbacks.
|
||||||
|
* - Edges use these in getConnectionStatus() for color (default / updating / paused / error). See connectionStatus.ts.
|
||||||
|
*
|
||||||
|
* ## 3. UI state
|
||||||
|
* - renamingNodeId, fullscreenNodeId, connectionFrom (drag-from handle), flowActionsRef, isValidConnection.
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import type { Connection } from '@xyflow/react'
|
import type { Connection } from '@xyflow/react'
|
||||||
import type { AppNode, AppEdge } from './nodeTypes'
|
import type { AppNode, AppEdge } from './nodeTypes'
|
||||||
@@ -13,48 +30,36 @@ export type FlowActions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type FlowContextValue = {
|
export type FlowContextValue = {
|
||||||
|
// ---- Graph state ----
|
||||||
nodes: AppNode[]
|
nodes: AppNode[]
|
||||||
setNodes: (updater: AppNode[] | ((prev: AppNode[]) => AppNode[])) => void
|
setNodes: (updater: AppNode[] | ((prev: AppNode[]) => AppNode[])) => void
|
||||||
edges: AppEdge[]
|
edges: AppEdge[]
|
||||||
setEdges: (updater: AppEdge[] | ((prev: AppEdge[]) => AppEdge[])) => void
|
setEdges: (updater: AppEdge[] | ((prev: AppEdge[]) => AppEdge[])) => void
|
||||||
|
|
||||||
|
// ---- UI state ----
|
||||||
renamingNodeId: string | null
|
renamingNodeId: string | null
|
||||||
setRenamingNodeId: (id: string | null) => void
|
setRenamingNodeId: (id: string | null) => void
|
||||||
/** Set when user starts dragging from an output handle; cleared on connect end. Used to highlight valid targets. */
|
|
||||||
connectionFrom: ConnectionFrom
|
connectionFrom: ConnectionFrom
|
||||||
setConnectionFrom: (v: ConnectionFrom) => void
|
setConnectionFrom: (v: ConnectionFrom) => void
|
||||||
isValidConnection: (connection: Connection) => boolean
|
isValidConnection: (connection: Connection) => boolean
|
||||||
/** Set by FlowKeyboardShortcuts so Node menubar can trigger paste / fit view. */
|
|
||||||
flowActionsRef: React.MutableRefObject<FlowActions | null>
|
flowActionsRef: React.MutableRefObject<FlowActions | null>
|
||||||
/** When set, graph centers on this node and a fullscreen dialog shows the node. Cleared on close. */
|
|
||||||
fullscreenNodeId: string | null
|
fullscreenNodeId: string | null
|
||||||
setFullscreenNodeId: (id: string | null) => void
|
setFullscreenNodeId: (id: string | null) => void
|
||||||
/** Node ids currently updating (e.g. render loading, agent running). Only edges on those paths show the ant trail. */
|
|
||||||
|
// ---- Connection path state (edge status and path animation; see state.ts ConnectionPathState) ----
|
||||||
connectionPathUpdatingNodeIds: string[]
|
connectionPathUpdatingNodeIds: string[]
|
||||||
/** Node ids that triggered the current update (e.g. variable/config that changed). Path is restricted to downstream(trigger) ∩ upstream(updating). */
|
|
||||||
connectionPathTriggerNodeIds: string[]
|
connectionPathTriggerNodeIds: string[]
|
||||||
/** Call when this node's output changed and may trigger downstream updates (e.g. variable value, config content). */
|
|
||||||
addConnectionPathTrigger: (nodeId: string) => void
|
addConnectionPathTrigger: (nodeId: string) => void
|
||||||
/** All node ids on the path of an update. Edges with both endpoints in this set animate. */
|
|
||||||
connectionPathNodeIds: Set<string>
|
connectionPathNodeIds: Set<string>
|
||||||
/** Path nodes in the "paused" segment (from trigger up to Archon on hold). Those edges are yellow. */
|
|
||||||
connectionPathPausedSegmentNodeIds: Set<string>
|
connectionPathPausedSegmentNodeIds: Set<string>
|
||||||
/** Path nodes not in the paused segment (downstream of pause). Only those edges are blue (updating). */
|
|
||||||
connectionPathActiveSegmentNodeIds: Set<string>
|
connectionPathActiveSegmentNodeIds: Set<string>
|
||||||
/** Nodes that are on hold (e.g. Renderer in manual mode waiting for Run). */
|
|
||||||
connectionPathPausedNodeIds: string[]
|
connectionPathPausedNodeIds: string[]
|
||||||
/** Add this node as paused (on hold); remove when user continues. */
|
|
||||||
addConnectionPathPausedNode: (nodeId: string) => void
|
addConnectionPathPausedNode: (nodeId: string) => void
|
||||||
/** Remove this node from paused. */
|
|
||||||
removeConnectionPathPausedNode: (nodeId: string) => void
|
removeConnectionPathPausedNode: (nodeId: string) => void
|
||||||
/** Node ids that have an error (e.g. Render node). Incoming edges show error status (red). */
|
|
||||||
connectionPathErrorNodeIds: string[]
|
connectionPathErrorNodeIds: string[]
|
||||||
/** Call when this node has an error; remove when error is cleared. */
|
|
||||||
addConnectionPathError: (nodeId: string) => void
|
addConnectionPathError: (nodeId: string) => void
|
||||||
/** Call when this node's error is cleared. */
|
|
||||||
removeConnectionPathError: (nodeId: string) => void
|
removeConnectionPathError: (nodeId: string) => void
|
||||||
/** Call when a path update starts for this node. Animation runs at least CONNECTION_PATH_UPDATE_MIN_MS. */
|
|
||||||
startConnectionPathUpdate: (nodeId: string) => void
|
startConnectionPathUpdate: (nodeId: string) => void
|
||||||
/** Call when a path update ends for this node. If min duration not reached, animation continues until then. */
|
|
||||||
endConnectionPathUpdate: (nodeId: string) => void
|
endConnectionPathUpdate: (nodeId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,30 +2,21 @@
|
|||||||
* Node lifecycle: contract that nodes implement so the graph can show the right
|
* Node lifecycle: contract that nodes implement so the graph can show the right
|
||||||
* connection status (edge colors) and path animation.
|
* connection status (edge colors) and path animation.
|
||||||
*
|
*
|
||||||
|
* ## State flow
|
||||||
|
*
|
||||||
|
* Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus(id, state).
|
||||||
|
* This hook updates FlowContext sets; edges read them via getConnectionStatus() in connectionStatus.ts.
|
||||||
|
* See lib/graph/state.ts for the overall state flow (graph state, connection path state).
|
||||||
|
*
|
||||||
* ## Lifecycle phases (conceptual)
|
* ## Lifecycle phases (conceptual)
|
||||||
*
|
*
|
||||||
* - **Idle** – Node is not on an active path; edges to/from it use default style.
|
* - **Idle** – Node is not on an active path; edges use default style.
|
||||||
* - **Trigger** – Node's output changed (e.g. config content, variable value). Reported
|
* - **Trigger** – Node's output changed; path is computed from triggers + updating nodes.
|
||||||
* automatically when the node calls `updateData()` from useAbstractNode. Downstream
|
* - **Updating** – Node is doing async work. Report `updating: true` → false. Path edges show blue.
|
||||||
* path is computed from triggers + updating nodes.
|
* - **Paused** – Node is on hold (e.g. manual mode waiting for Run). Report `paused: true` → false. Segment shows yellow.
|
||||||
* - **Updating** – Node is doing async work (e.g. renderer loading, agent run triggered by renderer).
|
* - **Error** – Node has an error. Report `error: true` → false. Incoming edges show red.
|
||||||
* Report `updating: true` at start, `updating: false` when done. Incoming/outgoing
|
|
||||||
* edges on the path show "updating" (blue).
|
|
||||||
* - **Paused** – Node is on hold (e.g. renderer in manual mode waiting for Run after inputs changed).
|
|
||||||
* Report `paused: true` when waiting, `paused: false` when not. Edges in the paused
|
|
||||||
* segment show "paused" (yellow).
|
|
||||||
* - **Error** – Node has an error to show. Report `error: true` when error is set,
|
|
||||||
* `error: false` when cleared. Incoming edges to this node show "error" (red).
|
|
||||||
*
|
*
|
||||||
* ## Connection status integration
|
* Priority for edge status: error > paused > updating > default.
|
||||||
*
|
|
||||||
* Edge status is derived in getConnectionStatus() from the sets that this lifecycle
|
|
||||||
* feeds: connectionPathUpdatingNodeIds, connectionPathPausedNodeIds,
|
|
||||||
* connectionPathErrorNodeIds (plus path/paused segment from graphPath). Priority:
|
|
||||||
* error > paused > updating > default.
|
|
||||||
*
|
|
||||||
* Nodes that can be updating, paused, or in error should call useSyncConnectionStatus()
|
|
||||||
* with their current state so edges update correctly.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useContext, useEffect, useRef } from 'react'
|
import { useContext, useEffect, useRef } from 'react'
|
||||||
|
|||||||
87
frontend/src/lib/graph/state.ts
Normal file
87
frontend/src/lib/graph/state.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Central state contracts for the graph: in-memory graph, connection path (edge visuals),
|
||||||
|
* and node display status. Use these types to keep state flow predictable and readable.
|
||||||
|
*
|
||||||
|
* ## State flow (high level)
|
||||||
|
*
|
||||||
|
* 1. **Graph state** (nodes, edges) – Owned by useGraphStateWithHistory, persisted by useCanvasGraph.
|
||||||
|
* Updates via setNodes/setEdges (with undo) or setNodesSilent/setEdgesSilent (no history).
|
||||||
|
*
|
||||||
|
* 2. **Connection path state** – Owned by useCanvasConnectionPath, provided via FlowContext.
|
||||||
|
* Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus; the hook
|
||||||
|
* updates context sets. Edges read those sets and getConnectionStatus() to decide color (default / updating / paused / error).
|
||||||
|
*
|
||||||
|
* 3. **Node display state** – Each node type derives its own UI state (e.g. loading, success, error, initial).
|
||||||
|
* Rendering node uses displayStatus from useRenderingNodeState; other nodes can use getNodeDisplayStatus().
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AppNode, AppEdge } from './nodeTypes'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Graph state (in-memory)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** In-memory graph: nodes and edges. Updated via setNodes/setEdges; history in useGraphStateWithHistory. */
|
||||||
|
export type GraphState = {
|
||||||
|
nodes: AppNode[]
|
||||||
|
edges: AppEdge[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Connection path state (edge visuals and path animation)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slice of FlowContext that drives edge status and path animation.
|
||||||
|
* Nodes report updating/error/paused via useSyncConnectionStatus; this state is derived from those reports.
|
||||||
|
* Edges use pathNodeIds, pausedSegmentNodeIds, activeSegmentNodeIds, errorTargetNodeIds in getConnectionStatus().
|
||||||
|
*/
|
||||||
|
export type ConnectionPathState = {
|
||||||
|
/** Node ids currently doing async work (e.g. render loading). Path edges show "updating" (blue). */
|
||||||
|
connectionPathUpdatingNodeIds: string[]
|
||||||
|
/** Node ids that triggered the update (e.g. config changed). Path = downstream(trigger) ∩ upstream(updating). */
|
||||||
|
connectionPathTriggerNodeIds: string[]
|
||||||
|
/** All node ids on the path. Edges with both endpoints here animate. */
|
||||||
|
connectionPathNodeIds: Set<string>
|
||||||
|
/** Path nodes in the paused segment (yellow edges). */
|
||||||
|
connectionPathPausedSegmentNodeIds: Set<string>
|
||||||
|
/** Path nodes in the active segment (blue edges). */
|
||||||
|
connectionPathActiveSegmentNodeIds: Set<string>
|
||||||
|
/** Node ids on hold (e.g. render in manual mode). */
|
||||||
|
connectionPathPausedNodeIds: string[]
|
||||||
|
/** Node ids with error (incoming edges show red). */
|
||||||
|
connectionPathErrorNodeIds: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Node display status (for UI: status indicator, empty state)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Status for node UI: what to show in NodeStatusIndicator and empty/error states. */
|
||||||
|
export type NodeDisplayStatus = 'initial' | 'loading' | 'success' | 'error'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive display status from node state. Use in nodes to drive NodeStatusIndicator and empty/error UI.
|
||||||
|
* Priority: loading > error > hasContent (success) > initial.
|
||||||
|
*/
|
||||||
|
export function getNodeDisplayStatus(state: {
|
||||||
|
loading: boolean
|
||||||
|
error: unknown
|
||||||
|
hasContent: boolean
|
||||||
|
}): NodeDisplayStatus {
|
||||||
|
if (state.loading) return 'loading'
|
||||||
|
if (state.error != null) return 'error'
|
||||||
|
if (state.hasContent) return 'success'
|
||||||
|
return 'initial'
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Stored graph state (persistence)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Shape of graph state when saved/loaded (e.g. localStorage). version for future migrations. */
|
||||||
|
export type StoredGraphState = {
|
||||||
|
version: number
|
||||||
|
nodes: unknown[]
|
||||||
|
edges: unknown[]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user