algorembrant's picture
Upload 26 files
619120c verified
import React, { useRef, useEffect, useState } from 'react';
const NetworkMap = ({ users }) => {
const canvasRef = useRef(null);
const [hoveredUser, setHoveredUser] = useState(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
let animationFrameId;
// Simple auto-scaling projection
const lats = users.map(u => u.location.lat);
const lngs = users.map(u => u.location.lng);
const minLat = Math.min(...lats) - 5;
const maxLat = Math.max(...lats) + 5;
const minLng = Math.min(...lngs) - 5;
const maxLng = Math.max(...lngs) + 5;
const project = (lat, lng) => {
const x = ((lng - minLng) / (maxLng - minLng)) * canvas.width;
const y = canvas.height - ((lat - minLat) / (maxLat - minLat)) * canvas.height;
return { x, y };
};
const render = () => {
// Resize canvas to parent
if (canvas.width !== canvas.parentElement.clientWidth || canvas.height !== canvas.parentElement.clientHeight) {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw Grid
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
ctx.lineWidth = 1;
const gridSize = 40;
for (let x = 0; x < canvas.width; x += gridSize) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
}
for (let y = 0; y < canvas.height; y += gridSize) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke();
}
// Draw Connections (just for visual flair)
ctx.strokeStyle = 'rgba(0, 240, 255, 0.05)';
ctx.lineWidth = 0.5;
users.forEach((u, i) => {
const p1 = project(u.location.lat, u.location.lng);
// Connect to nearest neighbor (simplified, just connect to next few)
for (let j = i + 1; j < Math.min(i + 3, users.length); j++) {
const p2 = project(users[j].location.lat, users[j].location.lng);
ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke();
}
});
// Draw Users
users.forEach(user => {
const { x, y } = project(user.location.lat, user.location.lng);
const isActive = user.status === 'Online';
const isRoaming = user.isRoaming;
const color = isActive ? '#00ff9d' : (isRoaming ? '#ffbf00' : '#ff0055');
// Pulse
if (isActive) {
const time = Date.now() / 1000;
const radius = 4 + Math.sin(time * 2 + user.id) * 2;
ctx.beginPath();
ctx.arc(x, y, radius * 2, 0, Math.PI * 2);
ctx.fillStyle = `color-mix(in srgb, ${color} 20%, transparent)`;
ctx.fill();
}
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
// Label if active or Roaming
if (isRoaming) {
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.font = '10px Inter';
ctx.fillText(user.location.city, x + 8, y + 3);
}
});
animationFrameId = requestAnimationFrame(render);
};
render();
return () => cancelAnimationFrame(animationFrameId);
}, [users]);
return (
<div className="glass-panel" style={{ height: '100%', minHeight: '400px', position: 'relative', overflow: 'hidden' }}>
<canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />
<div style={{ position: 'absolute', top: 20, right: 20, background: 'rgba(0,0,0,0.5)', padding: '10px', borderRadius: '8px', fontSize: '12px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '4px' }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#00ff9d' }}></span> Online
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '4px' }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#ffbf00' }}></span> Roaming
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: '#ff0055' }}></span> Offline
</div>
</div>
</div>
);
};
export default NetworkMap;