@@ -68,11 +68,16 @@ const GLOBE_VIEW_STATE: MapViewState = {
6868 longitude : 0 ,
6969 latitude : 30 ,
7070 zoom : 2.25 ,
71- minZoom : 1.5 ,
72- maxZoom : 3 ,
71+ minZoom : 2 ,
72+ maxZoom : 5 ,
7373 pitch : 0 ,
7474 bearing : 0 ,
7575} ;
76+ const GLOBE_MOBILE_VIEW_STATE : MapViewState = {
77+ ...GLOBE_VIEW_STATE ,
78+ zoom : 1.75 ,
79+ minZoom : 1 ,
80+ } ;
7681const GLOBE_ROTATION_DEGREES_PER_SECOND = 10 ;
7782const GLOBE_USER_INTERACTION_PAUSE_MS = 5000 ;
7883const GLOBE_RECOVERY_DURATION_MS = 1200 ;
@@ -92,6 +97,8 @@ function buildRasterStyle(
9297 const layerId = `insightflare-raster-layer-${ theme } ` ;
9398 const endpoint = `/api/public/resources/map-tiles/{z}/{x}/{y}.png?theme=${ theme } ` ;
9499 const isGlobe = projectionMode === "globe" ;
100+ const backgroundColor =
101+ theme === "dark" ? "rgb(10, 10, 10)" : "rgb(255, 255, 255)" ;
95102
96103 return {
97104 version : 8 ,
@@ -102,12 +109,9 @@ function buildRasterStyle(
102109 ...( isGlobe
103110 ? {
104111 sky : {
105- "sky-color" :
106- theme === "dark" ? "rgb(8, 12, 18)" : "rgb(244, 247, 250)" ,
107- "horizon-color" :
108- theme === "dark" ? "rgb(17, 24, 39)" : "rgb(233, 238, 244)" ,
109- "fog-color" :
110- theme === "dark" ? "rgb(8, 12, 18)" : "rgb(244, 247, 250)" ,
112+ "sky-color" : backgroundColor ,
113+ "horizon-color" : backgroundColor ,
114+ "fog-color" : backgroundColor ,
111115 "atmosphere-blend" : 0.18 ,
112116 } ,
113117 }
@@ -127,9 +131,8 @@ function buildRasterStyle(
127131 id : "insightflare-globe-ocean" ,
128132 type : "background" as const ,
129133 paint : {
130- "background-color" :
131- theme === "dark" ? "rgb(15, 23, 42)" : "rgb(226, 233, 241)" ,
132- "background-opacity" : theme === "dark" ? 0.88 : 0.82 ,
134+ "background-color" : backgroundColor ,
135+ "background-opacity" : 1 ,
133136 } ,
134137 } ,
135138 ]
@@ -454,6 +457,7 @@ export function GeoPointsMap({
454457 null ,
455458 ) ;
456459 const [ hoveredCountryName , setHoveredCountryName ] = useState ( "" ) ;
460+ const [ mapSettled , setMapSettled ] = useState ( false ) ;
457461 const [ currentZoom , setCurrentZoom ] = useState (
458462 normalizeClusterZoom ( DEFAULT_VIEW_STATE . zoom ) ,
459463 ) ;
@@ -529,8 +533,12 @@ export function GeoPointsMap({
529533
530534 const initialViewState = useMemo (
531535 ( ) =>
532- isGlobe ? GLOBE_VIEW_STATE : computeInitialViewState ( normalizedPoints ) ,
533- [ isGlobe , normalizedPoints ] ,
536+ isGlobe
537+ ? isMobile
538+ ? GLOBE_MOBILE_VIEW_STATE
539+ : GLOBE_VIEW_STATE
540+ : computeInitialViewState ( normalizedPoints ) ,
541+ [ isGlobe , isMobile , normalizedPoints ] ,
534542 ) ;
535543 const mobileMinZoom =
536544 initialViewState . minZoom ?? DEFAULT_VIEW_STATE . minZoom ?? 0 ;
@@ -611,6 +619,10 @@ export function GeoPointsMap({
611619 ( ) => buildRasterStyle ( effectiveMapTheme , projectionMode ) ,
612620 [ effectiveMapTheme , projectionMode ] ,
613621 ) ;
622+
623+ useEffect ( ( ) => {
624+ setMapSettled ( false ) ;
625+ } , [ mapInitialViewState , mapStyle ] ) ;
614626 const normalizedSelectedCountryCode = useMemo (
615627 ( ) => normalizeCountryCode ( selectedCountryCode ) ,
616628 [ selectedCountryCode ] ,
@@ -785,30 +797,44 @@ export function GeoPointsMap({
785797 rotationRecoveryFromRef . current = null ;
786798 } , [ ] ) ;
787799
800+ const markMapSettled = useCallback ( ( map : MaplibreMap ) => {
801+ if ( ! map . isStyleLoaded ( ) || ! map . areTilesLoaded ( ) ) return ;
802+ setMapSettled ( true ) ;
803+ } , [ ] ) ;
804+
788805 const applyProjectionMode = useCallback (
789806 ( map : MaplibreMap ) => {
790807 if ( ! map . isStyleLoaded ( ) ) return false ;
791808 map . setProjection ( { type : isGlobe ? "globe" : "mercator" } ) ;
792809 if ( isGlobe ) {
793810 map . jumpTo ( {
794- center : [ GLOBE_VIEW_STATE . longitude , GLOBE_VIEW_STATE . latitude ] ,
795- zoom : GLOBE_VIEW_STATE . zoom ,
811+ center : [ initialViewState . longitude , initialViewState . latitude ] ,
812+ zoom : initialViewState . zoom ,
796813 bearing : 0 ,
797814 pitch : 0 ,
798815 } ) ;
799- setCurrentZoom ( normalizeClusterZoom ( GLOBE_VIEW_STATE . zoom ) ) ;
816+ setCurrentZoom (
817+ normalizeClusterZoom ( initialViewState . zoom ?? GLOBE_VIEW_STATE . zoom ) ,
818+ ) ;
800819 }
801820 return true ;
802821 } ,
803- [ isGlobe ] ,
822+ [ initialViewState , isGlobe ] ,
804823 ) ;
805824
806825 useEffect ( ( ) => {
807826 const map = mapRef . current ?. getMap ( ) ;
808827 if ( ! map || ! isMobile ) return ;
809- map . jumpTo ( { zoom : mobileMinZoom } ) ;
810- setCurrentZoom ( normalizeClusterZoom ( mobileMinZoom ) ) ;
811- } , [ isMobile , mobileMinZoom ] ) ;
828+ map . jumpTo ( {
829+ center : [ initialViewState . longitude , initialViewState . latitude ] ,
830+ zoom : initialViewState . zoom ,
831+ bearing : 0 ,
832+ pitch : 0 ,
833+ } ) ;
834+ setCurrentZoom (
835+ normalizeClusterZoom ( initialViewState . zoom ?? DEFAULT_VIEW_STATE . zoom ) ,
836+ ) ;
837+ } , [ initialViewState , isMobile ] ) ;
812838
813839 useEffect ( ( ) => {
814840 const map = mapRef . current ?. getMap ( ) ;
@@ -824,7 +850,7 @@ export function GeoPointsMap({
824850 } , [ applyProjectionMode , mapLoaded ] ) ;
825851
826852 useEffect ( ( ) => {
827- if ( ! autoRotateEnabled ) return ;
853+ if ( ! autoRotateEnabled || ! mapSettled ) return ;
828854
829855 const rotate = ( timestamp : number ) => {
830856 const map = mapRef . current ?. getMap ( ) ;
@@ -872,13 +898,16 @@ export function GeoPointsMap({
872898 const recoveryFrom = rotationRecoveryFromRef . current ;
873899 const nextLatitude = recoveryFrom
874900 ? recoveryFrom . latitude +
875- ( GLOBE_VIEW_STATE . latitude - recoveryFrom . latitude ) *
901+ ( ( initialViewState . latitude ?? GLOBE_VIEW_STATE . latitude ) -
902+ recoveryFrom . latitude ) *
876903 recoveryProgress
877- : GLOBE_VIEW_STATE . latitude ;
904+ : ( initialViewState . latitude ?? GLOBE_VIEW_STATE . latitude ) ;
878905 const nextZoom = recoveryFrom
879906 ? recoveryFrom . zoom +
880- ( GLOBE_VIEW_STATE . zoom - recoveryFrom . zoom ) * recoveryProgress
881- : GLOBE_VIEW_STATE . zoom ;
907+ ( ( initialViewState . zoom ?? GLOBE_VIEW_STATE . zoom ) -
908+ recoveryFrom . zoom ) *
909+ recoveryProgress
910+ : ( initialViewState . zoom ?? GLOBE_VIEW_STATE . zoom ) ;
882911 const nextLongitude = normalizeLongitude (
883912 center . lng +
884913 deltaSeconds * GLOBE_ROTATION_DEGREES_PER_SECOND * speedProgress ,
@@ -916,7 +945,7 @@ export function GeoPointsMap({
916945 rotationFrameRef . current = null ;
917946 }
918947 } ;
919- } , [ autoRotateEnabled ] ) ;
948+ } , [ autoRotateEnabled , initialViewState , mapSettled ] ) ;
920949
921950 if ( loading ) {
922951 return (
@@ -943,47 +972,68 @@ export function GeoPointsMap({
943972 } `}
944973 style = { MAP_VIEWPORT_RENDER_ISOLATION_STYLE }
945974 >
946- < Map
947- ref = { mapRef }
948- initialViewState = { mapInitialViewState }
949- mapStyle = { mapStyle }
950- attributionControl = { false }
951- scrollZoom
952- maxPitch = { 0 }
953- dragRotate = { false }
954- pitchWithRotate = { false }
955- renderWorldCopies = { ! isGlobe }
956- onDragStart = { ( event ) => {
957- if ( event . originalEvent ) pauseAutoRotate ( ) ;
958- } }
959- onDrag = { ( event ) => {
960- if ( event . originalEvent ) pauseAutoRotate ( ) ;
961- } }
962- onZoomStart = { ( event ) => {
963- if ( event . originalEvent ) pauseAutoRotate ( ) ;
964- } }
965- onMove = { ( event ) => {
966- if ( event . originalEvent ) pauseAutoRotate ( ) ;
967- } }
968- onMoveEnd = { ( event ) => {
969- if ( event . originalEvent ) pauseAutoRotate ( ) ;
970- } }
971- onZoom = { ( event ) => {
972- if ( event . originalEvent ) pauseAutoRotate ( ) ;
973- const nextZoom = normalizeClusterZoom ( event . viewState . zoom ) ;
974- setCurrentZoom ( ( prev ) =>
975- Math . abs ( prev - nextZoom ) > 0.0001 ? nextZoom : prev ,
976- ) ;
975+ { ! mapSettled ? (
976+ < div className = "pointer-events-none absolute inset-0 z-10 flex items-center justify-center bg-background" >
977+ < Spinner className = "size-6" />
978+ </ div >
979+ ) : null }
980+ < motion . div
981+ className = "absolute inset-0"
982+ initial = { false }
983+ animate = { {
984+ opacity : mapSettled ? 1 : 0 ,
985+ scale : mapSettled ? 1 : 0.5 ,
986+ y : mapSettled ? 0 : "50%" ,
977987 } }
978- onMouseDown = { pauseAutoRotate }
979- onTouchStart = { pauseAutoRotate }
980- onLoad = { ( event ) => {
981- setMapLoaded ( true ) ;
982- applyProjectionMode ( event . target ) ;
988+ transition = { {
989+ duration : 3 ,
990+ ease : [ 0.16 , 1.45 , 0.34 , 1 ] ,
983991 } }
984992 >
985- < DeckOverlay interleaved = { false } layers = { layers } />
986- </ Map >
993+ < Map
994+ ref = { mapRef }
995+ initialViewState = { mapInitialViewState }
996+ mapStyle = { mapStyle }
997+ attributionControl = { false }
998+ scrollZoom
999+ maxPitch = { 0 }
1000+ dragRotate = { false }
1001+ pitchWithRotate = { false }
1002+ renderWorldCopies = { ! isGlobe }
1003+ onDragStart = { ( event ) => {
1004+ if ( event . originalEvent ) pauseAutoRotate ( ) ;
1005+ } }
1006+ onDrag = { ( event ) => {
1007+ if ( event . originalEvent ) pauseAutoRotate ( ) ;
1008+ } }
1009+ onZoomStart = { ( event ) => {
1010+ if ( event . originalEvent ) pauseAutoRotate ( ) ;
1011+ } }
1012+ onMove = { ( event ) => {
1013+ if ( event . originalEvent ) pauseAutoRotate ( ) ;
1014+ } }
1015+ onMoveEnd = { ( event ) => {
1016+ if ( event . originalEvent ) pauseAutoRotate ( ) ;
1017+ } }
1018+ onZoom = { ( event ) => {
1019+ if ( event . originalEvent ) pauseAutoRotate ( ) ;
1020+ const nextZoom = normalizeClusterZoom ( event . viewState . zoom ) ;
1021+ setCurrentZoom ( ( prev ) =>
1022+ Math . abs ( prev - nextZoom ) > 0.0001 ? nextZoom : prev ,
1023+ ) ;
1024+ } }
1025+ onMouseDown = { pauseAutoRotate }
1026+ onTouchStart = { pauseAutoRotate }
1027+ onLoad = { ( event ) => {
1028+ setMapLoaded ( true ) ;
1029+ applyProjectionMode ( event . target ) ;
1030+ markMapSettled ( event . target ) ;
1031+ } }
1032+ onIdle = { ( event ) => markMapSettled ( event . target ) }
1033+ >
1034+ < DeckOverlay interleaved = { false } layers = { layers } />
1035+ </ Map >
1036+ </ motion . div >
9871037 < AnimatePresence >
9881038 { showCountryToolbar ? (
9891039 < motion . div
0 commit comments