From f418231b6b2f5e030a0d2dcc390c314602b1f740 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 6 Apr 2024 09:22:10 -0700 Subject: Move modern to the top --- src/map/core/MapView.jsx | 123 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/map/core/MapView.jsx (limited to 'src/map/core/MapView.jsx') 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 ( +
+ {mapReady && children} +
+ ); +}; + +export default MapView; -- cgit v1.2.3