Browse Source

minor fixes

pull/149/head
Sacha Weatherstone 3 years ago
parent
commit
f7ba8ad967
Failed to extract signature
  1. 12
      src/components/PageComponents/Connect/BLE.tsx
  2. 10
      src/components/PageComponents/Connect/HTTP.tsx
  3. 14
      src/components/PageComponents/Connect/Serial.tsx
  4. 2
      src/components/PageComponents/Messages/MessageInput.tsx
  5. 13
      src/core/hooks/useToast.ts
  6. 6
      src/core/stores/appStore.ts
  7. 85
      src/core/stores/deviceStore.ts
  8. 11
      src/core/subscriptions.ts
  9. 4
      src/core/utils/bitwise.ts
  10. 1
      src/index.tsx
  11. 1
      src/pages/Config/DeviceConfig.tsx
  12. 4
      src/pages/Dashboard/index.tsx
  13. 28
      src/pages/Map.tsx
  14. 10
      src/pages/Messages.tsx

12
src/components/PageComponents/Connect/BLE.tsx

@ -17,16 +17,16 @@ export const BLE = (): JSX.Element => {
}, []); }, []);
useEffect(() => { useEffect(() => {
void updateBleDeviceList(); updateBleDeviceList();
}, [updateBleDeviceList]); }, [updateBleDeviceList]);
const onConnect = async (BLEDevice: BluetoothDevice) => { const onConnect = async (bleDevice: BluetoothDevice) => {
const id = randId(); const id = randId();
const device = addDevice(id); const device = addDevice(id);
setSelectedDevice(id); setSelectedDevice(id);
const connection = new IBLEConnection(id); const connection = new IBLEConnection(id);
await connection.connect({ await connection.connect({
device: BLEDevice, device: bleDevice,
}); });
device.addConnection(connection); device.addConnection(connection);
subscribeAll(device, connection); subscribeAll(device, connection);
@ -39,7 +39,7 @@ export const BLE = (): JSX.Element => {
<Button <Button
key={device.id} key={device.id}
onClick={() => { onClick={() => {
void onConnect(device); onConnect(device);
}} }}
> >
{device.name} {device.name}
@ -50,8 +50,8 @@ export const BLE = (): JSX.Element => {
)} )}
</div> </div>
<Button <Button
onClick={() => { onClick={async () => {
void navigator.bluetooth await navigator.bluetooth
.requestDevice({ .requestDevice({
filters: [{ services: [Constants.ServiceUuid] }], filters: [{ services: [Constants.ServiceUuid] }],
}) })

10
src/components/PageComponents/Connect/HTTP.tsx

@ -1,7 +1,6 @@
import { Button } from "@components/UI/Button.js"; import { Button } from "@components/UI/Button.js";
import { Input } from "@components/UI/Input.js"; import { Input } from "@components/UI/Input.js";
import { Label } from "@components/UI/Label.js"; import { Label } from "@components/UI/Label.js";
import { SelectLabel } from "@components/UI/Select.js";
import { Switch } from "@components/UI/Switch.js"; import { Switch } from "@components/UI/Switch.js";
import { useAppStore } from "@core/stores/appStore.js"; import { useAppStore } from "@core/stores/appStore.js";
import { useDeviceStore } from "@core/stores/deviceStore.js"; import { useDeviceStore } from "@core/stores/deviceStore.js";
@ -27,19 +26,19 @@ export const HTTP = (): JSX.Element => {
}, },
}); });
const TLSEnabled = useWatch({ const tlsEnabled = useWatch({
control, control,
name: "tls", name: "tls",
defaultValue: location.protocol === "https:", defaultValue: location.protocol === "https:",
}); });
const onSubmit = handleSubmit((data) => { const onSubmit = handleSubmit(async (data) => {
const id = randId(); const id = randId();
const device = addDevice(id); const device = addDevice(id);
setSelectedDevice(id); setSelectedDevice(id);
const connection = new IHTTPConnection(id); const connection = new IHTTPConnection(id);
// TODO: Promise never resolves // TODO: Promise never resolves
void connection.connect({ await connection.connect({
address: data.ip, address: data.ip,
fetchInterval: 2000, fetchInterval: 2000,
tls: data.tls, tls: data.tls,
@ -49,13 +48,12 @@ export const HTTP = (): JSX.Element => {
}); });
return ( return (
// eslint-disable-next-line @typescript-eslint/no-misused-promises
<form className="flex w-full flex-col gap-2 p-4" onSubmit={onSubmit}> <form className="flex w-full flex-col gap-2 p-4" onSubmit={onSubmit}>
<div className="flex h-48 flex-col gap-2"> <div className="flex h-48 flex-col gap-2">
<Label>IP Address/Hostname</Label> <Label>IP Address/Hostname</Label>
<Input <Input
// label="IP Address/Hostname" // label="IP Address/Hostname"
prefix={TLSEnabled ? "https://" : "http://"} prefix={tlsEnabled ? "https://" : "http://"}
placeholder="000.000.000.000 / meshtastic.local" placeholder="000.000.000.000 / meshtastic.local"
{...register("ip")} {...register("ip")}
/> />

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

@ -17,13 +17,13 @@ export const Serial = (): JSX.Element => {
}, []); }, []);
navigator.serial.addEventListener("connect", () => { navigator.serial.addEventListener("connect", () => {
void updateSerialPortList(); updateSerialPortList();
}); });
navigator.serial.addEventListener("disconnect", () => { navigator.serial.addEventListener("disconnect", () => {
void updateSerialPortList(); updateSerialPortList();
}); });
useEffect(() => { useEffect(() => {
void updateSerialPortList(); updateSerialPortList();
}, [updateSerialPortList]); }, [updateSerialPortList]);
const onConnect = async (port: SerialPort) => { const onConnect = async (port: SerialPort) => {
@ -49,8 +49,8 @@ export const Serial = (): JSX.Element => {
<Button <Button
key={index} key={index}
disabled={port.readable !== null} disabled={port.readable !== null}
onClick={() => { onClick={async () => {
void onConnect(port); await onConnect(port);
}} }}
> >
{`# ${index} - ${port.getInfo().usbVendorId ?? "UNK"} - ${ {`# ${index} - ${port.getInfo().usbVendorId ?? "UNK"} - ${
@ -63,8 +63,8 @@ export const Serial = (): JSX.Element => {
)} )}
</div> </div>
<Button <Button
onClick={() => { onClick={async () => {
void navigator.serial.requestPort().then((port) => { await navigator.serial.requestPort().then((port) => {
setSerialPorts(serialPorts.concat(port)); setSerialPorts(serialPorts.concat(port));
}); });
}} }}

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

@ -61,7 +61,7 @@ export const MessageInput = ({
<div className="flex flex-grow gap-2"> <div className="flex flex-grow gap-2">
<span className="w-full"> <span className="w-full">
<Input <Input
autoFocus autoFocus={true}
minLength={2} minLength={2}
placeholder="Enter Message" placeholder="Enter Message"
value={messageDraft} value={messageDraft}

13
src/core/hooks/useToast.ts

@ -1,4 +1,4 @@
import * as React from "react"; import { useEffect, useState, ReactNode } from "react";
import type { ToastActionElement, ToastProps } from "@components/UI/Toast.js"; import type { ToastActionElement, ToastProps } from "@components/UI/Toast.js";
@ -7,8 +7,8 @@ const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & { type ToasterToast = ToastProps & {
id: string; id: string;
title?: React.ReactNode; title?: ReactNode;
description?: React.ReactNode; description?: ReactNode;
action?: ToastActionElement; action?: ToastActionElement;
}; };
@ -109,7 +109,7 @@ export const reducer = (state: State, action: Action): State => {
), ),
}; };
} }
case "REMOVE_TOAST": case "REMOVE_TOAST": {
if (action.toastId === undefined) { if (action.toastId === undefined) {
return { return {
...state, ...state,
@ -120,6 +120,7 @@ export const reducer = (state: State, action: Action): State => {
...state, ...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId), toasts: state.toasts.filter((t) => t.id !== action.toastId),
}; };
}
} }
}; };
@ -166,9 +167,9 @@ function toast({ ...props }: Toast) {
} }
function useToast() { function useToast() {
const [state, setState] = React.useState<State>(memoryState); const [state, setState] = useState<State>(memoryState);
React.useEffect(() => { useEffect(() => {
listeners.push(setState); listeners.push(setState);
return () => { return () => {
const index = listeners.indexOf(setState); const index = listeners.indexOf(setState);

6
src/core/stores/appStore.ts

@ -8,7 +8,7 @@ export interface RasterSource {
tileSize: number; tileSize: number;
} }
export type accentColor = export type AccentColor =
| "red" | "red"
| "orange" | "orange"
| "yellow" | "yellow"
@ -26,7 +26,7 @@ interface AppState {
rasterSources: RasterSource[]; rasterSources: RasterSource[];
commandPaletteOpen: boolean; commandPaletteOpen: boolean;
darkMode: boolean; darkMode: boolean;
accent: accentColor; accent: AccentColor;
connectDialogOpen: boolean; connectDialogOpen: boolean;
setRasterSources: (sources: RasterSource[]) => void; setRasterSources: (sources: RasterSource[]) => void;
@ -38,7 +38,7 @@ interface AppState {
removeDevice: (deviceId: number) => void; removeDevice: (deviceId: number) => void;
setCommandPaletteOpen: (open: boolean) => void; setCommandPaletteOpen: (open: boolean) => void;
setDarkMode: (enabled: boolean) => void; setDarkMode: (enabled: boolean) => void;
setAccent: (color: accentColor) => void; setAccent: (color: AccentColor) => void;
setConnectDialogOpen: (open: boolean) => void; setConnectDialogOpen: (open: boolean) => void;
} }

85
src/core/stores/deviceStore.ts

@ -4,7 +4,6 @@ import { produce } from "immer";
import { create } from "zustand"; import { create } from "zustand";
import { Protobuf, Types } from "@meshtastic/meshtasticjs"; import { Protobuf, Types } from "@meshtastic/meshtasticjs";
import { channel } from "diagnostics_channel";
export type Page = "messages" | "map" | "config" | "channels" | "peers"; export type Page = "messages" | "map" | "config" | "channels" | "peers";
@ -14,7 +13,7 @@ export interface MessageWithState extends Types.PacketMetadata<string> {
export type MessageState = "ack" | "waiting" | Protobuf.Routing_Error; export type MessageState = "ack" | "waiting" | Protobuf.Routing_Error;
export interface processPacketParams { export interface ProcessPacketParams {
from: number; from: number;
snr: number; snr: number;
time: number; time: number;
@ -84,7 +83,7 @@ export interface Device {
state: MessageState, state: MessageState,
) => void; ) => void;
setDialogOpen: (dialog: DialogVariant, open: boolean) => void; setDialogOpen: (dialog: DialogVariant, open: boolean) => void;
processPacket: (data: processPacketParams) => void; processPacket: (data: ProcessPacketParams) => void;
setMessageDraft: (message: string) => void; setMessageDraft: (message: string) => void;
} }
@ -152,27 +151,34 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
if (device) { if (device) {
switch (config.payloadVariant.case) { switch (config.payloadVariant.case) {
case "device": case "device": {
device.config.device = config.payloadVariant.value; device.config.device = config.payloadVariant.value;
break; break;
case "position": }
case "position": {
device.config.position = config.payloadVariant.value; device.config.position = config.payloadVariant.value;
break; break;
case "power": }
case "power": {
device.config.power = config.payloadVariant.value; device.config.power = config.payloadVariant.value;
break; break;
case "network": }
case "network": {
device.config.network = config.payloadVariant.value; device.config.network = config.payloadVariant.value;
break; break;
case "display": }
case "display": {
device.config.display = config.payloadVariant.value; device.config.display = config.payloadVariant.value;
break; break;
case "lora": }
case "lora": {
device.config.lora = config.payloadVariant.value; device.config.lora = config.payloadVariant.value;
break; break;
case "bluetooth": }
case "bluetooth": {
device.config.bluetooth = config.payloadVariant.value; device.config.bluetooth = config.payloadVariant.value;
break; break;
}
} }
} }
}), }),
@ -185,43 +191,58 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
if (device) { if (device) {
switch (config.payloadVariant.case) { switch (config.payloadVariant.case) {
case "mqtt": case "mqtt": {
device.moduleConfig.mqtt = config.payloadVariant.value; device.moduleConfig.mqtt = config.payloadVariant.value;
break; break;
case "serial": }
case "serial": {
device.moduleConfig.serial = config.payloadVariant.value; device.moduleConfig.serial = config.payloadVariant.value;
break; break;
case "externalNotification": }
case "externalNotification": {
device.moduleConfig.externalNotification = device.moduleConfig.externalNotification =
config.payloadVariant.value; config.payloadVariant.value;
break; break;
case "storeForward": }
case "storeForward": {
device.moduleConfig.storeForward = device.moduleConfig.storeForward =
config.payloadVariant.value; config.payloadVariant.value;
break; break;
case "rangeTest": }
case "rangeTest": {
device.moduleConfig.rangeTest = device.moduleConfig.rangeTest =
config.payloadVariant.value; config.payloadVariant.value;
break; break;
case "telemetry": }
case "telemetry": {
device.moduleConfig.telemetry = device.moduleConfig.telemetry =
config.payloadVariant.value; config.payloadVariant.value;
break; break;
case "cannedMessage": }
case "cannedMessage": {
device.moduleConfig.cannedMessage = device.moduleConfig.cannedMessage =
config.payloadVariant.value; config.payloadVariant.value;
break; break;
case "audio": }
case "audio": {
device.moduleConfig.audio = config.payloadVariant.value; device.moduleConfig.audio = config.payloadVariant.value;
break; break;
case "neighborInfo": }
device.moduleConfig.neighborInfo = config.payloadVariant.value; case "neighborInfo": {
device.moduleConfig.neighborInfo =
config.payloadVariant.value;
break;
}
case "ambientLighting": {
device.moduleConfig.ambientLighting =
config.payloadVariant.value;
break; break;
case "ambientLighting": }
device.moduleConfig.ambientLighting = config.payloadVariant.value; case "detectionSensor": {
device.moduleConfig.detectionSensor =
config.payloadVariant.value;
break; break;
case "detectionSensor": }
device.moduleConfig.detectionSensor = config.payloadVariant.value;
} }
} }
}), }),
@ -519,7 +540,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
}), }),
); );
}, },
processPacket(data: processPacketParams) { processPacket(data: ProcessPacketParams) {
set( set(
produce<DeviceState>((draft) => { produce<DeviceState>((draft) => {
const device = draft.devices.get(id); const device = draft.devices.get(id);
@ -527,7 +548,13 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
return; return;
} }
const node = device.nodes.get(data.from); const node = device.nodes.get(data.from);
if (!node) { if (node) {
device.nodes.set(data.from, {
...node,
lastHeard: data.time,
snr: data.snr,
});
} else {
device.nodes.set( device.nodes.set(
data.from, data.from,
new Protobuf.NodeInfo({ new Protobuf.NodeInfo({
@ -536,12 +563,6 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
snr: data.snr, snr: data.snr,
}), }),
); );
} else {
device.nodes.set(data.from, {
...node,
lastHeard: data.time,
snr: data.snr,
});
} }
}), }),
); );

11
src/core/subscriptions.ts

@ -16,20 +16,21 @@ export const subscribeAll = (
connection.events.onRoutingPacket.subscribe((routingPacket) => { connection.events.onRoutingPacket.subscribe((routingPacket) => {
switch (routingPacket.data.variant.case) { switch (routingPacket.data.variant.case) {
case "errorReason": case "errorReason": {
if (routingPacket.data.variant.value === Protobuf.Routing_Error.NONE) { if (routingPacket.data.variant.value === Protobuf.Routing_Error.NONE) {
return; return;
} }
console.log(`Routing Error: ${routingPacket.data.variant.value}`); console.log(`Routing Error: ${routingPacket.data.variant.value}`);
break; break;
case "routeReply": }
case "routeReply": {
console.log(`Route Reply: ${routingPacket.data.variant.value}`); console.log(`Route Reply: ${routingPacket.data.variant.value}`);
break; break;
case "routeRequest": }
case "routeRequest": {
console.log(`Route Request: ${routingPacket.data.variant.value}`); console.log(`Route Request: ${routingPacket.data.variant.value}`);
break; break;
}
} }
}); });

4
src/core/utils/bitwise.ts

@ -1,4 +1,4 @@
export interface enumLike { export interface EnumLike {
[key: number]: string | number; [key: number]: string | number;
} }
@ -8,7 +8,7 @@ export const bitwiseEncode = (enumValues: number[]): number => {
export const bitwiseDecode = ( export const bitwiseDecode = (
value: number, value: number,
decodeEnum: enumLike, decodeEnum: EnumLike,
): number[] => { ): number[] => {
const enumValues = Object.keys(decodeEnum).map(Number).filter(Boolean); const enumValues = Object.keys(decodeEnum).map(Number).filter(Boolean);
return enumValues.map((b) => value & b).filter(Boolean); return enumValues.map((b) => value & b).filter(Boolean);

1
src/index.tsx

@ -6,7 +6,6 @@ import { createRoot } from "react-dom/client";
import { App } from "@app/App.js"; import { App } from "@app/App.js";
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
const container = document.getElementById("root") as HTMLElement; const container = document.getElementById("root") as HTMLElement;
const root = createRoot(container); const root = createRoot(container);

1
src/pages/Config/DeviceConfig.tsx

@ -12,7 +12,6 @@ import {
TabsTrigger, TabsTrigger,
} from "@components/UI/Tabs.js"; } from "@components/UI/Tabs.js";
import { useDevice } from "@core/stores/deviceStore.js"; import { useDevice } from "@core/stores/deviceStore.js";
import { Fragment } from "react";
export const DeviceConfig = (): JSX.Element => { export const DeviceConfig = (): JSX.Element => {
const { metadata } = useDevice(); const { metadata } = useDevice();

4
src/pages/Dashboard/index.tsx

@ -6,9 +6,7 @@ import { H3 } from "@components/UI/Typography/H3.js";
import { Subtle } from "@components/UI/Typography/Subtle.js"; import { Subtle } from "@components/UI/Typography/Subtle.js";
import { import {
BluetoothIcon, BluetoothIcon,
CalendarIcon,
ListPlusIcon, ListPlusIcon,
MapPinIcon,
NetworkIcon, NetworkIcon,
PlusIcon, PlusIcon,
UsbIcon, UsbIcon,
@ -35,7 +33,7 @@ export const Dashboard = () => {
<div className="flex h-[450px] rounded-md border border-dashed border-slate-200 p-3 dark:border-slate-700"> <div className="flex h-[450px] rounded-md border border-dashed border-slate-200 p-3 dark:border-slate-700">
{devices.length ? ( {devices.length ? (
<ul role="list" className="grow divide-y divide-gray-200"> <ul className="grow divide-y divide-gray-200">
{devices.map((device) => { {devices.map((device) => {
return ( return (
<li key={device.id}> <li key={device.id}>

28
src/pages/Map.tsx

@ -14,9 +14,9 @@ import {
ZoomInIcon, ZoomInIcon,
ZoomOutIcon, ZoomOutIcon,
} from "lucide-react"; } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Layer, Marker, Source, useMap } from "react-map-gl"; import { Marker, useMap } from "react-map-gl";
import MapGL from "react-map-gl/maplibre"; import MapGl from "react-map-gl/maplibre";
export const MapPage = (): JSX.Element => { export const MapPage = (): JSX.Element => {
const { nodes, waypoints } = useDevice(); const { nodes, waypoints } = useDevice();
@ -28,11 +28,15 @@ export const MapPage = (): JSX.Element => {
const allNodes = Array.from(nodes.values()); const allNodes = Array.from(nodes.values());
const getBBox = () => { const getBBox = () => {
if (!map) return; if (!map) {
return;
}
const nodesWithPosition = allNodes.filter( const nodesWithPosition = allNodes.filter(
(node) => node.position?.latitudeI, (node) => node.position?.latitudeI,
); );
if (!nodesWithPosition.length) return; if (!nodesWithPosition.length) {
return;
}
if (nodesWithPosition.length === 1) { if (nodesWithPosition.length === 1) {
map.easeTo({ map.easeTo({
zoom: 12, zoom: 12,
@ -57,20 +61,22 @@ export const MapPage = (): JSX.Element => {
], ],
{ padding: { top: 10, bottom: 10, left: 10, right: 10 } }, { padding: { top: 10, bottom: 10, left: 10, right: 10 } },
); );
if (center) map.easeTo(center); if (center) {
map.easeTo(center);
}
}; };
useEffect(() => { useEffect(() => {
map?.on("zoom", () => { map?.on("zoom", () => {
setZoom(map?.getZoom() ?? 0); setZoom(map?.getZoom() ?? 0);
}); });
}, [map, zoom]); }, [map]);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
getBBox(); getBBox();
} }
}, [map]); }, [map, getBBox]);
return ( return (
<> <>
@ -83,7 +89,7 @@ export const MapPage = (): JSX.Element => {
</Sidebar> </Sidebar>
<PageLayout <PageLayout
label="Map" label="Map"
noPadding noPadding={true}
actions={[ actions={[
{ {
icon: ZoomInIcon, icon: ZoomInIcon,
@ -105,7 +111,7 @@ export const MapPage = (): JSX.Element => {
}, },
]} ]}
> >
<MapGL <MapGl
mapStyle="https://raw.githubusercontent.com/hc-oss/maplibre-gl-styles/master/styles/osm-mapnik/v8/default.json" mapStyle="https://raw.githubusercontent.com/hc-oss/maplibre-gl-styles/master/styles/osm-mapnik/v8/default.json"
// onClick={(e) => { // onClick={(e) => {
// const waypoint = new Protobuf.Waypoint({ // const waypoint = new Protobuf.Waypoint({
@ -177,7 +183,7 @@ export const MapPage = (): JSX.Element => {
); );
} }
})} })}
</MapGL> </MapGl>
</PageLayout> </PageLayout>
</> </>
); );

10
src/pages/Messages.tsx

@ -8,7 +8,7 @@ import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf, Types } from "@meshtastic/meshtasticjs"; import { Protobuf, Types } from "@meshtastic/meshtasticjs";
import { getChannelName } from "@pages/Channels.js"; import { getChannelName } from "@pages/Channels.js";
import { HashIcon } from "lucide-react"; import { HashIcon } from "lucide-react";
import { useMemo, useState } from "react"; import { useState } from "react";
export const MessagesPage = (): JSX.Element => { export const MessagesPage = (): JSX.Element => {
const { channels, nodes, hardware, messages } = useDevice(); const { channels, nodes, hardware, messages } = useDevice();
@ -37,8 +37,8 @@ export const MessagesPage = (): JSX.Element => {
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={() => {
@ -69,8 +69,8 @@ export const MessagesPage = (): JSX.Element => {
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 ?? "Unknown" ? nodes.get(activeChat)?.user?.longName ?? "Unknown"
: "Loading..." : "Loading..."
}`} }`}
> >
{allChannels.map( {allChannels.map(

Loading…
Cancel
Save