Map rework
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
"@floating-ui/react-dom": "^0.4.3",
|
"@floating-ui/react-dom": "^0.4.3",
|
||||||
"@headlessui/react": "^1.4.2",
|
"@headlessui/react": "^1.4.2",
|
||||||
"@meshtastic/components": "^1.0.15",
|
"@meshtastic/components": "^1.0.15",
|
||||||
"@meshtastic/meshtasticjs": "^0.6.36",
|
"@meshtastic/meshtasticjs": "^0.6.37",
|
||||||
"@reduxjs/toolkit": "^1.7.1",
|
"@reduxjs/toolkit": "^1.7.1",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"boring-avatars": "^1.6.1",
|
"boring-avatars": "^1.6.1",
|
||||||
|
|||||||
Generated
+4
-4
@@ -4,7 +4,7 @@ specifiers:
|
|||||||
'@floating-ui/react-dom': ^0.4.3
|
'@floating-ui/react-dom': ^0.4.3
|
||||||
'@headlessui/react': ^1.4.2
|
'@headlessui/react': ^1.4.2
|
||||||
'@meshtastic/components': ^1.0.15
|
'@meshtastic/components': ^1.0.15
|
||||||
'@meshtastic/meshtasticjs': ^0.6.36
|
'@meshtastic/meshtasticjs': ^0.6.37
|
||||||
'@reduxjs/toolkit': ^1.7.1
|
'@reduxjs/toolkit': ^1.7.1
|
||||||
'@types/mapbox-gl': ^2.6.0
|
'@types/mapbox-gl': ^2.6.0
|
||||||
'@types/react': ^17.0.38
|
'@types/react': ^17.0.38
|
||||||
@@ -62,7 +62,7 @@ dependencies:
|
|||||||
'@floating-ui/react-dom': 0.4.3_b3482aaf5744fc7c2aeb7941b0e0a78f
|
'@floating-ui/react-dom': 0.4.3_b3482aaf5744fc7c2aeb7941b0e0a78f
|
||||||
'@headlessui/react': 1.4.2_react-dom@17.0.2+react@17.0.2
|
'@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/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
|
'@reduxjs/toolkit': 1.7.1_react-redux@7.2.6+react@17.0.2
|
||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
boring-avatars: 1.6.1
|
boring-avatars: 1.6.1
|
||||||
@@ -1535,8 +1535,8 @@ packages:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@meshtastic/meshtasticjs/0.6.36:
|
/@meshtastic/meshtasticjs/0.6.37:
|
||||||
resolution: {integrity: sha512-Ibd6guVm1qC1P26smIBY+95WdjYFtWIKjDSDTS2Q0xvrvWT3gtL+kuGf+E+XKHqbtxVSAyJQ6RoG1hCEYRByXw==}
|
resolution: {integrity: sha512-HXl8/eTvZAW9b4MfNxoZa2/qrKP4Y2RlyPP8jQOO+FHy5u/BLuMwAEbA69YqsOZMF9SK5/xNwHlrBnMXwWKHDA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@protobuf-ts/runtime': 2.1.0
|
'@protobuf-ts/runtime': 2.1.0
|
||||||
sub-events: 1.8.9
|
sub-events: 1.8.9
|
||||||
|
|||||||
@@ -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<mapbox.MarkerOptions, 'element'> {
|
||||||
|
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<HTMLDivElement>(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]);
|
||||||
|
|
||||||
|
<div ref={ref}>{children}</div>;
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(children, ref.current);
|
||||||
|
};
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import mapbox from 'mapbox-gl';
|
|
||||||
import { renderToString } from 'react-dom/server';
|
|
||||||
import { FiAirplay } from 'react-icons/fi';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setBearing,
|
setBearing,
|
||||||
setLatLng,
|
setLatLng,
|
||||||
@@ -14,7 +10,6 @@ import {
|
|||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { useCreateMapbox } from '@hooks/useCreateMapbox';
|
import { useCreateMapbox } from '@hooks/useCreateMapbox';
|
||||||
import { Card } from '@meshtastic/components';
|
|
||||||
|
|
||||||
import { MapStyles } from '../Map/styles';
|
import { MapStyles } from '../Map/styles';
|
||||||
import { MapboxContext } from './mapboxContext';
|
import { MapboxContext } from './mapboxContext';
|
||||||
@@ -28,17 +23,9 @@ export const MapboxProvider = ({
|
|||||||
}: MapboxProviderProps): JSX.Element => {
|
}: MapboxProviderProps): JSX.Element => {
|
||||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
const darkMode = useAppSelector((state) => state.app.darkMode);
|
||||||
const mapState = useAppSelector((state) => state.map);
|
const mapState = useAppSelector((state) => state.map);
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const ref = React.useRef<HTMLDivElement>(null);
|
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({
|
const map = useCreateMapbox({
|
||||||
ref,
|
ref,
|
||||||
accessToken:
|
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<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(() => {
|
React.useEffect(() => {
|
||||||
map?.on('load', () => {
|
|
||||||
updateNodes();
|
|
||||||
});
|
|
||||||
map?.on('styledata', () => {
|
map?.on('styledata', () => {
|
||||||
if (!map.getSource('mapbox-dem')) {
|
if (!map.getSource('mapbox-dem')) {
|
||||||
map.addSource('mapbox-dem', {
|
map.addSource('mapbox-dem', {
|
||||||
@@ -151,7 +66,7 @@ export const MapboxProvider = ({
|
|||||||
map?.on('pitch', (e) => {
|
map?.on('pitch', (e) => {
|
||||||
dispatch(setPitch(e.target.getPitch()));
|
dispatch(setPitch(e.target.getPitch()));
|
||||||
});
|
});
|
||||||
}, [dispatch, map, updateNodes, mapState.exaggeration]);
|
}, [dispatch, map, mapState.exaggeration]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const center = map?.getCenter();
|
const center = map?.getCenter();
|
||||||
@@ -209,13 +124,6 @@ export const MapboxProvider = ({
|
|||||||
}
|
}
|
||||||
}, [map, mapState.style]);
|
}, [map, mapState.style]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Markers
|
|
||||||
*/
|
|
||||||
React.useEffect(() => {
|
|
||||||
updateNodes();
|
|
||||||
}, [nodes, updateNodes]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapboxContext.Provider value={{ map, ref }}>
|
<MapboxContext.Provider value={{ map, ref }}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
interface MapState {
|
interface MapState {
|
||||||
|
firstLoad: boolean;
|
||||||
latLng: mapboxgl.LngLat;
|
latLng: mapboxgl.LngLat;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
bearing: number;
|
bearing: number;
|
||||||
@@ -17,8 +18,9 @@ interface MapState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: MapState = {
|
const initialState: MapState = {
|
||||||
latLng: new mapboxgl.LngLat(-77.0305, 38.8868),
|
firstLoad: true,
|
||||||
zoom: 9,
|
latLng: new mapboxgl.LngLat(0, 0),
|
||||||
|
zoom: 2,
|
||||||
bearing: 0,
|
bearing: 0,
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
accessToken:
|
accessToken:
|
||||||
|
|||||||
+126
-114
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapbox from 'mapbox-gl';
|
||||||
import { FaSatellite } from 'react-icons/fa';
|
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 { GiLightningFrequency } from 'react-icons/gi';
|
||||||
import {
|
import {
|
||||||
MdAccountCircle,
|
MdAccountCircle,
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import TimeAgo from 'timeago-react';
|
import TimeAgo from 'timeago-react';
|
||||||
|
|
||||||
|
import { Marker } from '@components/Map/Marker';
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
import type { Node } from '@core/slices/meshtasticSlice';
|
||||||
import { Disclosure } from '@headlessui/react';
|
import { Disclosure } from '@headlessui/react';
|
||||||
import { useMapbox } from '@hooks/useMapbox';
|
import { useMapbox } from '@hooks/useMapbox';
|
||||||
@@ -81,124 +82,135 @@ export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => {
|
|||||||
// }, [node.positions]);
|
// }, [node.positions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Disclosure
|
<>
|
||||||
as="div"
|
{node.currentPosition && (
|
||||||
className="m-2 rounded-md shadow-md bg-gray-50 dark:bg-gray-700"
|
<Marker
|
||||||
>
|
center={
|
||||||
<Disclosure.Button
|
new mapbox.LngLat(
|
||||||
as="div"
|
node.currentPosition.longitudeI / 1e7,
|
||||||
className="flex w-full gap-2 p-2 bg-gray-100 rounded-md shadow-md dark:bg-primaryDark"
|
node.currentPosition.latitudeI / 1e7,
|
||||||
>
|
|
||||||
{myNodeInfo ? (
|
|
||||||
<MdAccountCircle className="my-auto" />
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={`my-auto w-3 h-3 rounded-full ${
|
|
||||||
age === 'young'
|
|
||||||
? 'bg-green-500'
|
|
||||||
: age === 'aging'
|
|
||||||
? 'bg-yellow-500'
|
|
||||||
: age === 'old'
|
|
||||||
? 'bg-red-500'
|
|
||||||
: 'bg-gray-500'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="my-auto">{node.user?.longName}</div>
|
|
||||||
|
|
||||||
<div className="my-auto ml-auto text-xs font-semibold">
|
|
||||||
{!myNodeInfo && (
|
|
||||||
<span>
|
|
||||||
{node.lastHeard.getTime() ? (
|
|
||||||
<TimeAgo datetime={node.lastHeard} />
|
|
||||||
) : (
|
|
||||||
'Never'
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<IconButton
|
|
||||||
disabled={PositionConfidence === 'none'}
|
|
||||||
onClick={(): void => {
|
|
||||||
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' ? (
|
|
||||||
<MdGpsFixed />
|
|
||||||
) : PositionConfidence === 'low' ? (
|
|
||||||
<MdGpsNotFixed />
|
|
||||||
) : (
|
|
||||||
<MdGpsOff />
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
popup={<div>Popup</div>}
|
||||||
</Disclosure.Button>
|
>
|
||||||
<Disclosure.Panel className="p-2">
|
<div className="z-50 bg-blue-500 border-2 border-blue-500 rounded-full bg-opacity-30">
|
||||||
{myNodeInfo && (
|
<div className="m-4 ">
|
||||||
<div>
|
<FiMapPin className="w-5 h-5" />
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="flex">
|
|
||||||
<MdSdStorage className="my-auto" />
|
|
||||||
Firmware Ver:
|
|
||||||
</span>
|
|
||||||
<span>{myNodeInfo.firmwareVersion}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="flex">
|
|
||||||
<GiLightningFrequency className="my-auto" />
|
|
||||||
Freq Bands:
|
|
||||||
</span>
|
|
||||||
<span>{myNodeInfo.numBands}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Marker>
|
||||||
<div className="flex">
|
)}
|
||||||
<div className="my-auto">
|
|
||||||
{Protobuf.HardwareModel[node.user?.hwModel ?? 0]}
|
<Disclosure
|
||||||
</div>
|
as="div"
|
||||||
<div className="ml-auto">
|
className="m-2 rounded-md shadow-md bg-gray-50 dark:bg-gray-700"
|
||||||
<IconButton icon={<FiCode className="w-5 h-5" />} />
|
>
|
||||||
</div>
|
<Disclosure.Button
|
||||||
</div>
|
as="div"
|
||||||
<div className="flex">
|
className="flex w-full gap-2 p-2 bg-gray-100 rounded-md shadow-md dark:bg-primaryDark"
|
||||||
<MdSignalCellularAlt className="my-auto" />
|
>
|
||||||
SNR:
|
{myNodeInfo ? (
|
||||||
{node.snr[node.snr.length - 1] < snrAverage ? (
|
<MdAccountCircle className="my-auto" />
|
||||||
<MdArrowDropDown className="text-red-500" />
|
|
||||||
) : (
|
) : (
|
||||||
<MdArrowDropUp className="text-green-500" />
|
<div
|
||||||
|
className={`my-auto w-3 h-3 rounded-full ${
|
||||||
|
age === 'young'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: age === 'aging'
|
||||||
|
? 'bg-yellow-500'
|
||||||
|
: age === 'old'
|
||||||
|
? 'bg-red-500'
|
||||||
|
: 'bg-gray-500'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{node.snr[node.snr.length - 1]}, Average: {snrAverage}
|
<div className="my-auto">{node.user?.longName}</div>
|
||||||
</div>
|
|
||||||
<div className="flex">
|
<div className="my-auto ml-auto text-xs font-semibold">
|
||||||
<FaSatellite className="my-auto" />
|
{!myNodeInfo && (
|
||||||
Sats:
|
<span>
|
||||||
{(node.currentPosition?.satsInView ?? 0) < satsAverage ? (
|
{node.lastHeard.getTime() ? (
|
||||||
<MdArrowDropDown className="text-red-500" />
|
<TimeAgo datetime={node.lastHeard} />
|
||||||
) : (
|
) : (
|
||||||
<MdArrowDropUp className="text-green-500" />
|
'Never'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
disabled={PositionConfidence === 'none'}
|
||||||
|
onClick={(e): void => {
|
||||||
|
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' ? (
|
||||||
|
<MdGpsFixed />
|
||||||
|
) : PositionConfidence === 'low' ? (
|
||||||
|
<MdGpsNotFixed />
|
||||||
|
) : (
|
||||||
|
<MdGpsOff />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Disclosure.Button>
|
||||||
|
<Disclosure.Panel className="p-2">
|
||||||
|
{myNodeInfo && (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="flex">
|
||||||
|
<MdSdStorage className="my-auto" />
|
||||||
|
Firmware Ver:
|
||||||
|
</span>
|
||||||
|
<span>{myNodeInfo.firmwareVersion}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="flex">
|
||||||
|
<GiLightningFrequency className="my-auto" />
|
||||||
|
Freq Bands:
|
||||||
|
</span>
|
||||||
|
<span>{myNodeInfo.numBands}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{node.currentPosition?.satsInView ?? 0}, Average: {satsAverage}
|
<div className="flex">
|
||||||
</div>
|
<div className="my-auto">
|
||||||
</Disclosure.Panel>
|
{Protobuf.HardwareModel[node.user?.hwModel ?? 0]}
|
||||||
</Disclosure>
|
</div>
|
||||||
|
<div className="ml-auto">
|
||||||
|
<IconButton icon={<FiCode className="w-5 h-5" />} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<MdSignalCellularAlt className="my-auto" />
|
||||||
|
SNR:
|
||||||
|
{node.snr[node.snr.length - 1] < snrAverage ? (
|
||||||
|
<MdArrowDropDown className="text-red-500" />
|
||||||
|
) : (
|
||||||
|
<MdArrowDropUp className="text-green-500" />
|
||||||
|
)}
|
||||||
|
{node.snr[node.snr.length - 1]}, Average: {snrAverage}
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<FaSatellite className="my-auto" />
|
||||||
|
Sats:
|
||||||
|
{(node.currentPosition?.satsInView ?? 0) < satsAverage ? (
|
||||||
|
<MdArrowDropDown className="text-red-500" />
|
||||||
|
) : (
|
||||||
|
<MdArrowDropUp className="text-green-500" />
|
||||||
|
)}
|
||||||
|
{node.currentPosition?.satsInView ?? 0}, Average: {satsAverage}
|
||||||
|
</div>
|
||||||
|
</Disclosure.Panel>
|
||||||
|
</Disclosure>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user