diff --git a/apps/export-node-spec/src/index.ts b/apps/export-node-spec/src/index.ts index 6028d648..6c92cb5d 100644 --- a/apps/export-node-spec/src/index.ts +++ b/apps/export-node-spec/src/index.ts @@ -6,7 +6,7 @@ import { ManualLifecycleEventEmitter, registerCoreProfile, validateNodeRegistry, - writeNodeSpecsToJSON + writeDefaultNodeSpecsToJSON, } from '@behave-graph/core'; import { DummyScene, registerSceneProfile } from '@behave-graph/scene'; import { program } from 'commander'; @@ -59,7 +59,7 @@ export const main = async () => { return; } - const nodeSpecJson = writeNodeSpecsToJSON(registry); + const nodeSpecJson = writeDefaultNodeSpecsToJSON(registry); nodeSpecJson.sort((a, b) => a.type.localeCompare(b.type)); const jsonOutput = JSON.stringify(nodeSpecJson, null, ' '); if (programOptions.csv) { diff --git a/packages/core/src/Graphs/IO/writeNodeSpecsToJSON.ts b/packages/core/src/Graphs/IO/writeNodeSpecsToJSON.ts index 90c3292a..a6245923 100644 --- a/packages/core/src/Graphs/IO/writeNodeSpecsToJSON.ts +++ b/packages/core/src/Graphs/IO/writeNodeSpecsToJSON.ts @@ -2,6 +2,7 @@ import { NodeCategory } from '../../Nodes/NodeDefinitions.js'; import { IRegistry } from '../../Registry.js'; import { Choices } from '../../Sockets/Socket.js'; import { createNode, makeGraphApi } from '../Graph.js'; +import { NodeConfigurationJSON } from './GraphJSON.js'; import { ChoiceJSON, InputSocketSpecJSON, @@ -16,64 +17,69 @@ function toChoices(valueChoices: Choices | undefined): ChoiceJSON | undefined { }); } -export function writeNodeSpecsToJSON(registry: IRegistry): NodeSpecJSON[] { - const nodeSpecsJSON: NodeSpecJSON[] = []; - - // const graph = new Graph(registry); - +// create JSON specs for a single node based on given configuration +export function writeNodeSpecToJSON(registry: IRegistry, nodeTypeName: string, configuration: NodeConfigurationJSON): NodeSpecJSON { const graph = makeGraphApi({ ...registry, customEvents: {}, variables: {} }); - Object.keys(registry.nodes).forEach((nodeTypeName) => { - const node = createNode({ - graph, - registry, - nodeTypeName - }); + const node = createNode({ + graph, + registry, + nodeTypeName, + nodeConfiguration: configuration, + }); + + const nodeSpecJSON: NodeSpecJSON = { + type: nodeTypeName, + category: node.description.category as NodeCategory, + label: node.description.label, + inputs: [], + outputs: [], + configuration: [] + }; + + node.inputs.forEach((inputSocket) => { + const valueType = + inputSocket.valueTypeName === 'flow' + ? undefined + : registry.values[inputSocket.valueTypeName]; - const nodeSpecJSON: NodeSpecJSON = { - type: nodeTypeName, - category: node.description.category as NodeCategory, - label: node.description.label, - inputs: [], - outputs: [], - configuration: [] + let defaultValue = inputSocket.value; + if (valueType !== undefined) { + defaultValue = valueType.serialize(defaultValue); + } + if (defaultValue === undefined && valueType !== undefined) { + defaultValue = valueType.serialize(valueType.creator()); + } + const socketSpecJSON: InputSocketSpecJSON = { + name: inputSocket.name, + valueType: inputSocket.valueTypeName, + defaultValue, + choices: toChoices(inputSocket.valueChoices) }; + nodeSpecJSON.inputs.push(socketSpecJSON); + }); - node.inputs.forEach((inputSocket) => { - const valueType = - inputSocket.valueTypeName === 'flow' - ? undefined - : registry.values[inputSocket.valueTypeName]; + node.outputs.forEach((outputSocket) => { + const socketSpecJSON: OutputSocketSpecJSON = { + name: outputSocket.name, + valueType: outputSocket.valueTypeName + }; + nodeSpecJSON.outputs.push(socketSpecJSON); + }); - let defaultValue = inputSocket.value; - if (valueType !== undefined) { - defaultValue = valueType.serialize(defaultValue); - } - if (defaultValue === undefined && valueType !== undefined) { - defaultValue = valueType.serialize(valueType.creator()); - } - const socketSpecJSON: InputSocketSpecJSON = { - name: inputSocket.name, - valueType: inputSocket.valueTypeName, - defaultValue, - choices: toChoices(inputSocket.valueChoices) - }; - nodeSpecJSON.inputs.push(socketSpecJSON); - }); + return nodeSpecJSON; +} - node.outputs.forEach((outputSocket) => { - const socketSpecJSON: OutputSocketSpecJSON = { - name: outputSocket.name, - valueType: outputSocket.valueTypeName - }; - nodeSpecJSON.outputs.push(socketSpecJSON); - }); +// create JSON specs for all nodes with empty configuration +export function writeDefaultNodeSpecsToJSON(registry: IRegistry): NodeSpecJSON[] { + const nodeSpecsJSON: NodeSpecJSON[] = []; - nodeSpecsJSON.push(nodeSpecJSON); + Object.keys(registry.nodes).forEach((nodeTypeName) => { + nodeSpecsJSON.push(writeNodeSpecToJSON(registry, nodeTypeName, {})); }); return nodeSpecsJSON; diff --git a/packages/flow/src/components/Controls.tsx b/packages/flow/src/components/Controls.tsx index 7c8a70bd..b2a7c786 100644 --- a/packages/flow/src/components/Controls.tsx +++ b/packages/flow/src/components/Controls.tsx @@ -1,4 +1,4 @@ -import { GraphJSON, NodeSpecJSON } from '@behave-graph/core'; +import { GraphJSON } from '@behave-graph/core'; import { faDownload, faPause, @@ -16,13 +16,14 @@ import { ClearModal } from './modals/ClearModal.js'; import { HelpModal } from './modals/HelpModal.js'; import { Examples, LoadModal } from './modals/LoadModal.js'; import { SaveModal } from './modals/SaveModal.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; export type CustomControlsProps = { playing: boolean; togglePlay: () => void; setBehaviorGraph: (value: GraphJSON) => void; examples: Examples; - specJson: NodeSpecJSON[] | undefined; + specGenerator: NodeSpecGenerator | undefined; }; export const CustomControls: React.FC = ({ @@ -30,13 +31,13 @@ export const CustomControls: React.FC = ({ togglePlay, setBehaviorGraph, examples, - specJson + specGenerator }: { playing: boolean; togglePlay: () => void; setBehaviorGraph: (value: GraphJSON) => void; examples: Examples; - specJson: NodeSpecJSON[] | undefined; + specGenerator: NodeSpecGenerator | undefined; }) => { const [loadModalOpen, setLoadModalOpen] = useState(false); const [saveModalOpen, setSaveModalOpen] = useState(false); @@ -68,10 +69,10 @@ export const CustomControls: React.FC = ({ setBehaviorGraph={setBehaviorGraph} examples={examples} /> - {specJson && ( + {specGenerator && ( setSaveModalOpen(false)} /> )} diff --git a/packages/flow/src/components/Flow.tsx b/packages/flow/src/components/Flow.tsx index 472ec4a0..b6e3b468 100644 --- a/packages/flow/src/components/Flow.tsx +++ b/packages/flow/src/components/Flow.tsx @@ -5,7 +5,7 @@ import { Background, BackgroundVariant, ReactFlow } from 'reactflow'; import { useBehaveGraphFlow } from '../hooks/useBehaveGraphFlow.js'; import { useFlowHandlers } from '../hooks/useFlowHandlers.js'; import { useGraphRunner } from '../hooks/useGraphRunner.js'; -import { useNodeSpecJson } from '../hooks/useNodeSpecJson.js'; +import { useNodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; import CustomControls from './Controls.js'; import { Examples } from './modals/LoadModal.js'; import { NodePicker } from './NodePicker.js'; @@ -21,7 +21,7 @@ export const Flow: React.FC = ({ registry, examples }) => { - const specJson = useNodeSpecJson(registry); + const specGenerator = useNodeSpecGenerator(registry); const { nodes, @@ -33,7 +33,7 @@ export const Flow: React.FC = ({ nodeTypes } = useBehaveGraphFlow({ initialGraphJson: graph, - specJson + specGenerator }); const { @@ -51,7 +51,7 @@ export const Flow: React.FC = ({ nodes, onEdgesChange, onNodesChange, - specJSON: specJson + specGenerator, }); const { togglePlay, playing } = useGraphRunner({ @@ -81,7 +81,7 @@ export const Flow: React.FC = ({ togglePlay={togglePlay} setBehaviorGraph={setGraphJson} examples={examples} - specJson={specJson} + specGenerator={specGenerator} /> = ({ filters={nodePickFilters} onPickNode={handleAddNode} onClose={closeNodePicker} - specJSON={specJson} + specJSON={specGenerator?.getAllNodeSpecs()} /> )} diff --git a/packages/flow/src/components/InputSocket.tsx b/packages/flow/src/components/InputSocket.tsx index 70ee9d01..245a0b2e 100644 --- a/packages/flow/src/components/InputSocket.tsx +++ b/packages/flow/src/components/InputSocket.tsx @@ -1,4 +1,4 @@ -import { InputSocketSpecJSON, NodeSpecJSON } from '@behave-graph/core'; +import { InputSocketSpecJSON } from '@behave-graph/core'; import { faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import cx from 'classnames'; @@ -8,12 +8,13 @@ import { Connection, Handle, Position, useReactFlow } from 'reactflow'; import { colors, valueTypeColorMap } from '../util/colors.js'; import { isValidConnection } from '../util/isValidConnection.js'; import { AutoSizeInput } from './AutoSizeInput.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; export type InputSocketProps = { connected: boolean; value: any | undefined; onChange: (key: string, value: any) => void; - specJSON: NodeSpecJSON[]; + specGenerator: NodeSpecGenerator; } & InputSocketSpecJSON; const InputFieldForValue = ({ @@ -95,7 +96,7 @@ const InputFieldForValue = ({ const InputSocket: React.FC = ({ connected, - specJSON, + specGenerator, ...rest }) => { const { value, name, valueType, defaultValue, choices } = rest; @@ -128,7 +129,7 @@ const InputSocket: React.FC = ({ position={Position.Left} className={cx(borderColor, connected ? backgroundColor : 'bg-gray-800')} isValidConnection={(connection: Connection) => - isValidConnection(connection, instance, specJSON) + isValidConnection(connection, instance, specGenerator) } /> diff --git a/packages/flow/src/components/Node.tsx b/packages/flow/src/components/Node.tsx index 1fbf849f..4b27d245 100644 --- a/packages/flow/src/components/Node.tsx +++ b/packages/flow/src/components/Node.tsx @@ -7,10 +7,11 @@ import { isHandleConnected } from '../util/isHandleConnected.js'; import InputSocket from './InputSocket.js'; import NodeContainer from './NodeContainer.js'; import OutputSocket from './OutputSocket.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; type NodeProps = FlowNodeProps & { spec: NodeSpecJSON; - allSpecs: NodeSpecJSON[]; + specGenerator: NodeSpecGenerator; }; const getPairs = (arr1: T[], arr2: U[]) => { @@ -28,7 +29,7 @@ export const Node: React.FC = ({ data, spec, selected, - allSpecs + specGenerator, }: NodeProps) => { const edges = useEdges(); const handleChange = useChangeNodeData(id); @@ -48,8 +49,8 @@ export const Node: React.FC = ({ {input && ( @@ -57,7 +58,7 @@ export const Node: React.FC = ({ {output && ( )} diff --git a/packages/flow/src/components/OutputSocket.tsx b/packages/flow/src/components/OutputSocket.tsx index 03f97914..f4bcc82a 100644 --- a/packages/flow/src/components/OutputSocket.tsx +++ b/packages/flow/src/components/OutputSocket.tsx @@ -1,4 +1,4 @@ -import { NodeSpecJSON, OutputSocketSpecJSON } from '@behave-graph/core'; +import { OutputSocketSpecJSON } from '@behave-graph/core'; import { faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import cx from 'classnames'; @@ -7,14 +7,15 @@ import { Connection, Handle, Position, useReactFlow } from 'reactflow'; import { colors, valueTypeColorMap } from '../util/colors.js'; import { isValidConnection } from '../util/isValidConnection.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; export type OutputSocketProps = { connected: boolean; - specJSON: NodeSpecJSON[]; + specGenerator: NodeSpecGenerator; } & OutputSocketSpecJSON; export default function OutputSocket({ - specJSON, + specGenerator, connected, valueType, name @@ -47,7 +48,7 @@ export default function OutputSocket({ position={Position.Right} className={cx(borderColor, connected ? backgroundColor : 'bg-gray-800')} isValidConnection={(connection: Connection) => - isValidConnection(connection, instance, specJSON) + isValidConnection(connection, instance, specGenerator) } /> diff --git a/packages/flow/src/components/modals/SaveModal.tsx b/packages/flow/src/components/modals/SaveModal.tsx index fc0f8b18..be00a30f 100644 --- a/packages/flow/src/components/modals/SaveModal.tsx +++ b/packages/flow/src/components/modals/SaveModal.tsx @@ -5,17 +5,18 @@ import { useEdges, useNodes } from 'reactflow'; import { flowToBehave } from '../../transformers/flowToBehave.js'; import { Modal } from './Modal.js'; +import { NodeSpecGenerator } from '../../hooks/useNodeSpecGenerator.js'; export type SaveModalProps = { open?: boolean; onClose: () => void; - specJson: NodeSpecJSON[]; + specGenerator: NodeSpecGenerator; }; export const SaveModal: React.FC = ({ open = false, onClose, - specJson + specGenerator }) => { const ref = useRef(null); const [copied, setCopied] = useState(false); @@ -24,8 +25,8 @@ export const SaveModal: React.FC = ({ const nodes = useNodes(); const flow = useMemo( - () => flowToBehave(nodes, edges, specJson), - [nodes, edges, specJson] + () => flowToBehave(nodes, edges, specGenerator), + [nodes, edges, specGenerator] ); const jsonString = JSON.stringify(flow, null, 2); diff --git a/packages/flow/src/hooks/useBehaveGraphFlow.ts b/packages/flow/src/hooks/useBehaveGraphFlow.ts index 55132d23..d068073f 100644 --- a/packages/flow/src/hooks/useBehaveGraphFlow.ts +++ b/packages/flow/src/hooks/useBehaveGraphFlow.ts @@ -1,4 +1,4 @@ -import { GraphJSON, NodeSpecJSON } from '@behave-graph/core'; +import { GraphJSON } from '@behave-graph/core'; import { useCallback, useEffect, useState } from 'react'; import { useEdgesState, useNodesState } from 'reactflow'; @@ -7,6 +7,7 @@ import { flowToBehave } from '../transformers/flowToBehave.js'; import { autoLayout } from '../util/autoLayout.js'; import { hasPositionMetaData } from '../util/hasPositionMetaData.js'; import { useCustomNodeTypes } from './useCustomNodeTypes.js'; +import { NodeSpecGenerator } from './useNodeSpecGenerator.js'; export const fetchBehaviorGraphJson = async (url: string) => // eslint-disable-next-line unicorn/no-await-expression-member @@ -21,10 +22,10 @@ export const fetchBehaviorGraphJson = async (url: string) => */ export const useBehaveGraphFlow = ({ initialGraphJson, - specJson + specGenerator }: { initialGraphJson: GraphJSON; - specJson: NodeSpecJSON[] | undefined; + specGenerator: NodeSpecGenerator | undefined; }) => { const [graphJson, setStoredGraphJson] = useState(); const [nodes, setNodes, onNodesChange] = useNodesState([]); @@ -53,14 +54,14 @@ export const useBehaveGraphFlow = ({ }, [initialGraphJson, setGraphJson]); useEffect(() => { - if (!specJson) return; + if (!specGenerator) return; // when nodes and edges are updated, update the graph json with the flow to behave behavior - const graphJson = flowToBehave(nodes, edges, specJson); + const graphJson = flowToBehave(nodes, edges, specGenerator); setStoredGraphJson(graphJson); - }, [nodes, edges, specJson]); + }, [nodes, edges, specGenerator]); const nodeTypes = useCustomNodeTypes({ - specJson + specGenerator }); return { diff --git a/packages/flow/src/hooks/useChangeNodeData.ts b/packages/flow/src/hooks/useChangeNodeData.ts index cee373f3..15bfc28b 100644 --- a/packages/flow/src/hooks/useChangeNodeData.ts +++ b/packages/flow/src/hooks/useChangeNodeData.ts @@ -13,8 +13,11 @@ export const useChangeNodeData = (id: string) => { ...n, data: { ...n.data, - [key]: value - } + values: { + ...n.data.values, + [key]: value + }, + }, }; }) ); diff --git a/packages/flow/src/hooks/useCustomNodeTypes.tsx b/packages/flow/src/hooks/useCustomNodeTypes.tsx index dbeb795b..18aa30d9 100644 --- a/packages/flow/src/hooks/useCustomNodeTypes.tsx +++ b/packages/flow/src/hooks/useCustomNodeTypes.tsx @@ -1,31 +1,31 @@ -import { NodeSpecJSON } from '@behave-graph/core'; -import React from 'react'; import { useEffect, useState } from 'react'; import { NodeTypes } from 'reactflow'; import { Node } from '../components/Node.js'; +import { NodeSpecGenerator } from './useNodeSpecGenerator.js'; -const getCustomNodeTypes = (allSpecs: NodeSpecJSON[]) => { - return allSpecs.reduce((nodes: NodeTypes, node) => { - nodes[node.type] = (props) => ( - - ); +const getCustomNodeTypes = (specGenerator: NodeSpecGenerator) => { + return specGenerator.getNodeTypes().reduce((nodes: NodeTypes, nodeType) => { + nodes[nodeType] = (props) => { + let spec = specGenerator.getNodeSpec(nodeType, props.data.configuration); + return ; + }; return nodes; }, {}); }; export const useCustomNodeTypes = ({ - specJson + specGenerator }: { - specJson: NodeSpecJSON[] | undefined; + specGenerator: NodeSpecGenerator | undefined; }) => { const [customNodeTypes, setCustomNodeTypes] = useState(); useEffect(() => { - if (!specJson) return; - const customNodeTypes = getCustomNodeTypes(specJson); + if (!specGenerator) return; + const customNodeTypes = getCustomNodeTypes(specGenerator); setCustomNodeTypes(customNodeTypes); - }, [specJson]); + }, [specGenerator]); return customNodeTypes; }; diff --git a/packages/flow/src/hooks/useFlowHandlers.ts b/packages/flow/src/hooks/useFlowHandlers.ts index 3e89a691..2e456992 100644 --- a/packages/flow/src/hooks/useFlowHandlers.ts +++ b/packages/flow/src/hooks/useFlowHandlers.ts @@ -1,4 +1,3 @@ -import { NodeSpecJSON } from '@behave-graph/core'; import { MouseEvent as ReactMouseEvent, useCallback, @@ -11,25 +10,26 @@ import { v4 as uuidv4 } from 'uuid'; import { calculateNewEdge } from '../util/calculateNewEdge.js'; import { getNodePickerFilters } from '../util/getPickerFilters.js'; import { useBehaveGraphFlow } from './useBehaveGraphFlow.js'; +import { NodeSpecGenerator } from './useNodeSpecGenerator.js'; type BehaveGraphFlow = ReturnType; const useNodePickFilters = ({ nodes, lastConnectStart, - specJSON + specGenerator, }: { nodes: Node[]; lastConnectStart: OnConnectStartParams | undefined; - specJSON: NodeSpecJSON[] | undefined; + specGenerator: NodeSpecGenerator | undefined; }) => { const [nodePickFilters, setNodePickFilters] = useState( - getNodePickerFilters(nodes, lastConnectStart, specJSON) + getNodePickerFilters(nodes, lastConnectStart, specGenerator) ); useEffect(() => { - setNodePickFilters(getNodePickerFilters(nodes, lastConnectStart, specJSON)); - }, [nodes, lastConnectStart, specJSON]); + setNodePickFilters(getNodePickerFilters(nodes, lastConnectStart, specGenerator)); + }, [nodes, lastConnectStart, specGenerator]); return nodePickFilters; }; @@ -38,10 +38,10 @@ export const useFlowHandlers = ({ onEdgesChange, onNodesChange, nodes, - specJSON + specGenerator, }: Pick & { nodes: Node[]; - specJSON: NodeSpecJSON[] | undefined; + specGenerator: NodeSpecGenerator | undefined; }) => { const [lastConnectStart, setLastConnectStart] = useState(); @@ -98,7 +98,7 @@ export const useFlowHandlers = ({ (node) => node.id === lastConnectStart.nodeId ); if (originNode === undefined) return; - if (!specJSON) return; + if (!specGenerator) return; onEdgesChange([ { type: 'add', @@ -107,7 +107,7 @@ export const useFlowHandlers = ({ nodeType, newNode.id, lastConnectStart, - specJSON + specGenerator, ) } ]); @@ -118,7 +118,7 @@ export const useFlowHandlers = ({ nodes, onEdgesChange, onNodesChange, - specJSON + specGenerator, ] ); @@ -151,7 +151,7 @@ export const useFlowHandlers = ({ const nodePickFilters = useNodePickFilters({ nodes, lastConnectStart, - specJSON + specGenerator, }); return { diff --git a/packages/flow/src/hooks/useNodeSpecGenerator.ts b/packages/flow/src/hooks/useNodeSpecGenerator.ts new file mode 100644 index 00000000..f1c64dbb --- /dev/null +++ b/packages/flow/src/hooks/useNodeSpecGenerator.ts @@ -0,0 +1,50 @@ +// Generates node specs based on provided configuration, +// and caches the results. + +import { + IRegistry, + NodeConfigurationJSON, + NodeSpecJSON, + writeDefaultNodeSpecsToJSON, + writeNodeSpecToJSON, +} from '@behave-graph/core'; +import { useEffect, useState } from 'react'; + +export class NodeSpecGenerator { + private specsWithoutConfig?: NodeSpecJSON[]; + private specsCache: { [cacheKey: string]: NodeSpecJSON } = {}; + + constructor(private registry: IRegistry) {} + + getNodeTypes(): string[] { + return Object.keys(this.registry.nodes); + } + + getNodeSpec(nodeTypeName: string, configuration: NodeConfigurationJSON): NodeSpecJSON { + let cacheKey = nodeTypeName + '\x01' + JSON.stringify(configuration); + + if (!this.specsCache[cacheKey]) { + this.specsCache[cacheKey] = writeNodeSpecToJSON(this.registry, nodeTypeName, configuration); + } + + return this.specsCache[cacheKey]; + } + + getAllNodeSpecs(): NodeSpecJSON[] { + if (!this.specsWithoutConfig) { + this.specsWithoutConfig = writeDefaultNodeSpecsToJSON(this.registry); + } + + return this.specsWithoutConfig; + } +} + +export const useNodeSpecGenerator = (registry: IRegistry) => { + const [specGenerator, setSpecGenerator] = useState(); + + useEffect(() => { + setSpecGenerator(new NodeSpecGenerator(registry)); + }, [registry.nodes, registry.values, registry.dependencies]); + + return specGenerator; +}; diff --git a/packages/flow/src/hooks/useNodeSpecJson.ts b/packages/flow/src/hooks/useNodeSpecJson.ts deleted file mode 100644 index 671210d9..00000000 --- a/packages/flow/src/hooks/useNodeSpecJson.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - IRegistry, - NodeSpecJSON, - writeNodeSpecsToJSON -} from '@behave-graph/core'; -import { useEffect, useState } from 'react'; - -export const useNodeSpecJson = (registry: IRegistry) => { - const [specJson, setSpecJson] = useState(); - - useEffect(() => { - if (!registry.nodes || !registry.values || !registry.dependencies) { - setSpecJson(undefined); - return; - } - setSpecJson(writeNodeSpecsToJSON(registry)); - }, [registry.nodes, registry.values, registry.dependencies]); - - return specJson; -}; diff --git a/packages/flow/src/index.ts b/packages/flow/src/index.ts index 5cb4cdd9..4a77606c 100644 --- a/packages/flow/src/index.ts +++ b/packages/flow/src/index.ts @@ -19,7 +19,6 @@ export * from './hooks/useOnPressKey.js'; export * from './hooks/useFlowHandlers.js'; export * from './hooks/useGraphRunner.js'; export * from './hooks/useBehaveGraphFlow.js'; -export * from './hooks/useNodeSpecJson.js'; export * from './hooks/useCustomNodeTypes.js'; export * from './hooks/useMergeMap.js'; diff --git a/packages/flow/src/transformers/behaveToFlow.ts b/packages/flow/src/transformers/behaveToFlow.ts index 583c3d5d..5a804bdf 100644 --- a/packages/flow/src/transformers/behaveToFlow.ts +++ b/packages/flow/src/transformers/behaveToFlow.ts @@ -1,4 +1,4 @@ -import { GraphJSON } from '@behave-graph/core'; +import { GraphJSON, NodeConfigurationJSON } from '@behave-graph/core'; import { Edge, Node } from 'reactflow'; import { v4 as uuidv4 } from 'uuid'; @@ -18,11 +18,20 @@ export const behaveToFlow = (graph: GraphJSON): [Node[], Edge[]] => { ? Number(nodeJSON.metadata?.positionY) : 0 }, - data: {} as { [key: string]: any } + data: { + configuration: {} as NodeConfigurationJSON, + values: {} as { [key: string]: any }, + }, }; nodes.push(node); + if (nodeJSON.configuration) { + for (const [inputKey, input] of Object.entries(nodeJSON.configuration)) { + node.data.configuration[inputKey] = input; + } + } + if (nodeJSON.parameters) { for (const [inputKey, input] of Object.entries(nodeJSON.parameters)) { if ('link' in input && input.link !== undefined) { @@ -35,7 +44,7 @@ export const behaveToFlow = (graph: GraphJSON): [Node[], Edge[]] => { }); } if ('value' in input) { - node.data[inputKey] = input.value; + node.data.values[inputKey] = input.value; } } } diff --git a/packages/flow/src/transformers/flowToBehave.test.ts b/packages/flow/src/transformers/flowToBehave.test.ts index 3b7b3458..5ec59207 100644 --- a/packages/flow/src/transformers/flowToBehave.test.ts +++ b/packages/flow/src/transformers/flowToBehave.test.ts @@ -1,12 +1,12 @@ import { getCoreRegistry, GraphJSON, - writeNodeSpecsToJSON } from '@behave-graph/core'; import rawFlowGraph from '../../../../graphs/react-flow/graph.json'; import { behaveToFlow } from './behaveToFlow.js'; import { flowToBehave } from './flowToBehave.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator'; const flowGraph = rawFlowGraph as GraphJSON; @@ -14,7 +14,7 @@ const [nodes, edges] = behaveToFlow(flowGraph); it('transforms from flow to behave', () => { const registry = getCoreRegistry(); - const specJSON = writeNodeSpecsToJSON(registry); - const output = flowToBehave(nodes, edges, specJSON); + const specGenerator = new NodeSpecGenerator(registry); + const output = flowToBehave(nodes, edges, specGenerator); expect(output).toEqual(flowGraph); }); diff --git a/packages/flow/src/transformers/flowToBehave.ts b/packages/flow/src/transformers/flowToBehave.ts index 60ca2a76..f6904c04 100644 --- a/packages/flow/src/transformers/flowToBehave.ts +++ b/packages/flow/src/transformers/flowToBehave.ts @@ -1,5 +1,6 @@ -import { GraphJSON, NodeJSON, NodeSpecJSON } from '@behave-graph/core'; +import { GraphJSON, NodeJSON, ValueJSON } from '@behave-graph/core'; import { Edge, Node } from 'reactflow'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; const isNullish = (value: any): value is null | undefined => value === undefined || value === null; @@ -7,17 +8,14 @@ const isNullish = (value: any): value is null | undefined => export const flowToBehave = ( nodes: Node[], edges: Edge[], - nodeSpecJSON: NodeSpecJSON[] + specGenerator: NodeSpecGenerator, ): GraphJSON => { const graph: GraphJSON = { nodes: [], variables: [], customEvents: [] }; nodes.forEach((node) => { if (node.type === undefined) return; - const nodeSpec = nodeSpecJSON.find( - (nodeSpec) => nodeSpec.type === node.type - ); - + const nodeSpec = specGenerator.getNodeSpec(node.type, node.data.configuration); if (nodeSpec === undefined) return; const behaveNode: NodeJSON = { @@ -29,7 +27,14 @@ export const flowToBehave = ( } }; - Object.entries(node.data).forEach(([key, value]) => { + Object.entries(node.data.configuration).forEach(([key, value]) => { + if (behaveNode.configuration === undefined) { + behaveNode.configuration = {}; + } + behaveNode.configuration[key] = value as ValueJSON; + }); + + Object.entries(node.data.values).forEach(([key, value]) => { if (behaveNode.parameters === undefined) { behaveNode.parameters = {}; } diff --git a/packages/flow/src/util/calculateNewEdge.ts b/packages/flow/src/util/calculateNewEdge.ts index 80cb71a6..e69ac7dc 100644 --- a/packages/flow/src/util/calculateNewEdge.ts +++ b/packages/flow/src/util/calculateNewEdge.ts @@ -1,19 +1,20 @@ -import { NodeSpecJSON } from '@behave-graph/core'; import { Node, OnConnectStartParams } from 'reactflow'; import { v4 as uuidv4 } from 'uuid'; import { getSocketsByNodeTypeAndHandleType } from './getSocketsByNodeTypeAndHandleType.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; export const calculateNewEdge = ( originNode: Node, destinationNodeType: string, destinationNodeId: string, connection: OnConnectStartParams, - specJSON: NodeSpecJSON[] + specGenerator: NodeSpecGenerator, ) => { const sockets = getSocketsByNodeTypeAndHandleType( - specJSON, + specGenerator, originNode.type, + originNode.data.configuration, connection.handleType ); const originSocket = sockets?.find( @@ -21,8 +22,9 @@ export const calculateNewEdge = ( ); const newSockets = getSocketsByNodeTypeAndHandleType( - specJSON, + specGenerator, destinationNodeType, + {}, connection.handleType === 'source' ? 'target' : 'source' ); const newSocket = newSockets?.find( diff --git a/packages/flow/src/util/getPickerFilters.ts b/packages/flow/src/util/getPickerFilters.ts index a340bffc..25814b22 100644 --- a/packages/flow/src/util/getPickerFilters.ts +++ b/packages/flow/src/util/getPickerFilters.ts @@ -1,23 +1,24 @@ -import { NodeSpecJSON } from '@behave-graph/core'; import { Node, OnConnectStartParams } from 'reactflow'; import { NodePickerFilters } from '../components/NodePicker.js'; import { getSocketsByNodeTypeAndHandleType } from './getSocketsByNodeTypeAndHandleType.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; export const getNodePickerFilters = ( nodes: Node[], params: OnConnectStartParams | undefined, - specJSON: NodeSpecJSON[] | undefined + specGenerator: NodeSpecGenerator | undefined ): NodePickerFilters | undefined => { if (params === undefined) return; const originNode = nodes.find((node) => node.id === params.nodeId); if (originNode === undefined) return; - const sockets = specJSON + const sockets = specGenerator ? getSocketsByNodeTypeAndHandleType( - specJSON, + specGenerator, originNode.type, + originNode.data.configuration, params.handleType ) : undefined; diff --git a/packages/flow/src/util/getSocketsByNodeTypeAndHandleType.ts b/packages/flow/src/util/getSocketsByNodeTypeAndHandleType.ts index cdaf50f9..91a92b5f 100644 --- a/packages/flow/src/util/getSocketsByNodeTypeAndHandleType.ts +++ b/packages/flow/src/util/getSocketsByNodeTypeAndHandleType.ts @@ -1,11 +1,13 @@ -import { NodeSpecJSON } from '@behave-graph/core'; +import { NodeConfigurationJSON } from '@behave-graph/core'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; export const getSocketsByNodeTypeAndHandleType = ( - nodes: NodeSpecJSON[], + specGenerator: NodeSpecGenerator, nodeType: string | undefined, - handleType: 'source' | 'target' | null + nodeConfiguration: NodeConfigurationJSON, + handleType: 'source' | 'target' | null, ) => { - const nodeSpec = nodes.find((node) => node.type === nodeType); - if (nodeSpec === undefined) return; + if (nodeType === undefined) return []; + const nodeSpec = specGenerator.getNodeSpec(nodeType, nodeConfiguration); return handleType === 'source' ? nodeSpec.outputs : nodeSpec.inputs; }; diff --git a/packages/flow/src/util/isValidConnection.ts b/packages/flow/src/util/isValidConnection.ts index e84d6114..66149983 100644 --- a/packages/flow/src/util/isValidConnection.ts +++ b/packages/flow/src/util/isValidConnection.ts @@ -3,11 +3,12 @@ import { Connection, ReactFlowInstance } from 'reactflow'; import { getSocketsByNodeTypeAndHandleType } from './getSocketsByNodeTypeAndHandleType.js'; import { isHandleConnected } from './isHandleConnected.js'; +import { NodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js'; export const isValidConnection = ( connection: Connection, instance: ReactFlowInstance, - specJSON: NodeSpecJSON[] + specGenerator: NodeSpecGenerator, ) => { if (connection.source === null || connection.target === null) return false; @@ -18,9 +19,10 @@ export const isValidConnection = ( if (sourceNode === undefined || targetNode === undefined) return false; const sourceSockets = getSocketsByNodeTypeAndHandleType( - specJSON, + specGenerator, sourceNode.type, - 'source' + sourceNode.data.configuration, + 'source', ); const sourceSocket = sourceSockets?.find( @@ -28,9 +30,10 @@ export const isValidConnection = ( ); const targetSockets = getSocketsByNodeTypeAndHandleType( - specJSON, + specGenerator, targetNode.type, - 'target' + targetNode.data.configuration, + 'target', ); const targetSocket = targetSockets?.find( diff --git a/website/scripts/generate-dynamic-pages/generate-pages-from-registry.ts b/website/scripts/generate-dynamic-pages/generate-pages-from-registry.ts index 5ae331d5..a78c26e9 100644 --- a/website/scripts/generate-dynamic-pages/generate-pages-from-registry.ts +++ b/website/scripts/generate-dynamic-pages/generate-pages-from-registry.ts @@ -8,7 +8,7 @@ import { NodeSpecJSON, Registry, ValueType, - writeNodeSpecsToJSON + writeDefaultNodeSpecsToJSON, } from '@behave-graph/core'; // We need to transform directories to kebab case because otherwise Docusaurus won't generate the toString one import { kebab, pascal } from 'case'; @@ -142,7 +142,8 @@ const generateNodePages = ( }); }; -// First registry includes only the nodes for that specific profile, second registry includes all nodes required to run writeNodeSpecsToJSON +// First registry includes only the nodes for that specific profile, +// second registry includes all nodes required to run writeDefaultNodeSpecsToJSON export default ( registry: Registry, baseDir: string, @@ -152,7 +153,7 @@ export default ( const values = registry.values.getAll(); - const nodeSpecJson = writeNodeSpecsToJSON(functionalRegistry || registry); + const nodeSpecJson = writeDefaultNodeSpecsToJSON(functionalRegistry || registry); const graphApi = new Graph(registry).makeApi();