revert: remove leaflet.gridlayer.googlemutant — incompatible with Next.js 15 bundler
The package consistently throws 'L.GridLayer.GoogleMutant is not a constructor' due to L-instance conflicts in the webpack bundle, despite multiple workaround attempts. Removed package, transpilePackages entry, type stub, env var, and all related component code. Traffic overlay dropped; geocoding (backend) unaffected.
This commit is contained in:
@@ -18,6 +18,10 @@ GCS_BUCKET=your-bucket-name
|
|||||||
# How long (seconds) before a node is marked offline if no checkin received
|
# How long (seconds) before a node is marked offline if no checkin received
|
||||||
NODE_OFFLINE_THRESHOLD=90
|
NODE_OFFLINE_THRESHOLD=90
|
||||||
|
|
||||||
|
# Google Maps — for geocoding location strings extracted from transcripts
|
||||||
|
# Enable "Geocoding API" in Cloud Console for this key
|
||||||
|
GOOGLE_MAPS_API_KEY=
|
||||||
|
|
||||||
# OpenAI — for transcription (Whisper), intelligence extraction, embeddings, and summaries
|
# OpenAI — for transcription (Whisper), intelligence extraction, embeddings, and summaries
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
SUMMARY_INTERVAL_MINUTES=15
|
SUMMARY_INTERVAL_MINUTES=15
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FeatureGroup,
|
FeatureGroup,
|
||||||
LayersControl,
|
LayersControl,
|
||||||
@@ -145,54 +145,6 @@ function computeGroups<T extends { id: string; lat: number; lng: number }>(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Google Maps traffic layer via leaflet-google-mutant ───────────────────────
|
|
||||||
// Uses dynamic import so the side-effect augments the same L instance at runtime,
|
|
||||||
// avoiding the "GoogleMutant is not a constructor" error from static top-level imports.
|
|
||||||
function GoogleTrafficManager({ enabled }: { enabled: boolean }) {
|
|
||||||
const map = useMap();
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const layerRef = useRef<any>(null);
|
|
||||||
const scriptLoadedRef = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!enabled) {
|
|
||||||
if (layerRef.current) { map.removeLayer(layerRef.current); layerRef.current = null; }
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;
|
|
||||||
if (!key) return;
|
|
||||||
|
|
||||||
function mountLayer() {
|
|
||||||
import("leaflet.gridlayer.googlemutant").then(() => {
|
|
||||||
if (layerRef.current) return; // already mounted
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const layer = (L as any).gridLayer.googleMutant({ type: "roadmap" });
|
|
||||||
layer.addGoogleLayer("TrafficLayer");
|
|
||||||
map.addLayer(layer);
|
|
||||||
layerRef.current = layer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if ((window as any).google?.maps) {
|
|
||||||
mountLayer();
|
|
||||||
} else if (!scriptLoadedRef.current) {
|
|
||||||
scriptLoadedRef.current = true;
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.src = `https://maps.googleapis.com/maps/api/js?key=${key}&loading=async`;
|
|
||||||
script.async = true;
|
|
||||||
script.onload = mountLayer;
|
|
||||||
document.head.appendChild(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (layerRef.current) { map.removeLayer(layerRef.current); layerRef.current = null; }
|
|
||||||
};
|
|
||||||
}, [enabled, map]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── MapRefCapture — exposes L.Map instance to parent ─────────────────────────
|
// ── MapRefCapture — exposes L.Map instance to parent ─────────────────────────
|
||||||
function MapRefCapture({ onReady }: { onReady: (m: L.Map) => void }) {
|
function MapRefCapture({ onReady }: { onReady: (m: L.Map) => void }) {
|
||||||
@@ -391,7 +343,6 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate
|
|||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [trafficEnabled, setTrafficEnabled] = useState(false);
|
|
||||||
|
|
||||||
// Live clock for TOC situational awareness
|
// Live clock for TOC situational awareness
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -489,9 +440,6 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate
|
|||||||
</FeatureGroup>
|
</FeatureGroup>
|
||||||
</LayersControl.Overlay>
|
</LayersControl.Overlay>
|
||||||
|
|
||||||
{/* Traffic managed outside LayersControl to avoid L-instance conflicts */}
|
|
||||||
<GoogleTrafficManager enabled={trafficEnabled} />
|
|
||||||
|
|
||||||
{/* Overlay: Weather Radar — NEXRAD via Iowa Env Mesonet; key forces remount on refresh */}
|
{/* Overlay: Weather Radar — NEXRAD via Iowa Env Mesonet; key forces remount on refresh */}
|
||||||
<LayersControl.Overlay name="Weather Radar">
|
<LayersControl.Overlay name="Weather Radar">
|
||||||
<TileLayer
|
<TileLayer
|
||||||
@@ -534,19 +482,6 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate
|
|||||||
⤢
|
⤢
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY && (
|
|
||||||
<button
|
|
||||||
onClick={() => setTrafficEnabled((v) => !v)}
|
|
||||||
title="Toggle traffic layer"
|
|
||||||
className={`w-8 h-8 border rounded text-xs font-mono font-bold leading-none transition-colors flex items-center justify-center select-none ${
|
|
||||||
trafficEnabled
|
|
||||||
? "bg-indigo-700 border-indigo-500 text-white"
|
|
||||||
: "bg-gray-950/90 border-gray-700 text-gray-400 hover:bg-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
TRF
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Clock — bottom-left for TOC situational awareness ───────────────── */}
|
{/* ── Clock — bottom-left for TOC situational awareness ───────────────── */}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
transpilePackages: ["leaflet", "react-leaflet", "leaflet.gridlayer.googlemutant"],
|
transpilePackages: ["leaflet", "react-leaflet"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
"react-dom": "^18.3.0",
|
"react-dom": "^18.3.0",
|
||||||
"firebase": "^10.12.0",
|
"firebase": "^10.12.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet.gridlayer.googlemutant": "0.16.0",
|
"react-leaflet": "^4.2.1"
|
||||||
"react-leaflet": "^4.2.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.4.0",
|
"typescript": "^5.4.0",
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
declare module "leaflet.gridlayer.googlemutant" {
|
|
||||||
// Side-effect import — augments L.gridLayer with googleMutant factory.
|
|
||||||
// No public API is consumed directly from this module.
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user