Browse Source

use new packages for imports & new webserial transport

pull/476/head
Sacha Weatherstone 1 year ago
parent
commit
5a62b67e79
Failed to extract signature
  1. 8
      bun.lock
  2. 2
      package.json
  3. 2
      src/components/Dialog/DeviceNameDialog.tsx
  4. 21
      src/components/Dialog/ImportDialog.tsx
  5. 12
      src/components/Dialog/LocationResponseDialog.tsx
  6. 273
      src/components/Dialog/NodeDetailsDialog.tsx
  7. 12
      src/components/Dialog/NodeOptionsDialog.tsx
  8. 10
      src/components/Dialog/QRDialog.tsx
  9. 8
      src/components/Dialog/TracerouteResponseDialog.tsx
  10. 67
      src/components/PageComponents/Channel.tsx
  11. 7
      src/components/PageComponents/Config/Bluetooth.tsx
  12. 2
      src/components/PageComponents/Config/Device.tsx
  13. 2
      src/components/PageComponents/Config/Display.tsx
  14. 2
      src/components/PageComponents/Config/LoRa.tsx
  15. 2
      src/components/PageComponents/Config/Network.tsx
  16. 2
      src/components/PageComponents/Config/Position.tsx
  17. 2
      src/components/PageComponents/Config/Power.tsx
  18. 2
      src/components/PageComponents/Config/Security/Security.tsx
  19. 14
      src/components/PageComponents/Connect/Serial.tsx
  20. 52
      src/components/PageComponents/Map/NodeDetail.tsx
  21. 10
      src/components/PageComponents/Messages/ChannelChat.tsx
  22. 2
      src/components/PageComponents/Messages/Message.tsx
  23. 6
      src/components/PageComponents/Messages/MessageInput.tsx
  24. 38
      src/components/PageComponents/Messages/TraceRoute.tsx
  25. 2
      src/components/PageComponents/ModuleConfig/AmbientLighting.tsx
  26. 2
      src/components/PageComponents/ModuleConfig/Audio.tsx
  27. 2
      src/components/PageComponents/ModuleConfig/CannedMessage.tsx
  28. 2
      src/components/PageComponents/ModuleConfig/DetectionSensor.tsx
  29. 2
      src/components/PageComponents/ModuleConfig/ExternalNotification.tsx
  30. 2
      src/components/PageComponents/ModuleConfig/MQTT.tsx
  31. 2
      src/components/PageComponents/ModuleConfig/NeighborInfo.tsx
  32. 2
      src/components/PageComponents/ModuleConfig/Paxcounter.tsx
  33. 2
      src/components/PageComponents/ModuleConfig/RangeTest.tsx
  34. 2
      src/components/PageComponents/ModuleConfig/Serial.tsx
  35. 2
      src/components/PageComponents/ModuleConfig/StoreForward.tsx
  36. 2
      src/components/PageComponents/ModuleConfig/Telemetry.tsx
  37. 2
      src/core/stores/appStore.ts
  38. 34
      src/core/stores/deviceStore.ts
  39. 2
      src/core/subscriptions.ts
  40. 8
      src/pages/Channels.tsx
  41. 2
      src/pages/Map/index.tsx
  42. 67
      src/pages/Messages.tsx
  43. 34
      src/pages/Nodes.tsx
  44. 8
      src/validation/channel.ts
  45. 14
      src/validation/config/bluetooth.ts
  46. 2
      src/validation/config/device.ts
  47. 2
      src/validation/config/display.ts
  48. 2
      src/validation/config/lora.ts
  49. 2
      src/validation/config/network.ts
  50. 14
      src/validation/config/position.ts
  51. 2
      src/validation/config/power.ts
  52. 14
      src/validation/config/security.ts
  53. 14
      src/validation/moduleConfig/ambientLighting.ts
  54. 2
      src/validation/moduleConfig/audio.ts
  55. 17
      src/validation/moduleConfig/cannedMessage.ts
  56. 14
      src/validation/moduleConfig/detectionSensor.ts
  57. 14
      src/validation/moduleConfig/externalNotification.ts
  58. 2
      src/validation/moduleConfig/mqtt.ts
  59. 5
      src/validation/moduleConfig/neighborInfo.ts
  60. 5
      src/validation/moduleConfig/paxcounter.ts
  61. 2
      src/validation/moduleConfig/rangeTest.ts
  62. 5
      src/validation/moduleConfig/serial.ts
  63. 2
      src/validation/moduleConfig/storeForward.ts
  64. 5
      src/validation/moduleConfig/telemetry.ts

8
bun.lock

@ -5,7 +5,9 @@
"name": "meshtastic-web", "name": "meshtastic-web",
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.2.3", "@bufbuild/protobuf": "^2.2.3",
"@meshtastic/core": "npm:@jsr/[email protected]",
"@meshtastic/js": "npm:@jsr/[email protected]", "@meshtastic/js": "npm:@jsr/[email protected]",
"@meshtastic/transport-web-serial": "npm:@jsr/meshtastic__transport-web-serial",
"@noble/curves": "^1.8.1", "@noble/curves": "^1.8.1",
"@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-checkbox": "^1.1.4",
@ -359,6 +361,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@jsr/meshtastic__core": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__core/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-Ks71sRagbBipotznULpsJZ1EMcQIqCEJQx6mf628dmCNVf2YECi2zi/i/5zErp1hGPgfbDvCz9oPogvsd/7fMA=="],
"@jsr/meshtastic__protobufs": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3" } }, "sha512-CGlgBdzAuQCZuGPrnzP8zU+EcLlmyYeeMbqFHuJ834cYfArWXDjDh1UYaPo2rI03LTjqa3MeWpfqDlzBR8kIMg=="], "@jsr/meshtastic__protobufs": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__protobufs/2.6.0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3" } }, "sha512-CGlgBdzAuQCZuGPrnzP8zU+EcLlmyYeeMbqFHuJ834cYfArWXDjDh1UYaPo2rI03LTjqa3MeWpfqDlzBR8kIMg=="],
"@mapbox/geojson-rewind": ["@mapbox/[email protected]", "", { "dependencies": { "get-stream": "^6.0.1", "minimist": "^1.2.6" }, "bin": { "geojson-rewind": "geojson-rewind" } }, "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA=="], "@mapbox/geojson-rewind": ["@mapbox/[email protected]", "", { "dependencies": { "get-stream": "^6.0.1", "minimist": "^1.2.6" }, "bin": { "geojson-rewind": "geojson-rewind" } }, "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA=="],
@ -379,8 +383,12 @@
"@maplibre/maplibre-gl-style-spec": ["@maplibre/[email protected]", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", "quickselect": "^3.0.0", "rw": "^1.3.3", "tinyqueue": "^3.0.0" }, "bin": { "gl-style-migrate": "dist/gl-style-migrate.mjs", "gl-style-validate": "dist/gl-style-validate.mjs", "gl-style-format": "dist/gl-style-format.mjs" } }, "sha512-R6/ihEuC5KRexmKIYkWqUv84Gm+/QwsOUgHyt1yy2XqCdGdLvlBWVWIIeTZWN4NGdwmY6xDzdSGU2R9oBLNg2w=="], "@maplibre/maplibre-gl-style-spec": ["@maplibre/[email protected]", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", "quickselect": "^3.0.0", "rw": "^1.3.3", "tinyqueue": "^3.0.0" }, "bin": { "gl-style-migrate": "dist/gl-style-migrate.mjs", "gl-style-validate": "dist/gl-style-validate.mjs", "gl-style-format": "dist/gl-style-format.mjs" } }, "sha512-R6/ihEuC5KRexmKIYkWqUv84Gm+/QwsOUgHyt1yy2XqCdGdLvlBWVWIIeTZWN4NGdwmY6xDzdSGU2R9oBLNg2w=="],
"@meshtastic/core": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__core/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-Ks71sRagbBipotznULpsJZ1EMcQIqCEJQx6mf628dmCNVf2YECi2zi/i/5zErp1hGPgfbDvCz9oPogvsd/7fMA=="],
"@meshtastic/js": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__js/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-+xpZpxK6oUIVOuEs7C+LyxRr2druvc7UNNNTK9Rl8ioXj63Jz1uQXlYe2Gj0xjnRAiSQLR7QVaPef21BR/YTxA=="], "@meshtastic/js": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__js/2.6.0-0.tgz", { "dependencies": { "@bufbuild/protobuf": "^2.2.3", "@jsr/meshtastic__protobufs": "^2.6.0", "crc": "^4.3.2", "ste-simple-events": "^3.0.11", "tslog": "^4.9.3" } }, "sha512-+xpZpxK6oUIVOuEs7C+LyxRr2druvc7UNNNTK9Rl8ioXj63Jz1uQXlYe2Gj0xjnRAiSQLR7QVaPef21BR/YTxA=="],
"@meshtastic/transport-web-serial": ["@jsr/[email protected]", "https://npm.jsr.io/~/11/@jsr/meshtastic__transport-web-serial/0.2.0.tgz", { "dependencies": { "@jsr/meshtastic__core": "^2.6.0-0" } }, "sha512-mP/nxOj0syABh3FkG5iIolWhUMiFh/qtJtvqihxLkaRoxdabUyW62mOtfhCMBEjxgVnKg4Gy7GkaXfC/eFy19Q=="],
"@noble/curves": ["@noble/[email protected]", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], "@noble/curves": ["@noble/[email protected]", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="],
"@noble/hashes": ["@noble/[email protected]", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], "@noble/hashes": ["@noble/[email protected]", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="],

2
package.json

@ -38,7 +38,9 @@
"homepage": "https://meshtastic.org", "homepage": "https://meshtastic.org",
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.2.3", "@bufbuild/protobuf": "^2.2.3",
"@meshtastic/core": "npm:@jsr/[email protected]",
"@meshtastic/js": "npm:@jsr/[email protected]", "@meshtastic/js": "npm:@jsr/[email protected]",
"@meshtastic/transport-web-serial": "npm:@jsr/meshtastic__transport-web-serial",
"@noble/curves": "^1.8.1", "@noble/curves": "^1.8.1",
"@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-checkbox": "^1.1.4",

2
src/components/Dialog/DeviceNameDialog.tsx

@ -11,7 +11,7 @@ import {
} from "@components/UI/Dialog.tsx"; } from "@components/UI/Dialog.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx"; import { Label } from "@components/UI/Label.tsx";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
export interface User { export interface User {

21
src/components/Dialog/ImportDialog.tsx

@ -13,7 +13,7 @@ import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx"; import { Label } from "@components/UI/Label.tsx";
import { Switch } from "@components/UI/Switch.tsx"; import { Switch } from "@components/UI/Switch.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { toByteArray } from "base64-js"; import { toByteArray } from "base64-js";
import { type JSX, useEffect, useState } from "react"; import { type JSX, useEffect, useState } from "react";
@ -73,10 +73,9 @@ export const ImportDialog = ({
connection?.setChannel( connection?.setChannel(
create(Protobuf.Channel.ChannelSchema, { create(Protobuf.Channel.ChannelSchema, {
index, index,
role: role: index === 0
index === 0 ? Protobuf.Channel.Channel_Role.PRIMARY
? Protobuf.Channel.Channel_Role.PRIMARY : Protobuf.Channel.Channel_Role.SECONDARY,
: Protobuf.Channel.Channel_Role.SECONDARY,
settings: ch, settings: ch,
}), }),
); );
@ -123,21 +122,25 @@ export const ImportDialog = ({
checked={channelSet?.loraConfig?.usePreset ?? true} checked={channelSet?.loraConfig?.usePreset ?? true}
/> />
</div> </div>
{/* <Select {
/* <Select
label="Modem Preset" label="Modem Preset"
disabled disabled
value={channelSet?.loraConfig?.modemPreset} value={channelSet?.loraConfig?.modemPreset}
> >
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)} {renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
</Select> */} </Select> */
}
</div> </div>
{/* <Select {
/* <Select
label="Region" label="Region"
disabled disabled
value={channelSet?.loraConfig?.region} value={channelSet?.loraConfig?.region}
> >
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)} {renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</Select> */} </Select> */
}
<span className="text-md block font-medium text-text-primary"> <span className="text-md block font-medium text-text-primary">
Channels: Channels:

12
src/components/Dialog/LocationResponseDialog.tsx

@ -6,7 +6,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "@components/UI/Dialog";
import type { Protobuf, Types } from "@meshtastic/js"; import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react"; import type { JSX } from "react";
@ -24,11 +24,9 @@ export const LocationResponseDialog = ({
const { nodes } = useDevice(); const { nodes } = useDevice();
const from = nodes.get(location?.from ?? 0); const from = nodes.get(location?.from ?? 0);
const longName = const longName = from?.user?.longName ??
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = const shortName = from?.user?.shortName ??
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
return ( return (
@ -44,7 +42,9 @@ export const LocationResponseDialog = ({
Coordinates:{" "} Coordinates:{" "}
<a <a
className="text-blue-500 dark:text-blue-400" className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${location?.data.latitudeI / 1e7}&mlon=${location?.data.longitudeI / 1e7}&layers=N`} href={`https://www.openstreetmap.org/?mlat=${
location?.data.latitudeI / 1e7
}&mlon=${location?.data.longitudeI / 1e7}&layers=N`}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >

273
src/components/Dialog/NodeDetailsDialog.tsx

@ -13,7 +13,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "@components/UI/Dialog";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { DeviceImage } from "../generic/DeviceImage"; import { DeviceImage } from "../generic/DeviceImage";
import { TimeAgo } from "../generic/TimeAgo"; import { TimeAgo } from "../generic/TimeAgo";
@ -32,132 +32,159 @@ export const NodeDetailsDialog = ({
const { nodeNumDetails } = useAppStore(); const { nodeNumDetails } = useAppStore();
const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails); const device: Protobuf.Mesh.NodeInfo = nodes.get(nodeNumDetails);
return device ? ( return device
<Dialog open={open} onOpenChange={onOpenChange}> ? (
<DialogContent> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogHeader> <DialogContent>
<DialogTitle> <DialogHeader>
Node Details for {device.user?.longName ?? "UNKNOWN"} ( <DialogTitle>
{device.user?.shortName ?? "UNK"}) Node Details for {device.user?.longName ?? "UNKNOWN"} (
</DialogTitle> {device.user?.shortName ?? "UNK"})
</DialogHeader> </DialogTitle>
<DialogFooter> </DialogHeader>
<div className="w-full"> <DialogFooter>
<DeviceImage <div className="w-full">
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800" <DeviceImage
deviceType={ className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0] deviceType={Protobuf.Mesh
} .HardwareModel[device.user?.hwModel ?? 0]}
/> />
<div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> <div className="bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Details:
</p>
<p>
Hardware:{" "}
{Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
</p>
<p>Node Number: {device.num}</p>
<p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
<p>
Role:{" "}
{
Protobuf.Config.Config_DeviceConfig_Role[
device.user?.role ?? 0
]
}
</p>
<p>
Last Heard:{" "}
{device.lastHeard === 0 ? (
"Never"
) : (
<TimeAgo timestamp={device.lastHeard * 1000} />
)}
</p>
</div>
{device.position ? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Position: Details:
</p> </p>
{device.position.latitudeI && device.position.longitudeI ? ( <p>
<p> Hardware:{" "}
Coordinates:{" "} {Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]}
<a </p>
className="text-blue-500 dark:text-blue-400" <p>Node Number: {device.num}</p>
href={`https://www.openstreetmap.org/?mlat=${device.position.latitudeI / 1e7}&mlon=${device.position.longitudeI / 1e7}&layers=N`} <p>Node HEX: !{numberToHexUnpadded(device.num)}</p>
target="_blank" <p>
rel="noreferrer" Role: {Protobuf.Config.Config_DeviceConfig_Role[
> device.user?.role ?? 0
{device.position.latitudeI / 1e7},{" "} ]}
{device.position.longitudeI / 1e7} </p>
</a> <p>
</p> Last Heard: {device.lastHeard === 0
) : null} ? (
{device.position.altitude ? ( "Never"
<p>Altitude: {device.position.altitude}m</p> )
) : null} : <TimeAgo timestamp={device.lastHeard * 1000} />}
</div>
) : null}
{device.deviceMetrics ? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Device Metrics:
</p> </p>
{device.deviceMetrics.airUtilTx ? (
<p>
Air TX utilization:{" "}
{device.deviceMetrics.airUtilTx.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.channelUtilization ? (
<p>
Channel utilization:{" "}
{device.deviceMetrics.channelUtilization.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.batteryLevel ? (
<p>
Battery level:{" "}
{device.deviceMetrics.batteryLevel.toFixed(2)}%
</p>
) : null}
{device.deviceMetrics.voltage ? (
<p>Voltage: {device.deviceMetrics.voltage.toFixed(2)}V</p>
) : null}
{device.deviceMetrics.uptimeSeconds ? (
<p>
Uptime:{" "}
<Uptime seconds={device.deviceMetrics.uptimeSeconds} />
</p>
) : null}
</div> </div>
) : null}
{device ? ( {device.position
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3"> ? (
<Accordion className="AccordionRoot" type="single" collapsible> <div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<AccordionItem className="AccordionItem" value="item-1"> <p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
<AccordionTrigger> Position:
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50"> </p>
All Raw Metrics: {device.position.latitudeI && device.position.longitudeI
</p> ? (
</AccordionTrigger> <p>
<AccordionContent className="overflow-x-scroll"> Coordinates:{" "}
<pre className="text-xs w-full"> <a
className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${
device.position.latitudeI / 1e7
}&mlon=${
device.position.longitudeI / 1e7
}&layers=N`}
target="_blank"
rel="noreferrer"
>
{device.position.latitudeI / 1e7},{" "}
{device.position.longitudeI / 1e7}
</a>
</p>
)
: null}
{device.position.altitude
? <p>Altitude: {device.position.altitude}m</p>
: null}
</div>
)
: null}
{device.deviceMetrics
? (
<div className="mt-5 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
Device Metrics:
</p>
{device.deviceMetrics.airUtilTx
? (
<p>
Air TX utilization:{" "}
{device.deviceMetrics.airUtilTx.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.channelUtilization
? (
<p>
Channel utilization:{" "}
{device.deviceMetrics.channelUtilization.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.batteryLevel
? (
<p>
Battery level:{" "}
{device.deviceMetrics.batteryLevel.toFixed(2)}%
</p>
)
: null}
{device.deviceMetrics.voltage
? (
<p>
Voltage: {device.deviceMetrics.voltage.toFixed(2)}V
</p>
)
: null}
{device.deviceMetrics.uptimeSeconds
? (
<p>
Uptime:{" "}
<Uptime
seconds={device.deviceMetrics.uptimeSeconds}
/>
</p>
)
: null}
</div>
)
: null}
{device
? (
<div className="mt-5 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<Accordion
className="AccordionRoot"
type="single"
collapsible
>
<AccordionItem className="AccordionItem" value="item-1">
<AccordionTrigger>
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
All Raw Metrics:
</p>
</AccordionTrigger>
<AccordionContent className="overflow-x-scroll">
<pre className="text-xs w-full">
{JSON.stringify(device, null, 2)} {JSON.stringify(device, null, 2)}
</pre> </pre>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
</div> </div>
) : null} )
</div> : null}
</DialogFooter> </div>
</DialogContent> </DialogFooter>
</Dialog> </DialogContent>
) : null; </Dialog>
)
: null;
}; };

12
src/components/Dialog/NodeOptionsDialog.tsx

@ -7,7 +7,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "@components/UI/Dialog";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import type { JSX } from "react"; import type { JSX } from "react";
@ -31,11 +31,9 @@ export const NodeOptionsDialog = ({
setChatType, setChatType,
setActiveChat, setActiveChat,
} = useAppStore(); } = useAppStore();
const longName = const longName = node?.user?.longName ??
node?.user?.longName ??
(node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown"); (node ? `!${numberToHexUnpadded(node?.num)}` : "Unknown");
const shortName = const shortName = node?.user?.shortName ??
node?.user?.shortName ??
(node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK"); (node ? `${numberToHexUnpadded(node?.num).substring(0, 4)}` : "UNK");
function handleDirectMessage() { function handleDirectMessage() {
@ -53,7 +51,7 @@ export const NodeOptionsDialog = ({
connection?.requestPosition(node.num).then(() => connection?.requestPosition(node.num).then(() =>
toast({ toast({
title: "Position request sent.", title: "Position request sent.",
}), })
); );
onOpenChange(); onOpenChange();
} }
@ -66,7 +64,7 @@ export const NodeOptionsDialog = ({
connection?.traceRoute(node.num).then(() => connection?.traceRoute(node.num).then(() =>
toast({ toast({
title: "Traceroute sent.", title: "Traceroute sent.",
}), })
); );
onOpenChange(); onOpenChange();
} }

10
src/components/Dialog/QRDialog.tsx

@ -10,7 +10,7 @@ import {
} from "@components/UI/Dialog.tsx"; } from "@components/UI/Dialog.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx"; import { Label } from "@components/UI/Label.tsx";
import { Protobuf, type Types } from "@meshtastic/js"; import { Protobuf, type Types } from "@meshtastic/core";
import { fromByteArray } from "base64-js"; import { fromByteArray } from "base64-js";
import { ClipboardIcon } from "lucide-react"; import { ClipboardIcon } from "lucide-react";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
@ -77,8 +77,8 @@ export const QRDialog = ({
{channel.settings?.name.length {channel.settings?.name.length
? channel.settings.name ? channel.settings.name
: channel.role === Protobuf.Channel.Channel_Role.PRIMARY : channel.role === Protobuf.Channel.Channel_Role.PRIMARY
? "Primary" ? "Primary"
: `Channel: ${channel.index}`} : `Channel: ${channel.index}`}
</Label> </Label>
<Checkbox <Checkbox
key={channel.index} key={channel.index}
@ -86,7 +86,9 @@ export const QRDialog = ({
onCheckedChange={() => { onCheckedChange={() => {
if (selectedChannels.includes(channel.index)) { if (selectedChannels.includes(channel.index)) {
setSelectedChannels( setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index), selectedChannels.filter((c) =>
c !== channel.index
),
); );
} else { } else {
setSelectedChannels([ setSelectedChannels([

8
src/components/Dialog/TracerouteResponseDialog.tsx

@ -6,7 +6,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@components/UI/Dialog"; } from "@components/UI/Dialog";
import type { Protobuf, Types } from "@meshtastic/js"; import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react"; import type { JSX } from "react";
import { TraceRoute } from "../PageComponents/Messages/TraceRoute"; import { TraceRoute } from "../PageComponents/Messages/TraceRoute";
@ -28,11 +28,9 @@ export const TracerouteResponseDialog = ({
const snrTowards = traceroute?.data.snrTowards ?? []; const snrTowards = traceroute?.data.snrTowards ?? [];
const snrBack = traceroute?.data.snrBack ?? []; const snrBack = traceroute?.data.snrBack ?? [];
const from = nodes.get(traceroute?.from ?? 0); const from = nodes.get(traceroute?.from ?? 0);
const longName = const longName = from?.user?.longName ??
from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown"); (from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
const shortName = const shortName = from?.user?.shortName ??
from?.user?.shortName ??
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK"); (from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : "UNK");
const to = nodes.get(traceroute?.to ?? 0); const to = nodes.get(traceroute?.to ?? 0);
return ( return (

67
src/components/PageComponents/Channel.tsx

@ -3,7 +3,7 @@ import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useToast } from "@core/hooks/useToast.ts"; import { useToast } from "@core/hooks/useToast.ts";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import cryptoRandomString from "crypto-random-string"; import cryptoRandomString from "crypto-random-string";
import { useState } from "react"; import { useState } from "react";
@ -24,8 +24,9 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.psk.length ?? 16, channel?.settings?.psk.length ?? 16,
); );
const [validationText, setValidationText] = useState<string>(); const [validationText, setValidationText] = useState<string>();
const [preSharedDialogOpen, setPreSharedDialogOpen] = const [preSharedDialogOpen, setPreSharedDialogOpen] = useState<boolean>(
useState<boolean>(false); false,
);
const onSubmit = (data: ChannelValidation) => { const onSubmit = (data: ChannelValidation) => {
const channel = create(Protobuf.Channel.ChannelSchema, { const channel = create(Protobuf.Channel.ChannelSchema, {
@ -107,7 +108,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.moduleSettings?.positionPrecision === 32, channel?.settings?.moduleSettings?.positionPrecision === 32,
positionPrecision: positionPrecision:
channel?.settings?.moduleSettings?.positionPrecision === channel?.settings?.moduleSettings?.positionPrecision ===
undefined undefined
? 10 ? 10
: channel?.settings?.moduleSettings?.positionPrecision, : channel?.settings?.moduleSettings?.positionPrecision,
}, },
@ -126,10 +127,9 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed", "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
properties: { properties: {
enumValue: enumValue: channel.index === 0
channel.index === 0 ? { PRIMARY: 1 }
? { PRIMARY: 1 } : { DISABLED: 0, SECONDARY: 2 },
: { DISABLED: 0, SECONDARY: 2 },
}, },
}, },
{ {
@ -192,32 +192,31 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
description: description:
"If not sharing precise location, position shared on channel will be accurate within this distance", "If not sharing precise location, position shared on channel will be accurate within this distance",
properties: { properties: {
enumValue: enumValue: config.display?.units === 0
config.display?.units === 0 ? {
? { "Within 23 km": 10,
"Within 23 km": 10, "Within 12 km": 11,
"Within 12 km": 11, "Within 5.8 km": 12,
"Within 5.8 km": 12, "Within 2.9 km": 13,
"Within 2.9 km": 13, "Within 1.5 km": 14,
"Within 1.5 km": 14, "Within 700 m": 15,
"Within 700 m": 15, "Within 350 m": 16,
"Within 350 m": 16, "Within 200 m": 17,
"Within 200 m": 17, "Within 90 m": 18,
"Within 90 m": 18, "Within 50 m": 19,
"Within 50 m": 19, }
} : {
: { "Within 15 miles": 10,
"Within 15 miles": 10, "Within 7.3 miles": 11,
"Within 7.3 miles": 11, "Within 3.6 miles": 12,
"Within 3.6 miles": 12, "Within 1.8 miles": 13,
"Within 1.8 miles": 13, "Within 0.9 miles": 14,
"Within 0.9 miles": 14, "Within 0.5 miles": 15,
"Within 0.5 miles": 15, "Within 0.2 miles": 16,
"Within 0.2 miles": 16, "Within 600 feet": 17,
"Within 600 feet": 17, "Within 300 feet": 18,
"Within 300 feet": 18, "Within 150 feet": 19,
"Within 150 feet": 19, },
},
}, },
}, },
], ],

7
src/components/PageComponents/Config/Bluetooth.tsx

@ -3,7 +3,7 @@ import type { BluetoothValidation } from "@app/validation/config/bluetooth.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { useState } from "react"; import { useState } from "react";
export const Bluetooth = () => { export const Bluetooth = () => {
@ -111,9 +111,8 @@ export const Bluetooth = () => {
disabledBy: [ disabledBy: [
{ {
fieldName: "mode", fieldName: "mode",
selector: selector: Protobuf.Config.Config_BluetoothConfig_PairingMode
Protobuf.Config.Config_BluetoothConfig_PairingMode .FIXED_PIN,
.FIXED_PIN,
invert: true, invert: true,
}, },
{ {

2
src/components/PageComponents/Config/Device.tsx

@ -2,7 +2,7 @@ import type { DeviceValidation } from "@app/validation/config/device.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Device = (): JSX.Element => { export const Device = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();

2
src/components/PageComponents/Config/Display.tsx

@ -2,7 +2,7 @@ import type { DisplayValidation } from "@app/validation/config/display.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Display = (): JSX.Element => { export const Display = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();

2
src/components/PageComponents/Config/LoRa.tsx

@ -2,7 +2,7 @@ import type { LoRaValidation } from "@app/validation/config/lora.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const LoRa = (): JSX.Element => { export const LoRa = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();

2
src/components/PageComponents/Config/Network.tsx

@ -6,7 +6,7 @@ import {
convertIntToIpAddress, convertIntToIpAddress,
convertIpAddressToInt, convertIpAddressToInt,
} from "@core/utils/ip.ts"; } from "@core/utils/ip.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Network = (): JSX.Element => { export const Network = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();

2
src/components/PageComponents/Config/Position.tsx

@ -6,7 +6,7 @@ import type { PositionValidation } from "@app/validation/config/position.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { useCallback } from "react"; import { useCallback } from "react";
export const Position = () => { export const Position = () => {

2
src/components/PageComponents/Config/Power.tsx

@ -2,7 +2,7 @@ import type { PowerValidation } from "@app/validation/config/power.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Power = (): JSX.Element => { export const Power = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice(); const { config, setWorkingConfig } = useDevice();

2
src/components/PageComponents/Config/Security/Security.tsx

@ -8,7 +8,7 @@ import {
import type { SecurityValidation } from "@app/validation/config/security.tsx"; import type { SecurityValidation } from "@app/validation/config/security.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import { useReducer } from "react"; import { useReducer } from "react";

14
src/components/PageComponents/Connect/Serial.tsx

@ -5,7 +5,8 @@ import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { subscribeAll } from "@core/subscriptions.ts"; import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts"; import { randId } from "@core/utils/randId.ts";
import { SerialConnection } from "@meshtastic/js"; import { MeshDevice } from "@meshtastic/core";
import { TransportWebSerial } from "@meshtastic/transport-web-serial";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
@ -31,14 +32,9 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
const id = randId(); const id = randId();
const device = addDevice(id); const device = addDevice(id);
setSelectedDevice(id); setSelectedDevice(id);
const connection = new SerialConnection(id); const transport = await TransportWebSerial.createFromPort(port);
await connection const connection = new MeshDevice(transport, id);
.connect({ connection.configure();
port,
baudRate: undefined,
concurrentLogOutput: true,
})
.catch((e: Error) => console.log(`Unable to Connect: ${e.message}`));
device.addConnection(connection); device.addConnection(connection);
subscribeAll(device, connection); subscribeAll(device, connection);

52
src/components/PageComponents/Map/NodeDetail.tsx

@ -5,8 +5,8 @@ import { formatQuantity } from "@app/core/utils/string";
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "@components/UI/Avatar";
import { Mono } from "@components/generic/Mono.tsx"; import { Mono } from "@components/generic/Mono.tsx";
import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { TimeAgo } from "@components/generic/TimeAgo.tsx";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import type { Protobuf as ProtobufType } from "@meshtastic/js"; import type { Protobuf as ProtobufType } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { import {
BatteryChargingIcon, BatteryChargingIcon,
@ -37,21 +37,23 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
<Avatar text={node.user?.shortName} /> <Avatar text={node.user?.shortName} />
<div> <div>
{node.user?.publicKey && node.user?.publicKey.length > 0 ? ( {node.user?.publicKey && node.user?.publicKey.length > 0
<LockIcon ? (
className="text-green-600" <LockIcon
size={12} className="text-green-600"
strokeWidth={3} size={12}
aria-label="Public Key Enabled" strokeWidth={3}
/> aria-label="Public Key Enabled"
) : ( />
<LockOpenIcon )
className="text-yellow-500" : (
size={12} <LockOpenIcon
strokeWidth={3} className="text-yellow-500"
aria-label="No Public Key" size={12}
/> strokeWidth={3}
)} aria-label="No Public Key"
/>
)}
</div> </div>
<Star <Star
@ -73,15 +75,13 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown" node.deviceMetrics?.voltage?.toPrecision(3) ?? "Unknown"
} volts`} } volts`}
> >
{node.deviceMetrics?.batteryLevel > 100 ? ( {node.deviceMetrics?.batteryLevel > 100
<BatteryChargingIcon size={22} /> ? <BatteryChargingIcon size={22} />
) : node.deviceMetrics?.batteryLevel > 80 ? ( : node.deviceMetrics?.batteryLevel > 80
<BatteryFullIcon size={22} /> ? <BatteryFullIcon size={22} />
) : node.deviceMetrics?.batteryLevel > 20 ? ( : node.deviceMetrics?.batteryLevel > 20
<BatteryMediumIcon size={22} /> ? <BatteryMediumIcon size={22} />
) : ( : <BatteryLowIcon size={22} />}
<BatteryLowIcon size={22} />
)}
<Subtle aria-label="Battery"> <Subtle aria-label="Battery">
{node.deviceMetrics?.batteryLevel > 100 {node.deviceMetrics?.batteryLevel > 100
? "Charging" ? "Charging"

10
src/components/PageComponents/Messages/ChannelChat.tsx

@ -4,7 +4,7 @@ import {
} from "@app/core/stores/deviceStore.ts"; } from "@app/core/stores/deviceStore.ts";
import { Message } from "@components/PageComponents/Messages/Message.tsx"; import { Message } from "@components/PageComponents/Messages/Message.tsx";
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx"; import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx";
import type { Types } from "@meshtastic/js"; import type { Types } from "@meshtastic/core";
import { InboxIcon } from "lucide-react"; import { InboxIcon } from "lucide-react";
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import type { JSX } from "react"; import type { JSX } from "react";
@ -34,8 +34,7 @@ export const ChannelChat = ({
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {
const scrollContainer = scrollContainerRef.current; const scrollContainer = scrollContainerRef.current;
if (scrollContainer) { if (scrollContainer) {
const isNearBottom = const isNearBottom = scrollContainer.scrollHeight -
scrollContainer.scrollHeight -
scrollContainer.scrollTop - scrollContainer.scrollTop -
scrollContainer.clientHeight < scrollContainer.clientHeight <
100; 100;
@ -72,9 +71,8 @@ export const ChannelChat = ({
key={message.id} key={message.id}
message={message} message={message}
sender={nodes.get(message.from)} sender={nodes.get(message.from)}
lastMsgSameUser={ lastMsgSameUser={index > 0 &&
index > 0 && messages[index - 1].from === message.from messages[index - 1].from === message.from}
}
/> />
); );
})} })}

2
src/components/PageComponents/Messages/Message.tsx

@ -12,7 +12,7 @@ import {
} from "@app/core/stores/deviceStore.ts"; } from "@app/core/stores/deviceStore.ts";
import { cn } from "@app/core/utils/cn"; import { cn } from "@app/core/utils/cn";
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "@components/UI/Avatar";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react"; import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import { useMemo } from "react"; import { useMemo } from "react";

6
src/components/PageComponents/Messages/MessageInput.tsx

@ -2,7 +2,7 @@ import { debounce } from "@app/core/utils/debounce";
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/js"; import type { Types } from "@meshtastic/core";
import { SendIcon } from "lucide-react"; import { SendIcon } from "lucide-react";
import { import {
type JSX, type JSX,
@ -51,7 +51,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
id, id,
"ack", "ack",
), )
) )
.catch((e: Types.PacketError) => .catch((e: Types.PacketError) =>
setMessageState( setMessageState(
@ -61,7 +61,7 @@ export const MessageInput = ({
myNodeNum, myNodeNum,
e.id, e.id,
e.error, e.error,
), )
); );
}, },
[channel, connection, myNodeNum, setMessageState, to], [channel, connection, myNodeNum, setMessageState, to],

38
src/components/PageComponents/Messages/TraceRoute.tsx

@ -1,5 +1,5 @@
import { useDevice } from "@app/core/stores/deviceStore.ts"; import { useDevice } from "@app/core/stores/deviceStore.ts";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import type { JSX } from "react"; import type { JSX } from "react";
@ -38,23 +38,25 @@ export const TraceRoute = ({
))} ))}
{from?.user?.longName} {from?.user?.longName}
</span> </span>
{routeBack ? ( {routeBack
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary"> ? (
<p className="font-semibold">Route back:</p> <span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
<p>{from?.user?.longName}</p> <p className="font-semibold">Route back:</p>
<p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p> <p>{from?.user?.longName}</p>
{routeBack.map((hop, i) => ( <p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
<span key={nodes.get(hop)?.num}> {routeBack.map((hop, i) => (
<p> <span key={nodes.get(hop)?.num}>
{nodes.get(hop)?.user?.longName ?? <p>
`!${numberToHexUnpadded(hop)}`} {nodes.get(hop)?.user?.longName ??
</p> `!${numberToHexUnpadded(hop)}`}
<p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p> </p>
</span> <p> {snrBack?.[i + 1] ? snrBack[i + 1] : "??"}dB</p>
))} </span>
{to?.user?.longName} ))}
</span> {to?.user?.longName}
) : null} </span>
)
: null}
</div> </div>
); );
}; };

2
src/components/PageComponents/ModuleConfig/AmbientLighting.tsx

@ -2,7 +2,7 @@ import { useDevice } from "@app/core/stores/deviceStore.ts";
import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx"; import type { AmbientLightingValidation } from "@app/validation/moduleConfig/ambientLighting.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const AmbientLighting = (): JSX.Element => { export const AmbientLighting = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/Audio.tsx

@ -2,7 +2,7 @@ import type { AudioValidation } from "@app/validation/moduleConfig/audio.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Audio = (): JSX.Element => { export const Audio = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/CannedMessage.tsx

@ -2,7 +2,7 @@ import type { CannedMessageValidation } from "@app/validation/moduleConfig/canne
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const CannedMessage = (): JSX.Element => { export const CannedMessage = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/DetectionSensor.tsx

@ -2,7 +2,7 @@ import { useDevice } from "@app/core/stores/deviceStore.ts";
import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx"; import type { DetectionSensorValidation } from "@app/validation/moduleConfig/detectionSensor.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const DetectionSensor = (): JSX.Element => { export const DetectionSensor = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/ExternalNotification.tsx

@ -2,7 +2,7 @@ import type { ExternalNotificationValidation } from "@app/validation/moduleConfi
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const ExternalNotification = (): JSX.Element => { export const ExternalNotification = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/MQTT.tsx

@ -2,7 +2,7 @@ import { useDevice } from "@app/core/stores/deviceStore.ts";
import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx"; import type { MqttValidation } from "@app/validation/moduleConfig/mqtt.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const MQTT = (): JSX.Element => { export const MQTT = (): JSX.Element => {
const { config, moduleConfig, setWorkingModuleConfig } = useDevice(); const { config, moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/NeighborInfo.tsx

@ -2,7 +2,7 @@ import { useDevice } from "@app/core/stores/deviceStore.ts";
import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx"; import type { NeighborInfoValidation } from "@app/validation/moduleConfig/neighborInfo.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const NeighborInfo = (): JSX.Element => { export const NeighborInfo = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/Paxcounter.tsx

@ -2,7 +2,7 @@ import type { PaxcounterValidation } from "@app/validation/moduleConfig/paxcount
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Paxcounter = (): JSX.Element => { export const Paxcounter = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/RangeTest.tsx

@ -2,7 +2,7 @@ import type { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const RangeTest = (): JSX.Element => { export const RangeTest = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/Serial.tsx

@ -2,7 +2,7 @@ import type { SerialValidation } from "@app/validation/moduleConfig/serial.tsx";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Serial = (): JSX.Element => { export const Serial = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/StoreForward.tsx

@ -2,7 +2,7 @@ import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeF
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const StoreForward = (): JSX.Element => { export const StoreForward = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/components/PageComponents/ModuleConfig/Telemetry.tsx

@ -2,7 +2,7 @@ import type { TelemetryValidation } from "@app/validation/moduleConfig/telemetry
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
export const Telemetry = (): JSX.Element => { export const Telemetry = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice(); const { moduleConfig, setWorkingModuleConfig } = useDevice();

2
src/core/stores/appStore.ts

@ -1,4 +1,4 @@
import { Types } from "@meshtastic/js"; import { Types } from "@meshtastic/core";
import { produce } from "immer"; import { produce } from "immer";
import { create } from "zustand"; import { create } from "zustand";

34
src/core/stores/deviceStore.ts

@ -1,5 +1,5 @@
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { Protobuf, Types } from "@meshtastic/js"; import { Protobuf, Types } from "@meshtastic/core";
import { produce } from "immer"; import { produce } from "immer";
import { createContext, useContext } from "react"; import { createContext, useContext } from "react";
import { create as createStore } from "zustand"; import { create as createStore } from "zustand";
@ -299,11 +299,11 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
if (!device) { if (!device) {
return; return;
} }
const workingModuleConfigIndex = const workingModuleConfigIndex = device?.workingModuleConfig
device?.workingModuleConfig.findIndex( .findIndex(
(wmc) => (wmc) =>
wmc.payloadVariant.case === wmc.payloadVariant.case ===
moduleConfig.payloadVariant.case, moduleConfig.payloadVariant.case,
); );
if (workingModuleConfigIndex !== -1) { if (workingModuleConfigIndex !== -1) {
device.workingModuleConfig[workingModuleConfigIndex] = device.workingModuleConfig[workingModuleConfigIndex] =
@ -445,8 +445,7 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
if (!device) { if (!device) {
return; return;
} }
const currentNode = const currentNode = device.nodes.get(user.from) ??
device.nodes.get(user.from) ??
create(Protobuf.Mesh.NodeInfoSchema); create(Protobuf.Mesh.NodeInfoSchema);
currentNode.user = user.data; currentNode.user = user.data;
device.nodes.set(user.from, currentNode); device.nodes.set(user.from, currentNode);
@ -460,8 +459,7 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
if (!device) { if (!device) {
return; return;
} }
const currentNode = const currentNode = device.nodes.get(position.from) ??
device.nodes.get(position.from) ??
create(Protobuf.Mesh.NodeInfoSchema); create(Protobuf.Mesh.NodeInfoSchema);
currentNode.position = position.data; currentNode.position = position.data;
device.nodes.set(position.from, currentNode); device.nodes.set(position.from, currentNode);
@ -486,12 +484,11 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
return; return;
} }
const messageGroup = device.messages[message.type]; const messageGroup = device.messages[message.type];
const messageIndex = const messageIndex = message.type === "direct"
message.type === "direct" ? message.from === device.hardware.myNodeNum
? message.from === device.hardware.myNodeNum ? message.to
? message.to : message.from
: message.from : message.channel;
: message.channel;
const messages = messageGroup.get(messageIndex); const messages = messageGroup.get(messageIndex);
if (messages) { if (messages) {
@ -564,12 +561,9 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
} }
const messageGroup = device.messages[type]; const messageGroup = device.messages[type];
const messageIndex = const messageIndex = type === "direct"
type === "direct" ? from === device.hardware.myNodeNum ? to : from
? from === device.hardware.myNodeNum : channelIndex;
? to
: from
: channelIndex;
const messages = messageGroup.get(messageIndex); const messages = messageGroup.get(messageIndex);
if (!messages) { if (!messages) {

2
src/core/subscriptions.ts

@ -1,5 +1,5 @@
import type { Device } from "@core/stores/deviceStore.ts"; import type { Device } from "@core/stores/deviceStore.ts";
import { Protobuf, type Types } from "@meshtastic/js"; import { Protobuf, type Types } from "@meshtastic/core";
export const subscribeAll = ( export const subscribeAll = (
device: Device, device: Device,

8
src/pages/Channels.tsx

@ -8,8 +8,8 @@ import { Channel } from "@components/PageComponents/Channel.tsx";
import { PageLayout } from "@components/PageLayout.tsx"; import { PageLayout } from "@components/PageLayout.tsx";
import { Sidebar } from "@components/Sidebar.tsx"; import { Sidebar } from "@components/Sidebar.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Types } from "@meshtastic/js"; import { Types } from "@meshtastic/core";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { ImportIcon, QrCodeIcon } from "lucide-react"; import { ImportIcon, QrCodeIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
@ -17,8 +17,8 @@ export const getChannelName = (channel: Protobuf.Channel.Channel) =>
channel.settings?.name.length channel.settings?.name.length
? channel.settings?.name ? channel.settings?.name
: channel.index === 0 : channel.index === 0
? "Primary" ? "Primary"
: `Ch ${channel.index}`; : `Ch ${channel.index}`;
const ChannelsPage = () => { const ChannelsPage = () => {
const { channels, setDialogOpen } = useDevice(); const { channels, setDialogOpen } = useDevice();

2
src/pages/Map/index.tsx

@ -4,7 +4,7 @@ import { useTheme } from "@app/core/hooks/useTheme";
import { PageLayout } from "@components/PageLayout.tsx"; import { PageLayout } from "@components/PageLayout.tsx";
import { Sidebar } from "@components/Sidebar.tsx"; import { Sidebar } from "@components/Sidebar.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { bbox, lineString } from "@turf/turf"; import { bbox, lineString } from "@turf/turf";
import { current } from "immer"; import { current } from "immer";
import { MapPinIcon } from "lucide-react"; import { MapPinIcon } from "lucide-react";

67
src/pages/Messages.tsx

@ -7,7 +7,7 @@ import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx";
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx";
import { useToast } from "@core/hooks/useToast.ts"; import { useToast } from "@core/hooks/useToast.ts";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf, Types } from "@meshtastic/js"; import { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { getChannelName } from "@pages/Channels.tsx"; import { getChannelName } from "@pages/Channels.tsx";
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react"; import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
@ -39,13 +39,11 @@ export const MessagesPage = () => {
{filteredChannels.map((channel) => ( {filteredChannels.map((channel) => (
<SidebarButton <SidebarButton
key={channel.index} key={channel.index}
label={ label={channel.settings?.name.length
channel.settings?.name.length ? channel.settings?.name
? channel.settings?.name : channel.index === 0
: channel.index === 0 ? "Primary"
? "Primary" : `Ch ${channel.index}`}
: `Ch ${channel.index}`
}
active={activeChat === channel.index} active={activeChat === channel.index}
onClick={() => { onClick={() => {
setChatType("broadcast"); setChatType("broadcast");
@ -69,9 +67,8 @@ export const MessagesPage = () => {
{filteredNodes.map((node) => ( {filteredNodes.map((node) => (
<SidebarButton <SidebarButton
key={node.num} key={node.num}
label={ label={node.user?.longName ??
node.user?.longName ?? `!${numberToHexUnpadded(node.num)}` `!${numberToHexUnpadded(node.num)}`}
}
active={activeChat === node.num} active={activeChat === node.num}
onClick={() => { onClick={() => {
setChatType("direct"); setChatType("direct");
@ -94,32 +91,30 @@ export const MessagesPage = () => {
chatType === "broadcast" && currentChannel chatType === "broadcast" && currentChannel
? getChannelName(currentChannel) ? getChannelName(currentChannel)
: chatType === "direct" && nodes.get(activeChat) : chatType === "direct" && nodes.get(activeChat)
? (nodes.get(activeChat)?.user?.longName ?? nodeHex) ? (nodes.get(activeChat)?.user?.longName ?? nodeHex)
: "Loading..." : "Loading..."
}`} }`}
actions={ actions={chatType === "direct"
chatType === "direct" ? [
? [ {
{ icon: nodes.get(activeChat)?.user?.publicKey.length
icon: nodes.get(activeChat)?.user?.publicKey.length ? LockIcon
? LockIcon : LockOpenIcon,
: LockOpenIcon, iconClasses: nodes.get(activeChat)?.user?.publicKey.length
iconClasses: nodes.get(activeChat)?.user?.publicKey.length ? "text-green-600"
? "text-green-600" : "text-yellow-300",
: "text-yellow-300", async onClick() {
async onClick() { const targetNode = nodes.get(activeChat)?.num;
const targetNode = nodes.get(activeChat)?.num; if (targetNode === undefined) return;
if (targetNode === undefined) return; toast({
toast({ title: nodes.get(activeChat)?.user?.publicKey.length
title: nodes.get(activeChat)?.user?.publicKey.length ? "Chat is using PKI encryption."
? "Chat is using PKI encryption." : "Chat is using PSK encryption.",
: "Chat is using PSK encryption.", });
}); },
}, },
}, ]
] : []}
: []
}
> >
{allChannels.map( {allChannels.map(
(channel) => (channel) =>

34
src/pages/Nodes.tsx

@ -8,7 +8,7 @@ import { Mono } from "@components/generic/Mono.tsx";
import { Table } from "@components/generic/Table/index.tsx"; import { Table } from "@components/generic/Table/index.tsx";
import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { TimeAgo } from "@components/generic/TimeAgo.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf, type Types } from "@meshtastic/js"; import { Protobuf, type Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { LockIcon, LockOpenIcon } from "lucide-react"; import { LockIcon, LockOpenIcon } from "lucide-react";
import { Fragment, type JSX, useCallback, useEffect, useState } from "react"; import { Fragment, type JSX, useCallback, useEffect, useState } from "react";
@ -107,9 +107,11 @@ const NodesPage = (): JSX.Element => {
> >
{node.user?.shortName ?? {node.user?.shortName ??
(node.user?.macaddr (node.user?.macaddr
? `${base16 ? `${
base16
.stringify(node.user?.macaddr.subarray(4, 6) ?? []) .stringify(node.user?.macaddr.subarray(4, 6) ?? [])
.toLowerCase()}` .toLowerCase()
}`
: `${numberToHexUnpadded(node.num).slice(-4)}`)} : `${numberToHexUnpadded(node.num).slice(-4)}`)}
</h1>, </h1>,
@ -120,9 +122,11 @@ const NodesPage = (): JSX.Element => {
> >
{node.user?.longName ?? {node.user?.longName ??
(node.user?.macaddr (node.user?.macaddr
? `Meshtastic ${base16 ? `Meshtastic ${
base16
.stringify(node.user?.macaddr.subarray(4, 6) ?? []) .stringify(node.user?.macaddr.subarray(4, 6) ?? [])
.toLowerCase()}` .toLowerCase()
}`
: `!${numberToHexUnpadded(node.num)}`)} : `!${numberToHexUnpadded(node.num)}`)}
</h1>, </h1>,
@ -136,11 +140,9 @@ const NodesPage = (): JSX.Element => {
?.join(":") ?? "UNK"} ?.join(":") ?? "UNK"}
</Mono>, </Mono>,
<Fragment key="lastHeard"> <Fragment key="lastHeard">
{node.lastHeard === 0 ? ( {node.lastHeard === 0
<p>Never</p> ? <p>Never</p>
) : ( : <TimeAgo timestamp={node.lastHeard * 1000} />}
<TimeAgo timestamp={node.lastHeard * 1000} />
)}
</Fragment>, </Fragment>,
<Mono key="snr"> <Mono key="snr">
{node.snr}db/ {node.snr}db/
@ -148,19 +150,17 @@ const NodesPage = (): JSX.Element => {
{(node.snr + 10) * 5}raw {(node.snr + 10) * 5}raw
</Mono>, </Mono>,
<Mono key="pki"> <Mono key="pki">
{node.user?.publicKey && node.user?.publicKey.length > 0 ? ( {node.user?.publicKey && node.user?.publicKey.length > 0
<LockIcon className="text-green-600" /> ? <LockIcon className="text-green-600" />
) : ( : <LockOpenIcon className="text-yellow-300 mx-auto" />}
<LockOpenIcon className="text-yellow-300 mx-auto" />
)}
</Mono>, </Mono>,
<Mono key="hops"> <Mono key="hops">
{node.lastHeard !== 0 {node.lastHeard !== 0
? node.viaMqtt === false && node.hopsAway === 0 ? node.viaMqtt === false && node.hopsAway === 0
? "Direct" ? "Direct"
: `${node.hopsAway.toString()} ${ : `${node.hopsAway.toString()} ${
node.hopsAway > 1 ? "hops" : "hop" node.hopsAway > 1 ? "hops" : "hop"
} away` } away`
: "-"} : "-"}
{node.viaMqtt === true ? ", via MQTT" : ""} {node.viaMqtt === true ? ", via MQTT" : ""}
</Mono>, </Mono>,

8
src/validation/channel.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { import {
IsBoolean, IsBoolean,
IsEnum, IsEnum,
@ -10,8 +10,7 @@ import {
} from "class-validator"; } from "class-validator";
export class ChannelValidation export class ChannelValidation
implements Omit<Protobuf.Channel.Channel, keyof Message | "settings"> implements Omit<Protobuf.Channel.Channel, keyof Message | "settings"> {
{
@IsNumber() @IsNumber()
index: number; index: number;
@ -22,8 +21,7 @@ export class ChannelValidation
} }
export class Channel_SettingsValidation export class Channel_SettingsValidation
implements Omit<Protobuf.Channel.ChannelSettings, keyof Message | "psk"> implements Omit<Protobuf.Channel.ChannelSettings, keyof Message | "psk"> {
{
@IsNumber() @IsNumber()
channelNum: number; channelNum: number;

14
src/validation/config/bluetooth.ts

@ -1,14 +1,12 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsEnum, IsInt } from "class-validator"; import { IsBoolean, IsEnum, IsInt } from "class-validator";
export class BluetoothValidation export class BluetoothValidation implements
implements Omit<
Omit< Protobuf.Config.Config_BluetoothConfig,
Protobuf.Config.Config_BluetoothConfig, keyof Message | "deviceLoggingEnabled"
keyof Message | "deviceLoggingEnabled" > {
>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

2
src/validation/config/device.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsEnum, IsInt, IsString } from "class-validator"; import { IsBoolean, IsEnum, IsInt, IsString } from "class-validator";
export class DeviceValidation export class DeviceValidation

2
src/validation/config/display.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsEnum, IsInt } from "class-validator"; import { IsBoolean, IsEnum, IsInt } from "class-validator";
export class DisplayValidation export class DisplayValidation

2
src/validation/config/lora.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsArray, IsBoolean, IsEnum, IsInt, Max, Min } from "class-validator"; import { IsArray, IsBoolean, IsEnum, IsInt, Max, Min } from "class-validator";
export class LoRaValidation export class LoRaValidation

2
src/validation/config/network.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { import {
IsBoolean, IsBoolean,
IsEnum, IsEnum,

14
src/validation/config/position.ts

@ -1,16 +1,14 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsArray, IsBoolean, IsEnum, IsInt } from "class-validator"; import { IsArray, IsBoolean, IsEnum, IsInt } from "class-validator";
const DeprecatedPositionValidationFields = ["gpsEnabled", "gpsAttemptTime"]; const DeprecatedPositionValidationFields = ["gpsEnabled", "gpsAttemptTime"];
export class PositionValidation export class PositionValidation implements
implements Omit<
Omit< Protobuf.Config.Config_PositionConfig,
Protobuf.Config.Config_PositionConfig, keyof Message | (typeof DeprecatedPositionValidationFields)[number]
keyof Message | (typeof DeprecatedPositionValidationFields)[number] > {
>
{
@IsInt() @IsInt()
positionBroadcastSecs: number; positionBroadcastSecs: number;

2
src/validation/config/power.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt, IsNumber, Max, Min } from "class-validator"; import { IsBoolean, IsInt, IsNumber, Max, Min } from "class-validator";
export class PowerValidation export class PowerValidation

14
src/validation/config/security.ts

@ -1,14 +1,12 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsString } from "class-validator"; import { IsBoolean, IsString } from "class-validator";
export class SecurityValidation export class SecurityValidation implements
implements Omit<
Omit< Protobuf.Config.Config_SecurityConfig,
Protobuf.Config.Config_SecurityConfig, keyof Message | "adminKey" | "privateKey" | "publicKey"
keyof Message | "adminKey" | "privateKey" | "publicKey" > {
>
{
@IsBoolean() @IsBoolean()
adminChannelEnabled: boolean; adminChannelEnabled: boolean;

14
src/validation/moduleConfig/ambientLighting.ts

@ -1,14 +1,12 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class AmbientLightingValidation export class AmbientLightingValidation implements
implements Omit<
Omit< Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig,
Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig, keyof Message
keyof Message > {
>
{
@IsBoolean() @IsBoolean()
ledState: boolean; ledState: boolean;

2
src/validation/moduleConfig/audio.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsEnum, IsInt } from "class-validator"; import { IsBoolean, IsEnum, IsInt } from "class-validator";
export class AudioValidation export class AudioValidation

17
src/validation/moduleConfig/cannedMessage.ts

@ -1,11 +1,13 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsEnum, IsInt, Length } from "class-validator"; import { IsBoolean, IsEnum, IsInt, Length } from "class-validator";
export class CannedMessageValidation export class CannedMessageValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig, keyof Message> Omit<
{ Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig,
keyof Message
> {
@IsBoolean() @IsBoolean()
rotary1Enabled: boolean; rotary1Enabled: boolean;
@ -19,13 +21,16 @@ export class CannedMessageValidation
inputbrokerPinPress: number; inputbrokerPinPress: number;
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar) @IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
inputbrokerEventCw: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar; inputbrokerEventCw:
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar) @IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
inputbrokerEventCcw: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar; inputbrokerEventCcw:
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
@IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar) @IsEnum(Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar)
inputbrokerEventPress: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar; inputbrokerEventPress:
Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig_InputEventChar;
@IsBoolean() @IsBoolean()
updown1Enabled: boolean; updown1Enabled: boolean;

14
src/validation/moduleConfig/detectionSensor.ts

@ -1,14 +1,12 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt, Length } from "class-validator"; import { IsBoolean, IsInt, Length } from "class-validator";
export class DetectionSensorValidation export class DetectionSensorValidation implements
implements Omit<
Omit< Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig,
Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig, keyof Message
keyof Message > {
>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

14
src/validation/moduleConfig/externalNotification.ts

@ -1,14 +1,12 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class ExternalNotificationValidation export class ExternalNotificationValidation implements
implements Omit<
Omit< Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig,
Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig, keyof Message
keyof Message > {
>
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

2
src/validation/moduleConfig/mqtt.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { import {
IsBoolean, IsBoolean,
IsNumber, IsNumber,

5
src/validation/moduleConfig/neighborInfo.ts

@ -1,11 +1,10 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class NeighborInfoValidation export class NeighborInfoValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig, keyof Message> Omit<Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig, keyof Message> {
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

5
src/validation/moduleConfig/paxcounter.ts

@ -1,11 +1,10 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class PaxcounterValidation export class PaxcounterValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig, keyof Message> Omit<Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig, keyof Message> {
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

2
src/validation/moduleConfig/rangeTest.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class RangeTestValidation export class RangeTestValidation

5
src/validation/moduleConfig/serial.ts

@ -1,11 +1,10 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import { Protobuf } from "@meshtastic/js"; import { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsEnum, IsInt } from "class-validator"; import { IsBoolean, IsEnum, IsInt } from "class-validator";
export class SerialValidation export class SerialValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_SerialConfig, keyof Message> Omit<Protobuf.ModuleConfig.ModuleConfig_SerialConfig, keyof Message> {
{
@IsBoolean() @IsBoolean()
enabled: boolean; enabled: boolean;

2
src/validation/moduleConfig/storeForward.ts

@ -1,5 +1,5 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class StoreForwardValidation export class StoreForwardValidation

5
src/validation/moduleConfig/telemetry.ts

@ -1,11 +1,10 @@
import type { Message } from "@bufbuild/protobuf"; import type { Message } from "@bufbuild/protobuf";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/core";
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
export class TelemetryValidation export class TelemetryValidation
implements implements
Omit<Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig, keyof Message> Omit<Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig, keyof Message> {
{
@IsInt() @IsInt()
deviceUpdateInterval: number; deviceUpdateInterval: number;

Loading…
Cancel
Save