refactoring
This commit is contained in:
@@ -31,6 +31,23 @@ This doc summarizes recent improvements and suggested next steps for readability
|
||||
- **CanvasContextMenuContent** (`app/canvas/CanvasContextMenuContent.tsx`): context menu content (Create Node grouped by classification, Paste). CanvasPage passes `onCreateNode` and `onPaste`.
|
||||
- **useCanvasConnectionPath** (`app/canvas/useCanvasConnectionPath.ts`): all connection-path state and callbacks (updating/trigger/paused/error node ids, path node ids, start/end update, add/remove paused/error). CanvasPage calls the hook with `edges` and passes the result into FlowContext.
|
||||
|
||||
### 6. useCanvasGraph and canvas graph utils
|
||||
|
||||
- **canvasGraphUtils.ts**: `getExampleGraph()`, `getInitialGraph(projectId)` (load from storage or return example). Example nodes/edges live here.
|
||||
- **useCanvasGraph(projectId)**: wraps `useGraphStateWithHistory` with initial graph from `getInitialGraph(projectId)` and debounced save to storage when `projectId` is set. CanvasPage uses this instead of inline state + persistence.
|
||||
|
||||
### 7. Config types registry
|
||||
|
||||
- **configTypes.ts**: `configTypeRegistry` (Map), `registerConfigType(type)`, `getConfigTypes()`, `getConfigTypeIds()`, `registerBuiltinConfigTypes()`. Built-in types are in `BUILTIN_CONFIG_TYPES` and registered at app init. `getConfigType(id)` looks up in the registry; `getConfigTypeId(data)` uses registered ids. ConfigNode uses `getConfigTypes()` instead of `CONFIG_TYPES`. New output types can call `registerConfigType()` without editing the core array.
|
||||
|
||||
### 8. Consistent NodeLike / EdgeLike
|
||||
|
||||
- **templateRefs.ts**: `NodeLike` includes `type?: string`; both types documented. Single source of truth for minimal node/edge shape in the graph lib.
|
||||
- **sourceRenderingLogic**: context uses `NodeLike[]` and `EdgeLike[]` from templateRefs.
|
||||
- **renderingSignatures**: imports and re-exports `NodeLike` / `EdgeLike` from templateRefs; no local duplicate types.
|
||||
- **config/renderingLogic**: uses context nodes/edges directly (no casts); `setVarInContext(src: NodeLike)`.
|
||||
- **useRenderingNodeState**: casts to `NodeLike[]` / `EdgeLike[]` when calling `buildSourceSignatures` (types from renderingSignatures).
|
||||
|
||||
## Design patterns in use
|
||||
|
||||
| Pattern | Where |
|
||||
@@ -43,8 +60,4 @@ This doc summarizes recent improvements and suggested next steps for readability
|
||||
|
||||
## Suggested next steps
|
||||
|
||||
1. **CanvasPage**: Further split optional: e.g. `useCanvasGraph()` for graph state + persistence + connection rules, so the page is mostly composition and layout.
|
||||
|
||||
2. **Config types**: If you add more output types (e.g. Mermaid), consider a small registry API (`registerConfigType`, `getConfigType`) instead of a single large `CONFIG_TYPES` array.
|
||||
|
||||
3. **Consistent node shape in lib**: `templateRefs` and `renderingSignatures` use `EdgeLike` / `NodeLike`; standardize where appropriate to reduce casts.
|
||||
- **CanvasPage**: Optional further split (e.g. move connection validation or node/edge change handlers into a hook) if the file grows again.
|
||||
|
||||
@@ -26,7 +26,8 @@ import { AnimatedEdge } from '@/components/graph/AnimatedEdge'
|
||||
import FlowContext from '@/lib/graph/flowContext'
|
||||
import { useTheme } from '@/lib/themeContext'
|
||||
import { usePlatform } from '@/app/kosmos/KosmosContext'
|
||||
import { useGraphStateWithHistory } from '@/hooks/useGraphStateWithHistory'
|
||||
import { useCanvasGraph } from '@/app/canvas/useCanvasGraph'
|
||||
import { getExampleGraph } from '@/app/canvas/canvasGraphUtils'
|
||||
import { ContextMenu, ContextMenuTrigger } from '@/components/ui/context-menu'
|
||||
import { CanvasContextMenuContent } from '@/app/canvas/CanvasContextMenuContent'
|
||||
import { useCanvasConnectionPath } from '@/app/canvas/useCanvasConnectionPath'
|
||||
@@ -48,7 +49,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { FolderOpen, FileStack, CircleDotDashed } from 'lucide-react'
|
||||
import { DEFAULT_NODE_STYLE, getDefaultDataForType, getNextNodeId } from '@/lib/graph/flowUtils'
|
||||
import { getDefaultDataForType, getNextNodeId } from '@/lib/graph/flowUtils'
|
||||
import {
|
||||
getRegisteredNodeTypes,
|
||||
getRegisteredNodeTypeIds,
|
||||
@@ -58,12 +59,7 @@ import {
|
||||
} from '@/lib/graph/nodeRegistry'
|
||||
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
loadGraphFromStorage,
|
||||
saveGraphToStorage,
|
||||
PROJECT_FILE_EXT,
|
||||
PROJECT_VERSION,
|
||||
} from '@/app/pleroma/projectGraphStorage'
|
||||
import { PROJECT_FILE_EXT, PROJECT_VERSION } from '@/app/pleroma/projectGraphStorage'
|
||||
|
||||
const SNAP_GRID: [number, number] = [15, 15]
|
||||
const DUPLICATE_OFFSET = { x: 30, y: 30 }
|
||||
@@ -72,52 +68,6 @@ const snapToGrid = (x: number, y: number): { x: number; y: number } => ({
|
||||
y: Math.round(y / SNAP_GRID[1]) * SNAP_GRID[1],
|
||||
})
|
||||
|
||||
const NODE_GAP = 150
|
||||
const initialNodes: AppNode[] = [
|
||||
{
|
||||
id: 'var_001',
|
||||
position: { x: 50, y: 100 },
|
||||
data: { value: 'Zoe', valueType: 'string' as const },
|
||||
type: 'variable',
|
||||
style: DEFAULT_NODE_STYLE.variable,
|
||||
},
|
||||
{
|
||||
id: 'cfg_001',
|
||||
position: { x: 50 + DEFAULT_NODE_STYLE.variable.width + NODE_GAP, y: 50 },
|
||||
data: {
|
||||
plantuml: '@startuml\nactor User\nparticipant "{{ var_001 }}" as R\nUser -> R : loves\n@enduml\n',
|
||||
title: 'config-cfg_001',
|
||||
},
|
||||
type: 'config',
|
||||
style: DEFAULT_NODE_STYLE.config,
|
||||
},
|
||||
{
|
||||
id: 'rnd_001',
|
||||
position: {
|
||||
x: 50 + DEFAULT_NODE_STYLE.variable.width + NODE_GAP + DEFAULT_NODE_STYLE.config.width + NODE_GAP,
|
||||
y: 50,
|
||||
},
|
||||
data: {},
|
||||
type: 'render',
|
||||
style: DEFAULT_NODE_STYLE.render,
|
||||
},
|
||||
]
|
||||
|
||||
const initialEdges: AppEdge[] = [
|
||||
{ id: 'e-var_001-cfg_001', source: 'var_001', target: 'cfg_001', type: 'animated' },
|
||||
{ id: 'e-cfg_001-rnd_001', source: 'cfg_001', target: 'rnd_001', type: 'animated' },
|
||||
]
|
||||
|
||||
function getExampleGraph(): { nodes: AppNode[]; edges: AppEdge[] } {
|
||||
return {
|
||||
nodes: initialNodes.map((n) => ({
|
||||
...n,
|
||||
data: n.data && typeof n.data === 'object' ? { ...(n.data as object) } : n.data,
|
||||
})),
|
||||
edges: initialEdges.map((e) => ({ ...e })),
|
||||
}
|
||||
}
|
||||
|
||||
function FlowFitViewOnLoad() {
|
||||
const nodesInitialized = useNodesInitialized()
|
||||
const { fitView } = useReactFlow()
|
||||
@@ -196,21 +146,9 @@ export type CanvasPageProps = {
|
||||
projectId?: string
|
||||
}
|
||||
|
||||
function getInitialGraph(projectId: string | undefined): { nodes: AppNode[]; edges: AppEdge[] } {
|
||||
if (projectId) {
|
||||
const stored = loadGraphFromStorage(projectId)
|
||||
if (stored && (stored.nodes.length > 0 || stored.edges.length > 0)) {
|
||||
return { nodes: stored.nodes as AppNode[], edges: stored.edges as AppEdge[] }
|
||||
}
|
||||
return { nodes: [], edges: [] }
|
||||
}
|
||||
return getExampleGraph()
|
||||
}
|
||||
|
||||
export function CanvasPage({ projectId }: CanvasPageProps) {
|
||||
const { theme } = useTheme()
|
||||
const { showMinimap } = usePlatform()
|
||||
const initialGraph = useMemo(() => getInitialGraph(projectId), [projectId])
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
@@ -225,20 +163,7 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
|
||||
canUndo,
|
||||
canRedo,
|
||||
setStateImmediate,
|
||||
} = useGraphStateWithHistory(initialGraph.nodes, initialGraph.edges)
|
||||
|
||||
// Persist graph to localStorage when projectId is set (debounced)
|
||||
const saveTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
React.useEffect(() => {
|
||||
if (!projectId) return
|
||||
const save = () => {
|
||||
saveGraphToStorage(projectId, { version: PROJECT_VERSION, nodes, edges })
|
||||
}
|
||||
saveTimeoutRef.current = setTimeout(save, 500)
|
||||
return () => {
|
||||
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
|
||||
}
|
||||
}, [projectId, nodes, edges])
|
||||
} = useCanvasGraph(projectId)
|
||||
|
||||
const importInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const [rfInstance, setRfInstance] = React.useState<unknown>(null)
|
||||
|
||||
66
frontend/src/app/canvas/canvasGraphUtils.ts
Normal file
66
frontend/src/app/canvas/canvasGraphUtils.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Canvas graph: example graph and initial graph from storage.
|
||||
* Used by useCanvasGraph and CanvasPage (e.g. Load example).
|
||||
*/
|
||||
|
||||
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
|
||||
import { DEFAULT_NODE_STYLE } from '@/lib/graph/flowUtils'
|
||||
import { loadGraphFromStorage } from '@/app/pleroma/projectGraphStorage'
|
||||
|
||||
const NODE_GAP = 150
|
||||
|
||||
const EXAMPLE_NODES: AppNode[] = [
|
||||
{
|
||||
id: 'var_001',
|
||||
position: { x: 50, y: 100 },
|
||||
data: { value: 'Zoe', valueType: 'string' as const },
|
||||
type: 'variable',
|
||||
style: DEFAULT_NODE_STYLE.variable,
|
||||
},
|
||||
{
|
||||
id: 'cfg_001',
|
||||
position: { x: 50 + DEFAULT_NODE_STYLE.variable.width + NODE_GAP, y: 50 },
|
||||
data: {
|
||||
plantuml: '@startuml\nactor User\nparticipant "{{ var_001 }}" as R\nUser -> R : loves\n@enduml\n',
|
||||
title: 'config-cfg_001',
|
||||
},
|
||||
type: 'config',
|
||||
style: DEFAULT_NODE_STYLE.config,
|
||||
},
|
||||
{
|
||||
id: 'rnd_001',
|
||||
position: {
|
||||
x: 50 + DEFAULT_NODE_STYLE.variable.width + NODE_GAP + DEFAULT_NODE_STYLE.config.width + NODE_GAP,
|
||||
y: 50,
|
||||
},
|
||||
data: {},
|
||||
type: 'render',
|
||||
style: DEFAULT_NODE_STYLE.render,
|
||||
},
|
||||
]
|
||||
|
||||
const EXAMPLE_EDGES: AppEdge[] = [
|
||||
{ id: 'e-var_001-cfg_001', source: 'var_001', target: 'cfg_001', type: 'animated' },
|
||||
{ id: 'e-cfg_001-rnd_001', source: 'cfg_001', target: 'rnd_001', type: 'animated' },
|
||||
]
|
||||
|
||||
export function getExampleGraph(): { nodes: AppNode[]; edges: AppEdge[] } {
|
||||
return {
|
||||
nodes: EXAMPLE_NODES.map((n) => ({
|
||||
...n,
|
||||
data: n.data && typeof n.data === 'object' ? { ...(n.data as object) } : n.data,
|
||||
})),
|
||||
edges: EXAMPLE_EDGES.map((e) => ({ ...e })),
|
||||
}
|
||||
}
|
||||
|
||||
export function getInitialGraph(projectId: string | undefined): { nodes: AppNode[]; edges: AppEdge[] } {
|
||||
if (projectId) {
|
||||
const stored = loadGraphFromStorage(projectId)
|
||||
if (stored && (stored.nodes.length > 0 || stored.edges.length > 0)) {
|
||||
return { nodes: stored.nodes as AppNode[], edges: stored.edges as AppEdge[] }
|
||||
}
|
||||
return { nodes: [], edges: [] }
|
||||
}
|
||||
return getExampleGraph()
|
||||
}
|
||||
33
frontend/src/app/canvas/useCanvasGraph.ts
Normal file
33
frontend/src/app/canvas/useCanvasGraph.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Hook for canvas graph state and persistence. Wraps useGraphStateWithHistory
|
||||
* with initial graph from project storage (or example) and debounced save.
|
||||
* Keeps CanvasPage focused on composition and layout.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useGraphStateWithHistory } from '@/hooks/useGraphStateWithHistory'
|
||||
import { getInitialGraph } from '@/app/canvas/canvasGraphUtils'
|
||||
import { saveGraphToStorage, PROJECT_VERSION } from '@/app/pleroma/projectGraphStorage'
|
||||
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
|
||||
|
||||
export type UseCanvasGraphResult = ReturnType<typeof useGraphStateWithHistory>
|
||||
|
||||
export function useCanvasGraph(projectId: string | undefined): UseCanvasGraphResult {
|
||||
const initialGraph = useMemo(() => getInitialGraph(projectId), [projectId])
|
||||
const result = useGraphStateWithHistory(initialGraph.nodes, initialGraph.edges)
|
||||
const { nodes, edges } = result
|
||||
|
||||
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
useEffect(() => {
|
||||
if (!projectId) return
|
||||
const save = () => {
|
||||
saveGraphToStorage(projectId, { version: PROJECT_VERSION, nodes, edges })
|
||||
}
|
||||
saveTimeoutRef.current = setTimeout(save, 500)
|
||||
return () => {
|
||||
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
|
||||
}
|
||||
}, [projectId, nodes, edges])
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { nunjucksCompletionSource } from '@/lib/nunjucksAutocomplete'
|
||||
import { plantumlLanguage } from '@/lib/plantumlLanguage'
|
||||
import { useTheme } from '@/lib/themeContext'
|
||||
import {
|
||||
CONFIG_TYPES,
|
||||
getConfigTypes,
|
||||
getConfigContent,
|
||||
getConfigType,
|
||||
getConfigTypeId,
|
||||
@@ -261,7 +261,7 @@ function ConfigNodeComponent({ id, data, width, height, selected }: Props) {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{CONFIG_TYPES.map((t) => (
|
||||
{getConfigTypes().map((t) => (
|
||||
<SelectItem key={t.id} value={t.id} className="text-xs">
|
||||
{t.label}
|
||||
</SelectItem>
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
import nunjucks from 'nunjucks'
|
||||
import type { ResolvedContentResult, SourceRenderingLogicContext } from '@/lib/graph/rendering'
|
||||
import { getConfigContent, getConfigType, getConfigTypeId, type ConfigTypeId } from '@/lib/graph/rendering'
|
||||
import { isReachable, resolveExtendsRef, getTemplateRefs } from '@/lib/graph/templateRefs'
|
||||
|
||||
type Node = { id: string; type?: string; data?: unknown }
|
||||
type Edge = { id: string; source: string; target: string }
|
||||
import { isReachable, resolveExtendsRef, getTemplateRefs, type NodeLike } from '@/lib/graph/templateRefs'
|
||||
|
||||
export async function getResolvedContentForConfig(context: SourceRenderingLogicContext): Promise<ResolvedContentResult> {
|
||||
const { nodes, edges, sourceNodeId, renderNodeId } = context
|
||||
@@ -24,11 +21,11 @@ export async function getResolvedContentForConfig(context: SourceRenderingLogicC
|
||||
const configIdsUsed = new Set<string>()
|
||||
|
||||
const addConfigAndRefs = (templateName: string, visited = new Set<string>()) => {
|
||||
const refId = resolveExtendsRef(nodes as { id: string; data?: unknown }[], templateName)
|
||||
const refId = resolveExtendsRef(nodes, templateName)
|
||||
if (visited.has(refId)) throw new Error(`Circular reference detected: ${templateName}`)
|
||||
const node = nodes.find((n) => n.id === refId && n.type === 'config')
|
||||
if (!node) throw new Error(`Config not found: ${templateName}`)
|
||||
if (refId !== srcId && !isReachable(edges as { source: string; target: string }[], refId, id))
|
||||
if (refId !== srcId && !isReachable(edges, refId, id))
|
||||
throw new Error(`Referenced config not connected to renderer: ${templateName}`)
|
||||
visited.add(refId)
|
||||
configIdsUsed.add(refId)
|
||||
@@ -40,10 +37,10 @@ export async function getResolvedContentForConfig(context: SourceRenderingLogicC
|
||||
|
||||
const configLoader = {
|
||||
getSource: (name: string): { src: string; path: string } | null => {
|
||||
const refId = resolveExtendsRef(nodes as { id: string; data?: unknown }[], name)
|
||||
const refId = resolveExtendsRef(nodes, name)
|
||||
const node = nodes.find((n) => n.id === refId && n.type === 'config')
|
||||
if (!node) return null
|
||||
if (refId !== srcId && !isReachable(edges as { source: string; target: string }[], refId, id))
|
||||
if (refId !== srcId && !isReachable(edges, refId, id))
|
||||
throw new Error(`Referenced config not connected to renderer: ${name}`)
|
||||
return {
|
||||
src: getConfigContent((node.data ?? undefined) as Record<string, unknown> | undefined),
|
||||
@@ -53,7 +50,7 @@ export async function getResolvedContentForConfig(context: SourceRenderingLogicC
|
||||
}
|
||||
|
||||
const nunjucksContext = Object.create(null) as Record<string, unknown>
|
||||
const setVarInContext = (src: Node) => {
|
||||
const setVarInContext = (src: NodeLike) => {
|
||||
const v = (src.data as Record<string, unknown>)?.value
|
||||
const str = v === undefined || v === null ? '' : String(v)
|
||||
nunjucksContext[src.id] =
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
processSvgDisplay,
|
||||
stripTemplateSyntax,
|
||||
} from '@/lib/graph/rendering'
|
||||
import { buildSourceSignatures } from '@/lib/graph/renderingSignatures'
|
||||
import { buildSourceSignatures, type NodeLike, type EdgeLike } from '@/lib/graph/renderingSignatures'
|
||||
import type { ConfigTypeId, SourceRenderingLogicContext } from '@/lib/graph/rendering'
|
||||
|
||||
export type RenderingNodeData = {
|
||||
@@ -137,13 +137,7 @@ export function useRenderingNodeState(
|
||||
: ''
|
||||
|
||||
const signatures = useMemo(
|
||||
() =>
|
||||
buildSourceSignatures(
|
||||
nodes as { id: string; type?: string; data?: unknown }[],
|
||||
edges as { source: string; target: string }[],
|
||||
id,
|
||||
incomingIds
|
||||
),
|
||||
() => buildSourceSignatures(nodes as NodeLike[], edges as EdgeLike[], id, incomingIds),
|
||||
[nodes, edges, id, incomingIds]
|
||||
)
|
||||
const {
|
||||
|
||||
@@ -144,7 +144,27 @@ async function renderWireframe(content: string, options?: RenderOptions): Promis
|
||||
return svg
|
||||
}
|
||||
|
||||
export const CONFIG_TYPES: ConfigType[] = [
|
||||
/** Registry for config (output) types. Built-in types register in registerBuiltinConfigTypes(). */
|
||||
const configTypeRegistry = new Map<string, ConfigType>()
|
||||
|
||||
export function registerConfigType(type: ConfigType): void {
|
||||
if (configTypeRegistry.has(type.id)) {
|
||||
console.warn(`[configTypes] Overwriting config type: ${type.id}`)
|
||||
}
|
||||
configTypeRegistry.set(type.id, type)
|
||||
}
|
||||
|
||||
/** All registered config types (for type selector, etc.). */
|
||||
export function getConfigTypes(): ConfigType[] {
|
||||
return Array.from(configTypeRegistry.values())
|
||||
}
|
||||
|
||||
/** All registered config type ids. */
|
||||
export function getConfigTypeIds(): string[] {
|
||||
return Array.from(configTypeRegistry.keys())
|
||||
}
|
||||
|
||||
const BUILTIN_CONFIG_TYPES: ConfigType[] = [
|
||||
{
|
||||
id: 'plantuml',
|
||||
label: 'Diagram',
|
||||
@@ -220,11 +240,17 @@ export const CONFIG_TYPES: ConfigType[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export const CONFIG_TYPE_IDS = CONFIG_TYPES.map((t) => t.id)
|
||||
export const DEFAULT_CONFIG_TYPE_ID: ConfigTypeId = 'plantuml'
|
||||
|
||||
export function getConfigType(id: ConfigTypeId): ConfigType {
|
||||
const t = CONFIG_TYPES.find((c) => c.id === id)
|
||||
/** Register built-in config types (plantuml, markdown, wireframe). Call once at app init. */
|
||||
export function registerBuiltinConfigTypes(): void {
|
||||
for (const t of BUILTIN_CONFIG_TYPES) {
|
||||
registerConfigType(t)
|
||||
}
|
||||
}
|
||||
|
||||
export function getConfigType(id: string): ConfigType {
|
||||
const t = configTypeRegistry.get(id)
|
||||
if (!t) throw new Error(`Unknown config type: ${id}`)
|
||||
return t
|
||||
}
|
||||
@@ -264,9 +290,9 @@ export function getConfigContent(data: Record<string, unknown> | undefined): str
|
||||
return typeof content === 'string' ? content : ''
|
||||
}
|
||||
|
||||
/** Config type id from data (default plantuml). */
|
||||
/** Config type id from data (default plantuml). Uses registered ids. */
|
||||
export function getConfigTypeId(data: Record<string, unknown> | undefined): ConfigTypeId {
|
||||
if (!data || data.configType == null) return 'plantuml'
|
||||
const id = data.configType
|
||||
return CONFIG_TYPE_IDS.includes(id as ConfigTypeId) ? (id as ConfigTypeId) : 'plantuml'
|
||||
const id = String(data.configType)
|
||||
return configTypeRegistry.has(id) ? (id as ConfigTypeId) : 'plantuml'
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
*
|
||||
* ## Implementing a new output type
|
||||
* - Add a config type (or equivalent) fulfilling `IOutputTypeRenderer`.
|
||||
* - Register in CONFIG_TYPES and use `getConfigType(id)` in the resolve step.
|
||||
* - Call registerConfigType(type) (e.g. from registerBuiltinConfigTypes) and use getConfigType(id) in the resolve step.
|
||||
*
|
||||
* ## Shared helpers
|
||||
* - templateRefs.ts: isReachable, resolveExtendsRef, getTemplateRefs (Nunjucks extends/include/import).
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/**
|
||||
* Pure functions to build "connected node ids" and source signatures for the
|
||||
* rendering pipeline. Used by useRenderingNodeState so the hook stays thin and
|
||||
* this logic is testable in isolation.
|
||||
* this logic is testable in isolation. Uses NodeLike/EdgeLike from templateRefs.
|
||||
*/
|
||||
|
||||
import { getConfigContent } from '@/lib/graph/rendering'
|
||||
import { isReachable, resolveExtendsRef, getTemplateRefs } from '@/lib/graph/templateRefs'
|
||||
import { isReachable, resolveExtendsRef, getTemplateRefs, type NodeLike, type EdgeLike } from '@/lib/graph/templateRefs'
|
||||
|
||||
export type NodeLike = { id: string; type?: string; data?: unknown }
|
||||
export type EdgeLike = { source: string; target: string }
|
||||
export type { NodeLike, EdgeLike } from '@/lib/graph/templateRefs'
|
||||
|
||||
/**
|
||||
* Collects all node ids that affect the render: direct incoming nodes plus
|
||||
|
||||
@@ -7,17 +7,18 @@
|
||||
*/
|
||||
|
||||
import type { ConfigTypeId } from './configTypes'
|
||||
import type { NodeLike, EdgeLike } from './templateRefs'
|
||||
export type { OutputMenuDescriptor, OutputMenuItemDescriptor, OutputMenuAction } from './configTypes'
|
||||
|
||||
/** Context passed into the resolve step (graph, source id, callbacks). See {@link IRenderingContext}. */
|
||||
export type SourceRenderingLogicContext = {
|
||||
nodes: { id: string; type?: string; data?: unknown }[]
|
||||
edges: { id: string; source: string; target: string }[]
|
||||
nodes: NodeLike[]
|
||||
edges: EdgeLike[]
|
||||
sourceNodeId: string
|
||||
renderNodeId: string
|
||||
viewportWidth?: number
|
||||
viewportHeight?: number
|
||||
setNodes?: (updater: (nodes: { id: string; type?: string; data?: unknown }[]) => { id: string; type?: string; data?: unknown }[]) => void
|
||||
setNodes?: (updater: (nodes: NodeLike[]) => NodeLike[]) => void
|
||||
aiConnection?: unknown
|
||||
onStreamingStart?: () => void
|
||||
onStreamingChunk?: (chunk: string) => void
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
* so template parsing and "connected configs" stay in one place.
|
||||
*/
|
||||
|
||||
/** Minimal edge shape for graph helpers (reachability, path, signatures). */
|
||||
export type EdgeLike = { source: string; target: string }
|
||||
export type NodeLike = { id: string; data?: unknown }
|
||||
|
||||
/** Minimal node shape for graph helpers (resolve refs, signatures). */
|
||||
export type NodeLike = { id: string; type?: string; data?: unknown }
|
||||
|
||||
/** BFS: is target reachable from start following directed edges? */
|
||||
export function isReachable(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { Toaster } from 'sonner'
|
||||
import { ThemeProvider } from './lib/themeContext'
|
||||
import { registerBuiltinNodes } from './lib/graph/registerBuiltinNodes'
|
||||
import { registerBuiltinConfigTypes } from './lib/graph/configTypes'
|
||||
import { KosmosPage } from './app/kosmos/KosmosPage'
|
||||
import { ProjectsPage } from './app/pleroma/PleromaPage'
|
||||
import { KeromaPage } from './app/keroma/KeromaPage'
|
||||
@@ -11,6 +12,7 @@ import { CanvasRoute } from './app/canvas/CanvasRoute'
|
||||
import './styles.css'
|
||||
import '@xyflow/react/dist/style.css'
|
||||
|
||||
registerBuiltinConfigTypes()
|
||||
registerBuiltinNodes()
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
|
||||
Reference in New Issue
Block a user