mirror of
https://github.com/dpup/meshstream.git
synced 2026-03-28 17:42:37 +01:00
Gate map layers behind onLoad to fix missing markers
Source/Layer components mounted before the map style finishes loading fail silently. Add mapLoaded state + onLoad callback to LocationMap, NodeLocationMap, and NetworkMap so GeoJSON sources and layers are only added after the style is ready. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState, useEffect, useMemo } from "react";
|
||||
import React, { useRef, useState, useEffect, useMemo, useCallback } from "react";
|
||||
import ReactMap, { Source, Layer } from "react-map-gl/maplibre";
|
||||
import type { FeatureCollection } from "geojson";
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
@@ -26,6 +26,8 @@ export const LocationMap: React.FC<LocationMapProps> = ({
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
const handleLoad = useCallback(() => setMapLoaded(true), []);
|
||||
|
||||
// Only mount the WebGL map when the container enters the viewport.
|
||||
// This prevents exhausting the browser's WebGL context limit (~8-16)
|
||||
@@ -79,7 +81,10 @@ export const LocationMap: React.FC<LocationMapProps> = ({
|
||||
initialViewState={{ longitude, latitude, zoom: effectiveZoom }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
attributionControl={{ compact: true }}
|
||||
onLoad={handleLoad}
|
||||
>
|
||||
{mapLoaded && (
|
||||
<>
|
||||
{showAccuracyCircle && (
|
||||
<Source id="circle" type="geojson" data={circleGeoJSON}>
|
||||
<Layer id="circle-fill" type="fill" paint={{ "fill-color": "#4ade80", "fill-opacity": 0.15 }} />
|
||||
@@ -98,6 +103,8 @@ export const LocationMap: React.FC<LocationMapProps> = ({
|
||||
}}
|
||||
/>
|
||||
</Source>
|
||||
</>
|
||||
)}
|
||||
</ReactMap>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import ReactMap, { Source, Layer } from "react-map-gl/maplibre";
|
||||
import type { FeatureCollection } from "geojson";
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
@@ -30,14 +30,12 @@ export const NodeLocationMap: React.FC<NodeLocationMapProps> = ({
|
||||
}) => {
|
||||
const accuracyMeters = calculateAccuracyFromPrecisionBits(precisionBits);
|
||||
const effectiveZoom = zoom ?? calculateZoomFromAccuracy(accuracyMeters);
|
||||
const showCenterDot = true;
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
|
||||
const markerGeoJSON = useMemo((): FeatureCollection => ({
|
||||
type: "FeatureCollection",
|
||||
features: showCenterDot
|
||||
? [{ type: "Feature", geometry: { type: "Point", coordinates: [lng, lat] }, properties: {} }]
|
||||
: [],
|
||||
}), [lat, lng, showCenterDot]);
|
||||
features: [{ type: "Feature", geometry: { type: "Point", coordinates: [lng, lat] }, properties: {} }],
|
||||
}), [lat, lng]);
|
||||
|
||||
const circleGeoJSON = useMemo((): FeatureCollection => ({
|
||||
type: "FeatureCollection",
|
||||
@@ -59,7 +57,10 @@ export const NodeLocationMap: React.FC<NodeLocationMapProps> = ({
|
||||
initialViewState={{ longitude: lng, latitude: lat, zoom: effectiveZoom }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
attributionControl={{ compact: true }}
|
||||
onLoad={() => setMapLoaded(true)}
|
||||
>
|
||||
{mapLoaded && (
|
||||
<>
|
||||
<Source id="circle" type="geojson" data={circleGeoJSON}>
|
||||
<Layer
|
||||
id="circle-fill"
|
||||
@@ -86,6 +87,8 @@ export const NodeLocationMap: React.FC<NodeLocationMapProps> = ({
|
||||
}}
|
||||
/>
|
||||
</Source>
|
||||
</>
|
||||
)}
|
||||
</ReactMap>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -187,7 +187,9 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
||||
onZoomStart={handleUserInteraction}
|
||||
onLoad={() => setMapLoaded(true)}
|
||||
>
|
||||
{/* Topology links — always mounted, visibility controlled via layout property */}
|
||||
{/* Sources and layers — only after map style has loaded */}
|
||||
{mapLoaded && (
|
||||
<>
|
||||
<Source id="links" type="geojson" data={linksGeoJSON}>
|
||||
<Layer
|
||||
id="links-line"
|
||||
@@ -205,7 +207,6 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
||||
/>
|
||||
</Source>
|
||||
|
||||
{/* Node circles */}
|
||||
<Source id="nodes" type="geojson" data={nodesGeoJSON}>
|
||||
<Layer
|
||||
id="nodes-circles"
|
||||
@@ -236,6 +237,8 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
||||
}}
|
||||
/>
|
||||
</Source>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Node popup */}
|
||||
{selectedNode && (
|
||||
|
||||
Reference in New Issue
Block a user