Browse Source

fix import paths, remove old form elements

pull/101/head
Sacha Weatherstone 3 years ago
parent
commit
292bd59099
  1. 2
      package.json
  2. 108
      pnpm-lock.yaml
  3. 4
      src/App.tsx
  4. 6
      src/components/Dashboard.tsx
  5. 4
      src/components/DeviceSelector.tsx
  6. 6
      src/components/Dialog/DeviceNameDialog.tsx
  7. 27
      src/components/Dialog/ImportDialog.tsx
  8. 12
      src/components/Dialog/NewDeviceDialog.tsx
  9. 50
      src/components/Dialog/QRDialog.tsx
  10. 16
      src/components/Dialog/RebootDialog.tsx
  11. 29
      src/components/Dialog/ShutdownDialog.tsx
  12. 22
      src/components/DynamicForm.tsx
  13. 69
      src/components/PageComponents/Channel.tsx
  14. 2
      src/components/PageComponents/Config/Bluetooth.tsx
  15. 2
      src/components/PageComponents/Config/Device.tsx
  16. 5
      src/components/PageComponents/Config/Display.tsx
  17. 2
      src/components/PageComponents/Config/LoRa.tsx
  18. 2
      src/components/PageComponents/Config/Network.tsx
  19. 2
      src/components/PageComponents/Config/Position.tsx
  20. 2
      src/components/PageComponents/Config/Power.tsx
  21. 26
      src/components/PageComponents/Connect/HTTP.tsx
  22. 46
      src/components/PageComponents/Messages/NewLocationMessage.tsx
  23. 2
      src/components/PageComponents/ModuleConfig/Audio.tsx
  24. 2
      src/components/PageComponents/ModuleConfig/CannedMessage.tsx
  25. 2
      src/components/PageComponents/ModuleConfig/ExternalNotification.tsx
  26. 2
      src/components/PageComponents/ModuleConfig/MQTT.tsx
  27. 2
      src/components/PageComponents/ModuleConfig/RangeTest.tsx
  28. 2
      src/components/PageComponents/ModuleConfig/Serial.tsx
  29. 2
      src/components/PageComponents/ModuleConfig/StoreForward.tsx
  30. 2
      src/components/PageComponents/ModuleConfig/Telemetry.tsx
  31. 7
      src/components/Sidebar.tsx
  32. 28
      src/components/UI/Checkbox.tsx
  33. 47
      src/components/UI/Input.tsx
  34. 77
      src/components/form/BitwiseSelect.tsx
  35. 36
      src/components/form/Checkbox.tsx
  36. 22
      src/components/form/FormSection.tsx
  37. 72
      src/components/form/IPInput.tsx
  38. 38
      src/components/form/InfoWrapper.tsx
  39. 72
      src/components/form/Input.tsx
  40. 28
      src/components/form/Toggle.tsx
  41. 10
      src/pages/Channels.tsx
  42. 8
      src/pages/Config/index.tsx
  43. 10
      src/pages/Map.tsx
  44. 8
      src/pages/Messages.tsx
  45. 2
      src/pages/Peers.tsx

2
package.json

@ -23,6 +23,8 @@
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^2.9.11",
"@meshtastic/meshtasticjs": "2.0.20-1",
"@radix-ui/react-accordion": "^1.1.0",
"@radix-ui/react-checkbox": "^1.0.1",
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-label": "^2.0.0",
"@radix-ui/react-menubar": "^1.0.0",

108
pnpm-lock.yaml

@ -5,6 +5,8 @@ specifiers:
'@hookform/error-message': ^2.0.1
'@hookform/resolvers': ^2.9.11
'@meshtastic/meshtasticjs': 2.0.20-1
'@radix-ui/react-accordion': ^1.1.0
'@radix-ui/react-checkbox': ^1.0.1
'@radix-ui/react-dialog': ^1.0.2
'@radix-ui/react-label': ^2.0.0
'@radix-ui/react-menubar': ^1.0.0
@ -73,7 +75,9 @@ dependencies:
'@emeraldpay/hashicon-react': 0.5.2
'@hookform/error-message': 2.0.1_zf7ga3u4zrffjlingb6kh5ipva
'@hookform/resolvers': 2.9[email protected]
'@meshtastic/meshtasticjs': 2.0.20-1
'@meshtastic/meshtasticjs': link:../js
'@radix-ui/react-accordion': 1.1.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-checkbox': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-dialog': 1.0.2_zula6vjvt3wdocc4mwcxqa6nzi
'@radix-ui/react-label': 2.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-menubar': 1.0.0_zula6vjvt3wdocc4mwcxqa6nzi
@ -1326,18 +1330,6 @@ packages:
to-fast-properties: 2.0.0
dev: true
/@buf/meshtastic_protobufs.bufbuild_es/1.0.0-20230211031647-2cce48659fb1.1_@[email protected]:
resolution: {registry: https://buf.build/gen/npm/v1, tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/1.0.0-20230211031647-2cce48659fb1.1/tarball}
peerDependencies:
'@bufbuild/protobuf': ^1.0.0
dependencies:
'@bufbuild/protobuf': 1.0.0
dev: false
/@bufbuild/protobuf/1.0.0:
resolution: {integrity: sha512-oH3jHBrZ6to8Qf4zLg7O8KqSY42kQZNBRXJRMp5uSi0mqE4L8NbyMnZHeOsbXmTb0xpptRyH11LfS+KeVhXzAA==}
dev: false
/@emeraldpay/hashicon-react/0.5.2:
resolution: {integrity: sha512-XCoYKpq8QQOniiSZf5ouzdvXbKfG6q4ICHRqCO/GNofiF0Ra+LR/7+tomHlXVcLPBS9sDAoZQQw/Sr24KRAbJg==}
engines: {node: '>=8'}
@ -1725,18 +1717,6 @@ packages:
engines: {node: '>=6.0.0'}
dev: false
/@meshtastic/meshtasticjs/2.0.20-1:
resolution: {integrity: sha512-ShYcQ0/T4reWHcfgMIGhqjBQIIz4GrTZOJ9p+O9ChXxND+sWdmFe4U3e5sCMnEZW33xbI6bgQhq7De2sjqAzcQ==}
dependencies:
'@buf/meshtastic_protobufs.bufbuild_es': 1.0.0-20230211031647-2cce48659fb1.1_@[email protected]
'@bufbuild/protobuf': 1.0.0
crc: 4.3.2
sub-events: 1.9.0
tslog: 4.7.2
transitivePeerDependencies:
- buffer
dev: false
/@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -1779,6 +1759,26 @@ packages:
'@babel/runtime': 7.20.13
dev: false
/@radix-ui/react-accordion/1.1.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-CNN9ZBgCK4i4SX7gFk5s8095j55DUWi85vwRNfkfBLs0QdAG5Tb4ku6sBeugCAiLvsmxw481GyNl+C3stoJVBQ==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-collapsible': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-collection': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-compose-refs': 1.0[email protected]
'@radix-ui/react-context': 1.0[email protected]
'@radix-ui/react-direction': 1.0[email protected]
'@radix-ui/react-id': 1.0[email protected]
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-controllable-state': 1.0[email protected]
react: 18.2.0
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-arrow/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-1yientwXqXcErDHEv8av9ZVNEBldH8L9scVR3is20lL+jOCfcJyMFZFEY5cgIrgexsq1qggSXqiEL/d/4f+QXA==}
peerDependencies:
@ -1791,6 +1791,44 @@ packages:
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-checkbox/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-TisH0B8hWmYP3ONRduYCyN04rR9yLPIw/Rwyn1RoC1suSoGCa8Wn+YPdSSSarSszeIbcg3p2lBkDp2XXit4sZw==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-compose-refs': 1.0[email protected]
'@radix-ui/react-context': 1.0[email protected]
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-controllable-state': 1.0[email protected]
'@radix-ui/react-use-previous': 1.0[email protected]
'@radix-ui/react-use-size': 1.0[email protected]
react: 18.2.0
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-collapsible/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-0maX4q91iYa4gjt3PsNf7dq/yqSR+HGAE8I5p54dQ6gnveS+ETWlMoijxrhmgV1k8svxpm34mQAtqIrJt4XZmA==}
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
'@babel/runtime': 7.20.13
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-compose-refs': 1.0[email protected]
'@radix-ui/react-context': 1.0[email protected]
'@radix-ui/react-id': 1.0[email protected]
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
'@radix-ui/react-use-controllable-state': 1.0[email protected]
'@radix-ui/react-use-layout-effect': 1.0[email protected]
react: 18.2.0
react-dom: 18.2[email protected]
dev: false
/@radix-ui/react-collection/1.0.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==}
peerDependencies:
@ -4273,16 +4311,6 @@ packages:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true
/crc/4.3.2:
resolution: {integrity: sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==}
engines: {node: '>=12'}
peerDependencies:
buffer: '>=6.0.3'
peerDependenciesMeta:
buffer:
optional: true
dev: false
/cross-spawn/7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@ -6753,11 +6781,6 @@ packages:
engines: {node: '>=8'}
dev: true
/sub-events/1.9.0:
resolution: {integrity: sha512-dnFBayilG9Ku0k/lNs1Y7WV4kv91+ovCoeBV3uIYrY49DylvBb6z9d9ED2ctcrvX2YlReFalpCgJNtSgmrOaJg==}
engines: {node: '>=10.0.0'}
dev: false
/supercluster/7.1.5:
resolution: {integrity: sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==}
dependencies:
@ -6974,11 +6997,6 @@ packages:
/tslib/2.5.0:
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
/tslog/4.7.2:
resolution: {integrity: sha512-NZCunFmbQK25tt+Egv28MLcmbo8M1HgUy6X2hdVbgrAlcR7zRGvPmM8SnpoljXZ48zHRRYWp9vYIHFHKWsR4HA==}
engines: {node: '>=16'}
dev: false
/tsutils/[email protected]:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}

4
src/App.tsx

@ -5,10 +5,10 @@ import { PageRouter } from "@app/PageRouter.js";
import { CommandPalette } from "@components/CommandPalette.js";
import { DeviceSelector } from "@components/DeviceSelector.js";
import { DialogManager } from "@components/Dialog/DialogManager.js";
import { Dashboard } from "@app/components/Dashboard.js";
import { Dashboard } from "@components/Dashboard.js";
import { useDeviceStore } from "@core/stores/deviceStore.js";
import { ThemeController } from "@components/generic/ThemeController.js";
import { NewDeviceDialog } from "./components/Dialog/NewDevice.js";
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.js";
export const App = (): JSX.Element => {
const { getDevice } = useDeviceStore();

6
src/components/Dashboard.tsx

@ -10,11 +10,11 @@ import {
UsbIcon,
NetworkIcon
} from "lucide-react";
import { Subtle } from "./UI/Typography/Subtle.js";
import { H3 } from "./UI/Typography/H3.js";
import { Subtle } from "@components/UI/Typography/Subtle.js";
import { H3 } from "@components/UI/Typography/H3.js";
import { useDeviceStore } from "@app/core/stores/deviceStore.js";
import { useMemo } from "react";
import { Separator } from "./UI/Seperator.js";
import { Separator } from "@components/UI/Seperator.js";
export const Dashboard = () => {
const { setConnectDialogOpen } = useAppStore();

4
src/components/DeviceSelector.tsx

@ -10,8 +10,8 @@ import {
GithubIcon,
TerminalIcon
} from "lucide-react";
import { Separator } from "./UI/Seperator.js";
import { Code } from "./UI/Typography/Code.js";
import { Separator } from "@components/UI/Seperator.js";
import { Code } from "@components/UI/Typography/Code.js";
import { DeviceSelectorButton } from "./DeviceSelectorButton.js";
export const DeviceSelector = (): JSX.Element => {

6
src/components/Dialog/DeviceNameDialog.tsx

@ -11,7 +11,7 @@ import { Button } from "@components/UI/Button.js";
import { useDevice } from "@app/core/stores/deviceStore.js";
import { useForm } from "react-hook-form";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Label } from "../UI/Label.js";
import { Label } from "@components/UI/Label.js";
export interface User {
longName: string;
@ -27,7 +27,7 @@ export const DeviceNameDialog = ({
open,
onOpenChange
}: DeviceNameDialogProps): JSX.Element => {
const { hardware, nodes, connection, setDialogOpen } = useDevice();
const { hardware, nodes, connection } = useDevice();
const myNode = nodes.find((n) => n.data.num === hardware.myNodeNum);
@ -45,7 +45,7 @@ export const DeviceNameDialog = ({
...data
})
);
setDialogOpen("deviceName", false);
onOpenChange(false);
});
return (

27
src/components/Dialog/ImportDialog.tsx

@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { toByteArray } from "base64-js";
import { Checkbox } from "@components/form/Checkbox.js";
import { Input } from "@components/form/Input.js";
import { Checkbox } from "@components/UI/Checkbox.js";
import { Input } from "@components/UI/Input.js";
import {
Dialog,
DialogContent,
@ -11,9 +11,10 @@ import {
DialogTitle
} from "@components/UI/Dialog.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Toggle } from "@components/form/Toggle.js";
import { Switch } from "@components/UI/Switch.js";
import { Button } from "@components/UI/Button.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Label } from "@components/UI/Label.js";
export interface ImportDialogProps {
open: boolean;
@ -82,8 +83,8 @@ export const ImportDialog = ({
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-3">
<Label>Channel Set/QR Code URL</Label>
<Input
label="Channel Set/QR Code URL"
value={QRCodeURL}
suffix={validURL ? "✅" : "❌"}
onChange={(e) => {
@ -94,8 +95,8 @@ export const ImportDialog = ({
<div className="flex flex-col gap-3">
<div className="flex w-full gap-2">
<div className="w-36">
<Toggle
label="Use Preset?"
<Label>Use Preset?</Label>
<Switch
disabled
checked={channelSet?.loraConfig?.usePreset ?? true}
/>
@ -121,14 +122,14 @@ export const ImportDialog = ({
</span>
<div className="flex w-40 flex-col gap-1">
{channelSet?.settings.map((channel, index) => (
<Checkbox
key={index}
label={
channel.name.length
<>
<Label>
{channel.name.length
? channel.name
: `Channel: ${channel.id}`
}
/>
: `Channel: ${channel.id}`}
</Label>
<Checkbox key={index} />
</>
))}
</div>
</div>

12
src/components/Dialog/NewDevice.tsx → src/components/Dialog/NewDeviceDialog.tsx

@ -1,4 +1,3 @@
import { Input } from "@components/form/Input.js";
import {
Dialog,
DialogContent,
@ -6,9 +5,14 @@ import {
DialogHeader,
DialogTitle
} from "@components/UI/Dialog.js";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../UI/Tabs.js";
import { Subtle } from "../UI/Typography/Subtle.js";
import { Link } from "../UI/Typography/Link.js";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger
} from "@components/UI/Tabs.js";
import { Subtle } from "@components/UI/Typography/Subtle.js";
import { Link } from "@components/UI/Typography/Link.js";
import { HTTP } from "../PageComponents/Connect/HTTP.js";
import { BLE } from "../PageComponents/Connect/BLE.js";
import { Serial } from "../PageComponents/Connect/Serial.js";

50
src/components/Dialog/QRDialog.tsx

@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { fromByteArray } from "base64-js";
import { QRCode } from "react-qrcode-logo";
import { Checkbox } from "@components/form/Checkbox.js";
import { Input } from "@components/form/Input.js";
import { Checkbox } from "@components/UI/Checkbox.js";
import { Input } from "@components/UI/Input.js";
import {
Dialog,
DialogContent,
@ -13,6 +13,7 @@ import {
} from "@components/UI/Dialog.js";
import { ClipboardIcon } from "lucide-react";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Label } from "@components/UI/Label.js";
export interface QRDialogProps {
open: boolean;
@ -62,39 +63,44 @@ export const QRDialog = ({
<div className="flex gap-3 px-4 py-5 sm:p-6">
<div className="flex w-40 flex-col gap-1">
{channels.map((channel) => (
<Checkbox
key={channel.index}
label={
channel.settings?.name.length
<>
<Label>
{channel.settings?.name.length
? channel.settings.name
: channel.role === Protobuf.Channel_Role.PRIMARY
? "Primary"
: `Channel: ${channel.index}`
}
checked={selectedChannels.includes(channel.index)}
onChange={() => {
if (selectedChannels.includes(channel.index)) {
setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index)
);
} else {
setSelectedChannels([...selectedChannels, channel.index]);
}
}}
/>
: `Channel: ${channel.index}`}
</Label>
<Checkbox
key={channel.index}
checked={selectedChannels.includes(channel.index)}
onChange={() => {
if (selectedChannels.includes(channel.index)) {
setSelectedChannels(
selectedChannels.filter((c) => c !== channel.index)
);
} else {
setSelectedChannels([
...selectedChannels,
channel.index
]);
}
}}
/>
</>
))}
</div>
<QRCode value={QRCodeURL} size={200} qrStyle="dots" />
</div>
</div>
<DialogFooter>
<Label>Sharable URL</Label>
<Input
label="Sharable URL"
value={QRCodeURL}
disabled
action={{
icon: <ClipboardIcon size={16} />,
action() {
icon: ClipboardIcon,
onClick() {
void navigator.clipboard.writeText(QRCodeURL);
}
}}

16
src/components/Dialog/RebootDialog.tsx

@ -10,7 +10,7 @@ import {
} from "@components/UI/Dialog.js";
import { ClockIcon, RefreshCwIcon } from "lucide-react";
import { Button } from "@components/UI/Button.js";
import { Input } from "@components/form/Input.js";
import { Input } from "@components/UI/Input.js";
export interface RebootDialogProps {
open: boolean;
@ -21,7 +21,7 @@ export const RebootDialog = ({
open,
onOpenChange
}: RebootDialogProps): JSX.Element => {
const { connection, setDialogOpen } = useDevice();
const { connection } = useDevice();
const [time, setTime] = useState<number>(5);
@ -40,21 +40,19 @@ export const RebootDialog = ({
value={time}
onChange={(e) => setTime(parseInt(e.target.value))}
action={{
icon: <ClockIcon size={16} />,
action() {
connection
?.reboot(time * 60)
.then(() => setDialogOpen("reboot", false));
icon: ClockIcon,
onClick() {
connection?.reboot(time * 60).then(() => onOpenChange(false));
}
}}
/>
<Button
className="w-24"
onClick={() => {
connection?.reboot(2).then(() => setDialogOpen("reboot", false));
connection?.reboot(2).then(() => onOpenChange(false));
}}
>
<RefreshCwIcon size={16} />
<RefreshCwIcon className="mr-2" size={16} />
Now
</Button>
</div>

29
src/components/Dialog/ShutdownDialog.tsx

@ -9,7 +9,7 @@ import {
} from "@components/UI/Dialog.js";
import { ClockIcon, PowerIcon } from "lucide-react";
import { Button } from "@components/UI/Button.js";
import { Input } from "@components/form/Input.js";
import { Input } from "@components/UI/Input.js";
export interface ShutdownDialogProps {
open: boolean;
@ -20,7 +20,7 @@ export const ShutdownDialog = ({
open,
onOpenChange
}: ShutdownDialogProps): JSX.Element => {
const { connection, setDialogOpen } = useDevice();
const { connection } = useDevice();
const [time, setTime] = useState<number>(5);
@ -39,25 +39,24 @@ export const ShutdownDialog = ({
type="number"
value={time}
onChange={(e) => setTime(parseInt(e.target.value))}
action={{
icon: <ClockIcon size={16} />,
action() {
connection
?.shutdown(time * 60)
.then(() => setDialogOpen("shutdown", false));
}
}}
suffix="Minutes"
/>
<Button
className="w-24"
onClick={() => {
connection
?.shutdown(2)
.then(() => setDialogOpen("shutdown", false));
connection?.shutdown(time * 60).then(() => onOpenChange(false));
}}
>
<ClockIcon size={16} />
</Button>
<Button
className="w-24"
onClick={() => {
connection?.shutdown(2).then(() => () => onOpenChange(false));
}}
>
<PowerIcon size={16} />
<span>Now</span>
<PowerIcon className="mr-2" size={16} />
Now
</Button>
</div>
</DialogContent>

22
src/components/DynamicForm.tsx

@ -6,8 +6,8 @@ import {
SubmitHandler,
useForm
} from "react-hook-form";
import { Input } from "./UI/Input.js";
import { Label } from "./UI/Label.js";
import { Input } from "@components/UI/Input.js";
import { Label } from "@components/UI/Label.js";
import { ErrorMessage } from "@hookform/error-message";
import {
Select,
@ -15,10 +15,10 @@ import {
SelectItem,
SelectTrigger,
SelectValue
} from "./UI/Select.js";
import { Switch } from "./UI/Switch.js";
import { H4 } from "./UI/Typography/H4.js";
import { Subtle } from "./UI/Typography/Subtle.js";
} from "@components/UI/Select.js";
import { Switch } from "@components/UI/Switch.js";
import { H4 } from "@components/UI/Typography/H4.js";
import { Subtle } from "@components/UI/Typography/Subtle.js";
interface DisabledBy<T> {
fieldName: Path<T>;
@ -114,6 +114,8 @@ export function DynamicForm<T extends FieldValues>({
return (
<FieldWrapper key={index} {...fieldWrapperData}>
<Input
type="text"
suffix={field.suffix}
disabled={fieldWrapperData.disabled}
{...register(field.name)}
/>
@ -129,8 +131,10 @@ export function DynamicForm<T extends FieldValues>({
<Input
type="number"
value={parseInt(value)}
suffix={field.suffix}
onChange={(e) => onChange(parseInt(e.target.value))}
disabled={fieldWrapperData.disabled}
{...rest}
/>
)}
/>
@ -141,7 +145,13 @@ export function DynamicForm<T extends FieldValues>({
<FieldWrapper key={index} {...fieldWrapperData}>
<Input
type="password"
suffix={field.suffix}
disabled={fieldWrapperData.disabled}
// action={{
// icon: hidden ? EyeIcon : EyeOffIcon,
// onClick: () => {
// }
// }}
{...register(field.name)}
/>
</FieldWrapper>

69
src/components/PageComponents/Channel.tsx

@ -2,12 +2,13 @@ import { useEffect, useState } from "react";
import { fromByteArray, toByteArray } from "base64-js";
import { Controller, useForm } from "react-hook-form";
import { ChannelSettingsValidation } from "@app/validation/channelSettings.js";
import { Input } from "@components/form/Input.js";
import { Toggle } from "@components/form/Toggle.js";
import { Input } from "@components/UI/Input.js";
import { Switch } from "@components/UI/Switch.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { RefreshCwIcon, EyeIcon, EyeOffIcon } from "lucide-react";
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Label } from "@radix-ui/react-label";
export interface SettingsPanelProps {
channel: Protobuf.Channel;
@ -94,18 +95,21 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Enabled"
description="Description"
checked={value}
{...rest}
/>
<>
<Label>Enabled</Label>
<Switch
// label="Enabled"
// description="Description"
checked={value}
{...rest}
/>
</>
)}
/>
<Label>Name</Label>
<Input
label="Name"
description="Max transmit power in dBm"
error={errors.name?.message}
// description="Max transmit power in dBm"
// error={errors.name?.message}
{...register("name")}
/>
</>
@ -131,42 +135,49 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
<option value={128}>128 Bit</option>
<option value={256}>256 Bit</option>
</Select> */}
<Label>Pre-Shared Key</Label>
<Input
width="100%"
label="Pre-Shared Key"
description="Channel key to encrypt data"
// width="100%"
// label="Pre-Shared Key"
// description="Channel key to encrypt data"
type={pskHidden ? "password" : "text"}
action={{
icon: pskHidden ? <EyeIcon size={16} /> : <EyeOffIcon size={16} />,
action: () => {
icon: pskHidden ? EyeIcon : EyeOffIcon,
onClick: () => {
setPskHidden(!pskHidden);
}
}}
error={errors.psk?.message}
// error={errors.psk?.message}
{...register("psk")}
/>
<Controller
name="uplinkEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Uplink Enabled"
description="Send packets to designated MQTT server"
checked={value}
{...rest}
/>
<>
<Label>Uplink Enabled</Label>
<Switch
// label="Uplink Enabled"
// description="Send packets to designated MQTT server"
checked={value}
{...rest}
/>
</>
)}
/>
<Controller
name="downlinkEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Downlink Enabled"
description="Recieve packets to designated MQTT server"
checked={value}
{...rest}
/>
<>
<Label>Downlink Enabled</Label>
<Switch
// label="Downlink Enabled"
// description="Recieve packets to designated MQTT server"
checked={value}
{...rest}
/>
</>
)}
/>
</form>

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

@ -1,7 +1,7 @@
import type { BluetoothValidation } from "@app/validation/config/bluetooth.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
export const Bluetooth = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice();

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

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

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

@ -1,7 +1,7 @@
import type { DisplayValidation } from "@app/validation/config/display.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
export const Display = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice();
@ -30,7 +30,8 @@ export const Display = (): JSX.Element => {
type: "number",
name: "screenOnSecs",
label: "Screen Timeout",
description: "Turn off the display after this long"
description: "Turn off the display after this long",
suffix: "seconds"
},
{
type: "select",

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

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

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

@ -1,7 +1,7 @@
import type { NetworkValidation } from "@app/validation/config/network.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
export const Network = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice();

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

@ -1,7 +1,7 @@
import type { PositionValidation } from "@app/validation/config/position.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
export const Position = (): JSX.Element => {
const { config, nodes, hardware, setWorkingConfig } = useDevice();

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

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

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

@ -1,12 +1,14 @@
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@components/form/Input.js";
import { Toggle } from "@components/form/Toggle.js";
import { Input } from "@components/UI/Input.js";
import { Switch } from "@components/UI/Switch.js";
import { Button } from "@components/UI/Button.js";
import { useAppStore } from "@core/stores/appStore.js";
import { useDeviceStore } from "@core/stores/deviceStore.js";
import { subscribeAll } from "@core/subscriptions.js";
import { randId } from "@core/utils/randId.js";
import { IHTTPConnection } from "@meshtastic/meshtasticjs";
import { Label } from "@components/UI/Label.js";
import { SelectLabel } from "@components/UI/Select.js";
export const HTTP = (): JSX.Element => {
const { addDevice } = useDeviceStore();
@ -50,8 +52,9 @@ export const HTTP = (): JSX.Element => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
<form className="flex w-full flex-col gap-2 p-4" onSubmit={onSubmit}>
<div className="flex h-48 flex-col gap-2">
<Label>IP Address/Hostname</Label>
<Input
label="IP Address/Hostname"
// label="IP Address/Hostname"
prefix={TLSEnabled ? "https://" : "http://"}
placeholder="000.000.000.000 / meshtastic.local"
{...register("ip")}
@ -60,13 +63,16 @@ export const HTTP = (): JSX.Element => {
name="tls"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Use TLS"
description="Description"
disabled={location.protocol === "https:"}
checked={value}
{...rest}
/>
<>
<Label>Use TLS</Label>
<Switch
// label="Use TLS"
// description="Description"
disabled={location.protocol === "https:"}
checked={value}
{...rest}
/>
</>
)}
/>
</div>

46
src/components/PageComponents/Messages/NewLocationMessage.tsx

@ -1,46 +0,0 @@
import { Input } from "@components/form/Input.js";
import { Button } from "@components/UI/Button.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
enum LocationType {
MGRS,
LatLng,
DecimalDegrees
}
export const NewLocationMessage = (): JSX.Element => {
const { connection } = useDevice();
return (
<div className="m-4 w-96">
<form
onSubmit={(e): void => {
e.preventDefault();
}}
>
<Input label="Name" />
<Input label="Description" />
{/* <Select label="Type" value={LocationType.MGRS}>
{renderOptions(LocationType)}
</Select> */}
<Input label="Coordinates" />
<Button
onClick={() => {
void connection?.sendWaypoint(
new Protobuf.Waypoint({
latitudeI: Math.floor(3.89103 * 1e7),
longitudeI: Math.floor(105.87005 * 1e7),
name: "TEST",
description: "This is a description"
}),
"broadcast"
);
}}
>
Send
</Button>
</form>
</div>
);
};

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

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

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

@ -1,7 +1,7 @@
import type { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
export const CannedMessage = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();

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

@ -1,7 +1,7 @@
import type { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
export const ExternalNotification = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();

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

@ -1,6 +1,6 @@
import type { MQTTValidation } from "@app/validation/moduleConfig/mqtt.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
import { useDevice } from "@app/core/stores/deviceStore.js";
export const MQTT = (): JSX.Element => {

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

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

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

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

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

@ -1,7 +1,7 @@
import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { DynamicForm } from "@app/components/DynamicForm.js";
import { DynamicForm } from "@components/DynamicForm.js";
export const StoreForward = (): JSX.Element => {
const { moduleConfig, setWorkingModuleConfig } = useDevice();

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

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

7
src/components/Sidebar.tsx

@ -10,10 +10,9 @@ import {
EditIcon,
LayoutGrid
} from "lucide-react";
import { Subtle } from "./UI/Typography/Subtle.js";
import { Button } from "./UI/Button.js";
import { SidebarSection } from "./UI/Sidebar/SidebarSection.js";
import { SidebarButton } from "./UI/Sidebar/sidebarButton.js";
import { Subtle } from "@components/UI/Typography/Subtle.js";
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
export interface SidebarProps {
children?: React.ReactNode;

28
src/components/UI/Checkbox.tsx

@ -0,0 +1,28 @@
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import { cn } from "@core/utils/cn.js";
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-slate-300 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };

47
src/components/UI/Input.tsx

@ -1,21 +1,50 @@
import * as React from "react";
import { cn } from "@core/utils/cn.js";
import type { LucideIcon } from "lucide-react";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
extends React.InputHTMLAttributes<HTMLInputElement> {
prefix?: string;
suffix?: string;
action?: {
icon: LucideIcon;
onClick: () => void;
};
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => {
({ className, prefix, suffix, action, ...props }, ref) => {
return (
<input
className={cn(
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
<div className="relative w-full">
{prefix && (
<span className="inline-flex items-center rounded-l-md bg-backgroundPrimary px-3 font-mono text-sm text-textSecondary brightness-hover">
{prefix}
</span>
)}
ref={ref}
{...props}
/>
<input
className={cn(
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
action && "pr-8",
className
)}
ref={ref}
{...props}
/>
{suffix && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 font-mono text-textSecondary">
<span className="text-gray-500 sm:text-sm">{suffix}</span>
</div>
)}
{action && (
<button
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-400 focus:outline-none "
onClick={action.onClick}
>
<action.icon size={20} />
</button>
)}
</div>
);
}
);

77
src/components/form/BitwiseSelect.tsx

@ -1,77 +0,0 @@
import React, { useEffect, useState } from "react";
import { bitwiseDecode, bitwiseEncode, enumLike } from "@core/utils/bitwise.js";
import { InfoWrapper } from "@components/form/InfoWrapper.js";
// import { Listbox } from "@headlessui/react";
import { Protobuf } from "@meshtastic/meshtasticjs";
export interface BitwiseSelectProps {
label?: string;
description?: string;
error?: string;
selected: number;
decodeEnun: enumLike;
onChange: (value: number) => void;
}
export const BitwiseSelect = ({
label,
description,
error,
selected,
decodeEnun,
onChange
}: BitwiseSelectProps): JSX.Element => {
const [decodedSelected, setDecodedSelected] = useState<string[]>([]);
const options = Object.entries(decodeEnun)
.filter((value) => typeof value[1] !== "number")
.map((value) => {
return {
value: parseInt(value[0]),
label: value[1]
.toString()
.replace("POS_", "")
.toLowerCase()
.toLocaleUpperCase() //TODO: Investigate
};
});
useEffect(() => {
setDecodedSelected(
bitwiseDecode(selected, Protobuf.Config_PositionConfig_PositionFlags).map(
(flag) =>
Protobuf.Config_PositionConfig_PositionFlags[flag]
.replace("POS_", "")
.toLowerCase()
)
);
}, [selected]);
return (
<InfoWrapper label={label} description={description} error={error}>
{/* <Listbox
value={bitwiseDecode(selected, decodeEnun)}
onChange={(value) => {
onChange(bitwiseEncode(value));
}}
multiple
>
<Listbox.Button
className={`bg-orange-100 focus:ring-orange-500 flex h-10 w-full items-center gap-2 rounded-md border-transparent px-3 text-sm focus:border-transparent focus:outline-none focus:ring-2`}
>
{decodedSelected.map((option) => (
<span className="bg-orange-300 rounded-md p-1">{option}</span>
))}
</Listbox.Button>
<Listbox.Options>
{options.map((option) => (
<Listbox.Option key={option.value} value={option.value}>
{option.label}
</Listbox.Option>
))}
</Listbox.Options>
</Listbox> */}
</InfoWrapper>
);
};

36
src/components/form/Checkbox.tsx

@ -1,36 +0,0 @@
import { forwardRef, InputHTMLAttributes } from "react";
export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
}
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
function Input({ label, disabled, ...rest }: CheckboxProps, ref) {
return (
<div className="relative flex items-start">
<div className="flex h-5 items-center">
<input
ref={ref}
type="checkbox"
className={`h-4 w-4 rounded border-none bg-backgroundPrimary text-accent focus:outline-none focus:ring-2 focus:ring-accent ${
disabled
? "bg-orange-50 cursor-not-allowed text-accent brightness-disabled"
: ""
}`}
disabled={disabled}
{...rest}
/>
</div>
<div className="ml-3 text-sm">
<label
className={`font-medium ${
disabled ? "text-textSecondary" : "text-textPrimary"
}`}
>
{label}
</label>
</div>
</div>
);
}
);

22
src/components/form/FormSection.tsx

@ -1,22 +0,0 @@
import type { ReactNode } from "react";
export interface FormSectionProps {
title: string;
children: ReactNode;
}
export const FormSection = ({
title,
children
}: FormSectionProps): JSX.Element => {
return (
<div className="relative">
<h3 className="absolute left-2 -top-2 bg-backgroundSecondary px-1 text-lg font-medium text-textPrimary">
{title}
</h3>
<div className="mt-2 rounded-md border-2 border-backgroundPrimary p-2">
{children}
</div>
</div>
);
};

72
src/components/form/IPInput.tsx

@ -1,72 +0,0 @@
import { forwardRef, useEffect } from "react";
import { InfoWrapper } from "@components/form/InfoWrapper.js";
import { useState } from "react";
import type { InputProps } from "@components/form/Input.js";
export const IPInput = forwardRef<HTMLInputElement, InputProps>(function Input(
{
label,
description,
prefix,
suffix,
action,
error,
disabled,
value,
...rest
}: InputProps,
ref
) {
const [numericalValue, setNumericalValue] = useState<number>();
const [facadeInputValue, setFacadeInputValue] = useState<string>();
useEffect(() => {
if (typeof value === "number") {
setFacadeInputValue(
value
.toString(16)
.match(/.{1,3}/g)
?.map((v) => parseInt(v, 10))
?.join(".")
);
}
}, [value]);
return (
<InfoWrapper label={label} description={description} error={error}>
<div className="relative flex rounded-md">
<input
value={numericalValue}
onChange={(e) => setNumericalValue(parseInt(e.target.value))}
ref={ref}
hidden
/>
<input
value={facadeInputValue}
onChange={(e) => {
setFacadeInputValue(e.target.value);
setNumericalValue(
parseInt(
e.target.value
.split(".")
.map((v) => parseInt(v).toString(16))
.join(""),
16
)
);
}}
className={`flex h-10 w-full rounded-md border-none bg-backgroundPrimary px-3 text-sm text-textPrimary focus:outline-none focus:ring-2 focus:ring-accent ${
prefix ? "rounded-l-none" : ""
} ${action ? "rounded-r-none" : ""} ${
disabled
? "cursor-not-allowed text-textSecondary brightness-disabled hover:brightness-disabled"
: ""
}`}
disabled={disabled}
step="any"
{...rest}
/>
</div>
</InfoWrapper>
);
});

38
src/components/form/InfoWrapper.tsx

@ -1,38 +0,0 @@
import { AlertCircleIcon } from "lucide-react";
import type { ReactNode } from "react";
export interface InfoWrapperProps {
label?: string;
description?: string;
error?: string;
children: ReactNode;
}
export const InfoWrapper = ({
label,
description,
error,
children
}: InfoWrapperProps): JSX.Element => {
return (
<div className="w-full">
{/* Label */}
{label && (
<label className="block text-sm font-medium text-textPrimary">
{label}
</label>
)}
{/* */}
{children}
{error && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<AlertCircleIcon size={16} className="text-red-500" />
</div>
)}
{description && (
<p className="mt-2 text-sm text-textSecondary">{description}</p>
)}
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
</div>
);
};

72
src/components/form/Input.tsx

@ -1,72 +0,0 @@
import { forwardRef, InputHTMLAttributes } from "react";
import { InfoWrapper, InfoWrapperProps } from "@components/form/InfoWrapper.js";
import { AlertCircleIcon } from "lucide-react";
export interface InputProps
extends InputHTMLAttributes<HTMLInputElement>,
Omit<InfoWrapperProps, "children"> {
prefix?: string;
suffix?: string;
action?: {
icon: JSX.Element;
action: () => void;
};
}
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
{
label,
description,
prefix,
suffix,
action,
error,
disabled,
...rest
}: InputProps,
ref
) {
return (
<InfoWrapper label={label} description={description} error={error}>
<div className="relative flex rounded-md">
{prefix && (
<span className="inline-flex items-center rounded-l-md bg-backgroundPrimary px-3 font-mono text-sm text-textSecondary brightness-hover">
{prefix}
</span>
)}
<input
ref={ref}
className={`flex h-10 w-full rounded-md border-none bg-backgroundPrimary px-3 text-sm text-textPrimary focus:outline-none focus:ring-2 focus:ring-accent ${
prefix ? "rounded-l-none" : ""
} ${action ? "rounded-r-none" : ""} ${
disabled
? "cursor-not-allowed text-textSecondary brightness-disabled hover:brightness-disabled"
: ""
}`}
disabled={disabled}
step="any"
{...rest}
/>
{suffix && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 font-mono text-textSecondary">
<span className="text-gray-500 sm:text-sm">{suffix}</span>
</div>
)}
{action && (
<button
type="button"
onClick={action.action}
className="relative -ml-px inline-flex items-center space-x-2 rounded-r-md bg-backgroundPrimary px-4 py-2 text-sm font-medium text-textSecondary brightness-hover hover:text-accent hover:brightness-hover focus:outline-none focus:ring-2 focus:ring-accent active:brightness-press"
>
{action.icon}
</button>
)}
{error && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<AlertCircleIcon size={16} className="text-red-500" />
</div>
)}
</div>
</InfoWrapper>
);
});

28
src/components/form/Toggle.tsx

@ -1,28 +0,0 @@
import { Switch } from "../UI/Switch.js";
import { InfoWrapper } from "./InfoWrapper.js";
export interface ToggleProps {
checked: boolean;
label?: string;
description?: string;
disabled?: boolean;
onChange?: (checked: boolean) => void;
}
export const Toggle = ({
checked,
label,
description,
disabled,
onChange
}: ToggleProps): JSX.Element => {
return (
<InfoWrapper label={label} description={description}>
<Switch
checked={checked}
disabled={disabled}
onCheckedChange={onChange}
/>
</InfoWrapper>
);
};

10
src/pages/Channels.tsx

@ -1,14 +1,14 @@
import { Sidebar } from "@app/components/Sidebar.js";
import { PageLayout } from "@app/components/PageLayout.js";
import { Sidebar } from "@components/Sidebar.js";
import { PageLayout } from "@components/PageLayout.js";
import { cn } from "@app/core/utils/cn.js";
import { Channel } from "@components/PageComponents/Channel.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { QrCodeIcon, ImportIcon } from "lucide-react";
import { Protobuf, Types } from "@meshtastic/meshtasticjs";
import { SidebarSection } from "@app/components/UI/Sidebar/SidebarSection.js";
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
import { useState } from "react";
import { Button } from "@app/components/UI/Button.js";
import { SidebarButton } from "@app/components/UI/Sidebar/sidebarButton.js";
import { Button } from "@components/UI/Button.js";
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
export const getChannelName = (channel: Protobuf.Channel) =>
channel.settings?.name.length

8
src/pages/Config/index.tsx

@ -1,12 +1,12 @@
import { Sidebar } from "@app/components/Sidebar.js";
import { Sidebar } from "@components/Sidebar.js";
import { SettingsIcon, BoxesIcon, SaveIcon } from "lucide-react";
import { DeviceConfig } from "@pages/Config/DeviceConfig.js";
import { ModuleConfig } from "@pages/Config/ModuleConfig.js";
import { PageLayout } from "@app/components/PageLayout.js";
import { SidebarSection } from "@app/components/UI/Sidebar/SidebarSection.js";
import { PageLayout } from "@components/PageLayout.js";
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
import { useState } from "react";
import { useDevice } from "@app/core/stores/deviceStore.js";
import { SidebarButton } from "@app/components/UI/Sidebar/sidebarButton.js";
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
export const ConfigPage = (): JSX.Element => {
const { workingConfig, workingModuleConfig, connection } = useDevice();

10
src/pages/Map.tsx

@ -3,8 +3,8 @@ import { Layer, Map, Marker, Source, useMap } from "react-map-gl";
import { useAppStore } from "@core/stores/appStore.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Sidebar } from "@app/components/Sidebar.js";
import { PageLayout } from "@app/components/PageLayout.js";
import { Sidebar } from "@components/Sidebar.js";
import { PageLayout } from "@components/PageLayout.js";
import {
ZoomInIcon,
ZoomOutIcon,
@ -12,9 +12,9 @@ import {
MapPinIcon
} from "lucide-react";
import { bbox, lineString } from "@turf/turf";
import { SidebarSection } from "@app/components/UI/Sidebar/SidebarSection.js";
import { Button } from "@app/components/UI/Button.js";
import { SidebarButton } from "@app/components/UI/Sidebar/sidebarButton.js";
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
import { Button } from "@components/UI/Button.js";
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
export const MapPage = (): JSX.Element => {

8
src/pages/Messages.tsx

@ -1,14 +1,14 @@
import { Sidebar } from "@app/components/Sidebar.js";
import { PageLayout } from "@app/components/PageLayout.js";
import { Sidebar } from "@components/Sidebar.js";
import { PageLayout } from "@components/PageLayout.js";
import { ChannelChat } from "@components/PageComponents/Messages/ChannelChat.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { HashIcon } from "lucide-react";
import { Protobuf, Types } from "@meshtastic/meshtasticjs";
import { SidebarSection } from "@app/components/UI/Sidebar/SidebarSection.js";
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
import { useState } from "react";
import { getChannelName } from "./Channels.js";
import { SidebarButton } from "@app/components/UI/Sidebar/sidebarButton.js";
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
export const MessagesPage = (): JSX.Element => {
const { channels, nodes, hardware } = useDevice();

2
src/pages/Peers.tsx

@ -5,7 +5,7 @@ import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Sidebar } from "@app/components/Sidebar.js";
import { Sidebar } from "@components/Sidebar.js";
export const PeersPage = (): JSX.Element => {
const { connection, nodes } = useDevice();

Loading…
Cancel
Save