feat: Implement interactive map views
Create interactive map views using SVG for nodes and connections. Implement navigation to region and system pages on click.
This commit is contained in:
122
src/components/RegionMap.tsx
Normal file
122
src/components/RegionMap.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
|
||||
import React, { useState, useRef, useCallback } 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';
|
||||
|
||||
interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface RegionMapProps {
|
||||
regionData: {
|
||||
name: string;
|
||||
systems: Array<{ id: string; name: string }>;
|
||||
nodePositions: Record<string, Position>;
|
||||
connections: Array<{ from: string; to: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
export const RegionMap: React.FC<RegionMapProps> = ({ regionData }) => {
|
||||
const navigate = useNavigate();
|
||||
const [draggedNode, setDraggedNode] = useState<string | null>(null);
|
||||
const [nodePositions, setNodePositions] = useState<Record<string, Position>>(regionData.nodePositions);
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
const handleSystemClick = (systemId: string) => {
|
||||
navigate(`/systems/${systemId}`);
|
||||
};
|
||||
|
||||
const handleMouseDown = useCallback((nodeId: string) => {
|
||||
setDraggedNode(nodeId);
|
||||
}, []);
|
||||
|
||||
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
||||
if (!draggedNode || !svgRef.current) return;
|
||||
|
||||
const rect = svgRef.current.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
setNodePositions(prev => ({
|
||||
...prev,
|
||||
[draggedNode]: { x, y }
|
||||
}));
|
||||
}, [draggedNode]);
|
||||
|
||||
const handleMouseUp = useCallback(() => {
|
||||
setDraggedNode(null);
|
||||
}, []);
|
||||
|
||||
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>
|
||||
|
||||
<div className="relative z-10 p-8">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate('/')}
|
||||
className="bg-black/20 border-cyan-500/30 text-cyan-200 hover:bg-cyan-500/20"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Back to Galaxy
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-white">{regionData.name} Region</h1>
|
||||
<p className="text-cyan-200">Explore the systems within this region</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full h-[calc(100vh-200px)] border border-cyan-500/30 rounded-lg overflow-hidden bg-black/20 backdrop-blur-sm">
|
||||
<svg
|
||||
ref={svgRef}
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 1200 800"
|
||||
className="cursor-crosshair"
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
>
|
||||
<defs>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</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) => (
|
||||
<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}
|
||||
type="system"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user