diff --git a/package.json b/package.json index a8b6aeff..0fbc172f 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@floating-ui/react-dom": "^0.4.3", "@headlessui/react": "^1.4.2", "@meshtastic/components": "^1.0.15", - "@meshtastic/meshtasticjs": "^0.6.36", + "@meshtastic/meshtasticjs": "^0.6.37", "@reduxjs/toolkit": "^1.7.1", "base64-js": "^1.5.1", "boring-avatars": "^1.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 074e779a..28426ef3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,7 +4,7 @@ specifiers: '@floating-ui/react-dom': ^0.4.3 '@headlessui/react': ^1.4.2 '@meshtastic/components': ^1.0.15 - '@meshtastic/meshtasticjs': ^0.6.36 + '@meshtastic/meshtasticjs': ^0.6.37 '@reduxjs/toolkit': ^1.7.1 '@types/mapbox-gl': ^2.6.0 '@types/react': ^17.0.38 @@ -62,7 +62,7 @@ dependencies: '@floating-ui/react-dom': 0.4.3_b3482aaf5744fc7c2aeb7941b0e0a78f '@headlessui/react': 1.4.2_react-dom@17.0.2+react@17.0.2 '@meshtastic/components': 1.0.15_@types+react@17.0.38 - '@meshtastic/meshtasticjs': 0.6.36 + '@meshtastic/meshtasticjs': 0.6.37 '@reduxjs/toolkit': 1.7.1_react-redux@7.2.6+react@17.0.2 base64-js: 1.5.1 boring-avatars: 1.6.1 @@ -1535,8 +1535,8 @@ packages: - '@types/react' dev: false - /@meshtastic/meshtasticjs/0.6.36: - resolution: {integrity: sha512-Ibd6guVm1qC1P26smIBY+95WdjYFtWIKjDSDTS2Q0xvrvWT3gtL+kuGf+E+XKHqbtxVSAyJQ6RoG1hCEYRByXw==} + /@meshtastic/meshtasticjs/0.6.37: + resolution: {integrity: sha512-HXl8/eTvZAW9b4MfNxoZa2/qrKP4Y2RlyPP8jQOO+FHy5u/BLuMwAEbA69YqsOZMF9SK5/xNwHlrBnMXwWKHDA==} dependencies: '@protobuf-ts/runtime': 2.1.0 sub-events: 1.8.9 diff --git a/src/components/Map/Marker.tsx b/src/components/Map/Marker.tsx new file mode 100644 index 00000000..b7b16701 --- /dev/null +++ b/src/components/Map/Marker.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import mapbox from 'mapbox-gl'; +import ReactDOMServer from 'react-dom/server'; + +import { useMapbox } from '@hooks/useMapbox'; + +export interface MarkerProps extends Omit { + children?: React.ReactNode; + center: mapbox.LngLatLike; + popup: JSX.Element; +} + +export const Marker = ({ + children, + center, + popup, + ...props +}: MarkerProps): JSX.Element => { + const { map } = useMapbox(); + const ref = React.useRef(document.createElement('div')); + + const addMarker = React.useCallback((): void => { + if (map) { + const marker = new mapbox.Marker(ref.current, props) + .setLngLat(center) + .setPopup( + new mapbox.Popup().setHTML(ReactDOMServer.renderToString(popup)), + ); + marker.addTo(map); + } + }, [map, center, props, popup]); + + React.useEffect(() => { + map?.on('load', () => { + addMarker(); + }); + }, [addMarker, map]); + + React.useEffect(() => { + if (map?.loaded()) { + addMarker(); + } + }, [addMarker, map]); + +
{children}
; + + return ReactDOM.createPortal(children, ref.current); +}; diff --git a/src/components/MapBox/MapboxProvider.tsx b/src/components/MapBox/MapboxProvider.tsx index b895a603..d770287a 100644 --- a/src/components/MapBox/MapboxProvider.tsx +++ b/src/components/MapBox/MapboxProvider.tsx @@ -1,9 +1,5 @@ import React from 'react'; -import mapbox from 'mapbox-gl'; -import { renderToString } from 'react-dom/server'; -import { FiAirplay } from 'react-icons/fi'; - import { setBearing, setLatLng, @@ -14,7 +10,6 @@ import { 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'; @@ -28,17 +23,9 @@ export const MapboxProvider = ({ }: 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(null); - const [markers, setMarkers] = React.useState< - { id: number; marker: mapbox.Marker }[] - >([]); - const [markerElements, setMarkerElements] = React.useState< - { id: number; element: JSX.Element; ref: React.RefObject }[] - >([]); - const map = useCreateMapbox({ ref, accessToken: @@ -52,79 +39,7 @@ export const MapboxProvider = ({ }, }); - 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(); - - const markerElement = markerElements.find( - (element) => element.id === node.number, - ) ?? { - element: ( -
- Test - -
- ), - 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( - -
-
- {node.user?.longName} -
-
    -
  • ID: {node.number}
  • -
-
-
, - ), - ), - ); - - 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', { @@ -151,7 +66,7 @@ export const MapboxProvider = ({ map?.on('pitch', (e) => { dispatch(setPitch(e.target.getPitch())); }); - }, [dispatch, map, updateNodes, mapState.exaggeration]); + }, [dispatch, map, mapState.exaggeration]); React.useEffect(() => { const center = map?.getCenter(); @@ -209,13 +124,6 @@ export const MapboxProvider = ({ } }, [map, mapState.style]); - /** - * Markers - */ - React.useEffect(() => { - updateNodes(); - }, [nodes, updateNodes]); - return ( {children} diff --git a/src/core/slices/mapSlice.ts b/src/core/slices/mapSlice.ts index 4abac745..b0a92f66 100644 --- a/src/core/slices/mapSlice.ts +++ b/src/core/slices/mapSlice.ts @@ -6,6 +6,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; interface MapState { + firstLoad: boolean; latLng: mapboxgl.LngLat; zoom: number; bearing: number; @@ -17,8 +18,9 @@ interface MapState { } const initialState: MapState = { - latLng: new mapboxgl.LngLat(-77.0305, 38.8868), - zoom: 9, + firstLoad: true, + latLng: new mapboxgl.LngLat(0, 0), + zoom: 2, bearing: 0, pitch: 0, accessToken: diff --git a/src/pages/Nodes/NodeCard.tsx b/src/pages/Nodes/NodeCard.tsx index 862efe74..7d20cc06 100644 --- a/src/pages/Nodes/NodeCard.tsx +++ b/src/pages/Nodes/NodeCard.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import mapboxgl from 'mapbox-gl'; +import mapbox from 'mapbox-gl'; import { FaSatellite } from 'react-icons/fa'; -import { FiCode } from 'react-icons/fi'; +import { FiCode, FiMapPin } from 'react-icons/fi'; import { GiLightningFrequency } from 'react-icons/gi'; import { MdAccountCircle, @@ -16,6 +16,7 @@ import { } from 'react-icons/md'; import TimeAgo from 'timeago-react'; +import { Marker } from '@components/Map/Marker'; import type { Node } from '@core/slices/meshtasticSlice'; import { Disclosure } from '@headlessui/react'; import { useMapbox } from '@hooks/useMapbox'; @@ -81,124 +82,135 @@ export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => { // }, [node.positions]); return ( - - + {node.currentPosition && ( + Popup} + > +
+
+ +
+
+
+ )} + + - {myNodeInfo ? ( - - ) : ( -
- )} -
{node.user?.longName}
+ + {myNodeInfo ? ( + + ) : ( +
+ )} +
{node.user?.longName}
-
- {!myNodeInfo && ( - - {node.lastHeard.getTime() ? ( - +
+ {!myNodeInfo && ( + + {node.lastHeard.getTime() ? ( + + ) : ( + 'Never' + )} + + )} +
+ { + e.stopPropagation(); + if (PositionConfidence !== 'none' && node.currentPosition) { + map?.flyTo({ + center: new mapbox.LngLat( + node.currentPosition.longitudeI / 1e7, + node.currentPosition.latitudeI / 1e7, + ), + zoom: 16, + }); + } + }} + icon={ + PositionConfidence === 'high' ? ( + + ) : PositionConfidence === 'low' ? ( + ) : ( - 'Never' - )} -
- )} -
- { - if (PositionConfidence !== 'none' && node.currentPosition) { - map?.flyTo({ - center: new mapboxgl.LngLat( - node.currentPosition.longitudeI / 1e7, - node.currentPosition.latitudeI / 1e7, - ), - zoom: 16, - }); + + ) } - // if (PositionConfidence !== 'none' && node.currentPosition) { - // dispatch( - // setLatLng( - // new mapboxgl.LngLat( - // node.currentPosition.longitudeI / 1e7, - // node.currentPosition.latitudeI / 1e7, - // ), - // ), - // ); - // } - }} - icon={ - PositionConfidence === 'high' ? ( - - ) : PositionConfidence === 'low' ? ( - - ) : ( - - ) - } - /> - - - {myNodeInfo && ( -
-
- - - Firmware Ver: - - {myNodeInfo.firmwareVersion} + /> + + + {myNodeInfo && ( +
+
+ + + Firmware Ver: + + {myNodeInfo.firmwareVersion} +
+
+ + + Freq Bands: + + {myNodeInfo.numBands} +
-
- - - Freq Bands: - - {myNodeInfo.numBands} + )} +
+
+ {Protobuf.HardwareModel[node.user?.hwModel ?? 0]} +
+
+ } />
- )} -
-
- {Protobuf.HardwareModel[node.user?.hwModel ?? 0]} +
+ + SNR: + {node.snr[node.snr.length - 1] < snrAverage ? ( + + ) : ( + + )} + {node.snr[node.snr.length - 1]}, Average: {snrAverage}
-
- } /> +
+ + Sats: + {(node.currentPosition?.satsInView ?? 0) < satsAverage ? ( + + ) : ( + + )} + {node.currentPosition?.satsInView ?? 0}, Average: {satsAverage}
-
-
- - SNR: - {node.snr[node.snr.length - 1] < snrAverage ? ( - - ) : ( - - )} - {node.snr[node.snr.length - 1]}, Average: {snrAverage} -
-
- - Sats: - {(node.currentPosition?.satsInView ?? 0) < satsAverage ? ( - - ) : ( - - )} - {node.currentPosition?.satsInView ?? 0}, Average: {satsAverage} -
- - + + + ); };