diff --git a/apps/web/src/components/MapComponent.tsx b/apps/web/src/components/MapComponent.tsx index 3dfea2e..af8c489 100644 --- a/apps/web/src/components/MapComponent.tsx +++ b/apps/web/src/components/MapComponent.tsx @@ -1,32 +1,8 @@ import React, { useEffect, useRef } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; -import mlcontour from 'maplibre-contour'; import { attachIconLoader } from '../utils/mapIcons'; - -interface MapProps { - onMapLoad: (map: maplibregl.Map) => void; - onMapClick?: (lat: number, lng: number) => void; - show3D?: boolean; - showPOIs?: boolean; - showTerrain?: boolean; - showContours?: boolean; - showAdminBoundaries?: boolean; - showWeather?: boolean; - currentRegion?: string; - onCityClick?: (city: any) => void; - losActive?: boolean; - losPointA?: [number, number] | null; - losPointB?: [number, number] | null; -} - -const demSource = new mlcontour.DemSource({ - url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png', - encoding: 'terrarium', - maxzoom: 14, - worker: true, -}); -demSource.setupMaplibre(maplibregl); +import { getSharedDemSource } from '../utils/demSource'; const MapComponent: React.FC = ({ onMapLoad, @@ -468,6 +444,7 @@ const MapComponent: React.FC = ({ }); } + const demSource = getSharedDemSource(); const contourUrl = demSource.contourProtocolUrl({ thresholds: { 10: [100, 500], diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 9dec7e4..0257dbd 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -73,14 +73,70 @@ function ViewNav({ hash }: { hash: string }) { ) } +class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean; error: any }> { + constructor(props: any) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: any) { + return { hasError: true, error }; + } + + componentDidCatch(error: any, errorInfo: any) { + console.error('App UI Error:', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( +
+
⚠️
+

حدث خطأ أثناء تحميل الواجهة

+

+ {this.state.error?.message || 'تعذر تحميل أحد مكونات الخريطة'} +

+ +
+ ); + } + return this.props.children; + } +} + function Root() { - const [hash, setHash] = useState(window.location.hash || '#tactical') + const [hash, setHash] = useState(window.location.hash || '#tactical'); useEffect(() => { const on = () => { const h = window.location.hash || '#tactical'; setHash(h); - // Trigger a resize event to ensure MapLibre maps adapt to visibility changes instantly setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 50); @@ -96,35 +152,40 @@ function Root() { const isMap = !isTactical && !isExecutive && !isCompare && !isReview; return ( - <> + - {/* Keep-Alive Persistent Views: Preserves Map Instances and GPU Tile Caches across Tab switches */} -
- -
+ {isTactical && ( +
+ +
+ )} -
- -
+ {isExecutive && ( +
+ +
+ )} -
- -
+ {isCompare && ( +
+ +
+ )} -
- -
+ {isReview && ( +
+ +
+ )} -
- -
- + {isMap && ( +
+ +
+ )} +
); } -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -) +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/apps/web/src/pages/CompareView.tsx b/apps/web/src/pages/CompareView.tsx index 2aaa42c..2c78af9 100644 --- a/apps/web/src/pages/CompareView.tsx +++ b/apps/web/src/pages/CompareView.tsx @@ -1,17 +1,8 @@ import React, { useEffect, useRef, useState } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; -import mlcontour from 'maplibre-contour'; import { attachIconLoader } from '../utils/mapIcons'; - -// Initialize DEM source for on-the-fly global contours (Jordan, Syria, Iraq, Egypt, etc.) -const demSource = new mlcontour.DemSource({ - url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png', - encoding: 'terrarium', - maxzoom: 14, - worker: true, -}); -demSource.setupMaplibre(maplibregl); +import { getSharedDemSource } from '../utils/demSource'; /** * CompareView — side-by-side, pan/zoom-synced map comparison. @@ -102,6 +93,7 @@ const CompareView: React.FC = () => { lMap.on('load', () => { // Dynamic On-the-Fly Contours from Elevation DEM + const demSource = getSharedDemSource(); const contourUrl = demSource.contourProtocolUrl({ thresholds: { 10: [100, 500], diff --git a/apps/web/src/pages/TacticalDefenseView.tsx b/apps/web/src/pages/TacticalDefenseView.tsx index 2795a63..8a9ac58 100644 --- a/apps/web/src/pages/TacticalDefenseView.tsx +++ b/apps/web/src/pages/TacticalDefenseView.tsx @@ -1,16 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import maplibregl from 'maplibre-gl'; -import mlcontour from 'maplibre-contour'; - -const demSource = new mlcontour.DemSource({ - url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png', - encoding: 'terrarium', - maxzoom: 15, - worker: true, - cacheSize: 100, -}); -demSource.setupMaplibre(maplibregl); import 'maplibre-gl/dist/maplibre-gl.css'; +import { getSharedDemSource } from '../utils/demSource'; import { Shield, Crosshair, @@ -456,6 +447,7 @@ export const TacticalDefenseView: React.FC = () => { }); // Dynamic On-the-Fly Contours from Elevation DEM + const demSource = getSharedDemSource(); const contourUrl = demSource.contourProtocolUrl({ thresholds: { 10: [100, 500], diff --git a/apps/web/src/utils/demSource.ts b/apps/web/src/utils/demSource.ts new file mode 100644 index 0000000..6e03a83 --- /dev/null +++ b/apps/web/src/utils/demSource.ts @@ -0,0 +1,27 @@ +import maplibregl from 'maplibre-gl'; +import mlcontour from 'maplibre-contour'; + +let sharedDemSource: any = null; +let isConfigured = false; + +export function getSharedDemSource() { + if (!sharedDemSource) { + sharedDemSource = new mlcontour.DemSource({ + url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png', + encoding: 'terrarium', + maxzoom: 15, + worker: true, + cacheSize: 100, + }); + + if (!isConfigured) { + try { + sharedDemSource.setupMaplibre(maplibregl); + isConfigured = true; + } catch (e) { + console.warn('DemSource setup warning:', e); + } + } + } + return sharedDemSource; +}