Map logic rework

This commit is contained in:
Sacha Weatherstone
2022-01-03 20:17:47 +11:00
parent b9364014d9
commit d5e1ae4941
44 changed files with 469 additions and 382 deletions
+8 -2
View File
@@ -4,7 +4,6 @@ import { ErrorBoundary } from 'react-error-boundary';
import { FiBell } from 'react-icons/fi';
import { DeviceStatus } from '@app/components/menu/buttons/DeviceStatus';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { Connection } from '@components/Connection';
import { MobileNavToggle } from '@components/menu/buttons/MobileNavToggle';
import { Notifications } from '@components/menu/buttons/Notifications';
@@ -14,6 +13,8 @@ import { MobileNav } from '@components/menu/MobileNav';
import { Navigation } from '@components/menu/Navigation';
import { useRoute } from '@core/router';
import { requestNotificationPermission } from '@core/utils/notifications';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Messages } from '@pages/Messages';
import { Nodes } from '@pages/Nodes/Index';
import { NotFound } from '@pages/NotFound';
@@ -21,6 +22,7 @@ import { Plugins } from '@pages/Plugins/Index';
import { Settings } from '@pages/settings/Index';
import { ErrorFallback } from './components/ErrorFallback';
import { MapboxProvider } from './components/MapBox/MapboxProvider';
import { addNotification, removeNotification } from './core/slices/appSlice';
export const App = (): JSX.Element => {
@@ -86,7 +88,11 @@ export const App = (): JSX.Element => {
<div className="flex w-full bg-gray-100 md:shadow-xl md:overflow-hidden dark:bg-secondaryDark md:rounded-b-3xl">
<ErrorBoundary FallbackComponent={ErrorFallback}>
{route.name === 'messages' && <Messages />}
{route.name === 'nodes' && <Nodes />}
{route.name === 'nodes' && (
<MapboxProvider>
<Nodes />
</MapboxProvider>
)}
{route.name === 'plugins' && <Plugins />}
{route.name === 'settings' && <Settings />}
{route.name === false && <NotFound />}
+2 -1
View File
@@ -1,6 +1,5 @@
import React from 'react';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { BLE } from '@components/connection/BLE';
import { HTTP } from '@components/connection/HTTP';
import { Serial } from '@components/connection/Serial';
@@ -12,6 +11,8 @@ import {
setConnectionParams,
setConnType,
} from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Button, Card, Select } from '@meshtastic/components';
import { Types } from '@meshtastic/meshtasticjs';
+7 -154
View File
@@ -1,22 +1,17 @@
import React from 'react';
import mapboxgl from 'mapbox-gl';
import { renderToString } from 'react-dom/server';
import { FaDirections, FaGlobeAfrica, FaMountain } from 'react-icons/fa';
import { MdFullscreen, MdRadar, MdWbShade } from 'react-icons/md';
import { useMapbox } from '@app/hooks/mapbox';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import {
setBearing,
setExaggeration,
setHillShade,
setLatLng,
setMapStyle,
setPitch,
setZoom,
} from '@core/slices/mapSlice';
import { Card, IconButton } from '@meshtastic/components';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { useMapbox } from '@hooks/useMapbox';
import { IconButton } from '@meshtastic/components';
import type { MapStyle } from './styles';
import { MapStyles } from './styles';
@@ -24,63 +19,10 @@ import { MapStyles } from './styles';
export const Map = (): JSX.Element => {
const dispatch = useAppDispatch();
const darkMode = useAppSelector((state) => state.app.darkMode);
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const mapState = useAppSelector((state) => state.map);
const [markers, setMarkers] = React.useState<
{ id: number; marker: mapboxgl.Marker }[]
>([]);
const mapRef = React.useRef<HTMLDivElement>(null);
const map = useMapbox(mapRef, mapState.accessToken, {
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 marker =
existingMarker ??
new mapboxgl.Marker({}).setLngLat([0, 0]).addTo(map);
marker
.setLngLat([
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
])
.setPopup(
new mapboxgl.Popup().setHTML(
renderToString(
<Card>
<div className="text-xl font-medium">
{node.user?.longName}
</div>
<ul>
<li>ID: {node.number}</li>
</ul>
</Card>,
),
),
);
if (!existingMarker) {
setMarkers((markers) => [
...markers,
{
id: node.number,
marker,
},
]);
}
}
});
}, [markers, map, nodes]);
const { ref } = useMapbox();
const ChangeMapStyle = React.useCallback(
(styleName: string, style: MapStyle) => {
@@ -97,95 +39,6 @@ export const Map = (): JSX.Element => {
[dispatch, darkMode, mapState.style.title],
);
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]);
/**
* 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 (
<div className="relative flex w-full h-full">
<div className="fixed right-0 z-20 p-2 m-4 space-y-2 bg-white rounded-md shadow-md md:mx-10 dark:bg-primaryDark">
@@ -232,7 +85,7 @@ export const Map = (): JSX.Element => {
<IconButton icon={<MdRadar />} />
</div>
<div className="flex w-full h-full">
<div className="flex-grow w-full h-full" ref={mapRef} />
<div className="flex-grow w-full h-full" ref={ref} />
</div>
</div>
);
+224
View File
@@ -0,0 +1,224 @@
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>
);
};
+12
View File
@@ -0,0 +1,12 @@
import React from 'react';
import type mapbox from 'mapbox-gl';
export interface MapboxContextValue {
ref: React.Ref<HTMLDivElement>;
map?: mapbox.Map;
}
export const MapboxContext = React.createContext<MapboxContextValue>(
{} as MapboxContextValue
);
+2 -1
View File
@@ -3,9 +3,10 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { FiSend } from 'react-icons/fi';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { connection } from '@core/connection';
import { ackMessage } from '@core/slices/meshtasticSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { IconButton, Input, Select } from '@meshtastic/components';
export interface MessageBarProps {
+2 -2
View File
@@ -10,7 +10,7 @@ import { Button, IconButton } from '@meshtastic/components';
export const BLE = (): JSX.Element => {
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
const { register, handleSubmit, control } = useForm<{
const { handleSubmit } = useForm<{
device?: BluetoothDevice;
}>();
@@ -23,7 +23,7 @@ export const BLE = (): JSX.Element => {
void updateBleDeviceList();
}, [updateBleDeviceList]);
const onSubmit = handleSubmit(async (data) => {
const onSubmit = handleSubmit(async () => {
await setConnection(connType.BLE);
});
+1 -1
View File
@@ -2,9 +2,9 @@ import type React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useAppDispatch } from '@app/hooks/redux.js';
import { connectionUrl, setConnection } from '@core/connection';
import { connType, setConnectionParams } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { Button, Checkbox, Input, Select } from '@meshtastic/components';
export const HTTP = (): JSX.Element => {
+3 -3
View File
@@ -3,16 +3,16 @@ import React from 'react';
import { useForm } from 'react-hook-form';
import { FiCheck } from 'react-icons/fi';
import { useAppDispatch } from '@app/hooks/redux';
import { serial, setConnection } from '@core/connection';
import { connType, setConnectionParams } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { Button, IconButton } from '@meshtastic/components';
export const Serial = (): JSX.Element => {
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
const dispatch = useAppDispatch();
const { register, handleSubmit, control } = useForm<{
const { handleSubmit } = useForm<{
device?: SerialPort;
}>();
@@ -25,7 +25,7 @@ export const Serial = (): JSX.Element => {
void updateSerialDeviceList();
}, [updateSerialDeviceList]);
const onSubmit = handleSubmit(async (data) => {
const onSubmit = handleSubmit(async () => {
await setConnection(connType.SERIAL);
});
+1 -1
View File
@@ -1,7 +1,7 @@
import type React from 'react';
import { useAppSelector } from '@app/hooks/redux';
import { Dialog } from '@headlessui/react';
import { useAppSelector } from '@hooks/useAppSelector';
type DefaultDivProps = JSX.IntrinsicElements['div'];
@@ -5,7 +5,7 @@ import type { Theme } from 'react-select';
import ReactSelect from 'react-select';
import { bitwiseDecode, bitwiseEncode } from '@app/core/utils/bitwise';
import { useAppSelector } from '@app/hooks/redux.js';
import { useAppSelector } from '@hooks/useAppSelector';
import { Label } from './Label';
+2 -1
View File
@@ -1,8 +1,9 @@
import type React from 'react';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { Drawer } from '@components/generic/Drawer';
import { closeMobileNav } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Logo } from './Logo';
import { Navigation } from './Navigation';
+2 -1
View File
@@ -2,8 +2,9 @@ import type React from 'react';
import { FiBluetooth, FiCpu, FiWifi } from 'react-icons/fi';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { connType, openConnectionModal } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Button } from '@meshtastic/components';
import { Types } from '@meshtastic/meshtasticjs';
@@ -3,10 +3,9 @@ import type React from 'react';
import { FiMenu } from 'react-icons/fi';
import { openMobileNav } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { IconButton } from '@meshtastic/components';
import { useAppDispatch } from '../../../hooks/redux';
export const MobileNavToggle = (): JSX.Element => {
const dispatch = useAppDispatch();
@@ -2,9 +2,9 @@ import React from 'react';
import { FiBell, FiX } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { shift, useFloating } from '@floating-ui/react-dom';
import { Popover } from '@headlessui/react';
import { useAppSelector } from '@hooks/useAppSelector';
import { Button, IconButton } from '@meshtastic/components';
export const Notifications = (): JSX.Element => {
+2 -1
View File
@@ -2,8 +2,9 @@ import type React from 'react';
import { FiMoon, FiSun } from 'react-icons/fi';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { setDarkModeEnabled } from '@core/slices/appSlice';
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { IconButton } from '@meshtastic/components';
export const ThemeToggle = (): JSX.Element => {
+1 -1
View File
@@ -2,11 +2,11 @@ import React from 'react';
import { FiXCircle } from 'react-icons/fi';
import { useBreakpoint } from '@app/hooks/breakpoint';
import { Drawer } from '@components/generic/Drawer';
import type { SidebarItemProps } from '@components/generic/SidebarItem';
import { SidebarItem } from '@components/generic/SidebarItem';
import { Tab } from '@headlessui/react';
import { useBreakpoint } from '@hooks/useBreakpoint';
import { IconButton } from '@meshtastic/components';
export interface PageLayoutProps {
+1 -1
View File
@@ -23,7 +23,7 @@ import {
Types,
} from '@meshtastic/meshtasticjs';
import { showNotification } from './utils/notifications.js';
import { showNotification } from './utils/notifications';
type connectionType = IBLEConnection | IHTTPConnection | ISerialConnection;
-28
View File
@@ -1,28 +0,0 @@
import 'mapbox-gl/dist/mapbox-gl.css';
import React from 'react';
import mapboxgl from 'mapbox-gl';
type AccessToken = string;
export function useMapbox(
ref: React.RefObject<HTMLDivElement>,
accessToken: AccessToken,
options?: Partial<mapboxgl.MapboxOptions>,
): mapboxgl.Map | undefined {
const [mapInstance, setMapInstance] = React.useState<mapboxgl.Map>();
React.useEffect(() => {
const container = ref.current;
if (mapInstance || !container) {
return;
}
mapboxgl.accessToken = accessToken;
const mergedOptions = { container, ...options };
const map = new mapboxgl.Map(mergedOptions);
setMapInstance(map);
}, [ref, accessToken, options, mapInstance]);
return mapInstance;
}
-9
View File
@@ -1,9 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import type { TypedUseSelectorHook } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import type { AppDispatch, RootState } from '@core/store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
+7
View File
@@ -0,0 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useDispatch } from 'react-redux';
import type { AppDispatch } from '@core/store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
+6
View File
@@ -0,0 +1,6 @@
import type { TypedUseSelectorHook } from 'react-redux';
import { useSelector } from 'react-redux';
import type { RootState } from '@core/store';
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
+36
View File
@@ -0,0 +1,36 @@
import 'mapbox-gl/dist/mapbox-gl.css';
import React from 'react';
import mapboxgl from 'mapbox-gl';
type AccessToken = string;
export interface useMapboxProps {
ref: React.RefObject<HTMLDivElement>;
accessToken: AccessToken;
options?: Partial<mapboxgl.MapboxOptions>;
}
export function useCreateMapbox({
ref,
accessToken,
options,
}: useMapboxProps): mapboxgl.Map | undefined {
const [mapInstance, setMapInstance] = React.useState<mapboxgl.Map>();
React.useEffect(() => {
const container = ref.current as HTMLDivElement;
if (mapInstance || !container) {
return;
}
mapboxgl.accessToken = accessToken;
const map = new mapboxgl.Map({
container,
antialias: true,
...options,
});
setMapInstance(map);
}, []);
return mapInstance;
}
+8
View File
@@ -0,0 +1,8 @@
import React from 'react';
import type { MapboxContextValue } from '@components/MapBox/mapboxContext';
import { MapboxContext } from '@components/MapBox/mapboxContext';
export const useMapbox = (): MapboxContextValue => {
return React.useContext(MapboxContext);
};
+12
View File
@@ -21,3 +21,15 @@
::-webkit-scrollbar-thumb:hover {
background-color: #a8bbbf;
}
.mapboxgl-popup-close-button,
.mapboxgl-popup-tip {
display: none;
}
.mapboxgl-popup-content {
background: transparent !important;
border-radius: 0 !important;
box-sizing: unset !important;
padding: 0 !important;
}
+1 -2
View File
@@ -4,11 +4,10 @@ import { FiHash } from 'react-icons/fi';
import { Message } from '@components/chat/Message';
import { MessageBar } from '@components/chat/MessageBar';
import { useAppSelector } from '@hooks/useAppSelector';
import { Select } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';
import { useAppSelector } from '../hooks/redux';
export const Messages = (): JSX.Element => {
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
+3 -2
View File
@@ -2,10 +2,10 @@ import React from 'react';
import { FiXCircle } from 'react-icons/fi';
import { useBreakpoint } from '@app/hooks/breakpoint';
import { useAppSelector } from '@app/hooks/redux';
import { Drawer } from '@components/generic/Drawer';
import { Map } from '@components/Map';
import { useAppSelector } from '@hooks/useAppSelector';
import { useBreakpoint } from '@hooks/useBreakpoint';
import { IconButton } from '@meshtastic/components';
import { NodeCard } from './NodeCard';
@@ -60,6 +60,7 @@ export const Nodes = (): JSX.Element => {
<NodeCard key={node.number} node={node} />
))}
</Drawer>
<Map />
</div>
);
+20 -11
View File
@@ -16,10 +16,9 @@ import {
} from 'react-icons/md';
import TimeAgo from 'timeago-react';
import { setLatLng } from '@app/core/slices/mapSlice.js';
import { useAppDispatch } from '@app/hooks/redux.js';
import type { Node } from '@core/slices/meshtasticSlice';
import { Disclosure } from '@headlessui/react';
import { useMapbox } from '@hooks/useMapbox';
import { IconButton } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';
@@ -32,9 +31,10 @@ export interface NodeCardProps {
}
export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => {
const dispatch = useAppDispatch();
const [snrAverage, setSnrAverage] = React.useState(0);
const [satsAverage, setSatsAverage] = React.useState(0);
const { map } = useMapbox();
React.useEffect(() => {
setSnrAverage(
node.snr
@@ -119,17 +119,26 @@ export const NodeCard = ({ node, myNodeInfo }: NodeCardProps): JSX.Element => {
</div>
<IconButton
disabled={PositionConfidence === 'none'}
onClick={() => {
onClick={(): void => {
if (PositionConfidence !== 'none' && node.currentPosition) {
dispatch(
setLatLng(
new mapboxgl.LngLat(
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
),
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' ? (
+1 -1
View File
@@ -4,9 +4,9 @@ import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { FormFooter } from '@app/components/FormFooter';
import { useAppSelector } from '@app/hooks/redux';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
+1 -1
View File
@@ -3,10 +3,10 @@ import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
+1 -1
View File
@@ -3,10 +3,10 @@ import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
+1 -1
View File
@@ -3,10 +3,10 @@ import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiMenu } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
+1 -1
View File
@@ -4,13 +4,13 @@ import { useForm, useWatch } from 'react-hook-form';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { Channel } from '@components/Channel';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { Loading } from '@components/generic/Loading';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import {
Card,
Checkbox,
+1 -1
View File
@@ -10,7 +10,7 @@ import {
FiZap,
} from 'react-icons/fi';
import type { SidebarItemProps } from '@app/components/generic/SidebarItem.js';
import type { SidebarItemProps } from '@app/components/generic/SidebarItem';
import { PageLayout } from '@components/templates/PageLayout';
import { Channels } from './Channels';
+1 -1
View File
@@ -6,12 +6,12 @@ import JSONPretty from 'react-json-pretty';
import type { Theme } from 'react-select';
import ReactSelect from 'react-select';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { Label } from '@components/generic/form/Label';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import {
Card,
Checkbox,
+1 -1
View File
@@ -4,11 +4,11 @@ import { useForm } from 'react-hook-form';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Select } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';
+1 -1
View File
@@ -4,11 +4,11 @@ import { useForm } from 'react-hook-form';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Select } from '@meshtastic/components';
import { Protobuf } from '@meshtastic/meshtasticjs';
+1 -1
View File
@@ -5,11 +5,11 @@ import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { base16 } from 'rfc4648';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import {
Card,
Checkbox,
+1 -1
View File
@@ -5,11 +5,11 @@ import { useTranslation } from 'react-i18next';
import { FiCode, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { FormFooter } from '@components/FormFooter';
import { Cover } from '@components/generic/Cover';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { connection } from '@core/connection';
import { useAppSelector } from '@hooks/useAppSelector';
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
import type { Protobuf } from '@meshtastic/meshtasticjs';