import React from 'react'; import moment from "moment"; import { formatPublicKey } from '@/lib/meshcore'; import { getNameIconLabel } from '@/lib/meshcore-map-nodeutils'; import { NodePosition } from '@/types/map'; interface NodeMarkerProps { node: NodePosition; showNodeNames?: boolean; isSelected?: boolean; isLoadingNeighbors?: boolean; } interface ClusterMarkerProps { children: any[]; } interface PopupContentProps { node: NodePosition; target?: '_blank' | '_self' | '_parent' | '_top'; } // Individual node marker component export function NodeMarker({ node, showNodeNames = true, isSelected = false, isLoadingNeighbors = false }: NodeMarkerProps) { const getMarkerClass = () => { let baseClass = "custom-node-marker"; if (node.type === "meshtastic") { baseClass += " custom-node-marker--green"; } else if (node.type === "meshcore") { baseClass += " custom-node-marker--blue custom-node-marker--top"; } // Only add loading class when actually loading neighbors if (isLoadingNeighbors) { baseClass += " custom-node-marker--loading"; } return baseClass; }; return (
{showNodeNames && node.short_name && (
{node.type === "meshcore" ? getNameIconLabel(node.name || node.short_name) : node.short_name}
)}
); } // Cluster marker component with pie chart export function ClusterMarker({ children }: ClusterMarkerProps) { let meshtasticCount = 0; let meshcoreCount = 0; // Convert children to array if it's not already const childrenArray = Array.isArray(children) ? children : [children]; childrenArray.forEach((marker: any) => { const node = marker.options && marker.options.nodeData; if (node?.type === 'meshtastic') meshtasticCount++; else if (node?.type === 'meshcore') meshcoreCount++; }); const total = meshtasticCount + meshcoreCount; const percentMeshcore = total ? meshcoreCount / total : 0; const percentMeshtastic = total ? meshtasticCount / total : 0; // Pie chart SVG calculations const r = 18; const c = 2 * Math.PI * r; const meshcoreArc = percentMeshcore * c; const meshtasticArc = percentMeshtastic * c; return (
{total}
); } // Popup content component export function PopupContent({ node, target = '_self' }: PopupContentProps) { return (
ID: {node.type === "meshcore" ? formatPublicKey(node.node_id) : node.node_id}
Full Name: {node.name ?? "-"}
Short Name: {node.type === "meshcore" && node.short_name ? getNameIconLabel(node.name || node.short_name) : (node.short_name ?? "-")}
Type: {node.type ?? "-"}
Lat: {node.latitude}
Lng: {node.longitude}
Alt: {node.altitude !== undefined ? node.altitude : "-"}
{node.last_seen ? (
Last seen: {moment.utc(node.last_seen).format('YYYY-MM-DD HH:mm:ss')} (UTC)
{moment.utc(node.last_seen).local().fromNow()}
) : (
Last seen: -
)} {node.first_seen ? (
First seen: {moment.utc(node.first_seen).format('YYYY-MM-DD HH:mm:ss')} (UTC)
{moment.utc(node.first_seen).local().fromNow()}
) : (
First seen: -
)} {node.type === "meshcore" && (
e.currentTarget.style.backgroundColor = '#2563eb'} onMouseOut={(e) => e.currentTarget.style.backgroundColor = '#3b82f6'} > View Node Details →
)}
); }