Skip to content

Commit

Permalink
💄 ♻️ redesign nodes and add toolbar to drag'n'drop new nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
linkdd committed Aug 30, 2024
1 parent 9d53917 commit 7df2fdd
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 78 deletions.
24 changes: 0 additions & 24 deletions web/components/floweditor/src/SourceNode.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions web/components/floweditor/src/dnd/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { createContext, useContext, useState } from 'react'

type DnDContextType = ReturnType<typeof useState<string | undefined>>

const DnDContext = createContext<DnDContextType>(['', () => {}])

export const useDnD = () => {
return useContext(DnDContext)
}

export const DnDProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
const [type, setType] = useState<string | undefined>('')

return (
<DnDContext.Provider value={[type, setType]}>
{children}
</DnDContext.Provider>
)
}

export default DnDContext
5 changes: 0 additions & 5 deletions web/components/floweditor/src/event.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import React, { DragEventHandler, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import {
ReactFlow,
Background,
Controls,
addEdge,
applyEdgeChanges,
applyNodeChanges,
useReactFlow,
type Node,
type Edge,
type OnNodesChange,
Expand All @@ -15,13 +16,15 @@ import {

import '@xyflow/react/dist/style.css'

import { AddNodeEvent } from './event'
import { HooksContext } from './context'
import HooksContext from './hooks'

import SourceNode from './SourceNode'
import TransformNode from './TransformNode'
import SwitchNode from './SwitchNode'
import RouterNode from './RouterNode'
import NodeSelector from './NodeSelector'

import SourceNode from './nodes/SourceNode'
import TransformNode from './nodes/TransformNode'
import SwitchNode from './nodes/SwitchNode'
import RouterNode from './nodes/RouterNode'
import { useDnD } from '../dnd/context'

const defaultSourceNode: Node = {
id: '__builtin__source',
Expand All @@ -34,10 +37,12 @@ const defaultSourceNode: Node = {
interface FlowEditorProps {
flow: string
onFlowChange: (value: string) => void
eventTarget: EventTarget
}

const FlowEditor: React.FC<FlowEditorProps> = ({ flow, onFlowChange, eventTarget }) => {
const FlowEditor: React.FC<FlowEditorProps> = ({ flow, onFlowChange }) => {
const { screenToFlowPosition } = useReactFlow()
const [dndNodeType] = useDnD()

const nodeTypes = useMemo(
() => ({
source: SourceNode,
Expand Down Expand Up @@ -83,7 +88,7 @@ const FlowEditor: React.FC<FlowEditorProps> = ({ flow, onFlowChange, eventTarget
},
[flow],
)

/*
useEffect(
() => {
const handleAddNode = (event: Event) => {
Expand Down Expand Up @@ -118,7 +123,7 @@ const FlowEditor: React.FC<FlowEditorProps> = ({ flow, onFlowChange, eventTarget
}
},
[eventTarget, setNodes],
)
)*/

useEffect(
() => {
Expand All @@ -142,6 +147,52 @@ const FlowEditor: React.FC<FlowEditorProps> = ({ flow, onFlowChange, eventTarget
[setEdges],
)

const onDragOver: DragEventHandler<HTMLDivElement> = useCallback(
(event) => {
event.preventDefault()
event.dataTransfer.dropEffect = 'move'
},
[],
)

const onDrop: DragEventHandler<HTMLDivElement> = useCallback(
(event) => {
event.preventDefault()

if (!dndNodeType) {
return
}

const type = dndNodeType
const position = screenToFlowPosition({
x: event.clientX,
y: event.clientY,
})

setNodes((nds) => {
const newNode = {
id: `node-${nds.length}`,
type,
position,
data: {},
}

switch (type) {
case 'transform':
newNode.data = {transformer: ''}
break

case 'router':
newNode.data = {stream: ''}
break
}

return [...nds, newNode]
})
},
[screenToFlowPosition, setNodes, dndNodeType],
)

return (
<div className="w-full h-full">
<ReactFlow
Expand All @@ -151,11 +202,14 @@ const FlowEditor: React.FC<FlowEditorProps> = ({ flow, onFlowChange, eventTarget
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onDrop={onDrop}
onDragOver={onDragOver}
snapToGrid
defaultEdgeOptions={{ animated: true, type: 'smoothstep' }}
>
<Background />
<Controls />
<NodeSelector />
</ReactFlow>
</div>
)
Expand Down
48 changes: 48 additions & 0 deletions web/components/floweditor/src/flow/NodeSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react'
import { Panel } from '@xyflow/react'
import { useDnD } from '../dnd/context'

const NodeSelector: React.FC = () => {
const [_, setType] = useDnD()

const onDragStart = (event: React.DragEvent<HTMLButtonElement>, nodeType: string) => {
setType(nodeType)
event.dataTransfer!.effectAllowed = 'move'
}

return (
<Panel position="bottom-center">
<div className="flex flex-row items-center gap-2 white z-depth-2 p-1">
<button
className="btn-small tooltipped blue"
data-position="top"
data-tooltip="Transform Node"
draggable
onDragStart={(event) => onDragStart(event, 'transform')}
>
<i className="material-icons">filter_alt</i>
</button>
<button
className="btn-small tooltipped red"
data-position="top"
data-tooltip="Switch Node"
draggable
onDragStart={(event) => onDragStart(event, 'switch')}
>
<i className="material-icons">device_hub</i>
</button>
<button
className="btn-small tooltipped purple"
data-position="top"
data-tooltip="Router Node"
draggable
onDragStart={(event) => onDragStart(event, 'router')}
>
<i className="material-icons">storage</i>
</button>
</div>
</Panel>
)
}

export default NodeSelector
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { createContext } from 'react'
import { Node } from '@xyflow/react'

export const HooksContext = createContext<{
export default createContext<{
setNodes: React.Dispatch<React.SetStateAction<Node[]>>
}>({
setNodes: () => {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useContext } from 'react'
import { Handle, Position, Node, NodeProps } from '@xyflow/react'

import { HooksContext } from './context'
import HooksContext from '../hooks'

export type RouterNode = Node<{
stream: string
Expand All @@ -28,17 +28,29 @@ const RouterNode: React.FC<NodeProps<RouterNode>> = ({ id, data }) => {

return (
<>
<Handle type="target" position={Position.Left} />
<Handle
type="target"
position={Position.Left}
style={{
width: '12px',
height: '12px',
}}
/>
<div
className="
flex flex-row items-center
z-depth-1 px-3 py-1 gap-2
purple lighten-4 black-text
flex flex-row items-stretch
z-depth-1 p-0 gap-2
white black-text
hoverable
"
style={{
border: '4px solid #6A1B9A',
}}
>
<i className="material-icons small">storage</i>
<div className="input-field">
<div className="purple white-text px-3 py-1 flex flex-row items-center">
<i className="material-icons small">storage</i>
</div>
<div className="input-field px-3 py-1">
<input
className="nodrag"
id={`router-${id}`}
Expand Down
38 changes: 38 additions & 0 deletions web/components/floweditor/src/flow/nodes/SourceNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'
import { Handle, Position, NodeProps } from '@xyflow/react'


const SourceNode: React.FC<NodeProps> = ({}) => {
return (
<>
<div
className="
flex flex-row items-stretch
z-depth-1 p-0 gap-2
white black-text
hoverable
"
style={{
border: '4px solid #EF6C00',
}}
>
<div className="orange white-text px-3 py-1 flex flex-row items-center">
<i className="material-icons small">input</i>
</div>
<div className="px-3 py-1 flex flex-row items-center">
<span className="font-semibold">Log Source</span>
</div>
</div>
<Handle
type="source"
position={Position.Right}
style={{
width: '12px',
height: '12px',
}}
/>
</>
)
}

export default SourceNode
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useContext } from 'react'
import { Handle, Position, Node, NodeProps } from '@xyflow/react'

import { HooksContext } from './context'
import HooksContext from '../hooks'

export type SwitchNode = Node<{
condition: string
Expand All @@ -28,17 +28,29 @@ const SwitchNode: React.FC<NodeProps<SwitchNode>> = ({ id, data }) => {

return (
<>
<Handle type="target" position={Position.Left} />
<Handle
type="target"
position={Position.Left}
style={{
width: '12px',
height: '12px',
}}
/>
<div
className="
flex flex-row items-center
z-depth-1 px-3 py-1 gap-2
red lighten-4 black-text
flex flex-row items-stretch
z-depth-1 px-0 gap-2
white black-text
hoverable
"
style={{
border: '4px solid #C62828',
}}
>
<i className="material-icons small">device_hub</i>
<div className="input-field">
<div className="red darken-2 white-text px-3 py-1 flex flex-row items-center">
<i className="material-icons small">device_hub</i>
</div>
<div className="input-field px-3 py-1">
<input
className="nodrag"
id={`switch-${id}`}
Expand All @@ -51,7 +63,14 @@ const SwitchNode: React.FC<NodeProps<SwitchNode>> = ({ id, data }) => {
</label>
</div>
</div>
<Handle type="source" position={Position.Right} />
<Handle
type="source"
position={Position.Right}
style={{
width: '12px',
height: '12px',
}}
/>
</>
)
}
Expand Down
Loading

0 comments on commit 7df2fdd

Please sign in to comment.