feat(frontend): implement via mode for setting routes with waypoints

This commit is contained in:
2025-08-09 19:57:04 +02:00
parent cd1cc6dc5f
commit ef57bf4cde
2 changed files with 51 additions and 66 deletions

View File

@@ -67,14 +67,11 @@ export const MapNode: React.FC<MapNodeProps> = ({
onDragEnd?.(e);
};
const nodeColor = security !== undefined
? getSecurityColor(security)
: '#a855f7'; // fallback purple color
const nodeColor = security !== undefined ? getSecurityColor(security) : '#a855f7';
if (type === 'region') {
// Further reduce region size to prevent overlap - made even smaller
const pillWidth = Math.max(name.length * 5, 40); // Reduced from 8 to 5, min from 60 to 40
const pillHeight = 18; // Reduced from 24 to 18
const pillWidth = Math.max(name.length * 5, 40);
const pillHeight = 18;
return (
<g
@@ -131,9 +128,8 @@ export const MapNode: React.FC<MapNodeProps> = ({
</g>
);
} else {
// Render system as a dot with external label
const nodeSize = 6;
const textOffset = 20; // Position text below the dot - moved down more
const textOffset = 20;
return (
<g
@@ -144,7 +140,7 @@ export const MapNode: React.FC<MapNodeProps> = ({
onMouseDown={handleMouseDown}
onClick={(e) => {
e.stopPropagation();
if (!disableNavigate) onClick();
onClick();
}}
onDoubleClick={(e) => {
e.stopPropagation();

View File

@@ -70,14 +70,34 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
const svgRef = useRef<SVGSVGElement>(null);
const [viaMode, setViaMode] = useState(false);
const [viaDest, setViaDest] = useState<string | null>(null);
const [viaQueue, setViaQueue] = useState<string[]>([]);
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setViaMode(false);
const onKeyDown = async (e: KeyboardEvent) => {
if (e.key === 'Escape' && viaMode) {
// Commit the route: post destination then queued waypoints
try {
if (!(await ensureAnyLoggedIn())) return;
if (viaDest) {
await SetDestinationForAll(viaDest, true, false);
for (const name of viaQueue) {
await AddWaypointForAllByName(name, false);
}
toast({ title: 'Route set', description: `${viaDest}${viaQueue.length ? ' via ' + viaQueue.join(', ') : ''}` });
}
} catch (err: any) {
toast({ title: 'Failed to set route', description: String(err), variant: 'destructive' });
} finally {
setViaMode(false);
setViaDest(null);
setViaQueue([]);
}
}
};
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, []);
}, [viaMode, viaDest, viaQueue]);
const { data: rsystems, isLoading, error } = useRegionData(regionName);
useEffect(() => {
@@ -85,18 +105,14 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
setSystems(rsystems);
}, [rsystems, isLoading, error]);
// Process connections when systems or nodePositions change
useEffect(() => {
if (!systems || systems.size === 0) return;
console.log("Computing node positions");
const positions = computeNodePositions(systems);
setPositions(positions);
console.log("Computing node connections");
const connections = computeNodeConnections(systems);
setConnections(connections);
}, [systems]);
// Load wormhole systems on mount if in wormhole region
useEffect(() => {
if (isWormholeRegion) {
loadWormholeSystems().then(wormholeSystems => {
@@ -121,14 +137,13 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
const handleSystemClick = async (systemName: string) => {
if (viaMode) {
try {
if (!(await ensureAnyLoggedIn())) return;
await AddWaypointForAllByName(systemName, false);
toast({ title: 'Waypoint added', description: systemName });
} catch (e: any) {
console.error('Append waypoint failed:', e);
toast({ title: 'Failed to add waypoint', description: String(e), variant: 'destructive' });
}
setViaQueue(prev => {
if (prev.includes(systemName)) return prev;
const next = [...prev, systemName];
return next;
});
console.log('Queued waypoint:', systemName);
toast({ title: 'Waypoint queued', description: systemName });
return;
}
if (focusSystem === systemName) return;
@@ -268,36 +283,20 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
if (!svgRef.current) return;
setIsPanning(true);
const rect = svgRef.current.getBoundingClientRect();
setLastPanPoint({
x: e.clientX - rect.left,
y: e.clientY - rect.top
});
setLastPanPoint({ x: e.clientX - rect.left, y: e.clientY - rect.top });
}, []);
const handleMouseMove = useCallback((e: React.MouseEvent) => {
if (!isPanning || !svgRef.current) return;
const rect = svgRef.current.getBoundingClientRect();
const currentPoint = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
const currentPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top };
const deltaX = (lastPanPoint.x - currentPoint.x) * (viewBox.width / rect.width);
const deltaY = (lastPanPoint.y - currentPoint.y) * (viewBox.height / rect.height);
setViewBox(prev => ({
...prev,
x: prev.x + deltaX,
y: prev.y + deltaY
}));
setViewBox(prev => ({ ...prev, x: prev.x + deltaX, y: prev.y + deltaY }));
setLastPanPoint(currentPoint);
}, [isPanning, lastPanPoint, viewBox.width, viewBox.height]);
const handleMouseUp = useCallback(() => {
setIsPanning(false);
}, []);
const handleMouseUp = useCallback(() => { setIsPanning(false); }, []);
const handleWheel = useCallback((e: React.WheelEvent) => {
e.preventDefault();
@@ -403,16 +402,18 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
const onSetDestination = async (systemName: string, wantVia: boolean) => {
try {
if (!(await ensureAnyLoggedIn())) return;
if (!viaMode) {
if (wantVia) {
setViaDest(systemName);
setViaQueue([]);
setViaMode(true);
console.log('Via mode start, dest:', systemName);
toast({ title: 'Via mode', description: `Destination ${systemName}. Click systems to add waypoints. Esc to commit.` });
} else {
await SetDestinationForAll(systemName, true, false);
toast({ title: 'Destination set', description: systemName });
if (wantVia) setViaMode(true);
} else {
await AddWaypointForAllByName(systemName, false);
toast({ title: 'Waypoint added', description: systemName });
}
} catch (e: any) {
console.error('Set destination failed:', e);
console.error('Failed to set destination:', e);
toast({ title: 'Failed to set destination', description: String(e), variant: 'destructive' });
}
};
@@ -459,20 +460,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
viewBox={`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`}
className="cursor-grab active:cursor-grabbing"
onMouseDown={handleMouseDown}
onMouseMove={(e) => {
if (isPanning) {
handleMouseMove(e);
} else {
handleSvgMouseMove(e);
}
}}
onMouseUp={(e) => {
if (isPanning) {
handleMouseUp();
} else {
handleSvgMouseUp(e);
}
}}
onMouseMove={(e) => { if (isPanning) { handleMouseMove(e); } else if (draggingNode) { handleSvgMouseMove(e); } }}
onMouseUp={(e) => { if (isPanning) { handleMouseUp(); } else if (draggingNode) { handleSvgMouseUp(e); } }}
onMouseLeave={handleMouseUp}
onWheel={handleWheel}
onDoubleClick={handleMapDoubleClick}
@@ -554,7 +543,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
{viaMode && (
<div className="absolute top-2 right-2 px-2 py-1 rounded bg-emerald-600 text-white text-xs shadow">
VIA mode (Esc to exit)
VIA mode: Dest {viaDest} ({viaQueue.length} waypoints). Esc to commit
</div>
)}
@@ -567,7 +556,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
onRename={(newName) => handleRenameSystem(contextMenu.system.solarSystemName, newName)}
onDelete={handleDeleteSystem}
onClearConnections={handleClearConnections}
onSetDestination={onSetDestination}
onSetDestination={(systemName) => onSetDestination(systemName, true)}
onClose={() => setContextMenu(null)}
/>
)}