Add query bar, maintenance panel, and heartbeat system
- Query bar with organized/grouped search results in portal - Maintenance panel UI for triggering and viewing maintenance status - Heartbeat service with periodic maintenance and dirty-tracking - Query organizer for grouping search results by tag/kind/parent - Slide-up animation for query panel
This commit is contained in:
@@ -4,8 +4,10 @@ import GraphView from './components/GraphView';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import NodePanel from './components/NodePanel';
|
||||
import AddNodeModal from './components/AddNodeModal';
|
||||
import QueryBar from './components/QueryBar';
|
||||
import LinkModal from './components/LinkModal';
|
||||
import Toast from './components/Toast';
|
||||
import MaintenancePanel from './components/MaintenancePanel';
|
||||
|
||||
export default function App() {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
@@ -13,6 +15,8 @@ export default function App() {
|
||||
const [linkFromId, setLinkFromId] = useState<string | null>(null);
|
||||
const [toast, setToast] = useState<string | null>(null);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [showQuery, setShowQuery] = useState(false);
|
||||
const [showMaintenance, setShowMaintenance] = useState(false);
|
||||
const qc = useQueryClient();
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
@@ -34,12 +38,13 @@ export default function App() {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
if (selectedId) setSelectedId(null);
|
||||
else if (showQuery) setShowQuery(false);
|
||||
else if (drawerOpen) setDrawerOpen(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handler);
|
||||
return () => window.removeEventListener('keydown', handler);
|
||||
}, [selectedId, drawerOpen]);
|
||||
}, [selectedId, drawerOpen, showQuery]);
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden relative">
|
||||
@@ -64,6 +69,20 @@ export default function App() {
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowQuery(!showQuery)}
|
||||
className="w-12 h-12 rounded-full bg-purple-600/90 backdrop-blur border border-purple-500 text-white hover:bg-purple-500 shadow-lg flex items-center justify-center text-lg font-bold"
|
||||
title="Query memory"
|
||||
>
|
||||
?
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowMaintenance(!showMaintenance)}
|
||||
className="w-12 h-12 rounded-full bg-emerald-600/90 backdrop-blur border border-emerald-500 text-white hover:bg-emerald-500 shadow-lg flex items-center justify-center text-lg"
|
||||
title="Maintenance"
|
||||
>
|
||||
⚙
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Sidebar drawer — slides in from left */}
|
||||
@@ -106,6 +125,22 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Query panel — slides up from bottom */}
|
||||
{showQuery && (
|
||||
<div className="fixed inset-0 z-40" onClick={() => setShowQuery(false)}>
|
||||
<div className="absolute inset-0 bg-black/40" />
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-[70vh] animate-slide-in-up"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<QueryBar
|
||||
onSelectNode={(id) => { setShowQuery(false); selectNode(id); }}
|
||||
onClose={() => setShowQuery(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showAddNode && (
|
||||
<AddNodeModal
|
||||
onClose={() => setShowAddNode(false)}
|
||||
@@ -119,6 +154,11 @@ export default function App() {
|
||||
onCreated={() => { refresh(); notify('Edge created'); }}
|
||||
/>
|
||||
)}
|
||||
{showMaintenance && (
|
||||
<div className="fixed bottom-20 left-5 z-50">
|
||||
<MaintenancePanel onClose={() => setShowMaintenance(false)} onNotify={(msg) => { notify(msg); refresh(); }} />
|
||||
</div>
|
||||
)}
|
||||
{toast && <Toast message={toast} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user