Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 41 additions & 14 deletions src/components/MapContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'maplibre-gl/dist/maplibre-gl.css';
import { isMobileDevice } from '@/utils';
import { MapComponent } from './Map';
import { DeckGLMap, type DeckMapView, type CountryClickPayload } from './DeckGLMap';
import { GlobeMap } from './GlobeMap';
import type { GlobeMap } from './GlobeMap';
import type {
MapLayers,
Hotspot,
Expand Down Expand Up @@ -87,6 +87,7 @@ interface TechEventMarker {
type FireMarker = { lat: number; lon: number; brightness: number; frp: number; confidence: number; region: string; acq_date: string; daynight: string };
type NewsLocationMarker = { lat: number; lon: number; title: string; threatLevel: string; timestamp?: Date };
type CIIScore = { code: string; score: number; level: string };
type GlobeMapCtor = typeof import('./GlobeMap').GlobeMap;

/**
* Unified map interface that delegates to either DeckGLMap or MapComponent
Expand All @@ -98,10 +99,13 @@ export class MapContainer {
private deckGLMap: DeckGLMap | null = null;
private svgMap: MapComponent | null = null;
private globeMap: GlobeMap | null = null;
private globeMapCtorPromise: Promise<GlobeMapCtor> | null = null;
private globeActivationId = 0;
private supplyChainPanel: import('@/components/SupplyChainPanel').SupplyChainPanel | null = null;
private initialState: MapContainerState;
private useDeckGL: boolean;
private useGlobe: boolean;
private destroyed = false;
private isResizingInternal = false;
private resizeObserver: ResizeObserver | null = null;

Expand Down Expand Up @@ -205,10 +209,39 @@ export class MapContainer {
this.svgMap = new MapComponent(this.container, this.initialState);
}

private loadGlobeMapCtor(): Promise<GlobeMapCtor> {
this.globeMapCtorPromise ??= import('./GlobeMap').then(({ GlobeMap }) => GlobeMap);
return this.globeMapCtorPromise;
}

private observeContainerResize(): void {
if (typeof ResizeObserver === 'undefined' || this.resizeObserver) return;
this.resizeObserver = new ResizeObserver(() => {
// Skip if we are already handling resize manually via drag handlers
if (this.isResizingInternal) return;
this.resize();
});
this.resizeObserver.observe(this.container);
}

private async initGlobeMap(
snapshot?: MapContainerState,
center?: { lat: number; lon: number } | null,
): Promise<void> {
const activationId = ++this.globeActivationId;
console.log('[MapContainer] Initializing 3D globe (globe.gl mode)');
const GlobeMapCtor = await this.loadGlobeMapCtor();
if (this.destroyed || !this.useGlobe || activationId !== this.globeActivationId) return;

this.globeMap = new GlobeMapCtor(this.container, this.initialState);
this.observeContainerResize();
if (snapshot) this.restoreViewport(snapshot, center ?? null);
this.rehydrateActiveMap();
}
Comment thread
koala73 marked this conversation as resolved.

private init(): void {
if (this.useGlobe) {
console.log('[MapContainer] Initializing 3D globe (globe.gl mode)');
this.globeMap = new GlobeMap(this.container, this.initialState);
void this.initGlobeMap();
} else if (this.useDeckGL) {
console.log('[MapContainer] Initializing deck.gl map (desktop mode)');
try {
Expand All @@ -226,14 +259,7 @@ export class MapContainer {
}

// Automatic resize on container change (fixes gaps on load/layout shift)
if (typeof ResizeObserver !== 'undefined') {
this.resizeObserver = new ResizeObserver(() => {
// Skip if we are already handling resize manually via drag handlers
if (this.isResizingInternal) return;
this.resize();
});
this.resizeObserver.observe(this.container);
}
this.observeContainerResize();
}

/** Switch to 3D globe mode at runtime (called from Settings). */
Expand All @@ -246,9 +272,7 @@ export class MapContainer {
this.destroyFlatMap();
this.useGlobe = true;
this.useDeckGL = false;
this.globeMap = new GlobeMap(this.container, this.initialState);
this.restoreViewport(snapshot, center);
this.rehydrateActiveMap();
void this.initGlobeMap(snapshot, center);
}

/** Reload basemap style (called when map provider changes in Settings). */
Expand All @@ -266,6 +290,7 @@ export class MapContainer {
this.globeMap?.destroy();
this.globeMap = null;
this.useGlobe = false;
this.globeActivationId++;
this.useDeckGL = this.shouldUseDeckGL();
this.init();
this.restoreViewport(snapshot, center);
Expand Down Expand Up @@ -1048,6 +1073,8 @@ export class MapContainer {
}

public destroy(): void {
this.destroyed = true;
this.globeActivationId++;
this.resizeObserver?.disconnect();
this.globeMap?.destroy();
this.deckGLMap?.destroy();
Expand Down
6 changes: 5 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ const BROTLI_EXTENSIONS = new Set(['.js', '.mjs', '.css', '.html', '.svg', '.jso
// silent-breakage failure mode where renaming a chunk in `manualChunks`
// re-eagerises the WebGL stack without any build-time error.
// - maplibre, deck-stack: heavy WebGL deps, only reachable via MapContainer
// - globe-stack: globe.gl + three.js, only reachable when globe mode loads
// - MapContainer: the dynamic-import target itself
const LAZY_HTML_PRELOAD_CHUNKS = ['maplibre', 'deck-stack', 'MapContainer'] as const;
const LAZY_HTML_PRELOAD_CHUNKS = ['maplibre', 'deck-stack', 'globe-stack', 'MapContainer'] as const;
const LAZY_HTML_PRELOAD_RE = new RegExp(
`/(${LAZY_HTML_PRELOAD_CHUNKS.join('|')})-[A-Za-z0-9_-]+\\.js$`,
);
Expand Down Expand Up @@ -987,6 +988,9 @@ export default defineConfig(({ mode }) => {
) {
return 'deck-stack';
}
if (id.includes('/globe.gl/') || id.includes('/three/')) {
return 'globe-stack';
}
if (id.includes('/d3/')) {
return 'd3';
}
Expand Down
Loading