| import '../styles/main.css'; |
| import { MapComponent } from '../components/Map'; |
| import { initI18n } from '../services/i18n'; |
|
|
| type MobileMapIntegrationHarness = { |
| ready: boolean; |
| pendingDeferredCallbacks: number; |
| flushDeferredCallbacks: () => void; |
| destroyMap: () => void; |
| getDynamicLayerChildCount: () => number; |
| getInitialDynamicRendered: () => boolean; |
| getWrapperTransform: () => string; |
| getPopupRect: () => { |
| left: number; |
| top: number; |
| right: number; |
| bottom: number; |
| width: number; |
| height: number; |
| viewportWidth: number; |
| viewportHeight: number; |
| } | null; |
| }; |
|
|
| declare global { |
| interface Window { |
| __mobileMapIntegrationHarness?: MobileMapIntegrationHarness; |
| } |
| } |
|
|
| const app = document.getElementById('app'); |
| if (!app) { |
| throw new Error('Missing #app container for mobile map integration harness'); |
| } |
|
|
| const controlDeferredCallbacks = new URLSearchParams(window.location.search).get('defer-control') === '1'; |
| const deferredCallbacks: IdleRequestCallback[] = []; |
| if (controlDeferredCallbacks) { |
| window.requestIdleCallback = ((callback: IdleRequestCallback) => { |
| deferredCallbacks.push(callback); |
| return deferredCallbacks.length; |
| }) as typeof window.requestIdleCallback; |
| } |
|
|
| document.body.style.margin = '0'; |
| document.body.style.overflow = 'hidden'; |
|
|
| app.className = 'map-container'; |
| app.style.width = '100vw'; |
| app.style.height = '100vh'; |
| app.style.position = 'relative'; |
| app.style.overflow = 'hidden'; |
|
|
| const MINIMAL_WORLD_TOPOLOGY = { |
| type: 'Topology', |
| objects: { |
| countries: { |
| type: 'GeometryCollection', |
| geometries: [ |
| { |
| type: 'Polygon', |
| id: 1, |
| arcs: [[0]], |
| }, |
| ], |
| }, |
| }, |
| arcs: [ |
| [ |
| [0, 0], |
| [3600, 0], |
| [0, 1800], |
| [-3600, 0], |
| [0, -1800], |
| ], |
| ], |
| transform: { |
| scale: [0.1, 0.1], |
| translate: [-180, -90], |
| }, |
| }; |
|
|
| const originalFetch = window.fetch.bind(window); |
| window.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { |
| const url = |
| typeof input === 'string' |
| ? input |
| : input instanceof URL |
| ? input.toString() |
| : input.url; |
|
|
| if (url.includes('/data/countries-50m.json')) { |
| return new Response(JSON.stringify(MINIMAL_WORLD_TOPOLOGY), { |
| status: 200, |
| headers: { 'Content-Type': 'application/json' }, |
| }); |
| } |
|
|
| return originalFetch(input, init); |
| }) as typeof fetch; |
|
|
| const layers = { |
| gpsJamming: false, |
| satellites: false, |
|
|
| conflicts: false, |
| bases: false, |
| cables: false, |
| pipelines: false, |
| hotspots: true, |
| ais: false, |
| nuclear: false, |
| irradiators: false, |
| sanctions: false, |
| weather: false, |
| economic: false, |
| waterways: false, |
| outages: false, |
| cyberThreats: false, |
| datacenters: false, |
| protests: false, |
| flights: false, |
| military: false, |
| natural: false, |
| spaceports: false, |
| minerals: false, |
| fires: false, |
| ucdpEvents: false, |
| displacement: false, |
| climate: false, |
| startupHubs: false, |
| cloudRegions: false, |
| accelerators: false, |
| techHQs: false, |
| techEvents: false, |
| stockExchanges: false, |
| financialCenters: false, |
| centralBanks: false, |
| commodityHubs: false, |
| gulfInvestments: false, |
| positiveEvents: false, |
| kindness: false, |
| happiness: false, |
| speciesRecovery: false, |
| renewableInstallations: false, |
| tradeRoutes: false, |
| iranAttacks: false, |
|
|
| ciiChoropleth: false, |
| resilienceScore: false, |
| dayNight: false, |
| miningSites: false, |
| processingPlants: false, |
| commodityPorts: false, |
| webcams: false, |
| diseaseOutbreaks: false, |
| }; |
|
|
| await initI18n(); |
|
|
| const map = new MapComponent(app, { |
| zoom: 2.7, |
| pan: { x: 0, y: 0 }, |
| view: 'global', |
| layers, |
| timeRange: 'all', |
| }); |
|
|
| let ready = false; |
| let fallbackInjected = false; |
| const ensureHotspotsRendered = (): void => { |
| if (document.querySelector('.hotspot')) { |
| ready = true; |
| return; |
| } |
|
|
| |
| if (!fallbackInjected) { |
| const mapInternals = map as unknown as { |
| worldData: unknown; |
| countryFeatures: unknown; |
| baseRendered: boolean; |
| hotspots: Array<{ |
| id: string; |
| name: string; |
| lat: number; |
| lon: number; |
| keywords: string[]; |
| level: 'low' | 'elevated' | 'high'; |
| description: string; |
| status: string; |
| }>; |
| state: { layers: { hotspots: boolean } }; |
| }; |
| mapInternals.worldData = MINIMAL_WORLD_TOPOLOGY; |
| mapInternals.countryFeatures = [ |
| { |
| type: 'Feature', |
| properties: { name: 'E2E Country' }, |
| geometry: { |
| type: 'Polygon', |
| coordinates: [[[-180, -90], [180, -90], [180, 90], [-180, 90], [-180, -90]]], |
| }, |
| }, |
| ]; |
| mapInternals.hotspots = [ |
| { |
| id: 'e2e-map-hotspot', |
| name: 'E2E Map Hotspot', |
| lat: 20, |
| lon: 10, |
| keywords: ['e2e', 'integration'], |
| level: 'high', |
| description: 'Integration harness hotspot', |
| status: 'monitoring', |
| }, |
| ]; |
| mapInternals.state.layers.hotspots = true; |
| mapInternals.baseRendered = false; |
| map.render(); |
| fallbackInjected = true; |
| } |
|
|
| requestAnimationFrame(ensureHotspotsRendered); |
| }; |
| ensureHotspotsRendered(); |
|
|
| window.__mobileMapIntegrationHarness = { |
| get ready() { |
| return ready; |
| }, |
| get pendingDeferredCallbacks() { |
| return deferredCallbacks.length; |
| }, |
| flushDeferredCallbacks: () => { |
| const callbacks = deferredCallbacks.splice(0); |
| callbacks.forEach((callback) => { |
| callback({ |
| didTimeout: false, |
| timeRemaining: () => 50, |
| }); |
| }); |
| }, |
| destroyMap: () => { |
| map.destroy(); |
| }, |
| getDynamicLayerChildCount: () => |
| document.querySelector('.map-dynamic')?.childElementCount ?? 0, |
| getInitialDynamicRendered: () => |
| Boolean((map as unknown as { initialDynamicRendered?: boolean }).initialDynamicRendered), |
| getWrapperTransform: () => |
| (document.querySelector('.map-wrapper') as HTMLElement | null)?.style.transform ?? '', |
| getPopupRect: () => { |
| const element = document.querySelector('.map-popup') as HTMLElement | null; |
| if (!element) return null; |
| const rect = element.getBoundingClientRect(); |
| return { |
| left: rect.left, |
| top: rect.top, |
| right: rect.right, |
| bottom: rect.bottom, |
| width: rect.width, |
| height: rect.height, |
| viewportWidth: window.innerWidth, |
| viewportHeight: window.innerHeight, |
| }; |
| }, |
| }; |
|
|