diff --git a/public/Derelik.json b/public/Derelik.json new file mode 100644 index 0000000..93199f7 --- /dev/null +++ b/public/Derelik.json @@ -0,0 +1,33 @@ + +[ + { + "solarSystemName": "Hophib", + "x": "15", + "y": "105", + "security": 0.02914748942441414 + }, + { + "solarSystemName": "Ziriert", + "x": "150", + "y": "200", + "security": 0.4 + }, + { + "solarSystemName": "Yashunen", + "x": "300", + "y": "150", + "security": 0.8 + }, + { + "solarSystemName": "Danera", + "x": "200", + "y": "350", + "security": -0.3 + }, + { + "solarSystemName": "Shastal", + "x": "400", + "y": "300", + "security": -0.8 + } +] diff --git a/public/Devoid.json b/public/Devoid.json new file mode 100644 index 0000000..c99e24f --- /dev/null +++ b/public/Devoid.json @@ -0,0 +1,21 @@ + +[ + { + "solarSystemName": "Amarr", + "x": "100", + "y": "200", + "security": 1.0 + }, + { + "solarSystemName": "Sarum Prime", + "x": "250", + "y": "300", + "security": 0.9 + }, + { + "solarSystemName": "Niarja", + "x": "400", + "y": "250", + "security": 0.5 + } +] diff --git a/public/Domain.json b/public/Domain.json new file mode 100644 index 0000000..848e785 --- /dev/null +++ b/public/Domain.json @@ -0,0 +1,21 @@ + +[ + { + "solarSystemName": "Jita", + "x": "200", + "y": "150", + "security": 0.9 + }, + { + "solarSystemName": "Dodixie", + "x": "350", + "y": "280", + "security": 0.7 + }, + { + "solarSystemName": "Rens", + "x": "150", + "y": "400", + "security": 0.6 + } +] diff --git a/public/Genesis.json b/public/Genesis.json new file mode 100644 index 0000000..f7a0a1e --- /dev/null +++ b/public/Genesis.json @@ -0,0 +1,21 @@ + +[ + { + "solarSystemName": "Yulai", + "x": "180", + "y": "120", + "security": 1.0 + }, + { + "solarSystemName": "Crielere", + "x": "320", + "y": "200", + "security": 0.3 + }, + { + "solarSystemName": "Promised Land", + "x": "250", + "y": "350", + "security": -0.5 + } +] diff --git a/public/Kador.json b/public/Kador.json new file mode 100644 index 0000000..0959085 --- /dev/null +++ b/public/Kador.json @@ -0,0 +1,21 @@ + +[ + { + "solarSystemName": "Kador Prime", + "x": "200", + "y": "180", + "security": 0.8 + }, + { + "solarSystemName": "Zirsem", + "x": "350", + "y": "250", + "security": 0.2 + }, + { + "solarSystemName": "Thebeka", + "x": "150", + "y": "320", + "security": -0.2 + } +] diff --git a/public/universe.json b/public/universe.json new file mode 100644 index 0000000..5d2cf14 --- /dev/null +++ b/public/universe.json @@ -0,0 +1,28 @@ + +[ + { + "regionName": "Derelik", + "x": "413", + "y": "357" + }, + { + "regionName": "Devoid", + "x": "200", + "y": "250" + }, + { + "regionName": "Domain", + "x": "600", + "y": "300" + }, + { + "regionName": "Genesis", + "x": "350", + "y": "500" + }, + { + "regionName": "Kador", + "x": "500", + "y": "180" + } +] diff --git a/src/components/GalaxyMap.tsx b/src/components/GalaxyMap.tsx index c6deef2..4326db5 100644 --- a/src/components/GalaxyMap.tsx +++ b/src/components/GalaxyMap.tsx @@ -1,23 +1,55 @@ -import React, { useState, useRef, useCallback } from 'react'; +import React, { useState, useRef, useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { MapNode } from './MapNode'; -import { Connection } from './Connection'; -import { galaxyData } from '../data/galaxyData'; +import { useQuery } from '@tanstack/react-query'; + +interface Region { + regionName: string; + x: string; + y: string; +} interface Position { x: number; y: number; } +const fetchUniverseData = async (): Promise => { + const response = await fetch('/universe.json'); + if (!response.ok) { + throw new Error('Failed to fetch universe data'); + } + return response.json(); +}; + export const GalaxyMap = () => { const navigate = useNavigate(); const [draggedNode, setDraggedNode] = useState(null); - const [nodePositions, setNodePositions] = useState>(galaxyData.nodePositions); + const [nodePositions, setNodePositions] = useState>({}); const svgRef = useRef(null); - const handleNodeClick = (regionId: string) => { - navigate(`/regions/${regionId}`); + const { data: regions, isLoading, error } = useQuery({ + queryKey: ['universe'], + queryFn: fetchUniverseData, + }); + + // Initialize node positions when data is loaded + useEffect(() => { + if (regions) { + const positions: Record = {}; + regions.forEach(region => { + positions[region.regionName] = { + x: parseInt(region.x), + y: parseInt(region.y) + }; + }); + setNodePositions(positions); + } + }, [regions]); + + const handleNodeClick = (regionName: string) => { + navigate(`/regions/${regionName}`); }; const handleMouseDown = useCallback((nodeId: string) => { @@ -41,6 +73,22 @@ export const GalaxyMap = () => { setDraggedNode(null); }, []); + if (isLoading) { + return ( +
+
Loading universe data...
+
+ ); + } + + if (error) { + return ( +
+
Error loading universe data
+
+ ); + } + return (
@@ -70,25 +118,16 @@ export const GalaxyMap = () => { - {/* Render connections first (behind nodes) */} - {galaxyData.connections.map((connection, index) => ( - - ))} - {/* Render nodes */} - {galaxyData.regions.map((region) => ( + {regions?.map((region) => ( handleNodeClick(region.id)} - onMouseDown={() => handleMouseDown(region.id)} - isDragging={draggedNode === region.id} + key={region.regionName} + id={region.regionName} + name={region.regionName} + position={nodePositions[region.regionName] || { x: 0, y: 0 }} + onClick={() => handleNodeClick(region.regionName)} + onMouseDown={() => handleMouseDown(region.regionName)} + isDragging={draggedNode === region.regionName} type="region" /> ))} diff --git a/src/components/MapNode.tsx b/src/components/MapNode.tsx index 5b0284a..46d0c3b 100644 --- a/src/components/MapNode.tsx +++ b/src/components/MapNode.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; +import { getSecurityColor } from '../utils/securityColors'; interface MapNodeProps { id: string; @@ -9,6 +10,7 @@ interface MapNodeProps { onMouseDown: () => void; isDragging: boolean; type: 'region' | 'system'; + security?: number; } export const MapNode: React.FC = ({ @@ -18,16 +20,20 @@ export const MapNode: React.FC = ({ onClick, onMouseDown, isDragging, - type + type, + security }) => { const [isHovered, setIsHovered] = useState(false); const nodeSize = type === 'region' ? 12 : 8; const textOffset = type === 'region' ? 20 : 15; - const nodeColor = type === 'region' - ? (isHovered ? '#8b5cf6' : '#a855f7') - : (isHovered ? '#06b6d4' : '#0891b2'); + // Use security-based color for systems, default colors for regions + const nodeColor = type === 'system' && security !== undefined + ? getSecurityColor(security) + : type === 'region' + ? (isHovered ? '#8b5cf6' : '#a855f7') + : (isHovered ? '#06b6d4' : '#0891b2'); return ( ; - nodePositions: Record; - connections: Array<{ from: string; to: string }>; - }; + regionName: string; } -export const RegionMap: React.FC = ({ regionData }) => { +const fetchRegionData = async (regionName: string): Promise => { + const response = await fetch(`/${regionName}.json`); + if (!response.ok) { + throw new Error(`Failed to fetch ${regionName} data`); + } + return response.json(); +}; + +export const RegionMap: React.FC = ({ regionName }) => { const navigate = useNavigate(); const [draggedNode, setDraggedNode] = useState(null); - const [nodePositions, setNodePositions] = useState>(regionData.nodePositions); + const [nodePositions, setNodePositions] = useState>({}); const svgRef = useRef(null); - const handleSystemClick = (systemId: string) => { - navigate(`/systems/${systemId}`); + const { data: systems, isLoading, error } = useQuery({ + queryKey: ['region', regionName], + queryFn: () => fetchRegionData(regionName), + }); + + // Initialize node positions when data is loaded + useEffect(() => { + if (systems) { + const positions: Record = {}; + systems.forEach(system => { + positions[system.solarSystemName] = { + x: parseInt(system.x), + y: parseInt(system.y) + }; + }); + setNodePositions(positions); + } + }, [systems]); + + const handleSystemClick = (systemName: string) => { + navigate(`/systems/${systemName}`); }; const handleMouseDown = useCallback((nodeId: string) => { @@ -51,6 +80,22 @@ export const RegionMap: React.FC = ({ regionData }) => { setDraggedNode(null); }, []); + if (isLoading) { + return ( +
+
Loading {regionName} data...
+
+ ); + } + + if (error) { + return ( +
+
Error loading {regionName} data
+
+ ); + } + return (
@@ -66,7 +111,7 @@ export const RegionMap: React.FC = ({ regionData }) => { Back to Galaxy
-

{regionData.name} Region

+

{regionName} Region

Explore the systems within this region

@@ -92,26 +137,18 @@ export const RegionMap: React.FC = ({ regionData }) => { - {/* Render connections first (behind nodes) */} - {regionData.connections.map((connection, index) => ( - - ))} - {/* Render systems */} - {regionData.systems.map((system) => ( + {systems?.map((system) => ( handleSystemClick(system.id)} - onMouseDown={() => handleMouseDown(system.id)} - isDragging={draggedNode === system.id} + key={system.solarSystemName} + id={system.solarSystemName} + name={system.solarSystemName} + position={nodePositions[system.solarSystemName] || { x: 0, y: 0 }} + onClick={() => handleSystemClick(system.solarSystemName)} + onMouseDown={() => handleMouseDown(system.solarSystemName)} + isDragging={draggedNode === system.solarSystemName} type="system" + security={system.security} /> ))} diff --git a/src/pages/RegionPage.tsx b/src/pages/RegionPage.tsx index 3bc6905..187159f 100644 --- a/src/pages/RegionPage.tsx +++ b/src/pages/RegionPage.tsx @@ -1,7 +1,6 @@ import { useParams } from 'react-router-dom'; import { RegionMap } from '../components/RegionMap'; -import { regionData } from '../data/galaxyData'; import { Button } from '@/components/ui/button'; import { ArrowLeft } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; @@ -10,7 +9,7 @@ const RegionPage = () => { const { region } = useParams<{ region: string }>(); const navigate = useNavigate(); - if (!region || !regionData[region]) { + if (!region) { return (
@@ -28,7 +27,7 @@ const RegionPage = () => { ); } - return ; + return ; }; export default RegionPage; diff --git a/src/utils/securityColors.ts b/src/utils/securityColors.ts new file mode 100644 index 0000000..c748e91 --- /dev/null +++ b/src/utils/securityColors.ts @@ -0,0 +1,24 @@ + +export const getSecurityColor = (security: number): string => { + if (security > 0.5) { + // Green to blue for high security (0.5, 1] + const ratio = (security - 0.5) / 0.5; + const green = Math.round(34 + (16 - 34) * ratio); // From #22c55e to #1065d4 + const blue = Math.round(197 + (212 - 197) * ratio); + return `rgb(${Math.round(34 * (1 - ratio) + 16 * ratio)}, ${green}, ${blue})`; + } else if (security >= 0) { + // Red to yellow for low security [0.0, 0.5] + const ratio = security / 0.5; + const red = Math.round(239 + (234 - 239) * ratio); // From #ef4444 to #eab308 + const green = Math.round(68 + (179 - 68) * ratio); + const blue = Math.round(68 + (8 - 68) * ratio); + return `rgb(${red}, ${green}, ${blue})`; + } else { + // Purple to red for null security [-1, 0) + const ratio = (security + 1) / 1; + const red = Math.round(147 + (239 - 147) * ratio); // From #9333ea to #ef4444 + const green = Math.round(51 + (68 - 51) * ratio); + const blue = Math.round(234 + (68 - 234) * ratio); + return `rgb(${red}, ${green}, ${blue})`; + } +};