yansari's picture
feat: intro page, undo/redo, transition fix, naming and stats polish
e0e4431
Raw
History Blame Contribute Delete
6.02 kB
import { useState } from 'react';
import type { GraphStatistics, ConnectivityInfo } from '../types';
import { NODE_TYPE_LABELS, NODE_COLORS } from '../constants';
interface Props {
statistics: GraphStatistics;
processingTime: number;
imageName: string;
edited?: boolean;
connectivity?: ConnectivityInfo | null;
onFocusNode?: (id: string) => void;
onToggleBroken?: (show: boolean) => void;
}
const MAX_LISTED = 40;
const REASON_LABEL: Record<string, string> = { isolated: 'no edges', room: 'room', exit: 'exit' };
export default function StatsPanel({
statistics,
processingTime,
imageName,
edited,
connectivity,
onFocusNode,
onToggleBroken,
}: Props) {
const [open, setOpen] = useState(true);
const [connOpen, setConnOpen] = useState(false);
const { total_nodes, total_edges, node_types, pruning_reduction } = statistics;
const broken = connectivity && !connectivity.fullyConnected;
const toggleConn = () =>
setConnOpen((o) => {
const next = !o;
onToggleBroken?.(next);
return next;
});
return (
<div className="panel">
<button className="panel-collapse-header" onClick={() => setOpen((o) => !o)}>
<h3>Statistics{edited ? ' (edited)' : ''}</h3>
<span className={`chevron${open ? ' open' : ''}`}></span>
</button>
{open && (
<div className="panel-collapse-body">
<div className="stat-row">
<span className="label">Image</span>
<span className="value stat-image" title={imageName}>{imageName}</span>
</div>
<div className="stat-row">
<span className="label">Processing Time</span>
<span className="value">
{processingTime > 0 ? `${processingTime.toFixed(1)}s` : 'Cached'}
</span>
</div>
<hr className="stat-divider" />
<div className="stat-row">
<span className="label">Total Nodes</span>
<span className="value">{total_nodes}</span>
</div>
<div className="stat-row">
<span className="label">Total Edges</span>
<span className="value">{total_edges}</span>
</div>
{connectivity && (
<>
<div className="stat-row">
<span className="label">Connectivity</span>
<span className="value">
{connectivity.score}%{' '}
{broken ? (
<button
className="conn-warn"
title="Issues found. Click to inspect and highlight on the graph."
onClick={toggleConn}
>
&#9888;
</button>
) : (
<span className="conn-ok" title="Rooms and exits fully connected">&#10003;</span>
)}
</span>
</div>
{broken && connOpen && (
<div className="conn-list">
<div className="conn-list-title">
{connectivity.isolatedCount > 0 && (
<div className="conn-flag">
{connectivity.isolatedCount} node
{connectivity.isolatedCount === 1 ? '' : 's'} with no edges
</div>
)}
{connectivity.roomsDisconnected > 0 && (
<div>
{connectivity.roomsDisconnected} room
{connectivity.roomsDisconnected === 1 ? '' : 's'} unreachable
</div>
)}
{connectivity.exitsDisconnected > 0 && (
<div>
{connectivity.exitsDisconnected} exit door
{connectivity.exitsDisconnected === 1 ? '' : 's'} unreachable
</div>
)}
<div className="conn-locate">
The smaller, disconnected subgraph is highlighted in red on
the graph. Click a node to zoom to it.
</div>
</div>
{connectivity.offenders.slice(0, MAX_LISTED).map((n) => (
<button key={n.id} className="conn-node" onClick={() => onFocusNode?.(n.id)}>
<span
className="type-swatch"
style={{ backgroundColor: NODE_COLORS[n.type] || '#999' }}
/>
<span className="conn-node-id">{n.id}</span>
<span className={`conn-reason conn-reason-${n.reason}`}>
{REASON_LABEL[n.reason]}
</span>
</button>
))}
{connectivity.offenders.length > MAX_LISTED && (
<div className="conn-more">
+ {connectivity.offenders.length - MAX_LISTED} more
</div>
)}
</div>
)}
</>
)}
{pruning_reduction != null && pruning_reduction > 0 && (
<div className="stat-row">
<span className="label">Pruning Reduction</span>
<span className="value">{pruning_reduction}%</span>
</div>
)}
<hr className="stat-divider" />
{Object.entries(node_types).map(([type, count]) => (
<div className="stat-row" key={type}>
<span className="label" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span
className="type-swatch"
style={{ backgroundColor: NODE_COLORS[type] || '#999' }}
/>
{NODE_TYPE_LABELS[type] || type}
</span>
<span className="value">{count}</span>
</div>
))}
</div>
)}
</div>
);
}