From 22188071b0c39417a864c86e7808adcae15e8788 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 16 Jan 2026 16:50:58 +0000 Subject: [PATCH] Generated by Spark: It's still allowing >1 arrow per connection point, maybe it should autoremap, should be 1:1 contention on both sides, should also save changes to database --- src/components/FeatureIdeaCloud.tsx | 172 ++++++++++++++++++---------- 1 file changed, 114 insertions(+), 58 deletions(-) diff --git a/src/components/FeatureIdeaCloud.tsx b/src/components/FeatureIdeaCloud.tsx index f478e1e..6714597 100644 --- a/src/components/FeatureIdeaCloud.tsx +++ b/src/components/FeatureIdeaCloud.tsx @@ -337,6 +337,7 @@ export function FeatureIdeaCloud() { style: { stroke: '#f87171', strokeWidth: 2.5 }, }, ]) + const [savedNodePositions, setSavedNodePositions] = useKV>('feature-idea-node-positions', {}) const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) @@ -353,6 +354,7 @@ export function FeatureIdeaCloud() { const safeIdeas = ideas || SEED_IDEAS const safeGroups = groups || [] const safeEdges = savedEdges || [] + const safeNodePositions = savedNodePositions || {} useEffect(() => { if (!ideas || ideas.length === 0) { @@ -364,7 +366,7 @@ export function FeatureIdeaCloud() { const groupNodes: Node[] = safeGroups.map((group) => ({ id: group.id, type: 'groupNode', - position: { x: 0, y: 0 }, + position: safeNodePositions[group.id] || { x: 0, y: 0 }, data: group, style: { zIndex: -1, @@ -374,7 +376,7 @@ export function FeatureIdeaCloud() { const ideaNodes: Node[] = safeIdeas.map((idea, index) => ({ id: idea.id, type: 'ideaNode', - position: { x: 100 + (index % 3) * 350, y: 100 + Math.floor(index / 3) * 250 }, + position: safeNodePositions[idea.id] || { x: 100 + (index % 3) * 350, y: 100 + Math.floor(index / 3) * 250 }, data: idea, parentNode: idea.parentGroup, extent: idea.parentGroup ? 'parent' : undefined, @@ -384,7 +386,7 @@ export function FeatureIdeaCloud() { })) setNodes([...groupNodes, ...ideaNodes]) - }, [safeIdeas, safeGroups, setNodes]) + }, [safeIdeas, safeGroups, safeNodePositions, setNodes]) useEffect(() => { setEdges(safeEdges) @@ -417,6 +419,16 @@ export function FeatureIdeaCloud() { const moveChange = changes.find((c: any) => c.type === 'position' && c.dragging === false) if (moveChange) { setTimeout(() => { + setNodes((currentNodes) => { + const positions: Record = {} + currentNodes.forEach(node => { + if (node.position) { + positions[node.id] = node.position + } + }) + setSavedNodePositions(positions) + return currentNodes + }) setEdges((currentEdges) => { setSavedEdges(currentEdges) return currentEdges @@ -424,7 +436,7 @@ export function FeatureIdeaCloud() { }, 100) } }, - [onNodesChange, setEdges, setSavedEdges] + [onNodesChange, setNodes, setEdges, setSavedNodePositions, setSavedEdges] ) const onEdgesChangeWrapper = useCallback( @@ -449,22 +461,22 @@ export function FeatureIdeaCloud() { const targetNodeId = params.target const targetHandleId = params.targetHandle || 'default' - const edgesToRemove: string[] = [] - - edges.forEach(edge => { - const edgeSourceHandle = edge.sourceHandle || 'default' - const edgeTargetHandle = edge.targetHandle || 'default' - - if (edge.source === sourceNodeId && edgeSourceHandle === sourceHandleId) { - edgesToRemove.push(edge.id) - } - - if (edge.target === targetNodeId && edgeTargetHandle === targetHandleId) { - edgesToRemove.push(edge.id) - } - }) - setEdges((eds) => { + const edgesToRemove: string[] = [] + + eds.forEach(edge => { + const edgeSourceHandle = edge.sourceHandle || 'default' + const edgeTargetHandle = edge.targetHandle || 'default' + + if (edge.source === sourceNodeId && edgeSourceHandle === sourceHandleId) { + edgesToRemove.push(edge.id) + } + + if (edge.target === targetNodeId && edgeTargetHandle === targetHandleId) { + edgesToRemove.push(edge.id) + } + }) + const filteredEdges = eds.filter(e => !edgesToRemove.includes(e.id)) const style = CONNECTION_STYLES[connectionType] @@ -492,16 +504,21 @@ export function FeatureIdeaCloud() { const updatedEdges = addEdge(newEdge, filteredEdges) setSavedEdges(updatedEdges) + + if (edgesToRemove.length > 0) { + setTimeout(() => { + toast.success(`Connection remapped! (${edgesToRemove.length} old connection${edgesToRemove.length > 1 ? 's' : ''} removed)`) + }, 0) + } else { + setTimeout(() => { + toast.success('Ideas connected!') + }, 0) + } + return updatedEdges }) - - if (edgesToRemove.length > 0) { - toast.success(`Connection remapped! (${edgesToRemove.length} old connection${edgesToRemove.length > 1 ? 's' : ''} removed)`) - } else { - toast.success('Ideas connected!') - } }, - [connectionType, edges, setEdges, setSavedEdges] + [connectionType, setEdges, setSavedEdges] ) const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => { @@ -526,38 +543,43 @@ export function FeatureIdeaCloud() { const targetNodeId = newConnection.target const targetHandleId = newConnection.targetHandle || 'default' - const edgesToRemove: string[] = [] - - edges.forEach(edge => { - if (edge.id === oldEdge.id) return - - const edgeSourceHandle = edge.sourceHandle || 'default' - const edgeTargetHandle = edge.targetHandle || 'default' - - if (edge.source === sourceNodeId && edgeSourceHandle === sourceHandleId) { - edgesToRemove.push(edge.id) - } - - if (edge.target === targetNodeId && edgeTargetHandle === targetHandleId) { - edgesToRemove.push(edge.id) - } - }) - edgeReconnectSuccessful.current = true setEdges((els) => { + const edgesToRemove: string[] = [] + + els.forEach(edge => { + if (edge.id === oldEdge.id) return + + const edgeSourceHandle = edge.sourceHandle || 'default' + const edgeTargetHandle = edge.targetHandle || 'default' + + if (edge.source === sourceNodeId && edgeSourceHandle === sourceHandleId) { + edgesToRemove.push(edge.id) + } + + if (edge.target === targetNodeId && edgeTargetHandle === targetHandleId) { + edgesToRemove.push(edge.id) + } + }) + const filteredEdges = els.filter(e => !edgesToRemove.includes(e.id)) const updatedEdges = reconnectEdge(oldEdge, newConnection, filteredEdges) setSavedEdges(updatedEdges) + + if (edgesToRemove.length > 0) { + setTimeout(() => { + toast.success(`Connection remapped! (${edgesToRemove.length} conflicting connection${edgesToRemove.length > 1 ? 's' : ''} removed)`) + }, 0) + } else { + setTimeout(() => { + toast.success('Connection remapped!') + }, 0) + } + return updatedEdges }) - - if (edgesToRemove.length > 0) { - toast.success(`Connection remapped! (${edgesToRemove.length} conflicting connection${edgesToRemove.length > 1 ? 's' : ''} removed)`) - } else { - toast.success('Connection remapped!') - } - }, [edges, setEdges, setSavedEdges]) + }, [setEdges, setSavedEdges]) const onReconnectEnd = useCallback((_: MouseEvent | TouchEvent, edge: Edge) => { if (!edgeReconnectSuccessful.current) { @@ -611,13 +633,19 @@ export function FeatureIdeaCloud() { }) if (!(ideas || []).find(i => i.id === selectedIdea.id)) { + const newPosition = { x: 400, y: 300 } const newNode: Node = { id: selectedIdea.id, type: 'ideaNode', - position: { x: 400, y: 300 }, + position: newPosition, data: selectedIdea, } setNodes((nds) => [...nds, newNode]) + + setSavedNodePositions((currentPositions) => ({ + ...(currentPositions || {}), + [selectedIdea.id]: newPosition, + })) } setEditDialogOpen(false) @@ -629,6 +657,12 @@ export function FeatureIdeaCloud() { setIdeas((currentIdeas) => (currentIdeas || []).filter(i => i.id !== id)) setNodes((nds) => nds.filter(n => n.id !== id)) + setSavedNodePositions((currentPositions) => { + const newPositions = { ...(currentPositions || {}) } + delete newPositions[id] + return newPositions + }) + const updatedEdges = edges.filter(e => e.source !== id && e.target !== id) setEdges(updatedEdges) setSavedEdges(updatedEdges) @@ -655,16 +689,22 @@ export function FeatureIdeaCloud() { }) if (!(groups || []).find(g => g.id === selectedGroup.id)) { + const newPosition = { x: 200, y: 200 } const newNode: Node = { id: selectedGroup.id, type: 'groupNode', - position: { x: 200, y: 200 }, + position: newPosition, data: selectedGroup, style: { zIndex: -1, }, } setNodes((nds) => [newNode, ...nds]) + + setSavedNodePositions((currentPositions) => ({ + ...(currentPositions || {}), + [selectedGroup.id]: newPosition, + })) } setGroupDialogOpen(false) @@ -682,6 +722,12 @@ export function FeatureIdeaCloud() { setGroups((currentGroups) => (currentGroups || []).filter(g => g.id !== id)) setNodes((nds) => nds.filter(n => n.id !== id)) + setSavedNodePositions((currentPositions) => { + const newPositions = { ...(currentPositions || {}) } + delete newPositions[id] + return newPositions + }) + setGroupDialogOpen(false) setSelectedGroup(null) toast.success('Group deleted') @@ -757,14 +803,24 @@ export function FeatureIdeaCloud() { setIdeas((currentIdeas) => [...(currentIdeas || []), ...newIdeas]) - const newNodes: Node[] = newIdeas.map((idea, index) => ({ - id: idea.id, - type: 'ideaNode', - position: { x: 400 + (index * 250), y: 300 + (index * 150) }, - data: idea, - })) + const newPositions: Record = {} + const newNodes: Node[] = newIdeas.map((idea, index) => { + const position = { x: 400 + (index * 250), y: 300 + (index * 150) } + newPositions[idea.id] = position + return { + id: idea.id, + type: 'ideaNode', + position, + data: idea, + } + }) setNodes((nds) => [...nds, ...newNodes]) + setSavedNodePositions((currentPositions) => ({ + ...(currentPositions || {}), + ...newPositions, + })) + toast.success(`Generated ${newIdeas.length} new ideas!`) } } catch (error) {