import { useState, useCallback, useEffect } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import GraphView from './components/GraphView'; import Sidebar from './components/Sidebar'; import NodePanel from './components/NodePanel'; import AddNodeModal from './components/AddNodeModal'; import LinkModal from './components/LinkModal'; import Toast from './components/Toast'; export default function App() { const [selectedId, setSelectedId] = useState(null); const [showAddNode, setShowAddNode] = useState(false); const [linkFromId, setLinkFromId] = useState(null); const [toast, setToast] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); const qc = useQueryClient(); const refresh = useCallback(() => { qc.invalidateQueries({ queryKey: ['graph'] }); qc.invalidateQueries({ queryKey: ['nodes'] }); }, [qc]); const notify = useCallback((msg: string) => { setToast(msg); setTimeout(() => setToast(null), 3000); }, []); const selectNode = useCallback((id: string) => { setSelectedId(id); setDrawerOpen(false); }, []); useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') { if (selectedId) setSelectedId(null); else if (drawerOpen) setDrawerOpen(false); } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [selectedId, drawerOpen]); return (
{/* Full-screen graph */} {/* Floating action buttons — bottom-left */}
{/* Sidebar drawer — slides in from left */} {drawerOpen && (
setDrawerOpen(false)}>
e.stopPropagation()} > { setDrawerOpen(false); setShowAddNode(true); }} onClose={() => setDrawerOpen(false)} />
)} {/* Node detail panel — slides in from right */} {selectedId && (
setSelectedId(null)} />
e.stopPropagation()} > setSelectedId(null)} onLink={(id) => setLinkFromId(id)} onRefresh={refresh} onNotify={notify} />
)} {showAddNode && ( setShowAddNode(false)} onCreated={() => { refresh(); notify('Node created'); }} /> )} {linkFromId && ( setLinkFromId(null)} onCreated={() => { refresh(); notify('Edge created'); }} /> )} {toast && }
); }