Browse Source

WIP 1.3

pull/22/head
Sacha Weatherstone 4 years ago
parent
commit
806e70b3cf
No known key found for this signature in database GPG Key ID: 7AB2D7E206124B31
  1. 25
      package.json
  2. 1524
      pnpm-lock.yaml
  3. 47
      src/components/Connection.tsx
  4. 20
      src/components/layout/Sidebar/Settings/GPS.tsx
  5. 4
      src/components/layout/Sidebar/Settings/Index.tsx
  6. 6
      src/components/layout/Sidebar/Settings/LoRa.tsx
  7. 11
      src/components/layout/Sidebar/Settings/Power.tsx
  8. 2
      src/components/layout/Sidebar/Settings/User.tsx
  9. 16
      src/components/layout/Sidebar/Settings/modules/Serial.tsx
  10. 2
      src/components/layout/Sidebar/Settings/modules/Telemetry.tsx
  11. 60
      src/components/menu/BottomNav.tsx
  12. 100
      src/core/slices/meshtasticSlice.ts
  13. 11
      src/pages/Extensions/Debug.tsx
  14. 4
      src/pages/Extensions/Info.tsx
  15. 20
      src/pages/Map/index.tsx
  16. 4
      src/pages/Messages/ChannelChat.tsx
  17. 12
      src/pages/Messages/DmChat.tsx
  18. 6
      src/pages/Messages/Message.tsx
  19. 8
      src/pages/Messages/index.tsx
  20. 36
      src/pages/Nodes/NodeCard.tsx
  21. 40
      src/pages/Nodes/index.tsx
  22. 2
      types/static.d.ts

25
package.json

@ -22,18 +22,18 @@
"dependencies": {
"@emeraldpay/hashicon-react": "^0.5.2",
"@meshtastic/eslint-config": "^1.0.7",
"@meshtastic/meshtasticjs": "^0.6.51",
"@meshtastic/meshtasticjs": "^0.6.54",
"@reduxjs/toolkit": "^1.8.0",
"@tippyjs/react": "^4.2.6",
"base64-js": "^1.5.1",
"framer-motion": "^6.2.8",
"mapbox-gl": "^2.7.0",
"prettier": "^2.5.1",
"mapbox-gl": "^2.7.1",
"prettier": "^2.6.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.4",
"react-flow-renderer": "^10.0.0-next.51",
"react-hook-form": "^7.27.1",
"react-flow-renderer": "^10.0.6",
"react-hook-form": "^7.28.1",
"react-icons": "^4.3.1",
"react-json-pretty": "^2.2.0",
"react-multi-select-component": "^4.2.3",
@ -48,23 +48,24 @@
},
"devDependencies": {
"@hookform/devtools": "^4.0.2",
"@types/chrome": "^0.0.180",
"@types/mapbox-gl": "^2.6.3",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.13",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"@types/w3c-web-serial": "^1.0.2",
"@types/web-bluetooth": "^0.0.12",
"@types/web-bluetooth": "^0.0.14",
"@vitejs/plugin-react": "^1.2.0",
"autoprefixer": "^10.4.2",
"autoprefixer": "^10.4.4",
"gzipper": "^7.0.0",
"postcss": "^8.4.8",
"postcss": "^8.4.12",
"rollup-plugin-visualizer": "^5.6.0",
"tailwindcss": "^3.0.23",
"tar": "^6.1.11",
"typescript": "^4.6.2",
"typescript": "^4.6.3",
"unimported": "^1.19.1",
"vite": "^2.8.6",
"vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-pwa": "^0.11.13",
"workbox-window": "^6.5.1"
"workbox-window": "^6.5.2"
}
}

1524
pnpm-lock.yaml

File diff suppressed because it is too large

47
src/components/Connection.tsx

@ -24,6 +24,7 @@ export const Connection = (): JSX.Element => {
const meshtasticState = useAppSelector((state) => state.meshtastic);
const appState = useAppSelector((state) => state.app);
const chromiunm = !!window.chrome;
useEffect(() => {
if (!import.meta.env.VITE_PUBLIC_HOSTED) {
@ -79,22 +80,36 @@ export const Connection = (): JSX.Element => {
}
/>
)}
{appState.connType === connType.BLE && (
<BLE
connecting={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
{appState.connType === connType.SERIAL && (
<Serial
connecting={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
{appState.connType === connType.BLE &&
(chromiunm ? (
<BLE
connecting={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
) : (
<div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
<p>Unsupported.</p>
<p>Please use a Chromium based browser.</p>
</div>
))}
{appState.connType === connType.SERIAL &&
(chromiunm ? (
<Serial
connecting={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
) : (
<div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
<p>Unsupported.</p>
<p>Please use a Chromium based browser.</p>
</div>
))}
</div>
</div>
<div className="md:w-1/2">

20
src/components/layout/Sidebar/Settings/GPS.tsx

@ -8,7 +8,6 @@ import { Checkbox } from '@components/generic/form/Checkbox';
import { Form } from '@components/generic/form/Form';
import { Input } from '@components/generic/form/Input';
import { Label } from '@components/generic/form/Label';
import { Select } from '@components/generic/form/Select';
import { connection } from '@core/connection';
import { bitwiseDecode, bitwiseEncode } from '@core/utils/bitwise';
import { useAppSelector } from '@hooks/useAppSelector';
@ -25,7 +24,7 @@ export const GPS = (): JSX.Element => {
...preferences,
positionBroadcastSecs:
preferences.positionBroadcastSecs === 0
? preferences.isRouter
? preferences.role === Protobuf.Role.Router
? 43200
: 900
: preferences.positionBroadcastSecs,
@ -54,20 +53,15 @@ export const GPS = (): JSX.Element => {
{...register('positionBroadcastSecs', { valueAsNumber: true })}
/>
<Checkbox
label="Use Smart Position"
{...register('positionBroadcastSmart')}
label="Disable Smart Position"
{...register('positionBroadcastSmartDisabled')}
/>
<Checkbox label="Use Fixed Position" {...register('fixedPosition')} />
<Select
label="Location Sharing"
optionsEnum={Protobuf.LocationSharing}
{...register('locationShare', { valueAsNumber: true })}
/>
<Select
label="GPS Mode"
optionsEnum={Protobuf.GpsOperation}
{...register('gpsOperation', { valueAsNumber: true })}
<Checkbox
label="Disable Location Sharing"
{...register('locationShareDisabled')}
/>
<Checkbox label="Disable GPS" {...register('gpsDisabled')} />
<Input
label="GPS Update Interval"
type="number"

4
src/components/layout/Sidebar/Settings/Index.tsx

@ -51,7 +51,7 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
const {
rangeTestModuleEnabled,
extNotificationModuleEnabled,
serialmoduleEnabled,
serialModuleEnabled,
storeForwardModuleEnabled,
mqttDisabled,
cannedMessageModuleEnabled,
@ -132,7 +132,7 @@ export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
<CollapsibleSection
icon={<FiAlignLeft />}
title="Serial"
status={serialmoduleEnabled}
status={serialModuleEnabled}
>
<SerialSettingsPanel />
</CollapsibleSection>

6
src/components/layout/Sidebar/Settings/LoRa.tsx

@ -42,7 +42,11 @@ export const LoRa = (): JSX.Element => {
{...register('hopLimit', { valueAsNumber: true })}
/>
<Checkbox label="Transmit Disabled" {...register('isLoraTxDisabled')} />
<Checkbox label="Router Mode" {...register('isRouter')} />
<Select
label="Operating Role"
optionsEnum={Protobuf.Role}
{...register('role')}
/>
<Input
label="Send Owner Interval"
type="number"

11
src/components/layout/Sidebar/Settings/Power.tsx

@ -20,7 +20,10 @@ export const Power = (): JSX.Element => {
useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: {
...preferences,
isLowPower: preferences.isRouter ? true : preferences.isLowPower,
isLowPower:
preferences.role === Protobuf.Role.Router
? true
: preferences.isLowPower,
},
});
@ -45,9 +48,11 @@ export const Power = (): JSX.Element => {
/>
<Checkbox
label="Powered by low power source (solar)"
disabled={preferences.isRouter}
disabled={preferences.role === Protobuf.Role.Router}
validationMessage={
preferences.isRouter ? 'Enabled by default in router mode' : ''
preferences.role === Protobuf.Role.Router
? 'Enabled by default in router mode'
: ''
}
{...register('isLowPower')}
/>

2
src/components/layout/Sidebar/Settings/User.tsx

@ -18,7 +18,7 @@ export const User = (): JSX.Element => {
(state) => state.meshtastic.radio.hardware,
).myNodeNum;
const node = useAppSelector((state) => state.meshtastic.nodes).find(
(node) => node.number === myNodeNum,
(node) => node.num === myNodeNum,
);
const { register, handleSubmit, formState, reset } = useForm<{
longName: string;

16
src/components/layout/Sidebar/Settings/modules/Serial.tsx

@ -37,24 +37,24 @@ export const SerialSettingsPanel = (): JSX.Element => {
const moduleEnabled = useWatch({
control,
name: 'serialmoduleEnabled',
name: 'serialModuleEnabled',
defaultValue: false,
});
return (
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
<Checkbox label="Module Enabled" {...register('serialmoduleEnabled')} />
<Checkbox label="Module Enabled" {...register('serialModuleEnabled')} />
<Checkbox
label="Echo"
disabled={!moduleEnabled}
{...register('serialmoduleEcho')}
{...register('serialModuleEcho')}
/>
<Input
type="number"
label="RX"
disabled={!moduleEnabled}
{...register('serialmoduleRxd', {
{...register('serialModuleRxd', {
valueAsNumber: true,
})}
/>
@ -62,7 +62,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number"
label="TX Pin"
disabled={!moduleEnabled}
{...register('serialmoduleTxd', {
{...register('serialModuleTxd', {
valueAsNumber: true,
})}
/>
@ -70,7 +70,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number"
label="Baud Rate"
disabled={!moduleEnabled}
{...register('serialmoduleBaud', {
{...register('serialModuleBaud', {
valueAsNumber: true,
})}
/>
@ -78,7 +78,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number"
label="Timeout"
disabled={!moduleEnabled}
{...register('serialmoduleTimeout', {
{...register('serialModuleTimeout', {
valueAsNumber: true,
})}
/>
@ -86,7 +86,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
type="number"
label="Mode"
disabled={!moduleEnabled}
{...register('serialmoduleMode', {
{...register('serialModuleMode', {
valueAsNumber: true,
})}
/>

2
src/components/layout/Sidebar/Settings/modules/Telemetry.tsx

@ -68,7 +68,7 @@ export const Telemetry = (): JSX.Element => {
/>
<Checkbox
label="Display Farenheit"
{...register('telemetryModuleDisplayFarenheit')}
{...register('telemetryModuleDisplayFahrenheit')}
/>
<Select
label="Sensor Type"

60
src/components/menu/BottomNav.tsx

@ -5,6 +5,7 @@ import {
FiBluetooth,
FiCpu,
FiGitBranch,
FiHexagon,
FiMenu,
FiMoon,
FiSun,
@ -12,6 +13,7 @@ import {
FiX,
} from 'react-icons/fi';
import { MdUpgrade } from 'react-icons/md';
import { AiOutlineHeart, AiFillHeart } from 'react-icons/ai';
import {
RiArrowDownLine,
RiArrowUpDownLine,
@ -29,6 +31,13 @@ import {
import { useAppDispatch } from '@hooks/useAppDispatch';
import { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
import {
IoBatteryChargingOutline,
IoBatteryDeadOutline,
IoBatteryFullOutline,
IoBatteryHalfOutline,
} from 'react-icons/io5';
import { FaTrafficLight } from 'react-icons/fa';
export const BottomNav = (): JSX.Element => {
const [showVersionInfo, setShowVersionInfo] = useState(false);
@ -38,6 +47,8 @@ export const BottomNav = (): JSX.Element => {
const primaryChannelSettings = useAppSelector(
(state) => state.meshtastic.radio.channels,
).find((channel) => channel.role === Protobuf.Channel_Role.PRIMARY)?.settings;
const telemetry =
meshtasticState.nodes[meshtasticState.radio.hardware.myNodeNum]?.telemetry;
return (
<div className="z-20 flex justify-between divide-x divide-gray-400 border-t border-gray-400 bg-white dark:divide-gray-600 dark:border-gray-600 dark:bg-secondaryDark">
@ -78,10 +89,57 @@ export const BottomNav = (): JSX.Element => {
)}
<div className="truncate text-xs font-medium">
{meshtasticState.nodes.find(
(node) => node.number === meshtasticState.radio.hardware.myNodeNum,
(node) => node.num === meshtasticState.radio.hardware.myNodeNum,
)?.user?.longName ?? 'Disconnected'}
</div>
</BottomNavItem>
<BottomNavItem tooltip="Router Heartbeat">
{telemetry?.routerHeartbeat ? (
<AiFillHeart className="h-4" />
) : (
<AiOutlineHeart className="h-4" />
)}
</BottomNavItem>
<BottomNavItem tooltip="Battery Level">
{!telemetry?.batteryLevel ? (
<IoBatteryDeadOutline className="h-4" />
) : telemetry?.batteryLevel > 50 ? (
<IoBatteryFullOutline className="h-4" />
) : telemetry?.batteryLevel > 0 ? (
<IoBatteryFullOutline className="h-4" />
) : (
<IoBatteryChargingOutline className="h-4" />
)}
<div className="truncate text-xs font-medium">
{telemetry?.batteryLevel
? `${telemetry?.batteryLevel}% - ${telemetry?.voltage}v`
: 'No Battery'}
</div>
</BottomNavItem>
<BottomNavItem tooltip="Network Utilization">
<div className="m-auto h-3 w-3 rounded-full bg-primary" />
<div className="truncate text-xs font-medium">
{`${telemetry?.airUtilTx ?? 0}% - Air`} |
</div>
<div
className={`m-auto h-3 w-3 rounded-full ${
!telemetry?.channelUtilization
? 'bg-primary'
: telemetry?.channelUtilization > 50
? 'bg-red-400'
: telemetry?.channelUtilization > 24
? 'bg-yellow-400'
: 'bg-primary'
}`}
/>
<div className="truncate text-xs font-medium">
{`${telemetry?.channelUtilization ?? 0}% - Ch`}
</div>
</BottomNavItem>
<BottomNavItem tooltip="MQTT Status">
{primaryChannelSettings?.uplinkEnabled &&

100
src/core/slices/meshtasticSlice.ts

@ -33,16 +33,6 @@ interface Route {
//speed stats?
}
export interface Node {
number: number;
lastHeard: Date;
snr: number[];
positions: Protobuf.Position[];
currentPosition?: CurrentPosition;
user?: Protobuf.User;
routes: Route[];
}
export interface Radio {
channels: Protobuf.Channel[];
preferences: Protobuf.RadioConfig_UserPreferences;
@ -53,7 +43,7 @@ interface MeshtasticState {
deviceStatus: Types.DeviceStatusEnum;
lastMeshInterraction: number;
ready: boolean;
nodes: Node[];
nodes: Protobuf.NodeInfo[];
radio: Radio;
chats: ChatEntries;
logs: Types.LogEventPacket[];
@ -97,73 +87,46 @@ export const meshtasticSlice = createSlice({
},
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
const node = state.nodes.find(
(node) => node.number === action.payload.packet.from,
(node) => node.num === action.payload.packet.from,
);
if (node) {
node.user = action.payload.data;
if (action.payload.packet.rxTime) {
node.lastHeard = new Date(action.payload.packet.rxTime * 1000);
node.lastHeard = new Date(
action.payload.packet.rxTime * 1000,
).getTime();
}
} else {
state.nodes.push({
number: action.payload.packet.from,
lastHeard: new Date(),
snr: [action.payload.packet.rxSnr],
user: action.payload.data,
positions: [],
routes: [],
num: action.payload.packet.from,
snr: action.payload.packet.rxSnr,
lastHeard: new Date().getTime(),
...action.payload.packet,
});
}
},
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
const node = state.nodes.find(
(node) => node.number === action.payload.packet.from,
(node) => node.num === action.payload.packet.from,
);
if (node) {
node.positions.push(action.payload.data);
if (
action.payload.data.latitudeI ||
action.payload.data.longitudeI ||
action.payload.data.altitude
) {
node.currentPosition = {
latitudeI:
action.payload.data.latitudeI ?? node.currentPosition?.latitudeI,
longitudeI:
action.payload.data.longitudeI ??
node.currentPosition?.longitudeI,
altitude:
action.payload.data.altitude ?? node.currentPosition?.altitude,
posTimestamp: action.payload.data.posTimestamp,
satsInView:
action.payload.data.satsInView ??
node.currentPosition?.satsInView,
};
}
node.position = action.payload.data;
if (action.payload.packet.rxTime) {
node.lastHeard = new Date(action.payload.packet.rxTime * 1000);
node.lastHeard = new Date(
action.payload.packet.rxTime * 1000,
).getTime();
}
}
},
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
const node = state.nodes.find(
(node) => node.number === action.payload.num,
);
const node = state.nodes.find((node) => node.num === action.payload.num);
if (node) {
node.lastHeard = new Date(action.payload.lastHeard * 1000);
node.snr.push(action.payload.snr);
node.lastHeard = new Date(action.payload.lastHeard * 1000).getTime();
node.snr = action.payload.snr;
} else {
state.nodes.push({
number: action.payload.num,
lastHeard: new Date(action.payload.lastHeard * 1000),
snr: [action.payload.snr],
positions: [],
routes: [],
});
state.nodes.push(action.payload);
}
},
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
@ -182,17 +145,16 @@ export const meshtasticSlice = createSlice({
}
},
addRoute: (state, action: PayloadAction<Route>) => {
const node = state.nodes.find(
(node) => node.number === action.payload.from,
);
const exists = node?.routes.findIndex(
(route) =>
route.from === action.payload.from && route.to === action.payload.to,
);
if (exists === -1) {
node?.routes.push(action.payload);
}
// const node = state.nodes.find(
// (node) => node.num === action.payload.from,
// );
// const exists = node?.routes.findIndex(
// (route) =>
// route.from === action.payload.from && route.to === action.payload.to,
// );
// if (exists === -1) {
// node?.routes.push(action.payload);
// }
},
setPreferences: (
state,
@ -243,11 +205,9 @@ export const meshtasticSlice = createSlice({
state,
action: PayloadAction<{ id: number; time: Date }>,
) => {
const node = state.nodes.find(
(node) => node.number === action.payload.id,
);
const node = state.nodes.find((node) => node.num === action.payload.id);
if (node) {
node.lastHeard = action.payload.time;
node.lastHeard = action.payload.time.getTime();
}
},
addChat: (state, action: PayloadAction<number>) => {

11
src/pages/Extensions/Debug.tsx

@ -10,9 +10,7 @@ export const Debug = (): JSX.Element => {
(state) => state.meshtastic.radio.hardware,
);
const node = useAppSelector((state) =>
state.meshtastic.nodes.find(
(node) => node.number === hardwareInfo.myNodeNum,
),
state.meshtastic.nodes.find((node) => node.num === hardwareInfo.myNodeNum),
);
return (
@ -33,6 +31,13 @@ export const Debug = (): JSX.Element => {
>
Get Preferences
</Button>
<Button
onClick={async (): Promise<void> => {
await connection.getAllChannels();
}}
>
Get All Channels
</Button>
</div>
</Card>
</div>

4
src/pages/Extensions/Info.tsx

@ -14,9 +14,7 @@ export const Info = (): JSX.Element => {
(state) => state.meshtastic.radio.hardware,
);
const node = useAppSelector((state) =>
state.meshtastic.nodes.find(
(node) => node.number === hardwareInfo.myNodeNum,
),
state.meshtastic.nodes.find((node) => node.num === hardwareInfo.myNodeNum),
);
return (

20
src/pages/Map/index.tsx

@ -7,14 +7,14 @@ import { RiRoadMapLine } from 'react-icons/ri';
import { Layout } from '@components/layout';
import { MapboxProvider } from '@components/MapBox/MapboxProvider';
import type { Node } from '@core/slices/meshtasticSlice';
import { useAppSelector } from '@hooks/useAppSelector';
import { MapContainer } from '@pages/Map/MapContainer';
import { Marker } from '@pages/Map/Marker';
import { NodeCard } from '@pages/Nodes/NodeCard';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export const Map = (): JSX.Element => {
const [selectedNode, setSelectedNode] = useState<Node>();
const [selectedNode, setSelectedNode] = useState<Protobuf.NodeInfo>();
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const myNodeNum = useAppSelector(
@ -25,13 +25,13 @@ export const Map = (): JSX.Element => {
<MapboxProvider>
{nodes.map((node) => {
return (
node.currentPosition && (
node.position && (
<Marker
key={node.number}
key={node.num}
center={
new mapboxgl.LngLat(
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
node.position.longitudeI / 1e7,
node.position.latitudeI / 1e7,
)
}
>
@ -40,7 +40,7 @@ export const Map = (): JSX.Element => {
setSelectedNode(node);
}}
className={`z-50 rounded-full border-2 bg-opacity-30 ${
node.number === selectedNode?.number
node.num === selectedNode?.num
? 'border-green-500 bg-green-500'
: 'border-blue-500 bg-blue-500'
}`}
@ -66,10 +66,10 @@ export const Map = (): JSX.Element => {
{nodes.map((node) => (
<NodeCard
key={node.number}
key={node.num}
node={node}
isMyNode={node.number === myNodeNum}
selected={selectedNode?.number === node.number}
isMyNode={node.num === myNodeNum}
selected={selectedNode?.num === node.num}
setSelected={(): void => {
setSelectedNode(node);
}}

4
src/pages/Messages/ChannelChat.tsx

@ -27,7 +27,7 @@ export const ChannelChat = ({
(state) => state.meshtastic.radio.hardware,
).myNodeNum;
const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
(node) => node.number !== myNodeNum,
(node) => node.num !== myNodeNum,
);
const chats = useAppSelector((state) => state.meshtastic.chats);
const channels = useAppSelector(
@ -80,7 +80,7 @@ export const ChannelChat = ({
<Tooltip
key={nodeId}
content={
nodes.find((node) => node.number === nodeId)?.user
nodes.find((node) => node.num === nodeId)?.user
?.longName ?? 'UNK'
}
>

12
src/pages/Messages/DmChat.tsx

@ -4,11 +4,11 @@ import { FiSettings } from 'react-icons/fi';
import { IconButton } from '@components/generic/button/IconButton';
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
import type { Node } from '@core/slices/meshtasticSlice';
import { Hashicon } from '@emeraldpay/hashicon-react';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export interface DmChatProps {
node: Node;
node: Protobuf.NodeInfo;
selectedIndex: number;
setSelectedIndex: (index: number) => void;
}
@ -20,16 +20,16 @@ export const DmChat = ({
}: DmChatProps): JSX.Element => {
return (
<SidebarItem
key={node.number}
selected={node.number === selectedIndex}
key={node.num}
selected={node.num === selectedIndex}
setSelected={(): void => {
setSelectedIndex(node.number);
setSelectedIndex(node.num);
}}
actions={<IconButton nested icon={<FiSettings />} />}
>
<div className="flex dark:text-white">
<div className="m-auto">
<Hashicon value={node.number.toString()} size={32} />
<Hashicon value={node.num.toString()} size={32} />
</div>
</div>
<div className="my-auto mr-auto font-semibold dark:text-white">

6
src/pages/Messages/Message.tsx

@ -3,15 +3,15 @@ import type React from 'react';
import { FiClock } from 'react-icons/fi';
import { Tooltip } from '@components/generic/Tooltip';
import type { Node } from '@core/slices/meshtasticSlice';
import { Hashicon } from '@emeraldpay/hashicon-react';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export interface MessageProps {
lastMsgSameUser: boolean;
message: string;
ack: boolean;
rxTime: Date;
sender?: Node;
sender?: Protobuf.NodeInfo;
}
export const Message = ({
@ -59,7 +59,7 @@ export const Message = ({
) : (
<div className="mx-6 flex gap-2">
<div className="my-auto w-8">
<Hashicon value={(sender?.number ?? 0).toString()} size={32} />
<Hashicon value={(sender?.num ?? 0).toString()} size={32} />
</div>
<div>
<div className="flex gap-2">

8
src/pages/Messages/index.tsx

@ -52,10 +52,10 @@ export const Messages = (): JSX.Element => {
<div className="mx-2 border-b border-gray-400 dark:border-gray-600" />
)}
{nodes
.filter((node) => node.number !== myNodeNum)
.filter((node) => node.num !== myNodeNum)
.map((node) => (
<DmChat
key={node.number}
key={node.num}
node={node}
selectedIndex={selectedChatIndex}
setSelectedIndex={setSelectedChatIndex}
@ -81,7 +81,7 @@ export const Messages = (): JSX.Element => {
</span>
) : (
<span className="text-gray-500 dark:text-gray-400">
{nodes.find((n) => n.number === selectedChatIndex)?.user
{nodes.find((node) => node.num === selectedChatIndex)?.user
?.longName ?? 'Unknown'}
</span>
)}
@ -106,7 +106,7 @@ export const Messages = (): JSX.Element => {
.packet.from === message.message.packet.from
}
sender={nodes.find(
(node) => node.number === message.message.packet.from,
(node) => node.num === message.message.packet.from,
)}
/>
))}

36
src/pages/Nodes/NodeCard.tsx

@ -21,14 +21,14 @@ import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay';
import { Tooltip } from '@components/generic/Tooltip';
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
import { CopyButton } from '@components/menu/buttons/CopyButton';
import type { Node } from '@core/slices/meshtasticSlice';
import { Hashicon } from '@emeraldpay/hashicon-react';
import { useMapbox } from '@hooks/useMapbox';
import type { Protobuf } from '@meshtastic/meshtasticjs';
type PositionConfidence = 'high' | 'low' | 'none';
export interface NodeCardProps {
node: Node;
node: Protobuf.NodeInfo;
isMyNode: boolean;
selected: boolean;
setSelected: () => void;
@ -47,14 +47,14 @@ export const NodeCard = ({
useEffect(() => {
setPositionConfidence(
node.currentPosition
? new Date(node.currentPosition.posTimestamp * 1000) >
node.position
? new Date(node.position.posTimestamp * 1000) >
new Date(new Date().getTime() - 1000 * 60 * 30)
? 'high'
: 'low'
: 'none',
);
}, [node.currentPosition]);
}, [node.position]);
return (
<>
<SidebarItem
@ -69,11 +69,11 @@ export const NodeCard = ({
onClick={(e): void => {
e.stopPropagation();
setSelected();
if (PositionConfidence !== 'none' && node.currentPosition) {
if (PositionConfidence !== 'none' && node.position) {
map?.flyTo({
center: new LngLat(
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
node.position.longitudeI / 1e7,
node.position.latitudeI / 1e7,
),
zoom: 16,
});
@ -113,12 +113,12 @@ export const NodeCard = ({
</m.div>
</Tooltip>
)}
<Hashicon value={node.number.toString()} size={32} />
<Hashicon value={node.num.toString()} size={32} />
</div>
</div>
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
{node.lastHeard.getTime()
? node.lastHeard.toLocaleTimeString(undefined, {
{node.lastHeard
? new Date(node.lastHeard).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
})
@ -136,7 +136,7 @@ export const NodeCard = ({
<CollapsibleSection title="User" icon={<FiUser />}>
<div className="flex p-2">
<div className="m-auto flex flex-col gap-2">
<Hashicon value={node.number.toString()} size={180} />
<Hashicon value={node.num.toString()} size={180} />
<div className="text-center text-lg font-medium dark:text-white">
{node?.user?.longName || 'Unknown'}
</div>
@ -146,18 +146,18 @@ export const NodeCard = ({
<CollapsibleSection title="Location" icon={<FiMapPin />}>
<>
<div className="flex h-10 select-none justify-between rounded-md border border-gray-400 bg-transparent bg-gray-300 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 ">
{node.currentPosition ? (
{node.position ? (
<>
<div className="my-auto px-1">
{(node.currentPosition.latitudeI / 1e7).toPrecision(6)}
{(node.position.latitudeI / 1e7).toPrecision(6)}
,&nbsp;
{(node.currentPosition?.longitudeI / 1e7).toPrecision(6)}
{(node.position?.longitudeI / 1e7).toPrecision(6)}
</div>
<CopyButton
data={
node.currentPosition
? `${node.currentPosition.latitudeI / 1e7},${
node.currentPosition.longitudeI / 1e7
node.position
? `${node.position.latitudeI / 1e7},${
node.position.longitudeI / 1e7
}`
: ''
}

40
src/pages/Nodes/index.tsx

@ -37,8 +37,8 @@ export const Nodes = (): JSX.Element => {
nodes.map((node, index) => {
tmpNodes.push({
id: node.number.toString(),
data: { label: node.user?.longName ?? `Unknown ${node.number}` },
id: node.num.toString(),
data: { label: node.user?.longName ?? `Unknown ${node.num}` },
position: { x: index * 160 + 500, y: 100 + 500 },
});
});
@ -50,7 +50,7 @@ export const Nodes = (): JSX.Element => {
const tmpEdges: Edge[] = [];
nodes.map((node, index) => {
if (node.number === myNodeNum) {
if (node.num === myNodeNum) {
tmpEdges.push({
id: `e${1}-${myNodeNum}`,
source: '1',
@ -63,15 +63,15 @@ export const Nodes = (): JSX.Element => {
});
}
node.routes.map((route) => {
tmpEdges.push({
id: `e${route.from}-${route.to}`,
source: node.number.toString(),
target: route.to.toString(),
type: 'smoothstep',
animated: true,
});
});
// node.routes.map((route) => {
// tmpEdges.push({
// id: `e${route.from}-${route.to}`,
// source: node.num.toString(),
// target: route.to.toString(),
// type: 'smoothstep',
// animated: true,
// });
// });
});
setGraphEdges(tmpEdges);
@ -85,17 +85,17 @@ export const Nodes = (): JSX.Element => {
<>
{nodes.map((node) => (
<SidebarItem
key={node.number}
selected={node.number === selected}
key={node.num}
selected={node.num === selected}
setSelected={(): void => {
setSelected(node.number);
setSelected(node.num);
}}
actions={
<IconButton
nested
onClick={(e): void => {
e.stopPropagation();
setSelected(node.number);
setSelected(node.num);
}}
icon={<FiSettings />}
/>
@ -103,7 +103,7 @@ export const Nodes = (): JSX.Element => {
>
<div className="flex dark:text-white">
<div className="relative m-auto">
{node.number === myNodeNum && (
{node.num === myNodeNum && (
<Tooltip content="Your Node">
<m.div
whileHover={{ scale: 1.05 }}
@ -113,12 +113,12 @@ export const Nodes = (): JSX.Element => {
</m.div>
</Tooltip>
)}
<Hashicon value={node.number.toString()} size={32} />
<Hashicon value={node.num.toString()} size={32} />
</div>
</div>
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
{node.lastHeard.getTime()
? node.lastHeard.toLocaleTimeString(undefined, {
{node.lastHeard
? new Date(node.lastHeard).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
})

2
types/static.d.ts

@ -1,6 +1,8 @@
/* Use this file to declare any custom file extensions for importing */
/* Use this folder to also add/extend a package d.ts file, if needed. */
///<reference types="chrome"/>
/* CSS MODULES */
declare module '*.module.css' {
const classes: { [key: string]: string };

Loading…
Cancel
Save