Compare commits
7 Commits
f7879c7ea8
...
dad6d79740
Author | SHA1 | Date | |
---|---|---|---|
dad6d79740 | |||
3b20e07b17 | |||
3a4e30d372 | |||
b0ad48985a | |||
c55b3bd882 | |||
c5f7fd483e | |||
c21f82667a |
@@ -12,6 +12,20 @@ import { ListCharacters, StartESILogin, SetDestinationForAll, PostRouteForAllByN
|
|||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { getSystemsRegions } from '@/utils/systemApi';
|
import { getSystemsRegions } from '@/utils/systemApi';
|
||||||
|
|
||||||
|
// Interaction/indicator constants
|
||||||
|
const SELECT_HOLD_MS = 300;
|
||||||
|
const PAN_THRESHOLD_PX = 6;
|
||||||
|
const DRAG_SNAP_DISTANCE = 20;
|
||||||
|
const VIA_WAYPOINT_RING_RADIUS = 14;
|
||||||
|
const VIA_WAYPOINT_RING_COLOR = '#10b981';
|
||||||
|
const INDICATED_RING_RADIUS = 20;
|
||||||
|
const INDICATED_RING_COLOR = '#f59e0b';
|
||||||
|
const INDICATED_RING_ANIM_VALUES = '18;22;18';
|
||||||
|
const INDICATED_RING_ANIM_DUR = '1.2s';
|
||||||
|
const SHIFT_SELECT_STROKE_COLOR = '#60a5fa';
|
||||||
|
const SHIFT_SELECT_FILL_COLOR = 'rgba(96,165,250,0.12)';
|
||||||
|
const SHIFT_SELECT_STROKE_WIDTH = 2;
|
||||||
|
|
||||||
interface RegionMapProps {
|
interface RegionMapProps {
|
||||||
regionName: string;
|
regionName: string;
|
||||||
focusSystem?: string;
|
focusSystem?: string;
|
||||||
@@ -100,6 +114,22 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
|
const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
|
||||||
const [focusUntil, setFocusUntil] = useState<number | null>(null);
|
const [focusUntil, setFocusUntil] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// New: selection/aim state for left-click aimbot behavior
|
||||||
|
const [isSelecting, setIsSelecting] = useState(false);
|
||||||
|
const [indicatedSystem, setIndicatedSystem] = useState<string | null>(null);
|
||||||
|
const selectTimerRef = useRef<number | null>(null);
|
||||||
|
const downClientPointRef = useRef<{ x: number; y: number } | null>(null);
|
||||||
|
const mouseButtonRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
// New: shift-drag circle selection state (VIA mode)
|
||||||
|
const [shiftSelecting, setShiftSelecting] = useState(false);
|
||||||
|
const [shiftCenter, setShiftCenter] = useState<Position | null>(null);
|
||||||
|
const [shiftRadius, setShiftRadius] = useState<number>(0);
|
||||||
|
|
||||||
|
// Interaction state machine (lightweight)
|
||||||
|
type InteractionMode = 'idle' | 'holding' | 'panning' | 'selecting' | 'shiftSelecting';
|
||||||
|
const [mode, setMode] = useState<InteractionMode>('idle');
|
||||||
|
|
||||||
// When focusSystem changes, set an expiry 20s in the future
|
// When focusSystem changes, set an expiry 20s in the future
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (focusSystem) {
|
if (focusSystem) {
|
||||||
@@ -296,12 +326,17 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
const handleSystemClick = async (systemName: string) => {
|
const handleSystemClick = async (systemName: string) => {
|
||||||
if (viaMode) {
|
if (viaMode) {
|
||||||
setViaQueue(prev => {
|
setViaQueue(prev => {
|
||||||
if (prev.includes(systemName)) return prev;
|
// toggle behavior: add if missing, remove if present
|
||||||
|
if (prev.includes(systemName)) {
|
||||||
|
const next = prev.filter(n => n !== systemName);
|
||||||
|
toast({ title: 'Waypoint removed', description: systemName });
|
||||||
|
return next;
|
||||||
|
}
|
||||||
const next = [...prev, systemName];
|
const next = [...prev, systemName];
|
||||||
|
toast({ title: 'Waypoint queued', description: systemName });
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
console.log('Queued waypoint:', systemName);
|
console.log('VIA waypoint toggle:', systemName);
|
||||||
toast({ title: 'Waypoint queued', description: systemName });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (focusSystem === systemName) return;
|
if (focusSystem === systemName) return;
|
||||||
@@ -381,7 +416,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
const dx = system.x - x;
|
const dx = system.x - x;
|
||||||
const dy = system.y - y;
|
const dy = system.y - y;
|
||||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
if (distance < 20) {
|
if (distance < DRAG_SNAP_DISTANCE) {
|
||||||
targetSystem = system;
|
targetSystem = system;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -437,24 +472,265 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
setTempConnection(null);
|
setTempConnection(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper: convert client to SVG coords
|
||||||
|
const clientToSvg = (clientX: number, clientY: number) => {
|
||||||
|
if (!svgRef.current) return { x: 0, y: 0 };
|
||||||
|
const pt = svgRef.current.createSVGPoint();
|
||||||
|
pt.x = clientX;
|
||||||
|
pt.y = clientY;
|
||||||
|
const svgPoint = pt.matrixTransform(svgRef.current.getScreenCTM()!.inverse());
|
||||||
|
return { x: svgPoint.x, y: svgPoint.y };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper: find nearest system name to SVG point
|
||||||
|
const findNearestSystem = (svgX: number, svgY: number): string | null => {
|
||||||
|
if (systems.size === 0) return null;
|
||||||
|
let nearestName: string | null = null;
|
||||||
|
let nearestDist2 = Number.POSITIVE_INFINITY;
|
||||||
|
systems.forEach((_sys, name) => {
|
||||||
|
const pos = positions[name];
|
||||||
|
if (!pos) return;
|
||||||
|
const dx = pos.x - svgX;
|
||||||
|
const dy = pos.y - svgY;
|
||||||
|
const d2 = dx * dx + dy * dy;
|
||||||
|
if (d2 < nearestDist2) {
|
||||||
|
nearestDist2 = d2;
|
||||||
|
nearestName = name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return nearestName;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Commit shift selection: toggle all systems within radius
|
||||||
|
const commitShiftSelection = useCallback(() => {
|
||||||
|
if (!shiftCenter || shiftRadius <= 0) return;
|
||||||
|
const within: string[] = [];
|
||||||
|
Object.keys(positions).forEach(name => {
|
||||||
|
const pos = positions[name];
|
||||||
|
if (!pos) return;
|
||||||
|
const dx = pos.x - shiftCenter.x;
|
||||||
|
const dy = pos.y - shiftCenter.y;
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
if (dist <= shiftRadius) within.push(name);
|
||||||
|
});
|
||||||
|
if (within.length === 0) return;
|
||||||
|
setViaQueue(prev => {
|
||||||
|
const prevSet = new Set(prev);
|
||||||
|
const toToggle = new Set(within);
|
||||||
|
// remove toggled ones that were present
|
||||||
|
const kept = prev.filter(n => !toToggle.has(n));
|
||||||
|
// add new ones (those within but not previously present), preserve within order
|
||||||
|
const additions = within.filter(n => !prevSet.has(n));
|
||||||
|
const next = kept.concat(additions);
|
||||||
|
toast({ title: 'VIA toggled', description: `${within.length} systems` });
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, [positions, shiftCenter, shiftRadius]);
|
||||||
|
|
||||||
|
const clearSelectTimer = () => {
|
||||||
|
if (selectTimerRef.current !== null) {
|
||||||
|
window.clearTimeout(selectTimerRef.current);
|
||||||
|
selectTimerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// const PAN_THRESHOLD_PX = 6; // movement before starting pan
|
||||||
|
|
||||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
if (!svgRef.current) return;
|
if (!svgRef.current) return;
|
||||||
setIsPanning(true);
|
|
||||||
|
// If context menu is open, left-click closes it and no selection should happen
|
||||||
|
if (contextMenu) {
|
||||||
|
if (e.button === 0) setContextMenu(null);
|
||||||
|
clearSelectTimer();
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIsPanning(false);
|
||||||
|
setShiftSelecting(false);
|
||||||
|
setMode('idle');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseButtonRef.current = e.button;
|
||||||
|
|
||||||
|
// SHIFT + VIA mode: start circle selection (left button only)
|
||||||
|
if (viaMode && e.shiftKey && e.button === 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const svgPt = clientToSvg(e.clientX, e.clientY);
|
||||||
|
setShiftSelecting(true);
|
||||||
|
setShiftCenter(svgPt);
|
||||||
|
setShiftRadius(0);
|
||||||
|
setMode('shiftSelecting');
|
||||||
|
// cancel any hold-to-select/pan intents
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIsPanning(false);
|
||||||
|
clearSelectTimer();
|
||||||
|
downClientPointRef.current = { x: e.clientX, y: e.clientY };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only left button initiates selection/panning
|
||||||
|
if (e.button !== 0) {
|
||||||
|
clearSelectTimer();
|
||||||
|
setIsSelecting(false);
|
||||||
|
setMode('idle');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// record down point (client) and seed pan origin
|
||||||
const rect = svgRef.current.getBoundingClientRect();
|
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 });
|
||||||
}, []);
|
downClientPointRef.current = { x: e.clientX, y: e.clientY };
|
||||||
|
|
||||||
|
// initial indicate nearest system under cursor
|
||||||
|
const svgPt = clientToSvg(e.clientX, e.clientY);
|
||||||
|
const near = findNearestSystem(svgPt.x, svgPt.y);
|
||||||
|
setIndicatedSystem(near);
|
||||||
|
|
||||||
|
// start delayed select mode timer
|
||||||
|
setIsSelecting(false);
|
||||||
|
setMode('holding');
|
||||||
|
clearSelectTimer();
|
||||||
|
selectTimerRef.current = window.setTimeout(() => {
|
||||||
|
setIsSelecting(true);
|
||||||
|
setMode('selecting');
|
||||||
|
}, SELECT_HOLD_MS);
|
||||||
|
}, [positions, systems, viaMode, contextMenu]);
|
||||||
|
|
||||||
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
||||||
if (!isPanning || !svgRef.current) return;
|
// if dragging node, delegate
|
||||||
|
if (draggingNode) { handleSvgMouseMove(e); return; }
|
||||||
|
|
||||||
|
if (!svgRef.current) return;
|
||||||
|
|
||||||
|
// Shift selection radius update
|
||||||
|
if (shiftSelecting && shiftCenter) {
|
||||||
|
const svgPt = clientToSvg(e.clientX, e.clientY);
|
||||||
|
const dx = svgPt.x - shiftCenter.x;
|
||||||
|
const dy = svgPt.y - shiftCenter.y;
|
||||||
|
setShiftRadius(Math.sqrt(dx * dx + dy * dy));
|
||||||
|
setMode('shiftSelecting');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rect = svgRef.current.getBoundingClientRect();
|
const rect = svgRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (isPanning) {
|
||||||
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 deltaX = (lastPanPoint.x - currentPoint.x) * (viewBox.width / rect.width);
|
||||||
const deltaY = (lastPanPoint.y - currentPoint.y) * (viewBox.height / rect.height);
|
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);
|
setLastPanPoint(currentPoint);
|
||||||
}, [isPanning, lastPanPoint, viewBox.width, viewBox.height]);
|
setMode('panning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleMouseUp = useCallback(() => { setIsPanning(false); }, []);
|
// determine if we should start panning (from holding)
|
||||||
|
const down = downClientPointRef.current;
|
||||||
|
if (down && !isSelecting) {
|
||||||
|
const dx = e.clientX - down.x;
|
||||||
|
const dy = e.clientY - down.y;
|
||||||
|
const dist2 = dx * dx + dy * dy;
|
||||||
|
if (dist2 > PAN_THRESHOLD_PX * PAN_THRESHOLD_PX) {
|
||||||
|
// user intends to pan; cancel selection
|
||||||
|
clearSelectTimer();
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIndicatedSystem(null);
|
||||||
|
setIsPanning(true);
|
||||||
|
setMode('panning');
|
||||||
|
// seed pan origin with current
|
||||||
|
const currentPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
||||||
|
setLastPanPoint(currentPoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// selection mode: update indicated nearest system as cursor moves
|
||||||
|
if (isSelecting) {
|
||||||
|
const svgPt = clientToSvg(e.clientX, e.clientY);
|
||||||
|
const near = findNearestSystem(svgPt.x, svgPt.y);
|
||||||
|
setIndicatedSystem(near);
|
||||||
|
setMode('selecting');
|
||||||
|
}
|
||||||
|
}, [draggingNode, isPanning, lastPanPoint, viewBox.width, viewBox.height, isSelecting, positions, systems, shiftSelecting, shiftCenter]);
|
||||||
|
|
||||||
|
const handleMouseUp = useCallback((e?: React.MouseEvent) => {
|
||||||
|
// if dragging node, delegate
|
||||||
|
if (draggingNode) { if (e) handleSvgMouseUp(e); return; }
|
||||||
|
|
||||||
|
// If context menu open, left click should just close it; do not select
|
||||||
|
if (contextMenu && mouseButtonRef.current === 0) {
|
||||||
|
setContextMenu(null);
|
||||||
|
clearSelectTimer();
|
||||||
|
setIsPanning(false);
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIndicatedSystem(null);
|
||||||
|
setShiftSelecting(false);
|
||||||
|
setShiftCenter(null);
|
||||||
|
setShiftRadius(0);
|
||||||
|
downClientPointRef.current = null;
|
||||||
|
mouseButtonRef.current = null;
|
||||||
|
setMode('idle');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit shift selection if active (only if left button initiated)
|
||||||
|
if (shiftSelecting) {
|
||||||
|
if (mouseButtonRef.current === 0) {
|
||||||
|
commitShiftSelection();
|
||||||
|
}
|
||||||
|
setShiftSelecting(false);
|
||||||
|
setShiftCenter(null);
|
||||||
|
setShiftRadius(0);
|
||||||
|
clearSelectTimer();
|
||||||
|
setIsPanning(false);
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIndicatedSystem(null);
|
||||||
|
downClientPointRef.current = null;
|
||||||
|
mouseButtonRef.current = null;
|
||||||
|
setMode('idle');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore non-left button for selection commit
|
||||||
|
if (mouseButtonRef.current !== 0) {
|
||||||
|
clearSelectTimer();
|
||||||
|
setIsPanning(false);
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIndicatedSystem(null);
|
||||||
|
downClientPointRef.current = null;
|
||||||
|
mouseButtonRef.current = null;
|
||||||
|
setMode('idle');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelectTimer();
|
||||||
|
|
||||||
|
if (isPanning) {
|
||||||
|
setIsPanning(false);
|
||||||
|
mouseButtonRef.current = null;
|
||||||
|
setMode('idle');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit selection if any
|
||||||
|
let target = indicatedSystem;
|
||||||
|
if (!target && e && svgRef.current) {
|
||||||
|
const svgPt = clientToSvg(e.clientX, e.clientY);
|
||||||
|
target = findNearestSystem(svgPt.x, svgPt.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
handleSystemClick(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset selection state
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIndicatedSystem(null);
|
||||||
|
downClientPointRef.current = null;
|
||||||
|
mouseButtonRef.current = null;
|
||||||
|
setMode('idle');
|
||||||
|
}, [draggingNode, isPanning, indicatedSystem, positions, systems, shiftSelecting, commitShiftSelection, contextMenu]);
|
||||||
|
|
||||||
const handleWheel = useCallback((e: React.WheelEvent) => {
|
const handleWheel = useCallback((e: React.WheelEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -622,6 +898,27 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
return () => document.removeEventListener('click', handleClickOutside);
|
return () => document.removeEventListener('click', handleClickOutside);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onWindowMouseUp = () => {
|
||||||
|
// if shift selection ongoing, commit on global mouseup as well
|
||||||
|
if (shiftSelecting && mouseButtonRef.current === 0) {
|
||||||
|
commitShiftSelection();
|
||||||
|
}
|
||||||
|
clearSelectTimer();
|
||||||
|
setIsPanning(false);
|
||||||
|
setIsSelecting(false);
|
||||||
|
setIndicatedSystem(null);
|
||||||
|
setShiftSelecting(false);
|
||||||
|
setShiftCenter(null);
|
||||||
|
setShiftRadius(0);
|
||||||
|
downClientPointRef.current = null;
|
||||||
|
mouseButtonRef.current = null;
|
||||||
|
setMode('idle');
|
||||||
|
};
|
||||||
|
window.addEventListener('mouseup', onWindowMouseUp);
|
||||||
|
return () => window.removeEventListener('mouseup', onWindowMouseUp);
|
||||||
|
}, [shiftSelecting, commitShiftSelection]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center">
|
<div className="h-full w-full bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center">
|
||||||
@@ -655,10 +952,10 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
viewBox={`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`}
|
viewBox={`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`}
|
||||||
className="cursor-grab active:cursor-grabbing"
|
className={`${(mode === 'selecting' || mode === 'shiftSelecting') ? 'cursor-crosshair' : (mode === 'panning' ? 'cursor-grabbing' : 'cursor-grab')}`}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseMove={(e) => { if (isPanning) { handleMouseMove(e); } else if (draggingNode) { handleSvgMouseMove(e); } }}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseUp={(e) => { if (isPanning) { handleMouseUp(); } else if (draggingNode) { handleSvgMouseUp(e); } }}
|
onMouseUp={(e) => handleMouseUp(e)}
|
||||||
onMouseLeave={handleMouseUp}
|
onMouseLeave={handleMouseUp}
|
||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
onDoubleClick={handleMapDoubleClick}
|
onDoubleClick={handleMapDoubleClick}
|
||||||
@@ -696,6 +993,20 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Shift selection circle (VIA mode) */}
|
||||||
|
{shiftSelecting && shiftCenter && (
|
||||||
|
<g style={{ pointerEvents: 'none' }}>
|
||||||
|
<circle
|
||||||
|
cx={shiftCenter.x}
|
||||||
|
cy={shiftCenter.y}
|
||||||
|
r={Math.max(shiftRadius, 0)}
|
||||||
|
fill={SHIFT_SELECT_FILL_COLOR}
|
||||||
|
stroke={SHIFT_SELECT_STROKE_COLOR}
|
||||||
|
strokeWidth={SHIFT_SELECT_STROKE_WIDTH}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Render existing systems */}
|
{/* Render existing systems */}
|
||||||
{Array.from(systems.entries()).map(([key, system]) => (
|
{Array.from(systems.entries()).map(([key, system]) => (
|
||||||
<MapNode
|
<MapNode
|
||||||
@@ -703,7 +1014,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
id={system.solarSystemName}
|
id={system.solarSystemName}
|
||||||
name={system.solarSystemName}
|
name={system.solarSystemName}
|
||||||
position={positions[system.solarSystemName] || { x: 0, y: 0 }}
|
position={positions[system.solarSystemName] || { x: 0, y: 0 }}
|
||||||
onClick={() => handleSystemClick(system.solarSystemName)}
|
onClick={() => { /* handled at svg-level aimbot commit */ }}
|
||||||
onDoubleClick={(e) => handleSystemDoubleClick(e, positions[system.solarSystemName])}
|
onDoubleClick={(e) => handleSystemDoubleClick(e, positions[system.solarSystemName])}
|
||||||
onDragStart={(e) => handleNodeDragStart(e, system.solarSystemName)}
|
onDragStart={(e) => handleNodeDragStart(e, system.solarSystemName)}
|
||||||
onDrag={handleSvgMouseMove}
|
onDrag={handleSvgMouseMove}
|
||||||
@@ -717,6 +1028,42 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* VIA waypoints indicator rings */}
|
||||||
|
{viaMode && viaQueue.map((name) => (
|
||||||
|
positions[name] ? (
|
||||||
|
<g key={`via-${name}`} style={{ pointerEvents: 'none' }}>
|
||||||
|
<circle
|
||||||
|
cx={positions[name].x}
|
||||||
|
cy={positions[name].y}
|
||||||
|
r={VIA_WAYPOINT_RING_RADIUS}
|
||||||
|
fill="none"
|
||||||
|
stroke={VIA_WAYPOINT_RING_COLOR}
|
||||||
|
strokeWidth="3"
|
||||||
|
opacity="0.9"
|
||||||
|
filter="url(#glow)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
) : null
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Indicated (aim) system ring */}
|
||||||
|
{indicatedSystem && positions[indicatedSystem] && (
|
||||||
|
<g style={{ pointerEvents: 'none' }}>
|
||||||
|
<circle
|
||||||
|
cx={positions[indicatedSystem].x}
|
||||||
|
cy={positions[indicatedSystem].y}
|
||||||
|
r={INDICATED_RING_RADIUS}
|
||||||
|
fill="none"
|
||||||
|
stroke={INDICATED_RING_COLOR}
|
||||||
|
strokeWidth="3"
|
||||||
|
opacity="0.9"
|
||||||
|
filter="url(#glow)"
|
||||||
|
>
|
||||||
|
<animate attributeName="r" values={INDICATED_RING_ANIM_VALUES} dur={INDICATED_RING_ANIM_DUR} repeatCount="indefinite" />
|
||||||
|
</circle>
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Character location markers */}
|
{/* Character location markers */}
|
||||||
{charLocs.map((c, idx) => {
|
{charLocs.map((c, idx) => {
|
||||||
const pos = positions[c.solar_system_name];
|
const pos = positions[c.solar_system_name];
|
||||||
|
Reference in New Issue
Block a user