Load data from JSON files

Load region and system data from JSON files, and use the security parameter to color the systems.
This commit is contained in:
gpt-engineer-app[bot]
2025-06-13 23:43:25 +00:00
parent 1fdf39d60a
commit 0264ac2f00
11 changed files with 310 additions and 60 deletions

33
public/Derelik.json Normal file
View File

@@ -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
}
]

21
public/Devoid.json Normal file
View File

@@ -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
}
]

21
public/Domain.json Normal file
View File

@@ -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
}
]

21
public/Genesis.json Normal file
View File

@@ -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
}
]

21
public/Kador.json Normal file
View File

@@ -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
}
]

28
public/universe.json Normal file
View File

@@ -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"
}
]

View File

@@ -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<Region[]> => {
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<string | null>(null);
const [nodePositions, setNodePositions] = useState<Record<string, Position>>(galaxyData.nodePositions);
const [nodePositions, setNodePositions] = useState<Record<string, Position>>({});
const svgRef = useRef<SVGSVGElement>(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<string, Position> = {};
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 (
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex items-center justify-center">
<div className="text-white text-xl">Loading universe data...</div>
</div>
);
}
if (error) {
return (
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex items-center justify-center">
<div className="text-red-400 text-xl">Error loading universe data</div>
</div>
);
}
return (
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 overflow-hidden relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-purple-900/20 via-slate-900/40 to-black"></div>
@@ -70,25 +118,16 @@ export const GalaxyMap = () => {
</filter>
</defs>
{/* Render connections first (behind nodes) */}
{galaxyData.connections.map((connection, index) => (
<Connection
key={index}
from={nodePositions[connection.from]}
to={nodePositions[connection.to]}
/>
))}
{/* Render nodes */}
{galaxyData.regions.map((region) => (
{regions?.map((region) => (
<MapNode
key={region.id}
id={region.id}
name={region.name}
position={nodePositions[region.id]}
onClick={() => 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"
/>
))}

View File

@@ -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<MapNodeProps> = ({
@@ -18,16 +20,20 @@ export const MapNode: React.FC<MapNodeProps> = ({
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 (
<g

View File

@@ -1,10 +1,17 @@
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 { Button } from '@/components/ui/button';
import { ArrowLeft } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';
interface System {
solarSystemName: string;
x: string;
y: string;
security: number;
}
interface Position {
x: number;
@@ -12,22 +19,44 @@ interface Position {
}
interface RegionMapProps {
regionData: {
name: string;
systems: Array<{ id: string; name: string }>;
nodePositions: Record<string, Position>;
connections: Array<{ from: string; to: string }>;
};
regionName: string;
}
export const RegionMap: React.FC<RegionMapProps> = ({ regionData }) => {
const fetchRegionData = async (regionName: string): Promise<System[]> => {
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<RegionMapProps> = ({ regionName }) => {
const navigate = useNavigate();
const [draggedNode, setDraggedNode] = useState<string | null>(null);
const [nodePositions, setNodePositions] = useState<Record<string, Position>>(regionData.nodePositions);
const [nodePositions, setNodePositions] = useState<Record<string, Position>>({});
const svgRef = useRef<SVGSVGElement>(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<string, Position> = {};
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<RegionMapProps> = ({ regionData }) => {
setDraggedNode(null);
}, []);
if (isLoading) {
return (
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-cyan-900 to-slate-900 flex items-center justify-center">
<div className="text-white text-xl">Loading {regionName} data...</div>
</div>
);
}
if (error) {
return (
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-cyan-900 to-slate-900 flex items-center justify-center">
<div className="text-red-400 text-xl">Error loading {regionName} data</div>
</div>
);
}
return (
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-cyan-900 to-slate-900 overflow-hidden relative">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-cyan-900/20 via-slate-900/40 to-black"></div>
@@ -66,7 +111,7 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionData }) => {
Back to Galaxy
</Button>
<div>
<h1 className="text-4xl font-bold text-white">{regionData.name} Region</h1>
<h1 className="text-4xl font-bold text-white">{regionName} Region</h1>
<p className="text-cyan-200">Explore the systems within this region</p>
</div>
</div>
@@ -92,26 +137,18 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionData }) => {
</filter>
</defs>
{/* Render connections first (behind nodes) */}
{regionData.connections.map((connection, index) => (
<Connection
key={index}
from={nodePositions[connection.from]}
to={nodePositions[connection.to]}
/>
))}
{/* Render systems */}
{regionData.systems.map((system) => (
{systems?.map((system) => (
<MapNode
key={system.id}
id={system.id}
name={system.name}
position={nodePositions[system.id]}
onClick={() => 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}
/>
))}
</svg>

View File

@@ -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 (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex items-center justify-center">
<div className="text-center">
@@ -28,7 +27,7 @@ const RegionPage = () => {
);
}
return <RegionMap regionData={regionData[region]} />;
return <RegionMap regionName={region} />;
};
export default RegionPage;

View File

@@ -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})`;
}
};