File size: 4,513 Bytes
ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e ef4c917 a1ace1e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | import type { RouteEndpoint, RouteInfo, InteractionMode } from '../types';
import { NODE_TYPE_LABELS, ROUTE_COLORS } from '../constants';
interface Props {
endpoints: RouteEndpoint[];
source: string | null;
target: string | null;
onSourceChange: (id: string | null) => void;
onTargetChange: (id: string | null) => void;
onSwap: () => void;
onClear: () => void;
info: RouteInfo | null;
mode: InteractionMode;
onArmStart: () => void;
onArmEnd: () => void;
}
function labelFor(endpoints: RouteEndpoint[], id: string | null): string {
if (!id) return 'not set';
const ep = endpoints.find((e) => e.id === id);
return ep ? ep.label : id;
}
function optionLabel(ep: RouteEndpoint): string {
const typeLabel = NODE_TYPE_LABELS[ep.type] || ep.type;
return ep.type === 'room' ? ep.label : `${ep.label} (${typeLabel})`;
}
export default function RoutePanel({
endpoints,
source,
target,
onSourceChange,
onTargetChange,
onSwap,
onClear,
info,
mode,
onArmStart,
onArmEnd,
}: Props) {
const hasRoute = !!source && !!target;
return (
<div className="panel">
<h3>Navigation</h3>
<p className="route-hint">
Click <strong>Set Start</strong>, then click a node on the graph. Do the
same for <strong>Set Destination</strong>. The shortest route is computed
with A* search.
</p>
<div className="route-pick">
<button
className={`btn route-pick-btn${mode === 'route-start' ? ' arming' : ''}`}
onClick={onArmStart}
>
<span className="route-dot" style={{ background: ROUTE_COLORS.source }} />
{mode === 'route-start' ? 'Click a node…' : 'Set Start'}
</button>
<div className="route-current">{labelFor(endpoints, source)}</div>
</div>
<div className="route-pick">
<button
className={`btn route-pick-btn${mode === 'route-end' ? ' arming' : ''}`}
onClick={onArmEnd}
>
<span className="route-dot" style={{ background: ROUTE_COLORS.target }} />
{mode === 'route-end' ? 'Click a node…' : 'Set Destination'}
</button>
<div className="route-current">{labelFor(endpoints, target)}</div>
</div>
<div className="route-buttons">
<button className="btn" onClick={onSwap} disabled={!hasRoute}>Swap</button>
<button className="btn" onClick={onClear} disabled={!source && !target}>Clear</button>
</div>
{hasRoute && info && (
<div className={`route-result${info.found ? '' : ' route-result-fail'}`}>
{info.found ? (
<>
<div className="route-result-row">
<span className="label">Path length</span>
<span className="value">{info.distance.toLocaleString()} px</span>
</div>
<div className="route-result-row">
<span className="label">Segments</span>
<span className="value">{info.segments}</span>
</div>
{info.crossFloor && <span className="route-badge">Crosses floors</span>}
</>
) : (
<span>No route found between these points.</span>
)}
</div>
)}
<details className="route-advanced">
<summary>Pick from list instead</summary>
<label className="route-field">
<span className="route-dot" style={{ background: ROUTE_COLORS.source }} />
<select
className="route-select"
value={source ?? ''}
onChange={(e) => onSourceChange(e.target.value || null)}
>
<option value="">Start…</option>
{endpoints.map((ep) => (
<option key={ep.id} value={ep.id} disabled={ep.id === target}>
{optionLabel(ep)}
</option>
))}
</select>
</label>
<label className="route-field">
<span className="route-dot" style={{ background: ROUTE_COLORS.target }} />
<select
className="route-select"
value={target ?? ''}
onChange={(e) => onTargetChange(e.target.value || null)}
>
<option value="">Destination…</option>
{endpoints.map((ep) => (
<option key={ep.id} value={ep.id} disabled={ep.id === source}>
{optionLabel(ep)}
</option>
))}
</select>
</label>
</details>
</div>
);
}
|