feat: add node help system and registry for extensible node types
- Implemented a help system for different node types (config, render, variable, function) with detailed usage instructions. - Created a node registry to manage node types, including registration, retrieval, and validation of connections between nodes. - Defined central node and edge types for the application to streamline state management. - Added Nunjucks autocomplete functionality to enhance user experience in template editing. - Developed a syntax highlighting parser for PlantUML and Nunjucks within the CodeMirror editor. - Registered built-in node types at application startup, including their default configurations and help entries. - Introduced a theme context provider to manage light/dark mode preferences across the application. - Created utility functions for class name management using clsx and tailwind-merge. - Set up Tailwind CSS for styling with custom themes and responsive design. - Configured Vite for development with proxy settings for backend API calls and Kroki diagram service.
This commit is contained in:
116
frontend/src/lib/nodeRegistry.ts
Normal file
116
frontend/src/lib/nodeRegistry.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Extensible node type registry. Register node types with registerNodeType();
|
||||
* built-in types are registered in registerBuiltinNodes.ts.
|
||||
* Use getRegisteredNodeTypes() / getNodeType(id) for defaults, validation, and UI.
|
||||
*/
|
||||
|
||||
import type React from 'react'
|
||||
import type { Node } from '@xyflow/react'
|
||||
|
||||
export type NodeHelpEntry = {
|
||||
title: string
|
||||
content: React.ReactNode
|
||||
}
|
||||
|
||||
export type NodeTypeDescriptor = {
|
||||
id: string
|
||||
component: React.ComponentType<any>
|
||||
defaultStyle: { width: number; height: number }
|
||||
defaultData: Record<string, unknown>
|
||||
idPrefix: string
|
||||
hasInput: boolean
|
||||
hasOutput: boolean
|
||||
/** When this type is the connection target, which source types are allowed. Omit = all. */
|
||||
allowedSourceTypes?: string[]
|
||||
/** When this type is the connection source, which target types are allowed. Omit = all. */
|
||||
allowedTargetTypes?: string[]
|
||||
help: NodeHelpEntry
|
||||
menuLabel: string
|
||||
menuIcon: React.ReactNode
|
||||
/** Optional: override default data for new nodes (e.g. set title from newId). */
|
||||
getDefaultData?: (newId?: string) => Record<string, unknown>
|
||||
/** Optional: data for Reset action; if omitted, getDefaultData(nodeId) or defaultData is used. */
|
||||
getResetData?: (nodeId?: string) => Record<string, unknown>
|
||||
/** Optional: label shown on edge when this type is the target (e.g. "render", "add input"). */
|
||||
connectionLabel?: string
|
||||
}
|
||||
|
||||
const registry = new Map<string, NodeTypeDescriptor>()
|
||||
|
||||
export function registerNodeType(descriptor: NodeTypeDescriptor): void {
|
||||
if (registry.has(descriptor.id)) {
|
||||
console.warn(`[nodeRegistry] Overwriting existing node type: ${descriptor.id}`)
|
||||
}
|
||||
registry.set(descriptor.id, descriptor)
|
||||
}
|
||||
|
||||
export function getNodeType(id: string): NodeTypeDescriptor | undefined {
|
||||
return registry.get(id)
|
||||
}
|
||||
|
||||
export function getRegisteredNodeTypes(): NodeTypeDescriptor[] {
|
||||
return Array.from(registry.values())
|
||||
}
|
||||
|
||||
export function getRegisteredNodeTypeIds(): string[] {
|
||||
return Array.from(registry.keys())
|
||||
}
|
||||
|
||||
/** Default data for a new node of this type. Uses getDefaultData from descriptor if provided. */
|
||||
export function getDefaultDataForType(type: string, newId?: string): Record<string, unknown> {
|
||||
const desc = registry.get(type)
|
||||
if (!desc) return {}
|
||||
if (desc.getDefaultData) return { ...desc.getDefaultData(newId) }
|
||||
const base = { ...desc.defaultData }
|
||||
if (type === 'config' && newId) (base as any).title = `${newId}`
|
||||
return base
|
||||
}
|
||||
|
||||
/** Data for Reset action. Uses getResetData from descriptor if provided. */
|
||||
export function getResetDataForType(type: string, nodeId?: string): Record<string, unknown> {
|
||||
const desc = registry.get(type)
|
||||
if (!desc) return getDefaultDataForType(type, nodeId)
|
||||
if (desc.getResetData) return { ...desc.getResetData(nodeId) }
|
||||
return getDefaultDataForType(type, nodeId)
|
||||
}
|
||||
|
||||
/** Id prefix for this type (e.g. cfg_, rnd_). Used by getNextNodeId. */
|
||||
export function getIdPrefix(type: string): string {
|
||||
return getNodeType(type)?.idPrefix ?? 'node_'
|
||||
}
|
||||
|
||||
/** Default style for this type. */
|
||||
export function getDefaultStyle(type: string): { width: number; height: number } {
|
||||
const desc = getNodeType(type)
|
||||
if (desc) return desc.defaultStyle
|
||||
return { width: 320, height: 320 }
|
||||
}
|
||||
|
||||
/** Whether a connection from source to target is allowed based on registered types. */
|
||||
export function isConnectionAllowed(
|
||||
sourceType: string,
|
||||
targetType: string,
|
||||
sourceNodeId: string,
|
||||
targetNodeId: string
|
||||
): boolean {
|
||||
if (sourceNodeId === targetNodeId) return false
|
||||
const sourceDesc = getNodeType(sourceType)
|
||||
const targetDesc = getNodeType(targetType)
|
||||
if (!sourceDesc || !targetDesc) return false
|
||||
if (!targetDesc.hasInput) return false
|
||||
if (!sourceDesc.hasOutput) return false
|
||||
if (targetDesc.allowedSourceTypes != null && !targetDesc.allowedSourceTypes.includes(sourceType)) return false
|
||||
if (sourceDesc.allowedTargetTypes != null && !sourceDesc.allowedTargetTypes.includes(targetType)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/** Edge label when target is of this type (for AnimatedEdge). */
|
||||
export function getConnectionLabelForTarget(targetType: string): string | undefined {
|
||||
return getNodeType(targetType)?.connectionLabel
|
||||
}
|
||||
|
||||
/** Help entry for a node type. Use in NodeHelpPopover. */
|
||||
export function getNodeHelp(nodeType: string): NodeHelpEntry {
|
||||
const desc = getNodeType(nodeType)
|
||||
return desc?.help ?? { title: nodeType, content: null }
|
||||
}
|
||||
Reference in New Issue
Block a user