diff --git a/src/components/focus/FocusPanel.tsx b/src/components/focus/FocusPanel.tsx index c93fb23..4514f8c 100644 --- a/src/components/focus/FocusPanel.tsx +++ b/src/components/focus/FocusPanel.tsx @@ -110,7 +110,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli const [edgesExpanded, setEdgesExpanded] = useState<{ [key: number]: boolean }>({}); // Connections section collapsed state (default closed) - const [showConnectionsModal, setShowConnectionsModal] = useState(false); + const [edgeSearchOpen, setEdgeSearchOpen] = useState(false); // Title expanded state for click-to-expand full title const [titleExpanded, setTitleExpanded] = useState<{ [key: number]: boolean }>({}); @@ -119,7 +119,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli const [regeneratingDescription, setRegeneratingDescription] = useState(null); // Content tab state: 'notes', 'desc', or 'source' - const [activeContentTab, setActiveContentTab] = useState<'notes' | 'desc' | 'source'>('notes'); + const [activeContentTab, setActiveContentTab] = useState<'notes' | 'desc' | 'edges' | 'source'>('notes'); // Desc (description) edit mode state const [descEditMode, setDescEditMode] = useState(false); @@ -1146,7 +1146,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli setAddingEdge(null); setEdgeExplanation(''); setPendingEdgeTarget(null); - setShowConnectionsModal(false); + setEdgeSearchOpen(false); } catch (error) { console.error('Error creating edge:', error); @@ -1184,7 +1184,7 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli setPendingEdgeTarget(null); setNodeSearchQuery(''); setNodeSearchSuggestions([]); - setShowConnectionsModal(false); + setEdgeSearchOpen(false); } catch (error) { console.error('Error creating edge:', error); @@ -1260,112 +1260,114 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli return (
- {/* Search Section */} -
-
- - - + {/* Search Section — only visible when toggled */} + {edgeSearchOpen && ( +
setNodeSearchQuery(e.target.value)} - onKeyDown={handleNodeSearchKeyDown} - placeholder="Connect to node..." + onKeyDown={(e) => { + if (e.key === 'Escape') { + e.preventDefault(); + setEdgeSearchOpen(false); + setNodeSearchQuery(''); + setNodeSearchSuggestions([]); + } else { + handleNodeSearchKeyDown(e); + } + }} + placeholder="Search for a node to connect..." style={{ - flex: 1, - background: 'none', - border: 'none', - outline: 'none', - color: '#fafafa', - fontSize: '16px', + width: '100%', + background: '#111', + border: '1px solid #2a2a2a', + borderRadius: '6px', + padding: '8px 12px', + color: '#e5e5e5', + fontSize: '12px', fontFamily: 'inherit', - fontWeight: 400 + outline: 'none', }} /> + {/* Search Suggestions */} + {nodeSearchSuggestions.length > 0 && ( +
+ {nodeSearchSuggestions.map((suggestion, index) => ( +
handleSelectNodeSuggestion(suggestion)} + style={{ + padding: '10px 14px', + cursor: 'pointer', + borderBottom: index < nodeSearchSuggestions.length - 1 ? '1px solid #1f1f1f' : 'none', + background: index === selectedSearchIndex ? '#1a1a1a' : 'transparent', + transition: 'background 100ms ease', + display: 'flex', + alignItems: 'center', + gap: '10px' + }} + onMouseEnter={(e) => { + if (index !== selectedSearchIndex) { + e.currentTarget.style.background = '#1a1a1a'; + } + }} + onMouseLeave={(e) => { + if (index !== selectedSearchIndex) { + e.currentTarget.style.background = 'transparent'; + } + }} + > + + {suggestion.id} + + + {suggestion.title} + + {index === selectedSearchIndex && ( + + )} +
+ ))} +
+ )}
- - {/* Search Suggestions */} - {nodeSearchSuggestions.length > 0 && ( -
- {nodeSearchSuggestions.map((suggestion, index) => ( -
handleSelectNodeSuggestion(suggestion)} - style={{ - padding: '10px 14px', - cursor: 'pointer', - borderBottom: index < nodeSearchSuggestions.length - 1 ? '1px solid #1f1f1f' : 'none', - background: index === selectedSearchIndex ? '#1a1a1a' : 'transparent', - transition: 'background 100ms ease', - display: 'flex', - alignItems: 'center', - gap: '10px' - }} - onMouseEnter={(e) => { - if (index !== selectedSearchIndex) { - e.currentTarget.style.background = '#1a1a1a'; - } - }} - onMouseLeave={(e) => { - if (index !== selectedSearchIndex) { - e.currentTarget.style.background = 'transparent'; - } - }} - > - - {suggestion.id} - - - {suggestion.title} - - {index === selectedSearchIndex && ( - - )} -
- ))} -
- )} - -
+ )} {/* Existing Connections */}
@@ -1963,9 +1965,9 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli )} - {/* Connections Button */} + {/* Connections Button — opens Edges tab */} +
)} + {/* Action button for Edges tab */} + {activeContentTab === 'edges' && ( +
+ +
+ )}
{/* Desc Tab Content */} @@ -2724,6 +2778,13 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli
)} + {/* Edges Tab Content */} + {activeContentTab === 'edges' && ( +
+ {renderConnectionsBody()} +
+ )} + {/* Source Tab Content */} {activeContentTab === 'source' && (
@@ -2966,442 +3027,6 @@ export default function FocusPanel({ openTabs, activeTab, onTabSelect, onNodeCli onConfirm={handleConfirmNodeDelete} onCancel={handleCancelNodeDelete} /> - {showConnectionsModal && activeTab && ( -
{ - if (e.target === e.currentTarget) { - setShowConnectionsModal(false); - setNodeSearchQuery(''); - setNodeSearchSuggestions([]); - } - }} - style={{ - position: 'fixed', - inset: 0, - background: 'rgba(0, 0, 0, 0.85)', - backdropFilter: 'blur(8px)', - display: 'flex', - justifyContent: 'center', - paddingTop: '10vh', - zIndex: 9999, - animation: 'backdropIn 200ms ease-out' - }} - > -
- {/* Search Input */} -
- - - - setNodeSearchQuery(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Escape') { - setShowConnectionsModal(false); - setNodeSearchQuery(''); - setNodeSearchSuggestions([]); - } else { - handleNodeSearchKeyDown(e); - } - }} - placeholder="Search to add connection..." - style={{ - flex: 1, - background: 'none', - border: 'none', - outline: 'none', - color: '#fafafa', - fontSize: '18px', - fontFamily: 'inherit', - fontWeight: 400 - }} - /> - - esc - -
- - {/* Search Results */} - {nodeSearchSuggestions.length > 0 && ( -
- {nodeSearchSuggestions.map((suggestion, index) => ( -
{ - handleSelectNodeSuggestion(suggestion); - setNodeSearchQuery(''); - setNodeSearchSuggestions([]); - }} - style={{ - padding: '10px 16px', - cursor: 'pointer', - borderBottom: index < nodeSearchSuggestions.length - 1 ? '1px solid #1f1f1f' : 'none', - background: index === selectedSearchIndex ? '#1a1a1a' : 'transparent', - transition: 'background 100ms ease', - display: 'flex', - alignItems: 'center', - gap: '10px' - }} - onMouseEnter={(e) => { - e.currentTarget.style.background = '#1a1a1a'; - }} - onMouseLeave={(e) => { - if (index !== selectedSearchIndex) { - e.currentTarget.style.background = 'transparent'; - } - }} - > - - {suggestion.id} - - - {suggestion.title} - - {index === selectedSearchIndex && ( - - )} -
- ))} -
- )} - - {/* Empty search state */} - {nodeSearchQuery && nodeSearchSuggestions.length === 0 && ( -
- No results for "{nodeSearchQuery}" -
- )} - - {/* Existing Connections */} - {!nodeSearchQuery && ( -
-
- Existing Connections ({(edgesData[activeTab] || []).length}) -
-
- {loadingEdges.has(activeTab) ? ( -
- Loading... -
- ) : (edgesData[activeTab] || []).length === 0 ? ( -
- No connections yet. Search above to add one. -
- ) : ( -
- {(edgesData[activeTab] || []).map((connection) => ( -
- {/* Connection header row */} -
- {/* Direction arrow: → if current node is FROM (outgoing), ← if current node is TO (incoming) */} - - {connection.edge.from_node_id === activeTab ? '→' : '←'} - - - {connection.connected_node.id} - - - {getNodeIcon(connection.connected_node, dimensionIcons, 12)} - - onNodeClick?.(connection.connected_node.id)} - style={{ - flex: 1, - fontSize: '13px', - color: '#e5e5e5', - cursor: 'pointer', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap' - }} - onMouseEnter={(e) => { e.currentTarget.style.color = '#22c55e'; }} - onMouseLeave={(e) => { e.currentTarget.style.color = '#e5e5e5'; }} - > - {connection.connected_node.title} - - -
- {/* Description row */} - {edgeEditingId === connection.edge.id ? ( -
- setEdgeEditingValue(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - saveEdgeExplanation(connection.edge.id, connection.edge.context); - } else if (e.key === 'Escape') { - e.preventDefault(); - cancelEditEdgeExplanation(); - } - }} - autoFocus - placeholder="Add explanation..." - style={{ - flex: 1, - fontSize: '11px', - color: '#e5e5e5', - background: '#0a0a0a', - border: '1px solid #333', - borderRadius: '6px', - padding: '6px 8px', - outline: 'none', - fontFamily: 'inherit' - }} - /> - - -
- ) : ( -
startEditEdgeExplanation(connection.edge.id, connection.edge.context?.explanation as string | undefined)} - style={{ - fontSize: '12px', - color: connection.edge.context?.explanation ? '#888' : '#525252', - cursor: 'pointer', - padding: '4px 0', - fontStyle: connection.edge.context?.explanation ? 'normal' : 'italic', - display: 'flex', - alignItems: 'center', - gap: '8px' - }} - onMouseEnter={(e) => { e.currentTarget.style.color = '#aaa'; }} - onMouseLeave={(e) => { e.currentTarget.style.color = connection.edge.context?.explanation ? '#888' : '#525252'; }} - > - {typeof connection.edge.context?.type === 'string' && ( - - {String(connection.edge.context.type).replace(/_/g, ' ')} - - )} - - {(connection.edge.context?.explanation as string) || 'Click to add explanation...'} - -
- )} -
- ))} -
- )} -
-
- )} -
- -
- )} - {/* Tab Context Menu */} {contextMenu && (