Files
meshtastic-web/src/components/MapBox/MapboxProvider.tsx
T
2022-01-03 20:17:47 +11:00

225 lines
5.7 KiB
TypeScript

import React from 'react';
import mapbox from 'mapbox-gl';
import { renderToString } from 'react-dom/server';
import { FiAirplay } from 'react-icons/fi';
import {
setBearing,
setLatLng,
setMapStyle,
setPitch,
setZoom,
} from '@core/slices/mapSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { useCreateMapbox } from '@hooks/useCreateMapbox';
import { Card } from '@meshtastic/components';
import { MapStyles } from '../Map/styles';
import { MapboxContext } from './mapboxContext';
export type MapboxProviderProps = {
children: React.ReactNode;
};
export const MapboxProvider = ({
children,
}: MapboxProviderProps): JSX.Element => {
const darkMode = useAppSelector((state) => state.app.darkMode);
const mapState = useAppSelector((state) => state.map);
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const dispatch = useAppDispatch();
const ref = React.useRef<HTMLDivElement>(null);
const [markers, setMarkers] = React.useState<
{ id: number; marker: mapbox.Marker }[]
>([]);
const [markerElements, setMarkerElements] = React.useState<
{ id: number; element: JSX.Element; ref: React.RefObject<HTMLDivElement> }[]
>([]);
const map = useCreateMapbox({
ref,
accessToken:
'pk.eyJ1Ijoic2FjaGF3IiwiYSI6ImNrNW9meXozZjBsdW0zbHBjM2FnNnV6cmsifQ.3E4n8eFGD9ZOFo-XDVeZnQ',
options: {
center: mapState.latLng,
zoom: mapState.zoom,
bearing: mapState.bearing,
pitch: mapState.pitch,
style: mapState.style.url,
},
});
const updateNodes = React.useCallback(() => {
nodes.map((node) => {
if (map?.loaded() && node.currentPosition) {
const existingMarker = markers.find(
(marker) => marker.id === node.number,
)?.marker;
const tmpRef = React.createRef<HTMLDivElement>();
const markerElement = markerElements.find(
(element) => element.id === node.number,
) ?? {
element: (
<div ref={tmpRef}>
Test
<FiAirplay />
</div>
),
id: node.number,
ref: tmpRef,
};
console.log(markerElement);
const marker =
existingMarker ??
new mapbox.Marker(markerElement.ref.current ?? undefined, {})
.setLngLat([0, 0])
.addTo(map);
marker
.setLngLat([
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
])
.setPopup(
new mapbox.Popup().setHTML(
renderToString(
<Card>
<div className="p-2">
<div className="text-xl font-medium">
{node.user?.longName}
</div>
<ul>
<li>ID: {node.number}</li>
</ul>
</div>
</Card>,
),
),
);
if (!existingMarker) {
setMarkers((markers) => [
...markers,
{
id: node.number,
marker,
},
]);
setMarkerElements((markerElements) => [
...markerElements,
markerElement,
]);
}
}
});
}, [markers, markerElements, map, nodes]);
React.useEffect(() => {
map?.on('load', () => {
updateNodes();
});
map?.on('styledata', () => {
if (!map.getSource('mapbox-dem')) {
map.addSource('mapbox-dem', {
type: 'raster-dem',
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14,
});
}
map.setTerrain({
source: 'mapbox-dem',
exaggeration: mapState.exaggeration ? 1.5 : 0,
});
});
map?.on('dragend', (e) => {
dispatch(setLatLng(e.target.getCenter()));
});
map?.on('zoomend', (e) => {
dispatch(setZoom(e.target.getZoom()));
});
map?.on('rotate', (e) => {
dispatch(setBearing(e.target.getBearing()));
});
map?.on('pitch', (e) => {
dispatch(setPitch(e.target.getPitch()));
});
}, [dispatch, map, updateNodes, mapState.exaggeration]);
React.useEffect(() => {
const center = map?.getCenter();
if (center !== mapState.latLng) {
map?.setCenter(mapState.latLng);
}
}, [map, mapState.latLng]);
React.useEffect(() => {
if (['Light', 'Dark'].includes(mapState.style.title)) {
dispatch(setMapStyle(darkMode ? MapStyles.Dark : MapStyles.Light));
}
}, [dispatch, darkMode, mapState.style.title]);
/**
* Hill Shading
*/
React.useEffect(() => {
if (map?.loaded()) {
if (mapState.hillShade) {
map.addLayer(
{
id: 'hillshading',
source: 'mapbox-dem',
type: 'hillshade',
// insert below waterway-river-canal-shadow;
// where hillshading sits in the Mapbox Outdoors style
},
'waterway-river-canal-shadow',
);
} else {
map.removeLayer('hillshading');
}
}
}, [map, mapState.hillShade]);
/**
* Exaggeration
*/
React.useEffect(() => {
if (map?.loaded()) {
map.setTerrain({
source: 'mapbox-dem',
exaggeration: mapState.exaggeration ? 1.5 : 0,
});
}
}, [map, mapState.exaggeration]);
/**
* Map Style
*/
React.useEffect(() => {
if (map?.loaded()) {
map.setStyle(mapState.style.url);
}
}, [map, mapState.style]);
/**
* Markers
*/
React.useEffect(() => {
updateNodes();
}, [nodes, updateNodes]);
return (
<MapboxContext.Provider value={{ map, ref }}>
{children}
</MapboxContext.Provider>
);
};