From c75c0cca86300178d28f6d7fe5aeb4ce157e95ef Mon Sep 17 00:00:00 2001 From: Wes Date: Fri, 12 May 2023 01:29:27 +0200 Subject: [PATCH] Improve graph nodes (#28) - Module nodes should be draggable now Screenshot 2023-05-12 at 12 22 20 AM Screenshot 2023-05-12 at 12 22 31 AM --- console/src/features/graph/GraphPage.tsx | 82 ++++++--------------- console/src/features/graph/GroupNode.tsx | 9 +++ console/src/features/graph/VerbNode.tsx | 27 +++++++ console/src/features/graph/create-layout.ts | 64 ++++++++++++++++ 4 files changed, 123 insertions(+), 59 deletions(-) create mode 100644 console/src/features/graph/GroupNode.tsx create mode 100644 console/src/features/graph/VerbNode.tsx create mode 100644 console/src/features/graph/create-layout.ts diff --git a/console/src/features/graph/GraphPage.tsx b/console/src/features/graph/GraphPage.tsx index 2c57f26d..520d9fa1 100644 --- a/console/src/features/graph/GraphPage.tsx +++ b/console/src/features/graph/GraphPage.tsx @@ -1,70 +1,34 @@ -import { useContext } from 'react' -import ReactFlow, { Controls, MiniMap, Node, Edge } from 'reactflow' - -import 'reactflow/dist/style.css' +import { useContext, useEffect } from 'react' +import ReactFlow, { Controls, MiniMap, useNodesState, useEdgesState } from 'reactflow' import { schemaContext } from '../../providers/schema-provider' -import { MetadataCalls } from '../../protos/xyz/block/ftl/v1/schema/schema_pb' +import { GroupNode } from './GroupNode' +import { VerbNode } from './VerbNode' +import { layoutNodes } from './create-layout' +import 'reactflow/dist/style.css' + +const nodeTypes = { groupNode: GroupNode, verbNode: VerbNode } export default function GraphPage() { const schema = useContext(schemaContext) + const [nodes, setNodes, onNodesChange] = useNodesState([]) + const [edges, setEdges, onEdgesChange] = useEdgesState([]) - const nodes: Node[] = [] - const edges: Edge[] = [] - let x = 0 - schema.forEach(module => { - const verbs = module.schema?.decls.filter(decl => decl.value.case === 'verb') - nodes.push({ - id: module.schema?.name ?? '', - position: { x: x, y: 0 }, - data: { label: module.schema?.name }, - connectable: false, - style: { - backgroundColor: 'rgba(79, 70, 229, 0.4)', - width: 190, - height: (verbs?.length ?? 1) * 50 + 50, - }, - }) - let y = 40 - module.schema?.decls - .filter(decl => decl.value.case === 'verb') - .forEach(verb => { - const calls = verb?.value.value?.metadata - .filter(meta => meta.value.case === 'calls') - .map(meta => meta.value.value as MetadataCalls) - - nodes.push({ - id: `${module.schema?.name}-${verb.value.value?.name}`, - position: { x: x + 20, y: y }, - connectable: false, - data: { label: verb.value.value?.name }, - // parent: module.schema?.name, - style: { - backgroundColor: 'rgb(79, 70, 229)', - }, - }) - - calls?.map(call => - call.calls.forEach(call => { - edges.push({ - id: `${module.schema?.name}-${verb.value.value?.name}-${call.module}-${call.name}`, - source: `${module.schema?.name}-${verb.value.value?.name}`, - target: `${call.module}-${call.name}`, - style: { stroke: 'rgb(251 113 133)' }, - animated: true, - }) - call.name - call.module - }), - ) - - y += 50 - }) - x += 300 - }) + useEffect(() => { + const { nodes, edges } = layoutNodes(schema) + setNodes(nodes) + setEdges(edges) + }, [schema, setEdges, setNodes]) return (
- + diff --git a/console/src/features/graph/GroupNode.tsx b/console/src/features/graph/GroupNode.tsx new file mode 100644 index 00000000..702ddaf3 --- /dev/null +++ b/console/src/features/graph/GroupNode.tsx @@ -0,0 +1,9 @@ +export function GroupNode({ data }) { + return ( + <> +
+
{data.title}
+
+ + ) +} diff --git a/console/src/features/graph/VerbNode.tsx b/console/src/features/graph/VerbNode.tsx new file mode 100644 index 00000000..059758c3 --- /dev/null +++ b/console/src/features/graph/VerbNode.tsx @@ -0,0 +1,27 @@ +import { Handle, Position } from 'reactflow' + +export function VerbNode({ data }) { + return ( + <> + + +
+
{data.title}
+
+ + + + ) +} diff --git a/console/src/features/graph/create-layout.ts b/console/src/features/graph/create-layout.ts new file mode 100644 index 00000000..1a796907 --- /dev/null +++ b/console/src/features/graph/create-layout.ts @@ -0,0 +1,64 @@ +import { Edge, Node } from 'reactflow' +import { PullSchemaResponse } from '../../protos/xyz/block/ftl/v1/ftl_pb' +import { MetadataCalls } from '../../protos/xyz/block/ftl/v1/schema/schema_pb' + +const groupWidth = 200 + +export function layoutNodes(schema: PullSchemaResponse[]) { + let x = 0 + const nodes: Node[] = [] + const edges: Edge[] = [] + schema.forEach(module => { + const verbs = module.schema?.decls.filter(decl => decl.value.case === 'verb') + nodes.push({ + id: module.schema?.name ?? '', + position: { x: x, y: 0 }, + data: { title: module.schema?.name }, + type: 'groupNode', + style: { + width: groupWidth, + height: (verbs?.length ?? 1) * 50 + 50, + zIndex: -1, + }, + }) + let y = 40 + module.schema?.decls + .filter(decl => decl.value.case === 'verb') + .forEach(verb => { + const calls = verb?.value.value?.metadata + .filter(meta => meta.value.case === 'calls') + .map(meta => meta.value.value as MetadataCalls) + + nodes.push({ + id: `${module.schema?.name}-${verb.value.value?.name}`, + position: { x: 20, y: y }, + connectable: false, + data: { title: verb.value.value?.name }, + type: 'verbNode', + parentNode: module.schema?.name, + style: { + width: groupWidth - 40, + height: 40, + }, + }) + + calls?.map(call => + call.calls.forEach(call => { + edges.push({ + id: `${module.schema?.name}-${verb.value.value?.name}-${call.module}-${call.name}`, + source: `${module.schema?.name}-${verb.value.value?.name}`, + target: `${call.module}-${call.name}`, + style: { stroke: 'rgb(251 113 133)' }, + animated: true, + }) + call.name + call.module + }), + ) + + y += 50 + }) + x += 300 + }) + return { nodes, edges } +}