2.0 Overhaul start

This commit is contained in:
Sacha Weatherstone
2022-09-22 16:35:40 +10:00
parent 4e45990ef5
commit a73eab1ea6
87 changed files with 2880 additions and 3573 deletions
+198
View File
@@ -0,0 +1,198 @@
import type React from "react";
import { useEffect, useState } from "react";
import { fromByteArray, toByteArray } from "base64-js";
import { Controller, useForm } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
import {
ArrowPathIcon,
EyeIcon,
EyeSlashIcon,
} from "@heroicons/react/24/outline";
import { Protobuf } from "@meshtastic/meshtasticjs";
import { Select } from "../form/Select.js";
import { Toggle } from "../form/Toggle.js";
export interface SettingsPanelProps {
channel: Protobuf.Channel;
}
export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
const { connection } = useDevice();
const [loading, setLoading] = useState(false);
const [keySize, setKeySize] = useState<128 | 256>(256);
const [pskHidden, setPskHidden] = useState(true);
const {
register,
handleSubmit,
formState: { errors, isDirty },
reset,
control,
setValue,
} = useForm<
Omit<Protobuf.ChannelSettings, "psk"> & { psk: string; enabled: boolean }
>({
defaultValues: {
enabled: [
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY,
].find((role) => role === channel?.role)
? true
: false,
...channel?.settings,
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0)),
},
});
useEffect(() => {
reset({
enabled: [
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY,
].find((role) => role === channel?.role)
? true
: false,
...channel?.settings,
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0)),
});
}, [channel, reset]);
const onSubmit = handleSubmit(async (data) => {
setLoading(true);
const channelData = Protobuf.Channel.create({
role:
channel?.role === Protobuf.Channel_Role.PRIMARY
? Protobuf.Channel_Role.PRIMARY
: data.enabled
? Protobuf.Channel_Role.SECONDARY
: Protobuf.Channel_Role.DISABLED,
index: channel?.index,
settings: {
...data,
psk: toByteArray(data.psk ?? ""),
},
});
await connection?.setChannel(channelData, (): Promise<void> => {
reset({ ...data });
setLoading(false);
return Promise.resolve();
});
});
return (
<Form
title="Channel Editor"
breadcrumbs={[
"Channels",
channel.settings?.name.length
? channel.settings.name
: channel.role === Protobuf.Channel_Role.PRIMARY
? "Primary"
: `Channel: ${channel.index}`,
]}
reset={() =>
reset({
enabled: [
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY,
].find((role) => role === channel?.role)
? true
: false,
...channel?.settings,
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0)),
})
}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
{channel?.index !== 0 && (
<>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
label="Name"
description="Max transmit power in dBm"
{...register("name")}
/>
</>
)}
<Select
label="Key Size"
description="Desired size of generated key."
value={keySize}
onChange={(e): void => {
setKeySize(parseInt(e.target.value) as 128 | 256);
}}
action={{
icon: <ArrowPathIcon className="h-4" />,
action: () => {
const key = new Uint8Array(keySize / 8);
crypto.getRandomValues(key);
setValue("psk", fromByteArray(key));
},
}}
>
<option value={128}>128 Bit</option>
<option value={256}>256 Bit</option>
</Select>
<Input
width="100%"
label="Pre-Shared Key"
description="Max transmit power in dBm"
type={pskHidden ? "password" : "text"}
action={{
icon: pskHidden ? (
<EyeIcon className="w-4" />
) : (
<EyeSlashIcon className="w-4" />
),
action: () => {
setPskHidden(!pskHidden);
},
}}
{...register("psk")}
/>
<Controller
name="uplinkEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Uplink Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="downlinkEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Downlink Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
@@ -1,9 +1,11 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, SelectField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Select } from "@app/components/form/Select.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { BluetoothValidation } from "@app/validation/config/bluetooth.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -54,43 +56,41 @@ export const Bluetooth = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Bluetooth Enabled"
description="Description"
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<SelectField
<Form
title="Bluetooth Config"
breadcrumbs={["Config", "Bluetooth"]}
reset={() => reset(config.bluetooth)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Select
label="Pairing mode"
description="This is a description."
isInvalid={!!errors.mode?.message}
validationMessage={errors.mode?.message}
{...register("mode", { valueAsNumber: true })}
>
{renderOptions(Protobuf.Config_BluetoothConfig_PairingMode)}
</SelectField>
</Select>
<TextInputField
display={
<Input
disabled={
pairingMode !== Protobuf.Config_BluetoothConfig_PairingMode.FIXED_PIN
? "none"
: "block"
}
label="Pin"
description="This is a description."
type="number"
isInvalid={!!errors.fixedPin?.message}
validationMessage={errors.fixedPin?.message}
{...register("fixedPin", {
valueAsNumber: true,
})}
+37 -49
View File
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, SelectField, Switch, toaster } from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { Select } from "@app/components/form/Select.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { DeviceValidation } from "@app/validation/config/device.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -39,7 +40,7 @@ export const Device = (): JSX.Element => {
},
},
async () => {
toaster.success("Successfully updated device config");
// toaster.success("Successfully updated device config");
reset({ ...data });
setLoading(false);
await Promise.resolve();
@@ -47,58 +48,45 @@ export const Device = (): JSX.Element => {
);
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<SelectField
<Form
title="Device Config"
breadcrumbs={["Config", "Device"]}
reset={() => reset(config.device)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Select
label="Role"
description="This is a description."
isInvalid={!!errors.role?.message}
validationMessage={errors.role?.message}
{...register("role", { valueAsNumber: true })}
>
{renderOptions(Protobuf.Config_DeviceConfig_Role)}
</SelectField>
<FormField
label="Serial Console Disabled"
description="Description"
isInvalid={!!errors.serialDisabled?.message}
validationMessage={errors.serialDisabled?.message}
>
<Controller
name="serialDisabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Factory Reset Device"
description="Description"
isInvalid={!!errors.factoryReset?.message}
validationMessage={errors.factoryReset?.message}
>
<Controller
name="factoryReset"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Enabled Debug Log"
description="Description"
isInvalid={!!errors.debugLogEnabled?.message}
validationMessage={errors.debugLogEnabled?.message}
>
<Controller
name="debugLogEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
</Select>
<Controller
name="serialEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Serial Output Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="debugLogEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Enabled Debug Log"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
@@ -1,9 +1,11 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, SelectField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Select } from "@app/components/form/Select.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { DisplayValidation } from "@app/validation/config/display.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -46,42 +48,47 @@ export const Display = (): JSX.Element => {
);
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<TextInputField
<Form
title="Display Config"
breadcrumbs={["Config", "Display"]}
reset={() => reset(config.display)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Input
label="Screen Timeout"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
{...register("screenOnSecs", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="Carousel Delay"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
{...register("autoScreenCarouselSecs", { valueAsNumber: true })}
/>
<SelectField
<Select
label="GPS Display Units"
description="This is a description."
{...register("gpsFormat", { valueAsNumber: true })}
>
{renderOptions(Protobuf.Config_DisplayConfig_GpsCoordinateFormat)}
</SelectField>
<FormField
label="Compass North Top"
description="Description"
isInvalid={!!errors.compassNorthTop?.message}
validationMessage={errors.compassNorthTop?.message}
>
<Controller
name="compassNorthTop"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
</Select>
<Controller
name="compassNorthTop"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Compass North Top"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
+76 -75
View File
@@ -1,9 +1,11 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, SelectField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Select } from "@app/components/form/Select.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { LoRaValidation } from "@app/validation/config/lora.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -14,7 +16,6 @@ import { Protobuf } from "@meshtastic/meshtasticjs";
export const LoRa = (): JSX.Element => {
const { config, connection } = useDevice();
const [loading, setLoading] = useState(false);
const [usePreset, setUsePreset] = useState(true);
const {
register,
@@ -27,6 +28,12 @@ export const LoRa = (): JSX.Element => {
resolver: classValidatorResolver(LoRaValidation),
});
const usePreset = useWatch({
control,
name: "usePreset",
defaultValue: true,
});
useEffect(() => {
reset(config.lora);
}, [reset, config.lora]);
@@ -49,115 +56,109 @@ export const LoRa = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Use Preset"
description="Description"
isInvalid={!!errors.txDisabled?.message}
validationMessage={errors.txDisabled?.message}
>
<Switch
height={24}
marginLeft="auto"
checked={usePreset}
onChange={(e) => setUsePreset(e.target.checked)}
/>
</FormField>
<SelectField
display={usePreset ? "block" : "none"}
<Form
title="LoRa Config"
breadcrumbs={["Config", "LoRa"]}
reset={() => reset(config.lora)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="usePreset"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Use Preset"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Select
label="Preset"
description="This is a description."
isInvalid={!!errors.modemPreset?.message}
validationMessage={errors.modemPreset?.message}
disabled={!usePreset}
{...register("modemPreset", { valueAsNumber: true })}
>
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
</SelectField>
</Select>
<TextInputField
display={usePreset ? "none" : "block"}
<Input
label="Bandwidth"
description="Max transmit power in dBm"
type="number"
hint="MHz"
isInvalid={!!errors.bandwidth?.message}
validationMessage={errors.bandwidth?.message}
suffix="MHz"
error={errors.bandwidth?.message}
{...register("bandwidth", {
valueAsNumber: true,
})}
disabled={usePreset}
/>
<TextInputField
display={usePreset ? "none" : "block"}
<Input
label="Spread Factor"
description="Max transmit power in dBm"
type="number"
hint="CPS"
isInvalid={!!errors.spreadFactor?.message}
validationMessage={errors.spreadFactor?.message}
suffix="CPS"
error={errors.spreadFactor?.message}
{...register("spreadFactor", {
valueAsNumber: true,
})}
disabled={usePreset}
/>
<TextInputField
display={usePreset ? "none" : "block"}
<Input
label="Coding Rate"
description="Max transmit power in dBm"
type="number"
isInvalid={!!errors.codingRate?.message}
validationMessage={errors.codingRate?.message}
error={errors.codingRate?.message}
{...register("codingRate", {
valueAsNumber: true,
})}
disabled={usePreset}
/>
<TextInputField
label="Transmit Power"
description="Max transmit power in dBm"
type="number"
isInvalid={!!errors.txPower?.message}
validationMessage={errors.txPower?.message}
{...register("txPower", { valueAsNumber: true })}
/>
<TextInputField
label="Hop Limit"
description="This is a description."
hint="Hops"
type="number"
isInvalid={!!errors.hopLimit?.message}
validationMessage={errors.hopLimit?.message}
{...register("hopLimit", { valueAsNumber: true })}
/>
<FormField
label="Transmit Disabled"
description="Description"
isInvalid={!!errors.txDisabled?.message}
validationMessage={errors.txDisabled?.message}
>
<Controller
name="txDisabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Input
label="Frequency Offset"
description="This is a description."
hint="Hz"
suffix="Hz"
type="number"
isInvalid={!!errors.frequencyOffset?.message}
validationMessage={errors.frequencyOffset?.message}
error={errors.frequencyOffset?.message}
{...register("frequencyOffset", { valueAsNumber: true })}
/>
<SelectField
<Select
label="Region"
description="This is a description."
isInvalid={!!errors.region?.message}
validationMessage={errors.region?.message}
{...register("region", { valueAsNumber: true })}
>
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</SelectField>
</Select>
<Input
label="Hop Limit"
description="This is a description."
suffix="Hops"
type="number"
error={errors.hopLimit?.message}
{...register("hopLimit", { valueAsNumber: true })}
/>
<Controller
name="txEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Transmit Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
label="Transmit Power"
description="Max transmit power in dBm"
type="number"
error={errors.txPower?.message}
{...register("txPower", { valueAsNumber: true })}
/>
</Form>
);
};
@@ -1,15 +1,11 @@
import type React from "react";
import { useEffect, useState } from "react";
import {
FormField,
SelectField,
Switch,
TextInputField,
toaster,
} from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Select } from "@app/components/form/Select.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { renderOptions } from "@app/core/utils/selectEnumOptions.js";
import { NetworkValidation } from "@app/validation/config/network.js";
import { Form } from "@components/form/Form";
@@ -51,7 +47,7 @@ export const Network = (): JSX.Element => {
},
},
async () => {
toaster.success("Successfully updated Network config");
// toaster.success("Successfully updated Network config");
reset({ ...data });
setLoading(false);
await Promise.resolve();
@@ -59,48 +55,53 @@ export const Network = (): JSX.Element => {
);
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="WiFi Enabled"
description="Description"
isInvalid={!!errors.wifiEnabled?.message}
validationMessage={errors.wifiEnabled?.message}
>
<Controller
name="wifiEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<SelectField
<Form
title="Network Config"
breadcrumbs={["Config", "Network"]}
reset={() => reset(config.network)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="wifiEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="WiFi Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Select
label="WiFi Mode"
description="This is a description."
disabled={!wifiEnabled}
{...register("wifiMode", { valueAsNumber: true })}
>
{renderOptions(Protobuf.Config_NetworkConfig_WiFiMode)}
</SelectField>
<TextInputField
</Select>
<Input
label="SSID"
description="This is a description."
isInvalid={!!errors.wifiSsid?.message}
validationMessage={errors.wifiSsid?.message}
error={errors.wifiSsid?.message}
disabled={!wifiEnabled}
{...register("wifiSsid")}
/>
<TextInputField
<Input
label="PSK"
type="password"
description="This is a description."
isInvalid={!!errors.wifiPsk?.message}
validationMessage={errors.wifiPsk?.message}
error={errors.wifiPsk?.message}
disabled={!wifiEnabled}
{...register("wifiPsk")}
/>
<TextInputField
<Input
label="NTP Server"
description="This is a description."
isInvalid={!!errors.ntpServer?.message}
validationMessage={errors.ntpServer?.message}
error={errors.ntpServer?.message}
{...register("ntpServer")}
/>
</Form>
@@ -1,21 +1,14 @@
import type React from "react";
import { useEffect, useState } from "react";
import {
Button,
FormField,
SelectMenu,
Switch,
TextInputField,
} from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { PositionValidation } from "@app/validation/config/position.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
import { bitwiseDecode } from "@core/utils/bitwise";
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
import { Protobuf } from "@meshtastic/meshtasticjs";
export const Position = (): JSX.Element => {
const { config, connection } = useDevice();
@@ -29,15 +22,6 @@ export const Position = (): JSX.Element => {
} = useForm<PositionValidation>({
defaultValues: config.position,
resolver: classValidatorResolver(PositionValidation),
// defaultValues: {
// ...preferences,
// positionBroadcastSecs:
// preferences.positionBroadcastSecs === 0
// ? preferences.role === Protobuf.Role.Router
// ? 43200
// : 900
// : preferences.positionBroadcastSecs,
// },
});
useEffect(() => {
@@ -62,76 +46,74 @@ export const Position = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<TextInputField
hint="Seconds"
<Form
title="Position Config"
breadcrumbs={["Config", "Position"]}
reset={() => reset(config.position)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Input
suffix="Seconds"
label="Broadcast Interval"
description="This is a description."
type="number"
isInvalid={!!errors.positionBroadcastSecs?.message}
validationMessage={errors.positionBroadcastSecs?.message}
error={errors.positionBroadcastSecs?.message}
{...register("positionBroadcastSecs", { valueAsNumber: true })}
/>
<FormField
label="Disable Smart Position"
description="Description"
isInvalid={!!errors.positionBroadcastSmartDisabled?.message}
validationMessage={errors.positionBroadcastSmartDisabled?.message}
>
<Controller
name="positionBroadcastSmartDisabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Use Fixed Position"
description="Description"
isInvalid={!!errors.fixedPosition?.message}
validationMessage={errors.fixedPosition?.message}
>
<Controller
name="fixedPosition"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Disable GPS"
description="Description"
isInvalid={!!errors.gpsDisabled?.message}
validationMessage={errors.gpsDisabled?.message}
>
<Controller
name="gpsDisabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
hint="Seconds"
<Controller
name="positionBroadcastSmartEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Enable Smart Position"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="fixedPosition"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Use Fixed Position"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="gpsEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="GPS Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
suffix="Seconds"
label="GPS Update Interval"
description="This is a description."
type="number"
isInvalid={!!errors.gpsUpdateInterval?.message}
validationMessage={errors.gpsUpdateInterval?.message}
error={errors.gpsUpdateInterval?.message}
{...register("gpsUpdateInterval", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="Last GPS Attempt"
description="This is a description."
type="number"
isInvalid={!!errors.gpsAttemptTime?.message}
validationMessage={errors.gpsAttemptTime?.message}
error={errors.gpsAttemptTime?.message}
{...register("gpsAttemptTime", { valueAsNumber: true })}
/>
<Controller
{/* <Controller
name="positionFlags"
control={control}
render={({ field, fieldState }): JSX.Element => {
@@ -218,7 +200,7 @@ export const Position = (): JSX.Element => {
</FormField>
);
}}
/>
/> */}
</Form>
);
};
+42 -43
View File
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { PowerValidation } from "@app/validation/config/power.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -44,81 +45,79 @@ export const Power = (): JSX.Element => {
);
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<TextInputField
<Form
title="Power Config"
breadcrumbs={["Config", "Power"]}
reset={() => reset(config.power)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Input
label="Shutdown on battery delay"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
isInvalid={!!errors.onBatteryShutdownAfterSecs?.message}
validationMessage={errors.onBatteryShutdownAfterSecs?.message}
error={errors.onBatteryShutdownAfterSecs?.message}
{...register("onBatteryShutdownAfterSecs", { valueAsNumber: true })}
/>
<FormField
label="Power Saving"
description="Description"
isInvalid={!!errors.isPowerSaving?.message}
validationMessage={errors.isPowerSaving?.message}
>
<Controller
name="isPowerSaving"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Controller
name="isPowerSaving"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Power Saving"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
label="ADC Multiplier Override ratio"
description="This is a description."
type="number"
isInvalid={!!errors.adcMultiplierOverride?.message}
validationMessage={errors.adcMultiplierOverride?.message}
error={errors.adcMultiplierOverride?.message}
{...register("adcMultiplierOverride", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="Minimum Wake Time"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
isInvalid={!!errors.minWakeSecs?.message}
validationMessage={errors.minWakeSecs?.message}
error={errors.minWakeSecs?.message}
{...register("minWakeSecs", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="Mesh SDS Timeout"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
isInvalid={!!errors.meshSdsTimeoutSecs?.message}
validationMessage={errors.meshSdsTimeoutSecs?.message}
error={errors.meshSdsTimeoutSecs?.message}
{...register("meshSdsTimeoutSecs", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="SDS"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
isInvalid={!!errors.sdsSecs?.message}
validationMessage={errors.sdsSecs?.message}
error={errors.sdsSecs?.message}
{...register("sdsSecs", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="LS"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
isInvalid={!!errors.lsSecs?.message}
validationMessage={errors.lsSecs?.message}
error={errors.lsSecs?.message}
{...register("lsSecs", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="Wait Bluetooth"
description="This is a description."
hint="Seconds"
suffix="Seconds"
type="number"
isInvalid={!!errors.waitBluetoothSecs?.message}
validationMessage={errors.waitBluetoothSecs?.message}
error={errors.waitBluetoothSecs?.message}
{...register("waitBluetoothSecs", { valueAsNumber: true })}
/>
</Form>
+36 -24
View File
@@ -1,10 +1,12 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, SelectField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { base16 } from "rfc4648";
import { Input } from "@app/components/form/Input.js";
import { Select } from "@app/components/form/Select.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { UserValidation } from "@app/validation/config/user.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -50,27 +52,39 @@ export const User = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<TextInputField
<Form
title="User Config"
breadcrumbs={["Config", "User"]}
reset={() => {
reset({
longName: myNode?.data.user?.longName,
shortName: myNode?.data.user?.shortName,
isLicensed: myNode?.data.user?.isLicensed,
});
}}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Input
label="Device ID"
description="Preset unique identifier for this device."
isInvalid={!!errors.id?.message}
validationMessage={errors.id?.message}
error={errors.id?.message}
{...register("id")}
readOnly
/>
<TextInputField
<Input
label="Device Name"
description="Personalised name for this device."
{...register("longName")}
/>
<TextInputField
<Input
label="Short Name"
description="This is a description."
maxLength={3}
{...register("shortName")}
/>
<TextInputField
<Input
label="Mac Address"
description="This is a description."
disabled
@@ -81,28 +95,26 @@ export const User = (): JSX.Element => {
?.join(":") ?? ""
}
/>
<SelectField
<Select
label="Hardware"
description="This is a description."
disabled
value={myNode?.data.user?.hwModel}
>
{renderOptions(Protobuf.HardwareModel)}
</SelectField>
<FormField
label="Licenced Operator?"
description="Description"
isInvalid={!!errors.isLicensed?.message}
validationMessage={errors.isLicensed?.message}
>
<Controller
name="isLicensed"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
</Select>
<Controller
name="isLicensed"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Licenced Operator?"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
@@ -0,0 +1,71 @@
import type React from "react";
import { useCallback, useEffect, useState } from "react";
import { Button } from "@components/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 { PlusCircleIcon } from "@heroicons/react/24/outline";
import { Constants, IBLEConnection } from "@meshtastic/meshtasticjs";
export const BLE = (): JSX.Element => {
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const updateBleDeviceList = useCallback(async (): Promise<void> => {
setBleDevices(await navigator.bluetooth.getDevices());
}, []);
useEffect(() => {
void updateBleDeviceList();
}, [updateBleDeviceList]);
const onConnect = async (BLEDevice: BluetoothDevice) => {
const id = randId();
const device = addDevice(id);
setSelectedDevice(id);
const connection = new IBLEConnection(id);
await connection.connect({
device: BLEDevice,
});
device.addConnection(connection);
subscribeAll(device, connection);
};
return (
<div className="flex flex-col p-4 gap-2 w-full">
<div className="flex gap-2 flex-col h-48 overflow-y-auto">
{bleDevices.map((device, index) => (
<Button
key={index}
variant="secondary"
onClick={() => {
void onConnect(device);
}}
>
{device.name}
</Button>
))}
</div>
<Button
iconBefore={<PlusCircleIcon className="w-4" />}
onClick={() => {
void navigator.bluetooth
.requestDevice({
filters: [{ services: [Constants.SERVICE_UUID] }],
})
.then((device) => {
const exists = bleDevices.findIndex((d) => d.id === device.id);
if (exists === -1) {
setBleDevices(bleDevices.concat(device));
}
});
}}
>
New device
</Button>
</div>
);
};
@@ -0,0 +1,77 @@
import type React from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { Button } from "@components/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 { PlusCircleIcon } from "@heroicons/react/24/outline";
import { IHTTPConnection } from "@meshtastic/meshtasticjs";
export const HTTP = (): JSX.Element => {
const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const { register, handleSubmit, control } = useForm<{
ip: string;
tls: boolean;
}>({
defaultValues: {
ip: "meshtastic.local",
tls: false,
},
});
const TLSEnabled = useWatch({
control,
name: "tls",
defaultValue: false,
});
const onSubmit = handleSubmit((data) => {
const id = randId();
const device = addDevice(id);
setSelectedDevice(id);
const connection = new IHTTPConnection(id);
// TODO: Promise never resolves
void connection.connect({
address: data.ip,
fetchInterval: 2000,
tls: data.tls,
});
device.addConnection(connection);
subscribeAll(device, connection);
});
return (
// eslint-disable-next-line @typescript-eslint/no-misused-promises
<form className="w-full p-4 gap-2 flex flex-col" onSubmit={onSubmit}>
<div className="h-48 flex flex-col gap-2">
<Input
label="IP Address/Hostname"
prefix={TLSEnabled ? "https://" : "http://"}
placeholder="000.000.000.000 / meshtastic.local"
{...register("ip")}
/>
<Controller
name="tls"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Use TLS"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</div>
<Button iconBefore={<PlusCircleIcon className="w-4" />} type="submit">
Connect
</Button>
</form>
);
};
@@ -0,0 +1,75 @@
import type React from "react";
import { useCallback, useEffect, useState } from "react";
import { Button } from "@components/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 { PlusCircleIcon } from "@heroicons/react/24/outline";
import { ISerialConnection } from "@meshtastic/meshtasticjs";
interface USBID {
id: number;
name: string;
}
export const Serial = (): JSX.Element => {
const [serialPorts, setSerialPorts] = useState<SerialPort[]>([]);
const { addDevice } = useDeviceStore();
const { setSelectedDevice } = useAppStore();
const updateSerialPortList = useCallback(async () => {
setSerialPorts(await navigator.serial.getPorts());
}, []);
navigator.serial.addEventListener("connect", () => {
void updateSerialPortList();
});
navigator.serial.addEventListener("disconnect", () => {
void updateSerialPortList();
});
useEffect(() => {
void updateSerialPortList();
}, [updateSerialPortList]);
const onConnect = async (port: SerialPort) => {
const id = randId();
const device = addDevice(id);
setSelectedDevice(id);
const connection = new ISerialConnection(id);
await connection.connect({
port,
});
device.addConnection(connection);
subscribeAll(device, connection);
};
return (
<div className="flex flex-col p-4 gap-2 w-full">
<div className="flex gap-2 flex-col h-48 overflow-y-auto">
{serialPorts.map((port, index) => (
<Button
key={index}
variant="secondary"
onClick={() => {
void onConnect(port);
}}
>
{port.getInfo().usbVendorId} - {port.getInfo().usbProductId}
</Button>
))}
</div>
<Button
iconBefore={<PlusCircleIcon className="w-4" />}
onClick={() => {
void navigator.serial.requestPort().then((port) => {
setSerialPorts(serialPorts.concat(port));
});
}}
>
New device
</Button>
</div>
);
};
@@ -0,0 +1,85 @@
import type React from "react";
import { ChangeEvent, useState } from "react";
import { Input } from "@app/components/form/Input.js";
import { IconButton } from "@app/components/IconButton.js";
import { Message } from "@components/PageComponents/Messages/Message.js";
import { useDevice } from "@core/providers/useDevice.js";
import type { Channel } from "@core/stores/deviceStore.js";
import { MapPinIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
export interface ChannelChatProps {
channel: Channel;
}
export const ChannelChat = ({ channel }: ChannelChatProps): JSX.Element => {
const { nodes, connection, ackMessage } = useDevice();
const [currentMessage, setCurrentMessage] = useState("");
const sendMessage = (): void => {
void connection?.sendText(
currentMessage,
undefined,
true,
channel.config.index,
(id) => {
ackMessage(channel.config.index, id);
return Promise.resolve();
}
);
setCurrentMessage("");
};
return (
<div className="flex flex-col flex-grow">
<div className="flex flex-col flex-grow">
{channel.messages.map((message, index) => (
<Message
key={index}
message={message}
lastMsgSameUser={
index === 0
? false
: channel.messages[index - 1].packet.from ===
message.packet.from
}
sender={
nodes.find((node) => node.data.num === message.packet.from)?.data
}
/>
))}
</div>
<div className="flex gap-2">
<form
className="w-full"
onSubmit={(e): void => {
e.preventDefault();
sendMessage();
}}
>
<div className="flex flex-grow gap-2">
<span className="w-full">
<Input
minLength={2}
label=""
placeholder="Enter Message"
value={currentMessage}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setCurrentMessage(e.target.value);
}}
/>
</span>
<IconButton
variant="secondary"
icon={<PaperAirplaneIcon className="h-4 text-slate-500" />}
/>
</div>
</form>
<IconButton
variant="secondary"
icon={<MapPinIcon className="h-4 text-slate-500" />}
/>
</div>
</div>
);
};
@@ -0,0 +1,89 @@
import type React from "react";
import { WaypointMessage } from "@components/PageComponents/Messages/WaypointMessage.js";
import { useDevice } from "@core/providers/useDevice.js";
import type { AllMessageTypes } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import {
CheckCircleIcon,
EllipsisHorizontalCircleIcon,
} from "@heroicons/react/24/outline";
import type { Protobuf } from "@meshtastic/meshtasticjs";
export interface MessageProps {
lastMsgSameUser: boolean;
message: AllMessageTypes;
sender?: Protobuf.NodeInfo;
}
export const Message = ({
lastMsgSameUser,
message,
sender,
}: MessageProps): JSX.Element => {
const { setPeerInfoOpen, setActivePeer } = useDevice();
const openPeer = (): void => {
setActivePeer(message.packet.from);
setPeerInfoOpen(true);
};
return lastMsgSameUser ? (
<div className="flex ml-4">
{message.ack ? (
<CheckCircleIcon className="my-auto text-slate-200 h-4" />
) : (
<EllipsisHorizontalCircleIcon className="my-auto text-slate-200 h-4" />
)}
{"waypointID" in message ? (
<WaypointMessage waypointID={message.waypointID} />
) : (
<span
className={`ml-4 pl-2 border-l-2 border-l-slate-200 ${
message.ack ? "text-black" : "text-slate-500"
}`}
>
{message.text}
</span>
)}
</div>
) : (
<div className="mx-4 gap-2 mt-2">
<div className="flex gap-2">
<div className="cursor-pointer w-6" onClick={openPeer}>
<Hashicon value={(sender?.num ?? 0).toString()} size={32} />
</div>
<span
className="cursor-pointer font-medium text-slate-700"
onClick={openPeer}
>
{sender?.user?.longName ?? "UNK"}
</span>
<span className="text-sm">
{new Date(message.packet.rxTime).toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
})}
</span>
</div>
<div className="flex">
{message.ack ? (
<CheckCircleIcon className="my-auto text-slate-200 h-4" />
) : (
<EllipsisHorizontalCircleIcon className="my-auto text-slate-200 h-4" />
)}
{"waypointID" in message ? (
<WaypointMessage waypointID={message.waypointID} />
) : (
<span
className={`ml-4 pl-2 border-l-2 border-l-slate-200 ${
message.ack ? "text-black" : "text-slate-500"
}`}
>
{message.text}
</span>
)}
</div>
</div>
);
};
@@ -0,0 +1,49 @@
import type React from "react";
import { Input } from "@app/components/form/Input.js";
import { Select } from "@app/components/form/Select.js";
import { Button } from "@components/Button.js";
import { useDevice } from "@core/providers/useDevice.js";
import { renderOptions } from "@core/utils/selectEnumOptions.js";
import { Protobuf } from "@meshtastic/meshtasticjs";
enum LocationType {
MGRS,
LatLng,
DecimalDegrees,
}
export const NewLocationMessage = (): JSX.Element => {
const { connection } = useDevice();
return (
<div className="w-96 m-4">
<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(
Protobuf.Waypoint.create({
latitudeI: Math.floor(3.89103 * 1e7),
longitudeI: Math.floor(105.87005 * 1e7),
name: "TEST",
description: "This is a description",
})
);
}}
>
Send
</Button>
</form>
</div>
);
};
@@ -0,0 +1,33 @@
import type React from "react";
import { useDevice } from "@app/core/providers/useDevice.js";
import { toMGRS } from "@core/utils/toMGRS.js";
import { MapPinIcon } from "@heroicons/react/24/outline";
export interface WaypointMessageProps {
waypointID: number;
}
export const WaypointMessage = ({
waypointID,
}: WaypointMessageProps): JSX.Element => {
const { waypoints } = useDevice();
const waypoint = waypoints.find((wp) => wp.id === waypointID);
return (
<div className="ml-4 pl-2 border-l-slate-200 border-l-2">
<div className="gap-2 flex rounded-md p-2 shadow-md shadow-orange-300">
<MapPinIcon className="m-auto w-6 text-slate-600" />
<div>
<div className="flex gap-2">
<div className="font-bold">{waypoint?.name}</div>
<span className="text-sm font-mono text-slate-500">
{toMGRS(waypoint?.latitudeI, waypoint?.longitudeI)}
</span>
</div>
<span className="text-sm">{waypoint?.description}</span>
</div>
</div>
</div>
);
};
@@ -1,9 +1,11 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, SelectField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Select } from "@app/components/form/Select.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -52,57 +54,60 @@ export const CannedMessage = (): JSX.Element => {
);
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Module Enabled"
description="This is a description."
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Rotary Encoder #1 Enabled"
description="This is a description."
isInvalid={!!errors.rotary1Enabled?.message}
validationMessage={errors.rotary1Enabled?.message}
>
<Controller
name="rotary1Enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Form
title="Canned Message Config"
breadcrumbs={["Module Config", "Canned Message"]}
reset={() => reset(moduleConfig.cannedMessage)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Module Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="rotary1Enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Rotary Encoder #1 Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
label="Encoder Pin A"
description="Max transmit power in dBm"
type="number"
disabled={moduleEnabled}
{...register("inputbrokerPinA", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="Encoder Pin B"
description="Max transmit power in dBm"
type="number"
disabled={moduleEnabled}
{...register("inputbrokerPinB", { valueAsNumber: true })}
/>
<TextInputField
<Input
label="Endoer Pin Press"
description="Max transmit power in dBm"
type="number"
disabled={moduleEnabled}
{...register("inputbrokerPinPress", { valueAsNumber: true })}
/>
<SelectField
<Select
label="Clockwise event"
description="This is a description."
disabled={moduleEnabled}
@@ -111,8 +116,8 @@ export const CannedMessage = (): JSX.Element => {
{renderOptions(
Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar
)}
</SelectField>
<SelectField
</Select>
<Select
label="Counter Clockwise event"
description="This is a description."
disabled={moduleEnabled}
@@ -121,8 +126,8 @@ export const CannedMessage = (): JSX.Element => {
{renderOptions(
Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar
)}
</SelectField>
<SelectField
</Select>
<Select
label="Press event"
description="This is a description."
disabled={moduleEnabled}
@@ -131,41 +136,37 @@ export const CannedMessage = (): JSX.Element => {
{renderOptions(
Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar
)}
</SelectField>
<FormField
label="Up Down enabled"
description="This is a description."
isInvalid={!!errors.updown1Enabled?.message}
validationMessage={errors.updown1Enabled?.message}
>
<Controller
name="updown1Enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
</Select>
<Controller
name="updown1Enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Up Down enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
label="Allow Input Source"
description="Max transmit power in dBm"
disabled={moduleEnabled}
{...register("allowInputSource")}
/>
<FormField
label="Send Bell"
description="This is a description."
isInvalid={!!errors.sendBell?.message}
validationMessage={errors.sendBell?.message}
>
<Controller
name="sendBell"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<Controller
name="sendBell"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Send Bell"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -50,32 +51,37 @@ export const ExternalNotification = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Module Enabled"
description="Description"
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Form
title="External Notification Config"
breadcrumbs={["Module Config", "External Notification"]}
reset={() => reset(moduleConfig.externalNotification)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Module Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
type="number"
label="Output MS"
description="Max transmit power in dBm"
hint="ms"
suffix="ms"
disabled={!moduleEnabled}
{...register("outputMs", {
valueAsNumber: true,
})}
/>
<TextInputField
<Input
type="number"
label="Output"
description="Max transmit power in dBm"
@@ -84,51 +90,42 @@ export const ExternalNotification = (): JSX.Element => {
valueAsNumber: true,
})}
/>
<FormField
label="Active"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.active?.message}
validationMessage={errors.active?.message}
>
<Controller
name="active"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Message"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.alertMessage?.message}
validationMessage={errors.alertMessage?.message}
>
<Controller
name="alertMessage"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Bell"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.alertBell?.message}
validationMessage={errors.alertBell?.message}
>
<Controller
name="alertBell"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<Controller
name="active"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Active"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="alertMessage"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Message"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="alertBell"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Bell"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { MQTTValidation } from "@app/validation/moduleConfig/mqtt.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -50,34 +51,39 @@ export const MQTT = (): JSX.Element => {
);
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Module Enabled"
description="Description"
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Form
title="MQTT Config"
breadcrumbs={["Module Config", "MQTT"]}
reset={() => reset(moduleConfig.mqtt)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Module Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
label="MQTT Server Address"
description="Description"
disabled={!moduleEnabled}
{...register("address")}
/>
<TextInputField
<Input
label="MQTT Username"
description="Description"
disabled={!moduleEnabled}
{...register("username")}
/>
<TextInputField
<Input
label="MQTT Password"
description="Description"
type="password"
@@ -85,36 +91,30 @@ export const MQTT = (): JSX.Element => {
disabled={!moduleEnabled}
{...register("password")}
/>
<FormField
label="Encryption Enabled"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.encryptionEnabled?.message}
validationMessage={errors.encryptionEnabled?.message}
>
<Controller
name="encryptionEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="JSON Output Enabled"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.jsonEnabled?.message}
validationMessage={errors.jsonEnabled?.message}
>
<Controller
name="jsonEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<Controller
name="encryptionEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Encryption Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="jsonEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="JSON Output Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -51,46 +52,48 @@ export const RangeTest = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Module Enabled"
description="Description"
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Form
title="Range Test Config"
breadcrumbs={["Module Config", "Range Test"]}
reset={() => reset(moduleConfig.rangeTest)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Module Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
type="number"
label="Message Interval"
description="Max transmit power in dBm"
disabled={!moduleEnabled}
hint="Seconds"
suffix="Seconds"
{...register("sender", {
valueAsNumber: true,
})}
/>
<FormField
label="Save CSV to storage"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.save?.message}
validationMessage={errors.save?.message}
>
<Controller
name="save"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<Controller
name="save"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Save CSV to storage"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { SerialValidation } from "@app/validation/moduleConfig/serial.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -51,37 +52,39 @@ export const Serial = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Module Enabled"
description="Description"
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Echo"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.echo?.message}
validationMessage={errors.echo?.message}
>
<Controller
name="echo"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Form
title="Serial Config"
breadcrumbs={["Module Config", "Serial"]}
reset={() => reset(moduleConfig.serial)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Module Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="echo"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Echo"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
type="number"
label="RX"
description="Max transmit power in dBm"
@@ -90,7 +93,7 @@ export const Serial = (): JSX.Element => {
valueAsNumber: true,
})}
/>
<TextInputField
<Input
type="number"
label="TX Pin"
description="Max transmit power in dBm"
@@ -99,7 +102,7 @@ export const Serial = (): JSX.Element => {
valueAsNumber: true,
})}
/>
<TextInputField
<Input
type="number"
label="Baud Rate"
description="Max transmit power in dBm"
@@ -108,7 +111,7 @@ export const Serial = (): JSX.Element => {
valueAsNumber: true,
})}
/>
<TextInputField
<Input
type="number"
label="Timeout"
description="Max transmit power in dBm"
@@ -117,7 +120,7 @@ export const Serial = (): JSX.Element => {
valueAsNumber: true,
})}
/>
<TextInputField
<Input
type="number"
label="Mode"
description="Max transmit power in dBm"
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm, useWatch } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -51,47 +52,49 @@ export const StoreForward = (): JSX.Element => {
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Module Enabled"
description="Description"
isInvalid={!!errors.enabled?.message}
validationMessage={errors.enabled?.message}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Heartbeat Enabled"
description="Description"
disabled={!moduleEnabled}
isInvalid={!!errors.heartbeat?.message}
validationMessage={errors.heartbeat?.message}
>
<Controller
name="heartbeat"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Form
title="Store & Forward Config"
breadcrumbs={["Module Config", "Store & Forward"]}
reset={() => reset(moduleConfig.storeForward)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="enabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Module Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="heartbeat"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Heartbeat Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
type="number"
label="Number of records"
description="Max transmit power in dBm"
hint="Records"
suffix="Records"
disabled={!moduleEnabled}
{...register("records", {
valueAsNumber: true,
})}
/>
<TextInputField
<Input
type="number"
label="History return max"
description="Max transmit power in dBm"
@@ -100,7 +103,7 @@ export const StoreForward = (): JSX.Element => {
valueAsNumber: true,
})}
/>
<TextInputField
<Input
type="number"
label="History return window"
description="Max transmit power in dBm"
@@ -1,9 +1,10 @@
import type React from "react";
import { useEffect, useState } from "react";
import { FormField, Switch, TextInputField } from "evergreen-ui";
import { Controller, useForm } from "react-hook-form";
import { Input } from "@app/components/form/Input.js";
import { Toggle } from "@app/components/form/Toggle.js";
import { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js";
import { Form } from "@components/form/Form";
import { useDevice } from "@core/providers/useDevice.js";
@@ -44,58 +45,59 @@ export const Telemetry = (): JSX.Element => {
);
});
return (
<Form loading={loading} dirty={isDirty} onSubmit={onSubmit}>
<FormField
label="Measurement Enabled"
description="Description"
isInvalid={!!errors.environmentMeasurementEnabled?.message}
validationMessage={errors.environmentMeasurementEnabled?.message}
>
<Controller
name="environmentMeasurementEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<FormField
label="Displayed on Screen"
description="Description"
isInvalid={!!errors.environmentScreenEnabled?.message}
validationMessage={errors.environmentScreenEnabled?.message}
>
<Controller
name="environmentScreenEnabled"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<TextInputField
<Form
title="Telemetry Config"
breadcrumbs={["Module Config", "Telemetry"]}
reset={() => reset(moduleConfig.telemetry)}
loading={loading}
dirty={isDirty}
onSubmit={onSubmit}
>
<Controller
name="environmentMeasurementEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Module Enabled"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Controller
name="environmentScreenEnabled"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Displayed on Screen"
description="Description"
checked={value}
{...rest}
/>
)}
/>
<Input
label="Update Interval"
description="Max transmit power in dBm"
hint="Seconds"
suffix="Seconds"
type="number"
{...register("environmentUpdateInterval", {
valueAsNumber: true,
})}
/>
<FormField
label="Display Farenheit"
description="Description"
isInvalid={!!errors.environmentDisplayFahrenheit?.message}
validationMessage={errors.environmentDisplayFahrenheit?.message}
>
<Controller
name="environmentDisplayFahrenheit"
control={control}
render={({ field: { value, ...field } }) => (
<Switch height={24} marginLeft="auto" checked={value} {...field} />
)}
/>
</FormField>
<Controller
name="environmentDisplayFahrenheit"
control={control}
render={({ field: { value, ...rest } }) => (
<Toggle
label="Display Farenheit"
description="Description"
checked={value}
{...rest}
/>
)}
/>
</Form>
);
};