ACPlayer_Webpage/components/live/LiveTrackMap.tsx

146 lines
5.0 KiB
TypeScript

// components/live/LiveTrackMap.tsx
'use client';
import { useEffect, useState } from 'react';
import { getTrackMapUrl, cleanTrackName, cleanTrackConfig } from '@/lib/trackUtils';
interface Car {
carID: number;
driver_name: string;
car_model: string;
normalizedSplinePos: number;
position: number;
lap_time?: number;
best_lap_time?: number;
}
interface LiveTrackMapProps {
track: string;
trackConfig: string;
cars: Car[];
}
export default function LiveTrackMap({ track, trackConfig, cars }: LiveTrackMapProps) {
const [imageError, setImageError] = useState(false);
// Get cleaned track map URL
const trackMapUrl = getTrackMapUrl(track, trackConfig);
const displayTrackName = cleanTrackName(track);
const displayTrackConfig = cleanTrackConfig(trackConfig);
// Calculate position on track (circular approximation)
const getCarPosition = (normalizedPos: number) => {
// normalizedPos is 0.0 to 1.0 around the track
// We'll place cars in a circular path for now
const angle = normalizedPos * Math.PI * 2 - Math.PI / 2; // Start at top
// Position relative to center (percentage)
const centerX = 50;
const centerY = 50;
const radius = 40; // 40% from center
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
return { x, y };
};
// Get color based on position
const getPositionColor = (position: number) => {
if (position === 1) return '#ffffff'; // P1 - White
if (position === 2) return '#d1d5db'; // P2 - Light gray
if (position === 3) return '#9ca3af'; // P3 - Gray
return '#6b7280'; // Others - Dark gray
};
return (
<div className="relative w-full aspect-square bg-black border border-white/10 overflow-hidden">
{/* Track Map Background */}
{!imageError ? (
<img
src={trackMapUrl}
alt={`${track} track map`}
className="absolute inset-0 w-full h-full object-contain opacity-60"
onError={() => setImageError(true)}
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-white/40 text-sm">
Track map not available
</div>
)}
{/* Grid overlay for reference */}
<div className="absolute inset-0 opacity-10">
<svg width="100%" height="100%" className="text-white">
<defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
{/* Car Positions */}
<div className="absolute inset-0">
{cars.map((car) => {
const pos = getCarPosition(car.normalizedSplinePos);
const color = getPositionColor(car.position);
return (
<div
key={car.carID}
className="absolute transition-all duration-300 ease-linear"
style={{
left: `${pos.x}%`,
top: `${pos.y}%`,
transform: 'translate(-50%, -50%)',
}}
>
{/* Car dot */}
<div
className="w-4 h-4 rounded-full border-2 animate-pulse"
style={{
backgroundColor: color,
borderColor: color,
boxShadow: `0 0 10px ${color}`,
}}
/>
{/* Position number */}
<div
className="absolute -top-6 left-1/2 transform -translate-x-1/2 text-xs font-bold whitespace-nowrap px-2 py-1 bg-black/80 border"
style={{ borderColor: color, color: color }}
>
P{car.position}
</div>
{/* Driver name on hover */}
<div className="absolute top-6 left-1/2 transform -translate-x-1/2 opacity-0 hover:opacity-100 transition-opacity whitespace-nowrap">
<div className="px-2 py-1 bg-black/90 border border-white/20 text-xs">
{car.driver_name}
</div>
</div>
</div>
);
})}
</div>
{/* Track info overlay */}
<div className="absolute top-4 left-4 bg-black/80 border border-white/20 px-3 py-2">
<div className="text-xs font-bold tracking-wider text-white/60">TRACK</div>
<div className="text-sm font-mono">{track}</div>
{trackConfig && trackConfig !== 'default' && (
<div className="text-xs text-white/60">{trackConfig}</div>
)}
</div>
{/* Live indicator */}
<div className="absolute top-4 right-4 flex items-center space-x-2 bg-black/80 border border-white/20 px-3 py-2">
<div className="w-2 h-2 bg-red-500 rounded-full animate-pulse"></div>
<span className="text-xs font-bold tracking-wider">LIVE</span>
</div>
</div>
);
}