import { useEffect, useRef } from 'react' import { useNMEA } from '../../hooks/useNMEA.js' import { getApi } from '../../mock/index.js' export default function ChartPlaceholder() { const { lat, lon, heading, sog } = useNMEA() const canvasRef = useRef(null) const trackRef = useRef([]) const signalkHost = import.meta.env.VITE_SIGNALK_HOST || 'localhost' const chartUrl = `http://${signalkHost}:3000/@signalk/freeboard-sk/` const isMock = import.meta.env.VITE_USE_MOCK === 'true' // Colors from CSS (hardcoded for canvas compatibility) const colors = { bg: '#07111f', surface: '#0a1928', border: '#1e2a3a', text: '#e2eaf2', muted: '#4a6080', accent: '#38bdf8', warning: '#f59e0b', } // Draw track and waypoints useEffect(() => { const canvas = canvasRef.current if (!canvas || !isMock) return // Add current position to track if (lat != null && lon != null) { trackRef.current.push({ lat, lon, time: Date.now() }) // Keep last 500 points for performance if (trackRef.current.length > 500) { trackRef.current.shift() } } const ctx = canvas.getContext('2d') if (!ctx) return // Get API for waypoints const api = getApi() const snapshot = api.signalk.getSnapshot?.() const waypoints = api.signalk.getWaypoints?.() || [] // Calculate bounds const allPoints = [...trackRef.current, ...waypoints] if (allPoints.length === 0) return let minLat = allPoints[0].lat let maxLat = allPoints[0].lat let minLon = allPoints[0].lon let maxLon = allPoints[0].lon allPoints.forEach(p => { minLat = Math.min(minLat, p.lat) maxLat = Math.max(maxLat, p.lat) minLon = Math.min(minLon, p.lon) maxLon = Math.max(maxLon, p.lon) }) const padding = Math.max(maxLat - minLat, maxLon - minLon) * 0.1 minLat -= padding maxLat += padding minLon -= padding maxLon += padding const width = canvas.width const height = canvas.height const latRange = maxLat - minLat const lonRange = maxLon - minLon const scale = Math.min(width / lonRange, height / latRange) const project = (lat, lon) => ({ x: (lon - minLon) * scale, y: height - (lat - minLat) * scale }) // Clear and draw background ctx.fillStyle = colors.surface ctx.fillRect(0, 0, width, height) ctx.strokeStyle = colors.border ctx.lineWidth = 1 ctx.strokeRect(0, 0, width, height) // Draw waypoint markers waypoints.forEach((wp, idx) => { const p = project(wp.lat, wp.lon) // Waypoint circle ctx.fillStyle = idx === snapshot?.currentWaypoint ? colors.accent : colors.warning ctx.beginPath() ctx.arc(p.x, p.y, 6, 0, Math.PI * 2) ctx.fill() // Waypoint number ctx.fillStyle = colors.bg ctx.font = 'bold 10px monospace' ctx.textAlign = 'center' ctx.textBaseline = 'middle' ctx.fillText(idx + 1, p.x, p.y) }) // Draw track line if (trackRef.current.length > 1) { ctx.strokeStyle = colors.accent ctx.lineWidth = 2 ctx.beginPath() trackRef.current.forEach((p, idx) => { const proj = project(p.lat, p.lon) if (idx === 0) ctx.moveTo(proj.x, proj.y) else ctx.lineTo(proj.x, proj.y) }) ctx.stroke() } // Draw ship marker if (lat != null && lon != null) { const p = project(lat, lon) // Ship heading indicator (triangle) const headRad = (heading ?? 0) * Math.PI / 180 const size = 12 ctx.save() ctx.translate(p.x, p.y) ctx.rotate(headRad) ctx.fillStyle = colors.accent ctx.beginPath() ctx.moveTo(0, -size) ctx.lineTo(-size / 2, size / 2) ctx.lineTo(size / 2, size / 2) ctx.closePath() ctx.fill() ctx.restore() // Ship circle ctx.strokeStyle = colors.accent ctx.lineWidth = 2 ctx.beginPath() ctx.arc(p.x, p.y, 8, 0, Math.PI * 2) ctx.stroke() } }, [lat, lon, heading, isMock]) if (!isMock) { // Show iframe for real SignalK server return (