diff options
author | Anton Tananaev <anton@traccar.org> | 2024-04-06 09:22:10 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2024-04-06 09:22:10 -0700 |
commit | f418231b6b2f5e030a0d2dcc390c314602b1f740 (patch) | |
tree | 10326adf3792bc2697e06bb5f2b8ef2a8f7e55fe /src/map/core/MapView.jsx | |
parent | b392a4af78e01c8e0f50aad5468e9583675b24be (diff) | |
download | trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.gz trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.tar.bz2 trackermap-web-f418231b6b2f5e030a0d2dcc390c314602b1f740.zip |
Move modern to the top
Diffstat (limited to 'src/map/core/MapView.jsx')
-rw-r--r-- | src/map/core/MapView.jsx | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/src/map/core/MapView.jsx b/src/map/core/MapView.jsx new file mode 100644 index 00000000..35b3a65a --- /dev/null +++ b/src/map/core/MapView.jsx @@ -0,0 +1,123 @@ +import 'maplibre-gl/dist/maplibre-gl.css'; +import maplibregl from 'maplibre-gl'; +import React, { + useRef, useLayoutEffect, useEffect, useState, +} from 'react'; +import { SwitcherControl } from '../switcher/switcher'; +import { useAttributePreference, usePreference } from '../../common/util/preferences'; +import usePersistedState, { savePersistedState } from '../../common/util/usePersistedState'; +import { mapImages } from './preloadImages'; +import useMapStyles from './useMapStyles'; + +const element = document.createElement('div'); +element.style.width = '100%'; +element.style.height = '100%'; +element.style.boxSizing = 'initial'; + +export const map = new maplibregl.Map({ + container: element, + attributionControl: false, +}); + +let ready = false; +const readyListeners = new Set(); + +const addReadyListener = (listener) => { + readyListeners.add(listener); + listener(ready); +}; + +const removeReadyListener = (listener) => { + readyListeners.delete(listener); +}; + +const updateReadyValue = (value) => { + ready = value; + readyListeners.forEach((listener) => listener(value)); +}; + +const initMap = async () => { + if (ready) return; + if (!map.hasImage('background')) { + Object.entries(mapImages).forEach(([key, value]) => { + map.addImage(key, value, { + pixelRatio: window.devicePixelRatio, + }); + }); + } + updateReadyValue(true); +}; + +map.addControl(new maplibregl.NavigationControl()); + +const switcher = new SwitcherControl( + () => updateReadyValue(false), + (styleId) => savePersistedState('selectedMapStyle', styleId), + () => { + map.once('styledata', () => { + const waiting = () => { + if (!map.loaded()) { + setTimeout(waiting, 33); + } else { + initMap(); + } + }; + waiting(); + }); + }, +); + +map.addControl(switcher); + +const MapView = ({ children }) => { + const containerEl = useRef(null); + + const [mapReady, setMapReady] = useState(false); + + const mapStyles = useMapStyles(); + const activeMapStyles = useAttributePreference('activeMapStyles', 'locationIqStreets,osm,carto'); + const [defaultMapStyle] = usePersistedState('selectedMapStyle', usePreference('map', 'locationIqStreets')); + const mapboxAccessToken = useAttributePreference('mapboxAccessToken'); + const maxZoom = useAttributePreference('web.maxZoom'); + + useEffect(() => { + if (maxZoom) { + map.setMaxZoom(maxZoom); + } + }, [maxZoom]); + + useEffect(() => { + maplibregl.accessToken = mapboxAccessToken; + }, [mapboxAccessToken]); + + useEffect(() => { + const filteredStyles = mapStyles.filter((s) => s.available && activeMapStyles.includes(s.id)); + const styles = filteredStyles.length ? filteredStyles : mapStyles.filter((s) => s.id === 'osm'); + switcher.updateStyles(styles, defaultMapStyle); + }, [mapStyles, defaultMapStyle]); + + useEffect(() => { + const listener = (ready) => setMapReady(ready); + addReadyListener(listener); + return () => { + removeReadyListener(listener); + }; + }, []); + + useLayoutEffect(() => { + const currentEl = containerEl.current; + currentEl.appendChild(element); + map.resize(); + return () => { + currentEl.removeChild(element); + }; + }, [containerEl]); + + return ( + <div style={{ width: '100%', height: '100%' }} ref={containerEl}> + {mapReady && children} + </div> + ); +}; + +export default MapView; |