feat: [US-021] - Add dialogue node from toolbar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gustavo Henrique Santos Souza de Miranda 2026-01-21 12:50:20 -03:00
parent 9d46aa744d
commit 99e2d5aa15
3 changed files with 80 additions and 11 deletions

48
package-lock.json generated
View File

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@supabase/ssr": "^0.8.0", "@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.91.0", "@supabase/supabase-js": "^2.91.0",
"nanoid": "^5.1.6",
"next": "16.1.4", "next": "16.1.4",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
@ -5511,9 +5512,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "5.1.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -5522,10 +5523,10 @@
], ],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.js"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^18 || >=20"
} }
}, },
"node_modules/napi-postinstall": { "node_modules/napi-postinstall": {
@ -5604,6 +5605,24 @@
} }
} }
}, },
"node_modules/next/node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -5928,6 +5947,25 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@supabase/ssr": "^0.8.0", "@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.91.0", "@supabase/supabase-js": "^2.91.0",
"nanoid": "^5.1.6",
"next": "16.1.4", "next": "16.1.4",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",

View File

@ -7,12 +7,15 @@ import ReactFlow, {
Controls, Controls,
useNodesState, useNodesState,
useEdgesState, useEdgesState,
useReactFlow,
ReactFlowProvider,
addEdge, addEdge,
Connection, Connection,
Node, Node,
Edge, Edge,
NodeTypes, NodeTypes,
} from 'reactflow' } from 'reactflow'
import { nanoid } from 'nanoid'
import 'reactflow/dist/style.css' 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'
@ -45,9 +48,8 @@ function toReactFlowEdges(edges: FlowchartEdge[]): Edge[] {
})) }))
} }
export default function FlowchartEditor({ // Inner component that uses useReactFlow hook
initialData, function FlowchartEditorInner({ initialData }: FlowchartEditorProps) {
}: FlowchartEditorProps) {
// Define custom node types - memoized to prevent re-renders // Define custom node types - memoized to prevent re-renders
const nodeTypes: NodeTypes = useMemo( const nodeTypes: NodeTypes = useMemo(
() => ({ () => ({
@ -56,6 +58,8 @@ export default function FlowchartEditor({
[] []
) )
const { getViewport } = useReactFlow()
const [nodes, setNodes, onNodesChange] = useNodesState( const [nodes, setNodes, onNodesChange] = useNodesState(
toReactFlowNodes(initialData.nodes) toReactFlowNodes(initialData.nodes)
) )
@ -68,10 +72,27 @@ export default function FlowchartEditor({
[setEdges] [setEdges]
) )
// Placeholder handlers - functionality will be added in future stories // Get center position of current viewport for placing new nodes
const getViewportCenter = useCallback(() => {
const viewport = getViewport()
// Calculate center based on viewport dimensions (assume ~800x600 visible area)
// Adjust based on zoom level
const centerX = (-viewport.x + 400) / viewport.zoom
const centerY = (-viewport.y + 300) / viewport.zoom
return { x: centerX, y: centerY }
}, [getViewport])
// Add dialogue node at viewport center
const handleAddDialogue = useCallback(() => { const handleAddDialogue = useCallback(() => {
// TODO: Implement in US-021 const position = getViewportCenter()
}, []) const newNode: Node = {
id: nanoid(),
type: 'dialogue',
position,
data: { speaker: '', text: '' },
}
setNodes((nodes) => [...nodes, newNode])
}, [getViewportCenter, setNodes])
const handleAddChoice = useCallback(() => { const handleAddChoice = useCallback(() => {
// TODO: Implement in US-023 // TODO: Implement in US-023
@ -120,3 +141,12 @@ export default function FlowchartEditor({
</div> </div>
) )
} }
// Outer wrapper component with ReactFlowProvider
export default function FlowchartEditor(props: FlowchartEditorProps) {
return (
<ReactFlowProvider>
<FlowchartEditorInner {...props} />
</ReactFlowProvider>
)
}