WIP 1.3
This commit is contained in:
+13
-12
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+773
-751
File diff suppressed because it is too large
Load Diff
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -68,7 +68,7 @@ export const Telemetry = (): JSX.Element => {
|
||||
/>
|
||||
<Checkbox
|
||||
label="Display Farenheit"
|
||||
{...register('telemetryModuleDisplayFarenheit')}
|
||||
{...register('telemetryModuleDisplayFahrenheit')}
|
||||
/>
|
||||
<Select
|
||||
label="Sensor Type"
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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>) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
+10
-10
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -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)}
|
||||
,
|
||||
{(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
|
||||
}`
|
||||
: ''
|
||||
}
|
||||
|
||||
+20
-20
@@ -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',
|
||||
})
|
||||
|
||||
Vendored
+2
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user