import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { MapNode } from './MapNode'; import { Connection } from './Connection'; import { useQuery } from '@tanstack/react-query'; import { getSecurityColor } from '../utils/securityColors'; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; const pocketbaseUrl = `https://evebase.site.quack-lab.dev/api/collections/regionview/records`; interface SolarSystem { solarSystemName: string; x: number; y: number; security: number; signatures: number; connectedSystems: string[]; } interface Position { x: number; y: number; } interface ProcessedConnection { key: string; from: Position; to: Position; color: string; } interface RegionMapProps { regionName: string; focusSystem?: string; isCompact?: boolean; } const fetchRegionData = async (regionName: string): Promise => { const response = await fetch(`/${regionName}.json`); if (!response.ok) { throw new Error('Failed to fetch region data'); } const systems = await response.json(); const regionSignatures = await fetch(`${pocketbaseUrl}?filter=(sysregion%3D'${regionName}')&perPage=1000`); const regionSignaturesJson = await regionSignatures.json(); console.log(regionSignaturesJson); if (regionSignaturesJson.items.length > 0) { for (const systemSigs of regionSignaturesJson.items) { const system = systems.find(s => s.solarSystemName === systemSigs.sysname); if (system) { system.signatures = systemSigs.sigcount; } } } return systems; }; const RegionMap = ({ regionName, focusSystem, isCompact = false }: RegionMapProps) => { const navigate = useNavigate(); const [viewBox, setViewBox] = useState({ x: 0, y: 0, width: 1200, height: 800 }); const [isPanning, setIsPanning] = useState(false); const [lastPanPoint, setLastPanPoint] = useState({ x: 0, y: 0 }); const [nodePositions, setNodePositions] = useState>({}); const svgRef = useRef(null); const { data: systems, isLoading, error } = useQuery({ queryKey: ['region', regionName], queryFn: () => fetchRegionData(regionName), }); // Process connections once when systems or nodePositions change const processedConnections = useMemo(() => { if (!systems || !nodePositions) return []; const connections = new Map(); systems.forEach(system => { system.connectedSystems?.forEach(connectedSystem => { const connectionKey = [system.solarSystemName, connectedSystem].sort().join('-'); if (connections.has(connectionKey)) return; const fromPos = nodePositions[system.solarSystemName]; const toPos = nodePositions[connectedSystem]; if (!fromPos || !toPos) return; const toSystem = systems.find(s => s.solarSystemName === connectedSystem); if (!toSystem) return; const avgSecurity = (system.security + toSystem.security) / 2; const connectionColor = getSecurityColor(avgSecurity); connections.set(connectionKey, { key: connectionKey, from: fromPos, to: toPos, color: connectionColor }); }); }); return Array.from(connections.values()); }, [systems, nodePositions]); // Initialize node positions and focus on system if specified useEffect(() => { if (systems) { const positions: Record = {}; systems.forEach(system => { positions[system.solarSystemName] = { x: system.x, y: system.y }; }); setNodePositions(positions); // If focusSystem is specified and we're in compact mode, center on it if (focusSystem && isCompact) { const focusSystemData = systems.find(s => s.solarSystemName === focusSystem); if (focusSystemData) { setViewBox({ x: focusSystemData.x - 200, y: focusSystemData.y - 150, width: 400, height: 300 }); } } } }, [systems, focusSystem, isCompact]); const handleSystemClick = (systemName: string) => { if (focusSystem === systemName) return; navigate(`/regions/${regionName}/${systemName}`); }; const handleMouseDown = useCallback((e: React.MouseEvent) => { if (!svgRef.current) return; setIsPanning(true); const rect = svgRef.current.getBoundingClientRect(); 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 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 })); setLastPanPoint(currentPoint); }, [isPanning, lastPanPoint, viewBox.width, viewBox.height]); const handleMouseUp = useCallback(() => { setIsPanning(false); }, []); const handleWheel = useCallback((e: React.WheelEvent) => { e.preventDefault(); if (!svgRef.current) return; const rect = svgRef.current.getBoundingClientRect(); const mouseX = e.clientX - rect.left; const mouseY = e.clientY - rect.top; const scale = e.deltaY < 0 ? 1.1 : 0.9; const newWidth = viewBox.width * scale; const newHeight = viewBox.height * scale; const mouseXInSVG = viewBox.x + (mouseX / rect.width) * viewBox.width; const mouseYInSVG = viewBox.y + (mouseY / rect.height) * viewBox.height; const newX = mouseXInSVG - (mouseX / rect.width) * newWidth; const newY = mouseYInSVG - (mouseY / rect.height) * newHeight; setViewBox({ x: newX, y: newY, width: newWidth, height: newHeight }); }, [viewBox]); if (isLoading) { return (
Loading {regionName} data...
); } if (error) { return (

Error Loading Region

Failed to load data for {regionName}

); } // Compact mode (for system page) if (isCompact) { return (
{/* Render connections */} {processedConnections.map(connection => ( ))} {/* Render systems */} {systems?.map((system) => ( handleSystemClick(system.solarSystemName)} type="system" security={system.security} signatures={system.signatures} /> ))} {/* Highlight focused system */} {focusSystem && nodePositions[focusSystem] && ( )}
); } // Full page mode (original region page) return (
{/* Breadcrumb Navigation */}
navigate("/")} className="text-purple-200 hover:text-white cursor-pointer" > Universe {regionName}

{regionName}

Solar systems in this region

{/* Render connections */} {processedConnections.map(connection => ( ))} {/* Render systems */} {systems?.map((system) => ( handleSystemClick(system.solarSystemName)} type="system" security={system.security} signatures={system.signatures} /> ))}
); }; export default RegionMap;