fix import paths, remove old form elements

This commit is contained in:
Sacha Weatherstone
2023-02-13 15:46:45 +10:00
parent 270718148a
commit 292bd59099
45 changed files with 323 additions and 602 deletions
+3 -3
View File
@@ -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();
+2 -2
View File
@@ -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 => {
+3 -3
View File
@@ -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 (
+14 -13
View File
@@ -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>
@@ -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";
+28 -22
View File
@@ -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);
}
}}
+7 -9
View File
@@ -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>
+14 -15
View File
@@ -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));
}}
>
<PowerIcon size={16} />
<span>Now</span>
<ClockIcon size={16} />
</Button>
<Button
className="w-24"
onClick={() => {
connection?.shutdown(2).then(() => () => onOpenChange(false));
}}
>
<PowerIcon className="mr-2" size={16} />
Now
</Button>
</div>
</DialogContent>
+16 -6
View File
@@ -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>
+40 -29
View File
@@ -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>
@@ -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();
@@ -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();
@@ -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",
@@ -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();
@@ -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();
@@ -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();
@@ -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();
+16 -10
View File
@@ -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>
@@ -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>
);
};
@@ -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();
@@ -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();
@@ -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();
@@ -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 => {
@@ -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();
@@ -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();
@@ -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();
@@ -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();
+3 -4
View File
@@ -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
View File
@@ -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 };
+38 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
);
};