mirror of
https://github.com/meshtastic/web.git
synced 2026-01-27 16:58:52 -05:00
Updates for telemetry rework
This commit is contained in:
52
package.json
52
package.json
@@ -21,51 +21,51 @@
|
||||
"homepage": "https://meshtastic.org",
|
||||
"dependencies": {
|
||||
"@emeraldpay/hashicon-react": "^0.5.2",
|
||||
"@meshtastic/eslint-config": "^1.0.7",
|
||||
"@meshtastic/meshtasticjs": "^0.6.54",
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"@meshtastic/eslint-config": "^1.0.8",
|
||||
"@meshtastic/meshtasticjs": "^0.6.55",
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"base64-js": "^1.5.1",
|
||||
"framer-motion": "^6.2.8",
|
||||
"mapbox-gl": "^2.7.1",
|
||||
"prettier": "^2.6.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"framer-motion": "^6.2.10",
|
||||
"mapbox-gl": "^2.8.0",
|
||||
"prettier": "^2.6.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-flow-renderer": "^10.0.6",
|
||||
"react-hook-form": "^7.28.1",
|
||||
"react-flow-renderer": "^10.1.0",
|
||||
"react-hook-form": "^7.29.0",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-json-pretty": "^2.2.0",
|
||||
"react-multi-select-component": "^4.2.3",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-use-clipboard": "^1.0.7",
|
||||
"react-multi-select-component": "^4.2.4",
|
||||
"react-redux": "^7.2.8",
|
||||
"react-use-clipboard": "^1.0.8",
|
||||
"rfc4648": "^1.5.1",
|
||||
"swr": "^1.2.2",
|
||||
"swr": "^1.3.0",
|
||||
"timeago-react": "^3.0.4",
|
||||
"tippy.js": "^6.3.7",
|
||||
"type-route": "^0.6.0",
|
||||
"vite-plugin-environment": "^1.1.0"
|
||||
"type-route": "^0.7.1",
|
||||
"vite-plugin-environment": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hookform/devtools": "^4.0.2",
|
||||
"@types/chrome": "^0.0.180",
|
||||
"@types/mapbox-gl": "^2.6.3",
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/react-dom": "^17.0.14",
|
||||
"@hookform/devtools": "^4.1.0",
|
||||
"@types/chrome": "^0.0.181",
|
||||
"@types/mapbox-gl": "^2.6.4",
|
||||
"@types/react": "^18.0.3",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/w3c-web-serial": "^1.0.2",
|
||||
"@types/web-bluetooth": "^0.0.14",
|
||||
"@vitejs/plugin-react": "^1.2.0",
|
||||
"@vitejs/plugin-react": "^1.3.0",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"gzipper": "^7.0.0",
|
||||
"gzipper": "^7.1.0",
|
||||
"postcss": "^8.4.12",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"tar": "^6.1.11",
|
||||
"typescript": "^4.6.3",
|
||||
"unimported": "^1.19.1",
|
||||
"vite": "^2.8.6",
|
||||
"vite": "^2.9.1",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"vite-plugin-pwa": "^0.11.13",
|
||||
"workbox-window": "^6.5.2"
|
||||
"workbox-window": "^6.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
1655
pnpm-lock.yaml
generated
1655
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ export const SidebarOverlay = ({
|
||||
}
|
||||
transition={{ type: 'just' }}
|
||||
>
|
||||
{/* @ts-expect-error */}
|
||||
<AnimateSharedLayout>
|
||||
{/* <div className="flex gap-2 border-b border-gray-400 p-2 dark:border-gray-600"> */}
|
||||
<div className="bg-white px-1 pt-1 drop-shadow-md dark:bg-primaryDark">
|
||||
|
||||
@@ -47,12 +47,6 @@ export const LoRa = (): JSX.Element => {
|
||||
optionsEnum={Protobuf.Role}
|
||||
{...register('role')}
|
||||
/>
|
||||
<Input
|
||||
label="Send Owner Interval"
|
||||
type="number"
|
||||
suffix="Seconds"
|
||||
{...register('sendOwnerInterval', { valueAsNumber: true })}
|
||||
/>
|
||||
<Input
|
||||
label="Frequency Offset"
|
||||
type="number"
|
||||
|
||||
@@ -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.num === myNodeNum,
|
||||
(node) => node.data.num === myNodeNum,
|
||||
);
|
||||
const { register, handleSubmit, formState, reset } = useForm<{
|
||||
longName: string;
|
||||
@@ -30,51 +30,47 @@ export const User = (): JSX.Element => {
|
||||
txPowerDbm: number;
|
||||
}>({
|
||||
defaultValues: {
|
||||
longName: node?.user?.longName,
|
||||
shortName: node?.user?.shortName,
|
||||
isLicensed: node?.user?.isLicensed,
|
||||
team: node?.user?.team,
|
||||
antAzimuth: node?.user?.antAzimuth,
|
||||
antGainDbi: node?.user?.antGainDbi,
|
||||
txPowerDbm: node?.user?.txPowerDbm,
|
||||
longName: node?.data.user?.longName,
|
||||
shortName: node?.data.user?.shortName,
|
||||
isLicensed: node?.data.user?.isLicensed,
|
||||
team: node?.data.user?.team,
|
||||
antAzimuth: node?.data.user?.antAzimuth,
|
||||
antGainDbi: node?.data.user?.antGainDbi,
|
||||
txPowerDbm: node?.data.user?.txPowerDbm,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset({
|
||||
longName: node?.user?.longName,
|
||||
shortName: node?.user?.shortName,
|
||||
isLicensed: node?.user?.isLicensed,
|
||||
team: node?.user?.team,
|
||||
longName: node?.data.user?.longName,
|
||||
shortName: node?.data.user?.shortName,
|
||||
isLicensed: node?.data.user?.isLicensed,
|
||||
team: node?.data.user?.team,
|
||||
});
|
||||
}, [reset, node]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setLoading(true);
|
||||
|
||||
if (node?.user) {
|
||||
void connection.setOwner({ ...node.user, ...data }, async () => {
|
||||
if (node?.data.user) {
|
||||
void connection.setOwner({ ...node.data.user, ...data }, async () => {
|
||||
reset({ ...data });
|
||||
setLoading(false);
|
||||
await Promise.resolve();
|
||||
});
|
||||
// TODO: can be removed once getUser is implemented
|
||||
// dispatch(
|
||||
// addUser({ ...node.user, ...{ data: { ...node.user.data, ...data } } }),
|
||||
// );
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
|
||||
<Input label="Device ID" value={node?.user?.id} disabled />
|
||||
<Input label="Device ID" value={node?.data.user?.id} disabled />
|
||||
<Input label="Device Name" {...register('longName')} />
|
||||
<Input label="Short Name" maxLength={3} {...register('shortName')} />
|
||||
<Input
|
||||
label="Mac Address"
|
||||
defaultValue={
|
||||
base16
|
||||
.stringify(node?.user?.macaddr ?? [])
|
||||
.stringify(node?.data.user?.macaddr ?? [])
|
||||
.match(/.{1,2}/g)
|
||||
?.join(':') ?? ''
|
||||
}
|
||||
@@ -84,7 +80,7 @@ export const User = (): JSX.Element => {
|
||||
label="Hardware (DEPRECATED)"
|
||||
value={
|
||||
Protobuf.HardwareModel[
|
||||
node?.user?.hwModel ?? Protobuf.HardwareModel.UNSET
|
||||
node?.data.user?.hwModel ?? Protobuf.HardwareModel.UNSET
|
||||
]
|
||||
}
|
||||
disabled
|
||||
|
||||
@@ -50,41 +50,42 @@ export const CannedMessage = (): JSX.Element => {
|
||||
{...register('rotary1Enabled')}
|
||||
/>
|
||||
<Input
|
||||
label="Encoder #1 Pin A"
|
||||
label="Encoder Pin A"
|
||||
type="number"
|
||||
disabled={moduleEnabled}
|
||||
{...register('rotary1PinA', { valueAsNumber: true })}
|
||||
{...register('inputbrokerPinA', { valueAsNumber: true })}
|
||||
/>
|
||||
<Input
|
||||
label="Encoder #1 Pin B"
|
||||
label="Encoder Pin B"
|
||||
type="number"
|
||||
disabled={moduleEnabled}
|
||||
{...register('rotary1PinB', { valueAsNumber: true })}
|
||||
{...register('inputbrokerPinB', { valueAsNumber: true })}
|
||||
/>
|
||||
<Input
|
||||
label="Endoer #1 Pin Press"
|
||||
label="Endoer Pin Press"
|
||||
type="number"
|
||||
disabled={moduleEnabled}
|
||||
{...register('rotary1PinPress', { valueAsNumber: true })}
|
||||
{...register('inputbrokerPinPress', { valueAsNumber: true })}
|
||||
/>
|
||||
<Select
|
||||
label="Clockwise event"
|
||||
disabled={moduleEnabled}
|
||||
optionsEnum={Protobuf.InputEventChar}
|
||||
{...register('rotary1EventCw', { valueAsNumber: true })}
|
||||
{...register('inputbrokerEventCw', { valueAsNumber: true })}
|
||||
/>
|
||||
<Select
|
||||
label="Counter Clockwise event"
|
||||
disabled={moduleEnabled}
|
||||
optionsEnum={Protobuf.InputEventChar}
|
||||
{...register('rotary1EventCcw', { valueAsNumber: true })}
|
||||
{...register('inputbrokerEventCcw', { valueAsNumber: true })}
|
||||
/>
|
||||
<Select
|
||||
label="Press event"
|
||||
disabled={moduleEnabled}
|
||||
optionsEnum={Protobuf.InputEventChar}
|
||||
{...register('rotary1EventPress', { valueAsNumber: true })}
|
||||
{...register('inputbrokerEventPress', { valueAsNumber: true })}
|
||||
/>
|
||||
<Checkbox label="Up Down enabled" {...register('updown1Enabled')} />
|
||||
<Input
|
||||
label="Allow Input Source"
|
||||
disabled={moduleEnabled}
|
||||
|
||||
@@ -37,16 +37,16 @@ export const Telemetry = (): JSX.Element => {
|
||||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}>
|
||||
<Checkbox
|
||||
label="Measurement Enabled"
|
||||
{...register('telemetryModuleMeasurementEnabled')}
|
||||
{...register('telemetryModuleEnvironmentMeasurementEnabled')}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Displayed on Screen"
|
||||
{...register('telemetryModuleScreenEnabled')}
|
||||
{...register('telemetryModuleEnvironmentScreenEnabled')}
|
||||
/>
|
||||
<Input
|
||||
label="Read Error Count Threshold"
|
||||
type="number"
|
||||
{...register('telemetryModuleReadErrorCountThreshold', {
|
||||
{...register('telemetryModuleEnvironmentReadErrorCountThreshold', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
@@ -54,7 +54,7 @@ export const Telemetry = (): JSX.Element => {
|
||||
label="Update Interval"
|
||||
suffix="Seconds"
|
||||
type="number"
|
||||
{...register('telemetryModuleUpdateInterval', {
|
||||
{...register('telemetryModuleEnvironmentUpdateInterval', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
@@ -62,23 +62,27 @@ export const Telemetry = (): JSX.Element => {
|
||||
label="Recovery Interval"
|
||||
suffix="Seconds"
|
||||
type="number"
|
||||
{...register('telemetryModuleRecoveryInterval', {
|
||||
{...register('telemetryModuleEnvironmentRecoveryInterval', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Display Farenheit"
|
||||
{...register('telemetryModuleDisplayFahrenheit')}
|
||||
{...register('telemetryModuleEnvironmentDisplayFahrenheit')}
|
||||
/>
|
||||
<Select
|
||||
label="Sensor Type"
|
||||
optionsEnum={Protobuf.RadioConfig_UserPreferences_TelemetrySensorType}
|
||||
{...register('telemetryModuleSensorType', { valueAsNumber: true })}
|
||||
{...register('telemetryModuleEnvironmentSensorType', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
<Input
|
||||
label="Sensor Pin"
|
||||
type="number"
|
||||
{...register('telemetryModuleSensorPin', { valueAsNumber: true })}
|
||||
{...register('telemetryModuleEnvironmentSensorPin', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -5,15 +5,18 @@ import {
|
||||
FiBluetooth,
|
||||
FiCpu,
|
||||
FiGitBranch,
|
||||
FiHexagon,
|
||||
FiMenu,
|
||||
FiMoon,
|
||||
FiSun,
|
||||
FiWifi,
|
||||
FiX,
|
||||
} from 'react-icons/fi';
|
||||
import {
|
||||
IoBatteryChargingOutline,
|
||||
IoBatteryDeadOutline,
|
||||
IoBatteryFullOutline,
|
||||
} from 'react-icons/io5';
|
||||
import { MdUpgrade } from 'react-icons/md';
|
||||
import { AiOutlineHeart, AiFillHeart } from 'react-icons/ai';
|
||||
import {
|
||||
RiArrowDownLine,
|
||||
RiArrowUpDownLine,
|
||||
@@ -31,13 +34,6 @@ 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);
|
||||
@@ -47,8 +43,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;
|
||||
const metrics =
|
||||
meshtasticState.nodes[meshtasticState.radio.hardware.myNodeNum]?.metrics;
|
||||
|
||||
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">
|
||||
@@ -89,32 +85,26 @@ export const BottomNav = (): JSX.Element => {
|
||||
)}
|
||||
<div className="truncate text-xs font-medium">
|
||||
{meshtasticState.nodes.find(
|
||||
(node) => node.num === meshtasticState.radio.hardware.myNodeNum,
|
||||
)?.user?.longName ?? 'Disconnected'}
|
||||
(node) =>
|
||||
node.data.num === meshtasticState.radio.hardware.myNodeNum,
|
||||
)?.data.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 ? (
|
||||
{!metrics?.batteryLevel ? (
|
||||
<IoBatteryDeadOutline className="h-4" />
|
||||
) : telemetry?.batteryLevel > 50 ? (
|
||||
) : metrics?.batteryLevel > 50 ? (
|
||||
<IoBatteryFullOutline className="h-4" />
|
||||
) : telemetry?.batteryLevel > 0 ? (
|
||||
) : metrics?.batteryLevel > 0 ? (
|
||||
<IoBatteryFullOutline className="h-4" />
|
||||
) : (
|
||||
<IoBatteryChargingOutline className="h-4" />
|
||||
)}
|
||||
|
||||
<div className="truncate text-xs font-medium">
|
||||
{telemetry?.batteryLevel
|
||||
? `${telemetry?.batteryLevel}% - ${telemetry?.voltage}v`
|
||||
{metrics?.batteryLevel
|
||||
? `${metrics?.batteryLevel}% - ${metrics?.voltage}v`
|
||||
: 'No Battery'}
|
||||
</div>
|
||||
</BottomNavItem>
|
||||
@@ -122,22 +112,22 @@ export const BottomNav = (): JSX.Element => {
|
||||
<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`} |
|
||||
{`${metrics?.airUtilTx ?? 0}% - Air`} |
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`m-auto h-3 w-3 rounded-full ${
|
||||
!telemetry?.channelUtilization
|
||||
!metrics?.channelUtilization
|
||||
? 'bg-primary'
|
||||
: telemetry?.channelUtilization > 50
|
||||
: metrics?.channelUtilization > 50
|
||||
? 'bg-red-400'
|
||||
: telemetry?.channelUtilization > 24
|
||||
: metrics?.channelUtilization > 24
|
||||
? 'bg-yellow-400'
|
||||
: 'bg-primary'
|
||||
}`}
|
||||
/>
|
||||
<div className="truncate text-xs font-medium">
|
||||
{`${telemetry?.channelUtilization ?? 0}% - Ch`}
|
||||
{`${metrics?.channelUtilization ?? 0}% - Ch`}
|
||||
</div>
|
||||
</BottomNavItem>
|
||||
|
||||
|
||||
@@ -13,14 +13,6 @@ export interface Chat {
|
||||
messages: MessageWithAck[];
|
||||
}
|
||||
|
||||
interface CurrentPosition {
|
||||
latitudeI: number;
|
||||
longitudeI: number;
|
||||
altitude: number;
|
||||
posTimestamp: number;
|
||||
satsInView: number;
|
||||
}
|
||||
|
||||
type ChatEntries = {
|
||||
[key in number]: Chat;
|
||||
};
|
||||
@@ -33,6 +25,11 @@ interface Route {
|
||||
//speed stats?
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
metrics: Protobuf.DeviceMetrics;
|
||||
data: Protobuf.NodeInfo;
|
||||
}
|
||||
|
||||
export interface Radio {
|
||||
channels: Protobuf.Channel[];
|
||||
preferences: Protobuf.RadioConfig_UserPreferences;
|
||||
@@ -43,7 +40,7 @@ interface MeshtasticState {
|
||||
deviceStatus: Types.DeviceStatusEnum;
|
||||
lastMeshInterraction: number;
|
||||
ready: boolean;
|
||||
nodes: Protobuf.NodeInfo[];
|
||||
nodes: Node[];
|
||||
radio: Radio;
|
||||
chats: ChatEntries;
|
||||
logs: Types.LogEventPacket[];
|
||||
@@ -87,46 +84,56 @@ export const meshtasticSlice = createSlice({
|
||||
},
|
||||
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
|
||||
const node = state.nodes.find(
|
||||
(node) => node.num === action.payload.packet.from,
|
||||
(node) => node.data.num === action.payload.packet.from,
|
||||
);
|
||||
if (node) {
|
||||
node.user = action.payload.data;
|
||||
node.data.user = action.payload.data;
|
||||
if (action.payload.packet.rxTime) {
|
||||
node.lastHeard = new Date(
|
||||
node.data.lastHeard = new Date(
|
||||
action.payload.packet.rxTime * 1000,
|
||||
).getTime();
|
||||
}
|
||||
} else {
|
||||
state.nodes.push({
|
||||
num: action.payload.packet.from,
|
||||
snr: action.payload.packet.rxSnr,
|
||||
lastHeard: new Date().getTime(),
|
||||
...action.payload.packet,
|
||||
data: {
|
||||
num: action.payload.packet.from,
|
||||
snr: action.payload.packet.rxSnr,
|
||||
lastHeard: new Date().getTime(),
|
||||
...action.payload.packet,
|
||||
},
|
||||
metrics: Protobuf.DeviceMetrics.create(),
|
||||
});
|
||||
}
|
||||
},
|
||||
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||
const node = state.nodes.find(
|
||||
(node) => node.num === action.payload.packet.from,
|
||||
(node) => node.data.num === action.payload.packet.from,
|
||||
);
|
||||
|
||||
if (node) {
|
||||
node.position = action.payload.data;
|
||||
node.data.position = action.payload.data;
|
||||
if (action.payload.packet.rxTime) {
|
||||
node.lastHeard = new Date(
|
||||
node.data.lastHeard = new Date(
|
||||
action.payload.packet.rxTime * 1000,
|
||||
).getTime();
|
||||
}
|
||||
}
|
||||
},
|
||||
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
||||
const node = state.nodes.find((node) => node.num === action.payload.num);
|
||||
const node = state.nodes.find(
|
||||
(node) => node.data.num === action.payload.num,
|
||||
);
|
||||
|
||||
if (node) {
|
||||
node.lastHeard = new Date(action.payload.lastHeard * 1000).getTime();
|
||||
node.snr = action.payload.snr;
|
||||
node.data.lastHeard = new Date(
|
||||
action.payload.lastHeard * 1000,
|
||||
).getTime();
|
||||
node.data.snr = action.payload.snr;
|
||||
} else {
|
||||
state.nodes.push(action.payload);
|
||||
state.nodes.push({
|
||||
data: action.payload,
|
||||
metrics: Protobuf.DeviceMetrics.create(),
|
||||
});
|
||||
}
|
||||
},
|
||||
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
|
||||
@@ -205,9 +212,11 @@ export const meshtasticSlice = createSlice({
|
||||
state,
|
||||
action: PayloadAction<{ id: number; time: Date }>,
|
||||
) => {
|
||||
const node = state.nodes.find((node) => node.num === action.payload.id);
|
||||
const node = state.nodes.find(
|
||||
(node) => node.data.num === action.payload.id,
|
||||
);
|
||||
if (node) {
|
||||
node.lastHeard = action.payload.time.getTime();
|
||||
node.data.lastHeard = action.payload.time.getTime();
|
||||
}
|
||||
},
|
||||
addChat: (state, action: PayloadAction<number>) => {
|
||||
|
||||
@@ -2,9 +2,9 @@ import '@app/index.css';
|
||||
|
||||
import type React from 'react';
|
||||
import { StrictMode } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
|
||||
import { domAnimation, LazyMotion } from 'framer-motion';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
@@ -14,7 +14,9 @@ import { ReloadPrompt } from '@components/pwa/ReloadPrompt';
|
||||
import { RouteProvider } from '@core/router';
|
||||
import { store } from '@core/store';
|
||||
|
||||
render(
|
||||
const container = document.getElementById('root') as HTMLElement;
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<RouteProvider>
|
||||
@@ -27,5 +29,4 @@ render(
|
||||
</RouteProvider>
|
||||
</ErrorBoundary>
|
||||
</StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
||||
@@ -10,7 +10,9 @@ export const Debug = (): JSX.Element => {
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
);
|
||||
const node = useAppSelector((state) =>
|
||||
state.meshtastic.nodes.find((node) => node.num === hardwareInfo.myNodeNum),
|
||||
state.meshtastic.nodes.find(
|
||||
(node) => node.data.num === hardwareInfo.myNodeNum,
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -14,7 +14,9 @@ export const Info = (): JSX.Element => {
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
);
|
||||
const node = useAppSelector((state) =>
|
||||
state.meshtastic.nodes.find((node) => node.num === hardwareInfo.myNodeNum),
|
||||
state.meshtastic.nodes.find(
|
||||
(node) => node.data.num === hardwareInfo.myNodeNum,
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -27,7 +29,7 @@ export const Info = (): JSX.Element => {
|
||||
<div className="m-auto flex flex-col gap-2">
|
||||
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
|
||||
<div className="text-center text-lg font-medium dark:text-white">
|
||||
{node?.user?.longName || 'Unknown'}
|
||||
{node?.data.user?.longName || 'Unknown'}
|
||||
</div>
|
||||
</div>
|
||||
{/* <img
|
||||
|
||||
@@ -8,10 +8,10 @@ import { RiRoadMapLine } from 'react-icons/ri';
|
||||
import { Layout } from '@components/layout';
|
||||
import { MapboxProvider } from '@components/MapBox/MapboxProvider';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
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<Protobuf.NodeInfo>();
|
||||
@@ -25,22 +25,22 @@ export const Map = (): JSX.Element => {
|
||||
<MapboxProvider>
|
||||
{nodes.map((node) => {
|
||||
return (
|
||||
node.position && (
|
||||
node.data.position && (
|
||||
<Marker
|
||||
key={node.num}
|
||||
key={node.data.num}
|
||||
center={
|
||||
new mapboxgl.LngLat(
|
||||
node.position.longitudeI / 1e7,
|
||||
node.position.latitudeI / 1e7,
|
||||
node.data.position.longitudeI / 1e7,
|
||||
node.data.position.latitudeI / 1e7,
|
||||
)
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={(): void => {
|
||||
setSelectedNode(node);
|
||||
setSelectedNode(node.data);
|
||||
}}
|
||||
className={`z-50 rounded-full border-2 bg-opacity-30 ${
|
||||
node.num === selectedNode?.num
|
||||
node.data.num === selectedNode?.num
|
||||
? 'border-green-500 bg-green-500'
|
||||
: 'border-blue-500 bg-blue-500'
|
||||
}`}
|
||||
@@ -66,12 +66,12 @@ export const Map = (): JSX.Element => {
|
||||
|
||||
{nodes.map((node) => (
|
||||
<NodeCard
|
||||
key={node.num}
|
||||
node={node}
|
||||
isMyNode={node.num === myNodeNum}
|
||||
selected={selectedNode?.num === node.num}
|
||||
key={node.data.num}
|
||||
node={node.data}
|
||||
isMyNode={node.data.num === myNodeNum}
|
||||
selected={selectedNode?.num === node.data.num}
|
||||
setSelected={(): void => {
|
||||
setSelectedNode(node);
|
||||
setSelectedNode(node.data);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ChannelChat = ({
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
).myNodeNum;
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
|
||||
(node) => node.num !== myNodeNum,
|
||||
(node) => node.data.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.num === nodeId)?.user
|
||||
nodes.find((node) => node.data.num === nodeId)?.data.user
|
||||
?.longName ?? 'UNK'
|
||||
}
|
||||
>
|
||||
|
||||
@@ -52,11 +52,11 @@ export const Messages = (): JSX.Element => {
|
||||
<div className="mx-2 border-b border-gray-400 dark:border-gray-600" />
|
||||
)}
|
||||
{nodes
|
||||
.filter((node) => node.num !== myNodeNum)
|
||||
.filter((node) => node.data.num !== myNodeNum)
|
||||
.map((node) => (
|
||||
<DmChat
|
||||
key={node.num}
|
||||
node={node}
|
||||
key={node.data.num}
|
||||
node={node.data}
|
||||
selectedIndex={selectedChatIndex}
|
||||
setSelectedIndex={setSelectedChatIndex}
|
||||
/>
|
||||
@@ -81,8 +81,8 @@ export const Messages = (): JSX.Element => {
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-500 dark:text-gray-400">
|
||||
{nodes.find((node) => node.num === selectedChatIndex)?.user
|
||||
?.longName ?? 'Unknown'}
|
||||
{nodes.find((node) => node.data.num === selectedChatIndex)
|
||||
?.data.user?.longName ?? 'Unknown'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -105,9 +105,11 @@ export const Messages = (): JSX.Element => {
|
||||
: chats[selectedChatIndex].messages[index - 1].message
|
||||
.packet.from === message.message.packet.from
|
||||
}
|
||||
sender={nodes.find(
|
||||
(node) => node.num === message.message.packet.from,
|
||||
)}
|
||||
sender={
|
||||
nodes.find(
|
||||
(node) => node.data.num === message.message.packet.from,
|
||||
)?.data
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -37,8 +37,8 @@ export const Nodes = (): JSX.Element => {
|
||||
|
||||
nodes.map((node, index) => {
|
||||
tmpNodes.push({
|
||||
id: node.num.toString(),
|
||||
data: { label: node.user?.longName ?? `Unknown ${node.num}` },
|
||||
id: node.data.num.toString(),
|
||||
data: { label: node.data.user?.longName ?? `Unknown ${node.data.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.num === myNodeNum) {
|
||||
if (node.data.num === myNodeNum) {
|
||||
tmpEdges.push({
|
||||
id: `e${1}-${myNodeNum}`,
|
||||
source: '1',
|
||||
@@ -85,17 +85,17 @@ export const Nodes = (): JSX.Element => {
|
||||
<>
|
||||
{nodes.map((node) => (
|
||||
<SidebarItem
|
||||
key={node.num}
|
||||
selected={node.num === selected}
|
||||
key={node.data.num}
|
||||
selected={node.data.num === selected}
|
||||
setSelected={(): void => {
|
||||
setSelected(node.num);
|
||||
setSelected(node.data.num);
|
||||
}}
|
||||
actions={
|
||||
<IconButton
|
||||
nested
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
setSelected(node.num);
|
||||
setSelected(node.data.num);
|
||||
}}
|
||||
icon={<FiSettings />}
|
||||
/>
|
||||
@@ -103,7 +103,7 @@ export const Nodes = (): JSX.Element => {
|
||||
>
|
||||
<div className="flex dark:text-white">
|
||||
<div className="relative m-auto">
|
||||
{node.num === myNodeNum && (
|
||||
{node.data.num === myNodeNum && (
|
||||
<Tooltip content="Your Node">
|
||||
<m.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
@@ -113,15 +113,18 @@ export const Nodes = (): JSX.Element => {
|
||||
</m.div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Hashicon value={node.num.toString()} size={32} />
|
||||
<Hashicon value={node.data.num.toString()} size={32} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
|
||||
{node.lastHeard
|
||||
? new Date(node.lastHeard).toLocaleTimeString(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
{node.data.lastHeard
|
||||
? new Date(node.data.lastHeard).toLocaleTimeString(
|
||||
undefined,
|
||||
{
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
},
|
||||
)
|
||||
: 'Never'}
|
||||
</div>
|
||||
</SidebarItem>
|
||||
|
||||
Reference in New Issue
Block a user