feat: [US-025] - Create custom variable node component
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
64d67b9aed
commit
1ad3c183a7
|
|
@ -20,6 +20,7 @@ import 'reactflow/dist/style.css'
|
||||||
import Toolbar from '@/components/editor/Toolbar'
|
import Toolbar from '@/components/editor/Toolbar'
|
||||||
import DialogueNode from '@/components/editor/nodes/DialogueNode'
|
import DialogueNode from '@/components/editor/nodes/DialogueNode'
|
||||||
import ChoiceNode from '@/components/editor/nodes/ChoiceNode'
|
import ChoiceNode from '@/components/editor/nodes/ChoiceNode'
|
||||||
|
import VariableNode from '@/components/editor/nodes/VariableNode'
|
||||||
import type { FlowchartData, FlowchartNode, FlowchartEdge } from '@/types/flowchart'
|
import type { FlowchartData, FlowchartNode, FlowchartEdge } from '@/types/flowchart'
|
||||||
|
|
||||||
type FlowchartEditorProps = {
|
type FlowchartEditorProps = {
|
||||||
|
|
@ -56,6 +57,7 @@ function FlowchartEditorInner({ initialData }: FlowchartEditorProps) {
|
||||||
() => ({
|
() => ({
|
||||||
dialogue: DialogueNode,
|
dialogue: DialogueNode,
|
||||||
choice: ChoiceNode,
|
choice: ChoiceNode,
|
||||||
|
variable: VariableNode,
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useCallback, ChangeEvent } from 'react'
|
||||||
|
import { Handle, Position, NodeProps, useReactFlow } from 'reactflow'
|
||||||
|
|
||||||
|
type VariableNodeData = {
|
||||||
|
variableName: string
|
||||||
|
operation: 'set' | 'add' | 'subtract'
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VariableNode({ id, data }: NodeProps<VariableNodeData>) {
|
||||||
|
const { setNodes } = useReactFlow()
|
||||||
|
|
||||||
|
const updateVariableName = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setNodes((nodes) =>
|
||||||
|
nodes.map((node) =>
|
||||||
|
node.id === id
|
||||||
|
? { ...node, data: { ...node.data, variableName: e.target.value } }
|
||||||
|
: node
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[id, setNodes]
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateOperation = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setNodes((nodes) =>
|
||||||
|
nodes.map((node) =>
|
||||||
|
node.id === id
|
||||||
|
? { ...node, data: { ...node.data, operation: e.target.value as 'set' | 'add' | 'subtract' } }
|
||||||
|
: node
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[id, setNodes]
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateValue = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = parseFloat(e.target.value) || 0
|
||||||
|
setNodes((nodes) =>
|
||||||
|
nodes.map((node) =>
|
||||||
|
node.id === id
|
||||||
|
? { ...node, data: { ...node.data, value } }
|
||||||
|
: node
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[id, setNodes]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-w-[200px] rounded-lg border-2 border-orange-500 bg-orange-50 p-3 shadow-md dark:border-orange-400 dark:bg-orange-950">
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
id="input"
|
||||||
|
className="!h-3 !w-3 !border-2 !border-orange-500 !bg-white dark:!bg-zinc-800"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mb-2 text-xs font-semibold uppercase tracking-wide text-orange-700 dark:text-orange-300">
|
||||||
|
Variable
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={data.variableName || ''}
|
||||||
|
onChange={updateVariableName}
|
||||||
|
placeholder="variableName"
|
||||||
|
className="mb-2 w-full rounded border border-orange-300 bg-white px-2 py-1 text-sm focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500 dark:border-orange-600 dark:bg-zinc-800 dark:text-white dark:placeholder-zinc-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mb-2 flex gap-2">
|
||||||
|
<select
|
||||||
|
value={data.operation || 'set'}
|
||||||
|
onChange={updateOperation}
|
||||||
|
className="flex-1 rounded border border-orange-300 bg-white px-2 py-1 text-sm focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500 dark:border-orange-600 dark:bg-zinc-800 dark:text-white"
|
||||||
|
>
|
||||||
|
<option value="set">set</option>
|
||||||
|
<option value="add">add</option>
|
||||||
|
<option value="subtract">subtract</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={data.value ?? 0}
|
||||||
|
onChange={updateValue}
|
||||||
|
className="w-20 rounded border border-orange-300 bg-white px-2 py-1 text-sm focus:border-orange-500 focus:outline-none focus:ring-1 focus:ring-orange-500 dark:border-orange-600 dark:bg-zinc-800 dark:text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
id="output"
|
||||||
|
className="!h-3 !w-3 !border-2 !border-orange-500 !bg-white dark:!bg-zinc-800"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue