feat: add node picker dropdown to action node form for cross-references
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { DynamicArrayField } from './DynamicArrayField'
|
||||
import { useTreeEditorStore } from '@/store/treeEditorStore'
|
||||
import { useTreeEditorStore, collectAllNodesFlat } from '@/store/treeEditorStore'
|
||||
import { Link2, X } from 'lucide-react'
|
||||
import { MarkdownContent } from '@/components/ui/MarkdownContent'
|
||||
import { InfoTip } from '@/components/common/InfoTip'
|
||||
import type { TreeStructure } from '@/types'
|
||||
@@ -158,16 +159,65 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Next step hint */}
|
||||
{hasNextNode ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Next step is linked — click it on the canvas to edit.
|
||||
{/* Link to existing node */}
|
||||
<div>
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
<Link2 className="h-3.5 w-3.5" />
|
||||
Next Step
|
||||
</label>
|
||||
{hasNextNode ? (
|
||||
<div className="mt-1 flex items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2">
|
||||
<span className="flex-1 truncate text-sm text-foreground">
|
||||
Linked to: {(() => {
|
||||
const treeStructure = useTreeEditorStore.getState().treeStructure
|
||||
const allNodes = collectAllNodesFlat(treeStructure)
|
||||
const target = allNodes.find(n => n.id === node.next_node_id)
|
||||
return target ? target.label : node.next_node_id
|
||||
})()}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onUpdate({ next_node_id: undefined })}
|
||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
title="Remove link"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<select
|
||||
value=""
|
||||
onChange={(e) => {
|
||||
if (e.target.value) {
|
||||
onUpdate({ next_node_id: e.target.value })
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
||||
'bg-card text-foreground',
|
||||
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
<option value="">Link to existing node...</option>
|
||||
{(() => {
|
||||
const treeStructure = useTreeEditorStore.getState().treeStructure
|
||||
const allNodes = collectAllNodesFlat(treeStructure)
|
||||
return allNodes
|
||||
.filter(n => n.id !== node.id && n.type !== 'answer')
|
||||
.map(n => (
|
||||
<option key={n.id} value={n.id}>
|
||||
{' '.repeat(n.depth)}{n.type === 'decision' ? '?' : n.type === 'action' ? '>' : '*'} {n.label}
|
||||
</option>
|
||||
))
|
||||
})()}
|
||||
</select>
|
||||
)}
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{hasNextNode
|
||||
? 'This action will navigate to the linked node.'
|
||||
: 'Select a node to navigate to after this action, or save to create a new placeholder.'}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-yellow-400/70">
|
||||
Save to create a placeholder for the next step.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -123,6 +123,25 @@ export const findNodeInTree = (
|
||||
return null
|
||||
}
|
||||
|
||||
/** Collect all nodes in the tree as a flat list with depth info. */
|
||||
export function collectAllNodesFlat(
|
||||
root: TreeStructure | null
|
||||
): Array<{ id: string; label: string; type: string; depth: number }> {
|
||||
if (!root) return []
|
||||
const result: Array<{ id: string; label: string; type: string; depth: number }> = []
|
||||
|
||||
function walk(node: TreeStructure, depth: number) {
|
||||
const label = node.type === 'decision'
|
||||
? (node.question || 'Untitled Decision')
|
||||
: (node.title || `Untitled ${node.type}`)
|
||||
result.push({ id: node.id, label, type: node.type, depth })
|
||||
node.children?.forEach(child => walk(child, depth + 1))
|
||||
}
|
||||
|
||||
walk(root, 0)
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper to find parent of a node
|
||||
const findParentNode = (
|
||||
nodeId: string,
|
||||
|
||||
Reference in New Issue
Block a user