Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
90 changes: 50 additions & 40 deletions apps/lrauv-dash2/components/DeploymentMap.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import React, { useCallback, useState, useRef, useEffect, useMemo } from 'react'
import { useManagedWaypoints } from '@mbari/react-ui'
import useGoogleElevator from '../lib/useGoogleElevator'
Expand All @@ -11,14 +12,20 @@ import toast from 'react-hot-toast'
import { createLogger } from '@mbari/utils'
import VehicleColorsModal from './VehicleColorsModal'
import useTrackedVehicles from '../lib/useTrackedVehicles'
import { useDepthRequest } from '@mbari/utils/useDepthRequest'

// This is a tricky workaround to prevent leaflet from crashing next.js
// SSR. If we don't do this, the leaflet map will be loaded server side
// and throw a window error.
const Map = dynamic(() => import('@mbari/react-ui/dist/Map/Map'), {
ssr: false,
})

const MapDepthDisplay = dynamic(
() =>
import('@mbari/react-ui/dist/Map/Map').then((m) => ({
default: m.MapDepthDisplay,
})),
{ ssr: false }
)
const DraggableMarker = dynamic(() => import('./DraggableMarker'), {
ssr: false,
})
Expand Down Expand Up @@ -64,6 +71,7 @@ const DeploymentMap: React.FC<DeploymentMapProps> = ({
startTime,
endTime,
}) => {
const router = useRouter()
const mapRef = useRef<any>(null)
const {
updatedWaypoints,
Expand All @@ -80,18 +88,7 @@ const DeploymentMap: React.FC<DeploymentMapProps> = ({
),
[updatedWaypoints, handleWaypointsUpdate]
)
const { handleDepthRequest, elevationAvailable } = useGoogleElevator()
// Depth request hook
const { handleDepthRequestWithFeedback } = useDepthRequest(
handleDepthRequest,
{
warningToastId: 'depth-unavailable',
errorToastId: 'depth-result',
loadingToastId: 'depth-loading',
warningToastClass: 'blue-toast',
toastDuration: 5000,
}
)
const { handleDepthRequest } = useGoogleElevator()

// Filter out waypoints with NaN lat/lon
const plottedWaypoints = updatedWaypoints.filter(
Expand Down Expand Up @@ -477,20 +474,17 @@ const DeploymentMap: React.FC<DeploymentMapProps> = ({
setShowLayersModal(false)
}, [])

const handleVehicleColorRequest = useCallback(() => {
// Add debugging to verify values
logger.debug('Opening color modal with:', {
vehicleName,
trackedVehicles,
modalTrackedVehicles: vehicleName ? [vehicleName] : [],
})

setColorModalPosition({
top: 100,
left: 100,
})
setColorModalOpen(true)
}, [vehicleName, trackedVehicles])
const handleVehicleColorRequest = useCallback(
(anchor?: { top: number; left: number }) => {
if (anchor) {
setColorModalPosition(anchor)
} else {
setColorModalPosition({ top: 100, left: 100 })
}
setColorModalOpen(true)
},
[]
)

const handleCloseVehicleColors = useCallback((vehicleName?: string) => {
setShowVehicleColors(false)
Expand Down Expand Up @@ -551,16 +545,22 @@ const DeploymentMap: React.FC<DeploymentMapProps> = ({
<PlatformsListModal onClose={handleClosePlatforms} />
) : null}
<Map
key={`deployment-map-${router.asPath}-${vehicleName ?? 'unknown'}`}
ref={mapRef}
className="h-full min-h-0 w-full"
maxZoom={17}
onMapReady={(map) => {
logger.debug('Map is ready!')
mapRef.current = map
}}
onRequestDepth={async (lat, lng) => {
const result = await handleDepthRequestWithFeedback(lat, lng)
return result.depth ?? 0
;[200, 800].forEach((delay) => {
setTimeout(() => {
try {
map.invalidateSize()
} catch (e) {
logger.warn('Could not invalidate map size:', e)
}
}, delay)
})
}}
center={center}
centerZoom={centerZoom}
Expand Down Expand Up @@ -624,6 +624,16 @@ const DeploymentMap: React.FC<DeploymentMapProps> = ({
)
}
>
<MapDepthDisplay
depthRequest={handleDepthRequest}
options={{
warningToastId: 'depth-unavailable',
errorToastId: 'depth-result',
loadingToastId: 'depth-loading',
warningToastClass: 'blue-toast',
toastDuration: 5000,
}}
/>
{selectedStations.map((station) => {
const lng = station.geojson.geometry.coordinates[0]
const lat = station.geojson.geometry.coordinates[1]
Expand Down Expand Up @@ -675,15 +685,15 @@ const DeploymentMap: React.FC<DeploymentMapProps> = ({
disableAutoFit={isTimelineScrubbing}
/>
)}
<VehicleColorsModal
isOpen={colorModalOpen}
onClose={() => setColorModalOpen(false)}
anchorPosition={colorModalPosition}
trackedVehicles={vehicleName ? [vehicleName] : []}
activeVehicle={vehicleName || undefined}
forceShowAll={true}
/>
</Map>
<VehicleColorsModal
isOpen={colorModalOpen}
onClose={() => setColorModalOpen(false)}
anchorPosition={colorModalPosition}
trackedVehicles={vehicleName ? [vehicleName] : []}
activeVehicle={vehicleName || undefined}
forceShowAll={true}
/>
</div>
)
}
Expand Down
58 changes: 11 additions & 47 deletions apps/lrauv-dash2/components/GoogleMapsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,35 @@ import React, { useState, useEffect } from 'react'
import { useGoogleMapsApiKey } from './useGoogleMapsApiKey'
import toast from 'react-hot-toast'
import { createLogger } from '@mbari/utils'
import { initLeafletGoogle } from '../lib/leafletPlugins'

const logger = createLogger('GoogleMapsProvider')

// Global variable to track if script has been loaded
let googleMapsScriptAdded = false

interface GoogleMapsProviderProps {
children: React.ReactNode
}

export const GoogleMapsProvider: React.FC<GoogleMapsProviderProps> = ({
children,
}) => {
const { apiKey, isLoading, error, keySource } = useGoogleMapsApiKey()
const { apiKey, isLoading, keySource } = useGoogleMapsApiKey()
const [isLoaded, setIsLoaded] = useState(false)

// Check if Maps API is already available on mount
useEffect(() => {
if (typeof window !== 'undefined' && window.google?.maps) {
setIsLoaded(true)
logger.debug('Google Maps already available on mount')
}
}, [])

// Handle script loading based on API key
useEffect(() => {
if (isLoading) return

// Log the source of the API key
if (keySource === 'server') {
logger.debug('Using Google Maps from Tethys API')
} else if (keySource === 'local') {
logger.debug('Using Google Maps from local .env')
} else if (keySource === 'none' && !apiKey) {
// Notify but don't block interface
toast.error(
'Google Maps API unavailable - Google Hybrid map and elevation data unavailable‼️ ',
{
Expand All @@ -48,62 +42,32 @@ export const GoogleMapsProvider: React.FC<GoogleMapsProviderProps> = ({
return
}

// If Maps already loaded, nothing more to do
if (window.google?.maps) {
logger.debug('Google Maps already loaded via window.google')
setIsLoaded(true)
return
}

// If we already started loading the script, don't add it again
if (googleMapsScriptAdded) {
logger.debug('Google Maps script tag already added')
return
}

// Load the script if we have an API key
if (apiKey) {
logger.debug('Loading Google Maps API script')

// Check if script already exists
const existingScript = document.getElementById('google-maps-script')
if (existingScript) {
logger.debug('Found existing Google Maps script tag')
return
}
if (!apiKey) return

const script = document.createElement('script')
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`
script.async = true
script.defer = true
script.id = 'google-maps-script'

script.onload = () => {
logger.debug('✅ Google Maps API script loaded successfully')
logger.debug('Loading Google Maps API via initializer')
initLeafletGoogle(apiKey)
.then(() => {
logger.debug('✅ Google Maps API and Leaflet plugins ready')
setIsLoaded(true)
}

script.onerror = (e) => {
logger.error('❌ Google Maps script failed to load:', e)
})
.catch((e) => {
logger.error('❌ Google Maps initializer failed:', e)
toast.error('Google Hybrid map unavailable', {
duration: 3000,
id: 'maps-loading-error',
})
}

document.head.appendChild(script)

// Mark as added to prevent duplicate loading
googleMapsScriptAdded = true
}
})
}, [isLoading, keySource, apiKey])

// Just a minimal loading indicator that doesn't take much space
if (isLoading) {
return <div className="opacity-0">Loading maps...</div>
}

// Always render children - maps will be available when loaded
// This avoids the flash of content and UI blocking
return <>{children}</>
}
2 changes: 1 addition & 1 deletion apps/lrauv-dash2/components/VehicleColorsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useCallback } from 'react'
import { useVehicleColors } from './VehicleColorsContext'
import { Modal } from '@mbari/react-ui/src/Modal/Modal'
import { Modal } from '@mbari/react-ui'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTimes, faEyeDropper } from '@fortawesome/free-solid-svg-icons'
import { SketchPicker } from 'react-color'
Expand Down
23 changes: 19 additions & 4 deletions apps/lrauv-dash2/components/VehiclePath.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@mbari/api-client'
import { Polyline, useMap, Circle, Tooltip } from 'react-leaflet'
import { LatLng, LeafletMouseEventHandlerFn } from 'leaflet'
import { useRouter } from 'next/router'
import { useSharedPath } from './SharedPathContextProvider'
import { distance } from '@turf/turf'
import { parseISO, getTime } from 'date-fns'
Expand Down Expand Up @@ -69,6 +70,7 @@ const VehiclePath: React.FC<VehiclePathProps> = ({
disableAutoFit = false,
}) => {
const map = useMap()
const router = useRouter()
const { sharedPath, dispatch } = useSharedPath()

const { data: lastDeployment } = useLastDeployment(
Expand Down Expand Up @@ -282,13 +284,26 @@ const VehiclePath: React.FC<VehiclePathProps> = ({
])

// OVERVIEW MAP
// Fit bounds for OverViewMap
// Re-run grouped fitBounds on route/tab switches after layout settles.
useEffect(() => {
if (!grouped) return

const coords = Object.values(sharedPath).flat()
if (grouped && coords.length > 1) {
map.fitBounds(coords)
if (coords.length <= 1) return

const applyFit = () => {
try {
map.invalidateSize()
map.fitBounds(coords)
} catch {
// noop; next delayed retry may succeed after layout settles
}
}
}, [sharedPath, grouped, map])

applyFit()
const timers = [250, 800].map((delay) => setTimeout(applyFit, delay))
return () => timers.forEach((t) => clearTimeout(t))
}, [sharedPath, grouped, map, router.asPath])

// Determine Time Difference since last gpsFix
const latest =
Expand Down
3 changes: 1 addition & 2 deletions apps/lrauv-dash2/lib/elevationService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { createLogger } from '@mbari/utils'

// Extend the Window interface
// Extend the Window interface (google is declared in types/global.d.ts)
declare global {
interface Window {
[GOOGLE_MAPS_LOADED_FLAG]?: boolean
google?: any
}
}

Expand Down
Loading