Fix blank portal: add root element heights, use dagre for graph layout
- Set explicit height on html/body/#root so React Flow container renders - Replace @dagrejs/dagre (broken CJS dynamic require) with dagre v0.8.5 - Hierarchical top-down layout with dagre, arrow markers on edges - Move React Flow controls to top-right to avoid floating button overlap
This commit is contained in:
@@ -8,9 +8,12 @@ import {
|
||||
useEdgesState,
|
||||
type Node as FlowNode,
|
||||
type Edge as FlowEdge,
|
||||
Position,
|
||||
MarkerType,
|
||||
} from '@xyflow/react';
|
||||
import Dagre from 'dagre';
|
||||
import { useGraph } from '../hooks/useGraph';
|
||||
import type { CortexNode } from '../types';
|
||||
import type { CortexNode, CortexEdge } from '../types';
|
||||
|
||||
const KIND_COLORS: Record<string, string> = {
|
||||
memory: '#6366f1',
|
||||
@@ -19,23 +22,43 @@ const KIND_COLORS: Record<string, string> = {
|
||||
decision: '#ef4444',
|
||||
};
|
||||
|
||||
function layoutNodes(nodes: CortexNode[]): FlowNode[] {
|
||||
const cols = Math.max(Math.ceil(Math.sqrt(nodes.length)), 1);
|
||||
return nodes.map((n, i) => ({
|
||||
id: n.id,
|
||||
position: { x: (i % cols) * 280 + 50, y: Math.floor(i / cols) * 140 + 50 },
|
||||
data: { label: n.title, kind: n.kind, status: n.status },
|
||||
style: {
|
||||
background: KIND_COLORS[n.kind] || '#6b7280',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: 8,
|
||||
padding: '12px 16px',
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
minWidth: 160,
|
||||
},
|
||||
}));
|
||||
const NODE_WIDTH = 180;
|
||||
const NODE_HEIGHT = 60;
|
||||
|
||||
function layoutGraph(nodes: CortexNode[], edges: CortexEdge[]): FlowNode[] {
|
||||
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
g.setGraph({ rankdir: 'TB', nodesep: 60, ranksep: 100, marginx: 40, marginy: 40 });
|
||||
|
||||
for (const n of nodes) {
|
||||
g.setNode(n.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
|
||||
}
|
||||
for (const e of edges) {
|
||||
g.setEdge(e.fromId, e.toId);
|
||||
}
|
||||
|
||||
Dagre.layout(g);
|
||||
|
||||
return nodes.map((n) => {
|
||||
const pos = g.node(n.id);
|
||||
return {
|
||||
id: n.id,
|
||||
position: { x: pos.x - NODE_WIDTH / 2, y: pos.y - NODE_HEIGHT / 2 },
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top,
|
||||
data: { label: n.title, kind: n.kind, status: n.status },
|
||||
style: {
|
||||
background: KIND_COLORS[n.kind] || '#6b7280',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: 8,
|
||||
padding: '10px 16px',
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
width: NODE_WIDTH,
|
||||
textAlign: 'center' as const,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default function GraphView({
|
||||
@@ -47,7 +70,11 @@ export default function GraphView({
|
||||
}) {
|
||||
const { data, isLoading } = useGraph();
|
||||
|
||||
const initialNodes = useMemo(() => (data ? layoutNodes(data.nodes) : []), [data]);
|
||||
const initialNodes = useMemo(
|
||||
() => (data ? layoutGraph(data.nodes, data.edges) : []),
|
||||
[data],
|
||||
);
|
||||
|
||||
const initialEdges = useMemo<FlowEdge[]>(
|
||||
() =>
|
||||
data
|
||||
@@ -57,8 +84,12 @@ export default function GraphView({
|
||||
target: e.toId,
|
||||
label: e.type,
|
||||
animated: e.type === 'blocked_by',
|
||||
style: { stroke: '#64748b' },
|
||||
labelStyle: { fill: '#94a3b8', fontSize: 11 },
|
||||
style: { stroke: '#64748b', strokeWidth: 1.5 },
|
||||
labelStyle: { fill: '#94a3b8', fontSize: 10 },
|
||||
labelBgStyle: { fill: '#1e293b', fillOpacity: 0.9 },
|
||||
labelBgPadding: [6, 3] as [number, number],
|
||||
labelBgBorderRadius: 4,
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#64748b', width: 16, height: 16 },
|
||||
}))
|
||||
: [],
|
||||
[data],
|
||||
@@ -67,7 +98,6 @@ export default function GraphView({
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
// Sync when data changes
|
||||
useMemo(() => {
|
||||
setNodes(initialNodes);
|
||||
setEdges(initialEdges);
|
||||
@@ -94,11 +124,16 @@ export default function GraphView({
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.3 }}
|
||||
className="bg-gray-950"
|
||||
>
|
||||
<Background color="#334155" gap={20} />
|
||||
<Controls className="!bg-gray-800 !border-gray-700 [&>button]:!bg-gray-800 [&>button]:!border-gray-700 [&>button]:!text-gray-300" />
|
||||
<Controls
|
||||
position="top-right"
|
||||
className="!bg-gray-800 !border-gray-700 [&>button]:!bg-gray-800 [&>button]:!border-gray-700 [&>button]:!text-gray-300"
|
||||
/>
|
||||
<MiniMap
|
||||
position="bottom-right"
|
||||
nodeColor={(n) => KIND_COLORS[(n.data as any)?.kind] || '#6b7280'}
|
||||
className="!bg-gray-900 !border-gray-700"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user