From 53a4ae290c4bcf85f2095987dd2036774761e9d5 Mon Sep 17 00:00:00 2001 From: Sacha Weatherstone Date: Mon, 13 Feb 2023 22:46:29 +1000 Subject: [PATCH] Make DynamicForm more modular --- src/components/DynamicForm.tsx | 262 ------------------ src/components/Form/DynamicForm.tsx | 97 +++++++ src/components/Form/DynamicFormField.tsx | 41 +++ src/components/Form/FormInput.tsx | 48 ++++ src/components/Form/FormSelect.tsx | 71 +++++ src/components/Form/FormToggle.tsx | 32 +++ src/components/Form/FormWrapper.tsx | 31 +++ .../PageComponents/Config/Bluetooth.tsx | 13 +- .../PageComponents/Config/Device.tsx | 18 +- .../PageComponents/Config/Display.tsx | 26 +- src/components/PageComponents/Config/LoRa.tsx | 39 ++- .../PageComponents/Config/Network.tsx | 6 +- .../PageComponents/Config/Position.tsx | 6 +- .../PageComponents/Config/Power.tsx | 26 +- .../PageComponents/ModuleConfig/Audio.tsx | 6 +- .../ModuleConfig/CannedMessage.tsx | 20 +- .../ModuleConfig/ExternalNotification.tsx | 9 +- .../PageComponents/ModuleConfig/MQTT.tsx | 2 +- .../PageComponents/ModuleConfig/RangeTest.tsx | 2 +- .../PageComponents/ModuleConfig/Serial.tsx | 25 +- .../ModuleConfig/StoreForward.tsx | 9 +- .../PageComponents/ModuleConfig/Telemetry.tsx | 13 +- src/components/PageLayout.tsx | 12 +- 23 files changed, 478 insertions(+), 336 deletions(-) delete mode 100644 src/components/DynamicForm.tsx create mode 100644 src/components/Form/DynamicForm.tsx create mode 100644 src/components/Form/DynamicFormField.tsx create mode 100644 src/components/Form/FormInput.tsx create mode 100644 src/components/Form/FormSelect.tsx create mode 100644 src/components/Form/FormToggle.tsx create mode 100644 src/components/Form/FormWrapper.tsx diff --git a/src/components/DynamicForm.tsx b/src/components/DynamicForm.tsx deleted file mode 100644 index 16b4d2c9..00000000 --- a/src/components/DynamicForm.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import { - Controller, - DeepPartial, - FieldValues, - Path, - SubmitHandler, - useForm -} from "react-hook-form"; -import { Input } from "@components/UI/Input.js"; -import { Label } from "@components/UI/Label.js"; -import { ErrorMessage } from "@hookform/error-message"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue -} 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 { - fieldName: Path; - selector?: number; - invert?: boolean; -} - -interface BasicFieldProps { - name: Path; - label: string; - description?: string; - active?: boolean; - required?: boolean; - disabledBy?: DisabledBy[]; -} - -interface InputFieldProps extends BasicFieldProps { - type: "text" | "number" | "password"; - suffix?: string; -} - -interface SelectFieldProps extends BasicFieldProps { - type: "select" | "multiSelect"; - - enumValue: { - [s: string]: string | number; - }; - formatEnumName?: boolean; -} - -interface ToggleFieldProps extends BasicFieldProps { - type: "toggle"; -} - -export interface FormProps { - onSubmit: SubmitHandler; - defaultValues?: DeepPartial; - fieldGroups: { - label: string; - description: string; - fields: (InputFieldProps | SelectFieldProps | ToggleFieldProps)[]; - }[]; -} - -export function DynamicForm({ - fieldGroups, - onSubmit, - defaultValues -}: FormProps) { - const { register, handleSubmit, control, getValues } = useForm({ - mode: "onChange", - defaultValues: defaultValues - }); - - const isDisabled = (disabledBy?: DisabledBy[]): boolean => { - if (!disabledBy) return false; - - return disabledBy.some((field) => { - const value = getValues(field.fieldName); - if (typeof value === "boolean") return field.invert ? value : !value; - if (typeof value === "number") - return field.invert - ? field.selector !== value - : field.selector === value; - return false; - }); - }; - - return ( -
- {fieldGroups.map((fieldGroup, index) => ( -
-
-

{fieldGroup.label}

- {fieldGroup.description} -
- - {fieldGroup.fields.map((field, index) => { - const fieldWrapperData: FieldWrapperProps = { - label: field.label, - description: field.description, - disabled: isDisabled(field.disabledBy) - }; - - switch (field.type) { - case "text": - return ( - - - - ); - case "number": - return ( - - ( - onChange(parseInt(e.target.value))} - disabled={fieldWrapperData.disabled} - {...rest} - /> - )} - /> - - ); - case "password": - return ( - - { - // } - // }} - {...register(field.name)} - /> - - ); - case "toggle": - return ( - - ( - - )} - /> - - ); - case "select": - const optionsEnumValues = field.enumValue - ? Object.entries(field.enumValue).filter( - (value) => typeof value[1] === "number" - ) - : []; - return ( - - ( - - )} - /> - - ); - case "multiSelect": - return ( - - tmp - - ); - } - })} -
- ))} -
- ); -} - -interface FieldWrapperProps { - label: string; - description?: string; - disabled?: boolean; - children?: React.ReactNode; -} - -const FieldWrapper = ({ - label, - description, - disabled, - children -}: FieldWrapperProps): JSX.Element => ( -
-
-
- -
-
-

{description}

-
-
{children}
-
-
-
-
-
-
-); diff --git a/src/components/Form/DynamicForm.tsx b/src/components/Form/DynamicForm.tsx new file mode 100644 index 00000000..643bcfa2 --- /dev/null +++ b/src/components/Form/DynamicForm.tsx @@ -0,0 +1,97 @@ +import { + Control, + DeepPartial, + FieldValues, + Path, + SubmitHandler, + useForm +} from "react-hook-form"; +import { H4 } from "@components/UI/Typography/H4.js"; +import { Subtle } from "@components/UI/Typography/Subtle.js"; +import { DynamicFormField, FieldProps } from "./DynamicFormField.js"; +import { FieldWrapper } from "./FormWrapper.js"; + +interface DisabledBy { + fieldName: Path; + selector?: number; + invert?: boolean; +} + +export interface BaseFormBuilderProps { + name: Path; + disabledBy?: DisabledBy[]; + label: string; + description?: string; + properties?: {}; +} + +export interface GenericFormElementProps { + control: Control; + disabled?: boolean; + field: Y; +} + +export interface DynamicFormProps { + onSubmit: SubmitHandler; + defaultValues?: DeepPartial; + fieldGroups: { + label: string; + description: string; + fields: FieldProps[]; + }[]; +} + +export function DynamicForm({ + fieldGroups, + onSubmit, + defaultValues +}: DynamicFormProps) { + const { handleSubmit, control, getValues } = useForm({ + mode: "onChange", + defaultValues: defaultValues + }); + + const isDisabled = (disabledBy?: DisabledBy[]): boolean => { + if (!disabledBy) return false; + + return disabledBy.some((field) => { + const value = getValues(field.fieldName); + if (typeof value === "boolean") return field.invert ? value : !value; + if (typeof value === "number") + return field.invert + ? field.selector !== value + : field.selector === value; + return false; + }); + }; + + return ( +
+ {fieldGroups.map((fieldGroup, index) => ( +
+
+

{fieldGroup.label}

+ {fieldGroup.description} +
+ + {fieldGroup.fields.map((field, index) => ( + + + + ))} +
+ ))} +
+ ); +} diff --git a/src/components/Form/DynamicFormField.tsx b/src/components/Form/DynamicFormField.tsx new file mode 100644 index 00000000..ca641ae8 --- /dev/null +++ b/src/components/Form/DynamicFormField.tsx @@ -0,0 +1,41 @@ +import type { Control, FieldValues } from "react-hook-form"; +import { GenericInput, InputFieldProps } from "./FormInput.js"; +import { ToggleFieldProps, ToggleInput } from "./FormToggle.js"; +import { SelectFieldProps, SelectInput } from "./FormSelect.js"; + +export type FieldProps = + | InputFieldProps + | SelectFieldProps + | ToggleFieldProps; + +export interface DynamicFormFieldProps { + field: FieldProps; + control: Control; + disabled?: boolean; +} + +export function DynamicFormField({ + field, + control, + disabled +}: DynamicFormFieldProps) { + switch (field.type) { + case "text": + case "password": + case "number": + return ( + + ); + + case "toggle": + return ( + + ); + case "select": + return ( + + ); + case "multiSelect": + return
tmp
; + } +} diff --git a/src/components/Form/FormInput.tsx b/src/components/Form/FormInput.tsx new file mode 100644 index 00000000..1672e55c --- /dev/null +++ b/src/components/Form/FormInput.tsx @@ -0,0 +1,48 @@ +import type { LucideIcon } from "lucide-react"; +import type { + BaseFormBuilderProps, + GenericFormElementProps +} from "./DynamicForm.js"; +import { Input } from "../UI/Input.js"; +import { Controller, FieldValues } from "react-hook-form"; + +export interface InputFieldProps extends BaseFormBuilderProps { + type: "text" | "number" | "password"; + properties?: { + prefix?: string; + suffix?: string; + action?: { + icon: LucideIcon; + onClick: () => void; + }; + }; +} + +export function GenericInput({ + control, + disabled, + field +}: GenericFormElementProps>) { + return ( + ( + + onChange( + field.type === "number" + ? parseInt(e.target.value) + : e.target.value + ) + } + disabled={disabled} + {...field.properties} + {...rest} + /> + )} + /> + ); +} diff --git a/src/components/Form/FormSelect.tsx b/src/components/Form/FormSelect.tsx new file mode 100644 index 00000000..ff65b8c1 --- /dev/null +++ b/src/components/Form/FormSelect.tsx @@ -0,0 +1,71 @@ +import type { + BaseFormBuilderProps, + GenericFormElementProps +} from "./DynamicForm.js"; +import { Controller, FieldValues } from "react-hook-form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from "../UI/Select.js"; + +export interface SelectFieldProps extends BaseFormBuilderProps { + type: "select" | "multiSelect"; + properties: BaseFormBuilderProps["properties"] & { + enumValue: { + [s: string]: string | number; + }; + formatEnumName?: boolean; + }; +} + +export function SelectInput({ + control, + disabled, + field +}: GenericFormElementProps>) { + return ( + { + const { enumValue, formatEnumName, ...remainingProperties } = + field.properties; + const optionsEnumValues = enumValue + ? Object.entries(enumValue).filter( + (value) => typeof value[1] === "number" + ) + : []; + return ( + + ); + }} + /> + ); +} diff --git a/src/components/Form/FormToggle.tsx b/src/components/Form/FormToggle.tsx new file mode 100644 index 00000000..3b62db34 --- /dev/null +++ b/src/components/Form/FormToggle.tsx @@ -0,0 +1,32 @@ +import type { + BaseFormBuilderProps, + GenericFormElementProps +} from "./DynamicForm.js"; +import { Controller, FieldValues } from "react-hook-form"; +import { Switch } from "../UI/Switch.js"; + +export interface ToggleFieldProps extends BaseFormBuilderProps { + type: "toggle"; +} + +export function ToggleInput({ + control, + disabled, + field +}: GenericFormElementProps>) { + return ( + ( + + )} + /> + ); +} diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx new file mode 100644 index 00000000..6a5164e9 --- /dev/null +++ b/src/components/Form/FormWrapper.tsx @@ -0,0 +1,31 @@ +import { Label } from "../UI/Label.js"; +import { ErrorMessage } from "@hookform/error-message"; + +export interface FieldWrapperProps { + label: string; + description?: string; + disabled?: boolean; + children?: React.ReactNode; +} + +export const FieldWrapper = ({ + label, + description, + children +}: FieldWrapperProps): JSX.Element => ( +
+
+
+ +
+
+

{description}

+
+
{children}
+
+
+
+
+
+
+); diff --git a/src/components/PageComponents/Config/Bluetooth.tsx b/src/components/PageComponents/Config/Bluetooth.tsx index 4fd8a9c6..c2b4cacc 100644 --- a/src/components/PageComponents/Config/Bluetooth.tsx +++ b/src/components/PageComponents/Config/Bluetooth.tsx @@ -1,7 +1,7 @@ import type { BluetoothValidation } from "@app/validation/config/bluetooth.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Bluetooth = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); @@ -37,13 +37,15 @@ export const Bluetooth = (): JSX.Element => { name: "mode", label: "Pairing mode", description: "Pin selection behaviour.", - enumValue: Protobuf.Config_BluetoothConfig_PairingMode, - formatEnumName: true, disabledBy: [ { fieldName: "enabled" } - ] + ], + properties: { + enumValue: Protobuf.Config_BluetoothConfig_PairingMode, + formatEnumName: true + } }, { type: "number", @@ -60,7 +62,8 @@ export const Bluetooth = (): JSX.Element => { { fieldName: "enabled" } - ] + ], + properties: {} } ] } diff --git a/src/components/PageComponents/Config/Device.tsx b/src/components/PageComponents/Config/Device.tsx index 7bb15c97..7396b4a7 100644 --- a/src/components/PageComponents/Config/Device.tsx +++ b/src/components/PageComponents/Config/Device.tsx @@ -1,7 +1,7 @@ import type { DeviceValidation } from "@app/validation/config/device.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Device = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); @@ -31,8 +31,10 @@ export const Device = (): JSX.Element => { name: "role", label: "Role", description: "What role the device performs on the mesh", - enumValue: Protobuf.Config_DeviceConfig_Role, - formatEnumName: true + properties: { + enumValue: Protobuf.Config_DeviceConfig_Role, + formatEnumName: true + } }, { type: "toggle", @@ -64,15 +66,19 @@ export const Device = (): JSX.Element => { name: "rebroadcastMode", label: "Rebroadcast Mode", description: "How to handle rebroadcasting", - enumValue: Protobuf.Config_DeviceConfig_RebroadcastMode, - formatEnumName: true + properties: { + enumValue: Protobuf.Config_DeviceConfig_RebroadcastMode, + formatEnumName: true + } }, { type: "number", name: "nodeInfoBroadcastSecs", label: "Node Info Broadcast Interval", description: "How often to broadcast node info", - suffix: "Seconds" + properties: { + suffix: "Seconds" + } } ] } diff --git a/src/components/PageComponents/Config/Display.tsx b/src/components/PageComponents/Config/Display.tsx index cba020a1..44b9bd38 100644 --- a/src/components/PageComponents/Config/Display.tsx +++ b/src/components/PageComponents/Config/Display.tsx @@ -1,7 +1,7 @@ import type { DisplayValidation } from "@app/validation/config/display.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Display = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); @@ -31,14 +31,18 @@ export const Display = (): JSX.Element => { name: "screenOnSecs", label: "Screen Timeout", description: "Turn off the display after this long", - suffix: "seconds" + properties: { + suffix: "seconds" + } }, { type: "select", name: "gpsFormat", label: "GPS Display Units", description: "Coordinate display format", - enumValue: Protobuf.Config_DisplayConfig_GpsCoordinateFormat + properties: { + enumValue: Protobuf.Config_DisplayConfig_GpsCoordinateFormat + } }, { type: "number", @@ -63,23 +67,29 @@ export const Display = (): JSX.Element => { name: "units", label: "Display Units", description: "Display metric or imperial units", - enumValue: Protobuf.Config_DisplayConfig_DisplayUnits, - formatEnumName: true + properties: { + enumValue: Protobuf.Config_DisplayConfig_DisplayUnits, + formatEnumName: true + } }, { type: "select", name: "oled", label: "OLED Type", description: "Type of OLED screen attached to the device", - enumValue: Protobuf.Config_DisplayConfig_OledType + properties: { + enumValue: Protobuf.Config_DisplayConfig_OledType + } }, { type: "select", name: "displaymode", label: "Display Mode", description: "Screen layout variant", - enumValue: Protobuf.Config_DisplayConfig_DisplayMode, - formatEnumName: true + properties: { + enumValue: Protobuf.Config_DisplayConfig_DisplayMode, + formatEnumName: true + } }, { type: "toggle", diff --git a/src/components/PageComponents/Config/LoRa.tsx b/src/components/PageComponents/Config/LoRa.tsx index d203c7f6..9ddc2197 100644 --- a/src/components/PageComponents/Config/LoRa.tsx +++ b/src/components/PageComponents/Config/LoRa.tsx @@ -1,7 +1,7 @@ import type { LoRaValidation } from "@app/validation/config/lora.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const LoRa = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); @@ -31,7 +31,9 @@ export const LoRa = (): JSX.Element => { name: "region", label: "Region", description: "Sets the region for your node", - enumValue: Protobuf.Config_LoRaConfig_RegionCode + properties: { + enumValue: Protobuf.Config_LoRaConfig_RegionCode + } }, { type: "number", @@ -62,39 +64,46 @@ export const LoRa = (): JSX.Element => { name: "modemPreset", label: "Modem Preset", description: "Modem preset to use", - enumValue: Protobuf.Config_LoRaConfig_ModemPreset, - formatEnumName: true, disabledBy: [ { fieldName: "usePreset" } - ] + ], + properties: { + enumValue: Protobuf.Config_LoRaConfig_ModemPreset, + formatEnumName: true + } }, { type: "number", name: "bandwidth", label: "Bandwidth", description: "Channel bandwidth in MHz", - suffix: "MHz", disabledBy: [ { fieldName: "usePreset", invert: true } - ] + ], + properties: { + suffix: "MHz" + } }, { type: "number", name: "spreadFactor", label: "Spreading Factor", description: "Indicates the number of chirps per symbol", - suffix: "CPS", + disabledBy: [ { fieldName: "usePreset", invert: true } - ] + ], + properties: { + suffix: "CPS" + } }, { type: "number", @@ -125,7 +134,9 @@ export const LoRa = (): JSX.Element => { name: "txPower", label: "Transmit Power", description: "Max transmit power", - suffix: "dBm" + properties: { + suffix: "dBm" + } }, { type: "toggle", @@ -139,7 +150,9 @@ export const LoRa = (): JSX.Element => { label: "Frequency Offset", description: "Frequency offset to correct for crystal calibration errors", - suffix: "Hz" + properties: { + suffix: "Hz" + } }, { type: "toggle", @@ -152,7 +165,9 @@ export const LoRa = (): JSX.Element => { name: "overrideFrequency", label: "Override Frequency", description: "Override frequency", - suffix: "Hz" + properties: { + suffix: "Hz" + } } ] } diff --git a/src/components/PageComponents/Config/Network.tsx b/src/components/PageComponents/Config/Network.tsx index 576c7933..28b95674 100644 --- a/src/components/PageComponents/Config/Network.tsx +++ b/src/components/PageComponents/Config/Network.tsx @@ -1,7 +1,7 @@ import type { NetworkValidation } from "@app/validation/config/network.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Network = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); @@ -82,7 +82,9 @@ export const Network = (): JSX.Element => { name: "addressMode", label: "Address Mode", description: "Address assignment selection", - enumValue: Protobuf.Config_NetworkConfig_AddressMode + properties: { + enumValue: Protobuf.Config_NetworkConfig_AddressMode + } }, { type: "text", diff --git a/src/components/PageComponents/Config/Position.tsx b/src/components/PageComponents/Config/Position.tsx index c7859bfd..bdb65d32 100644 --- a/src/components/PageComponents/Config/Position.tsx +++ b/src/components/PageComponents/Config/Position.tsx @@ -1,7 +1,7 @@ import type { PositionValidation } from "@app/validation/config/position.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Position = (): JSX.Element => { const { config, nodes, hardware, setWorkingConfig } = useDevice(); @@ -51,7 +51,9 @@ export const Position = (): JSX.Element => { name: "positionFlags", label: "Position Flags", description: "Configuration options for Position messages", - enumValue: Protobuf.Config_PositionConfig_PositionFlags + properties: { + enumValue: Protobuf.Config_PositionConfig_PositionFlags + } }, { type: "number", diff --git a/src/components/PageComponents/Config/Power.tsx b/src/components/PageComponents/Config/Power.tsx index 7431ea92..a18f75fd 100644 --- a/src/components/PageComponents/Config/Power.tsx +++ b/src/components/PageComponents/Config/Power.tsx @@ -1,7 +1,7 @@ import type { PowerValidation } from "@app/validation/config/power.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Power = (): JSX.Element => { const { config, setWorkingConfig } = useDevice(); @@ -39,7 +39,9 @@ export const Power = (): JSX.Element => { label: "Shutdown on battery delay", description: "Automatically shutdown node after this long when on battery, 0 for indefinite", - suffix: "Seconds" + properties: { + suffix: "Seconds" + } }, { type: "number", @@ -53,7 +55,9 @@ export const Power = (): JSX.Element => { label: "No Connection Bluetooth Disabled", description: "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long", - suffix: "Seconds" + properties: { + suffix: "Seconds" + } } ] }, @@ -67,7 +71,9 @@ export const Power = (): JSX.Element => { label: "Mesh SDS Timeout", description: "The device will enter super deep sleep after this time", - suffix: "Seconds" + properties: { + suffix: "Seconds" + } }, { type: "number", @@ -75,14 +81,18 @@ export const Power = (): JSX.Element => { label: "Super Deep Sleep Duration", description: "How long the device will be in super deep sleep for", - suffix: "Seconds" + properties: { + suffix: "Seconds" + } }, { type: "number", name: "lsSecs", label: "Light Sleep Duration", description: "How long the device will be in light sleep for", - suffix: "Seconds" + properties: { + suffix: "Seconds" + } }, { type: "number", @@ -90,7 +100,9 @@ export const Power = (): JSX.Element => { label: "Minimum Wake Time", description: "Minimum amount of time the device will stay awake for after receiving a packet", - suffix: "Seconds" + properties: { + suffix: "Seconds" + } } ] } diff --git a/src/components/PageComponents/ModuleConfig/Audio.tsx b/src/components/PageComponents/ModuleConfig/Audio.tsx index 0e42bdfb..1995d77a 100644 --- a/src/components/PageComponents/ModuleConfig/Audio.tsx +++ b/src/components/PageComponents/ModuleConfig/Audio.tsx @@ -1,7 +1,7 @@ import type { AudioValidation } from "@app/validation/moduleConfig/audio.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Audio = (): JSX.Element => { const { moduleConfig, setWorkingModuleConfig } = useDevice(); @@ -43,7 +43,9 @@ export const Audio = (): JSX.Element => { name: "bitrate", label: "Bitrate", description: "Bitrate to use for audio encoding", - enumValue: Protobuf.ModuleConfig_AudioConfig_Audio_Baud + properties: { + enumValue: Protobuf.ModuleConfig_AudioConfig_Audio_Baud + } }, { type: "number", diff --git a/src/components/PageComponents/ModuleConfig/CannedMessage.tsx b/src/components/PageComponents/ModuleConfig/CannedMessage.tsx index 93237ebf..bc3eb0a4 100644 --- a/src/components/PageComponents/ModuleConfig/CannedMessage.tsx +++ b/src/components/PageComponents/ModuleConfig/CannedMessage.tsx @@ -1,7 +1,7 @@ import type { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const CannedMessage = (): JSX.Element => { const { moduleConfig, setWorkingModuleConfig } = useDevice(); @@ -55,24 +55,30 @@ export const CannedMessage = (): JSX.Element => { name: "inputbrokerEventCw", label: "Clockwise event", description: "Select input event.", - enumValue: - Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar + properties: { + enumValue: + Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar + } }, { type: "select", name: "inputbrokerEventCcw", label: "Counter Clockwise event", description: "Select input event.", - enumValue: - Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar + properties: { + enumValue: + Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar + } }, { type: "select", name: "inputbrokerEventPress", label: "Press event", description: "Select input event", - enumValue: - Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar + properties: { + enumValue: + Protobuf.ModuleConfig_CannedMessageConfig_InputEventChar + } }, { type: "toggle", diff --git a/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx b/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx index 6517c5b1..655af9b4 100644 --- a/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx +++ b/src/components/PageComponents/ModuleConfig/ExternalNotification.tsx @@ -1,7 +1,7 @@ import type { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const ExternalNotification = (): JSX.Element => { const { moduleConfig, setWorkingModuleConfig } = useDevice(); @@ -37,12 +37,15 @@ export const ExternalNotification = (): JSX.Element => { name: "outputMs", label: "Output MS", description: "Output MS", - suffix: "ms", + disabledBy: [ { fieldName: "enabled" } - ] + ], + properties: { + suffix: "ms" + } }, { type: "number", diff --git a/src/components/PageComponents/ModuleConfig/MQTT.tsx b/src/components/PageComponents/ModuleConfig/MQTT.tsx index a9323a3c..39e9ab61 100644 --- a/src/components/PageComponents/ModuleConfig/MQTT.tsx +++ b/src/components/PageComponents/ModuleConfig/MQTT.tsx @@ -1,6 +1,6 @@ import type { MQTTValidation } from "@app/validation/moduleConfig/mqtt.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; import { useDevice } from "@app/core/stores/deviceStore.js"; export const MQTT = (): JSX.Element => { diff --git a/src/components/PageComponents/ModuleConfig/RangeTest.tsx b/src/components/PageComponents/ModuleConfig/RangeTest.tsx index 92cad6f9..70f54bf3 100644 --- a/src/components/PageComponents/ModuleConfig/RangeTest.tsx +++ b/src/components/PageComponents/ModuleConfig/RangeTest.tsx @@ -1,7 +1,7 @@ import type { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const RangeTest = (): JSX.Element => { const { moduleConfig, setWorkingModuleConfig } = useDevice(); diff --git a/src/components/PageComponents/ModuleConfig/Serial.tsx b/src/components/PageComponents/ModuleConfig/Serial.tsx index 8ea0c774..f1b44ba9 100644 --- a/src/components/PageComponents/ModuleConfig/Serial.tsx +++ b/src/components/PageComponents/ModuleConfig/Serial.tsx @@ -1,7 +1,7 @@ import type { SerialValidation } from "@app/validation/moduleConfig/serial.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Serial = (): JSX.Element => { const { moduleConfig, setWorkingModuleConfig } = useDevice(); @@ -71,38 +71,47 @@ export const Serial = (): JSX.Element => { name: "baud", label: "Baud Rate", description: "The serial baud rate", - enumValue: Protobuf.ModuleConfig_SerialConfig_Serial_Baud, + disabledBy: [ { fieldName: "enabled" } - ] + ], + properties: { + enumValue: Protobuf.ModuleConfig_SerialConfig_Serial_Baud + } }, { type: "number", name: "timeout", label: "Timeout", - suffix: "Seconds", + description: "Seconds to wait before we consider your packet as 'done'", disabledBy: [ { fieldName: "enabled" } - ] + ], + properties: { + suffix: "Seconds" + } }, { type: "select", name: "mode", label: "Mode", description: "Select Mode", - enumValue: Protobuf.ModuleConfig_SerialConfig_Serial_Mode, - formatEnumName: true, + disabledBy: [ { fieldName: "enabled" } - ] + ], + properties: { + enumValue: Protobuf.ModuleConfig_SerialConfig_Serial_Mode, + formatEnumName: true + } } ] } diff --git a/src/components/PageComponents/ModuleConfig/StoreForward.tsx b/src/components/PageComponents/ModuleConfig/StoreForward.tsx index a4670f28..30bab377 100644 --- a/src/components/PageComponents/ModuleConfig/StoreForward.tsx +++ b/src/components/PageComponents/ModuleConfig/StoreForward.tsx @@ -1,7 +1,7 @@ import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const StoreForward = (): JSX.Element => { const { moduleConfig, setWorkingModuleConfig } = useDevice(); @@ -48,12 +48,15 @@ export const StoreForward = (): JSX.Element => { name: "records", label: "Number of records", description: "Number of records to store", - suffix: "Records", + disabledBy: [ { fieldName: "enabled" } - ] + ], + properties: { + suffix: "Records" + } }, { type: "number", diff --git a/src/components/PageComponents/ModuleConfig/Telemetry.tsx b/src/components/PageComponents/ModuleConfig/Telemetry.tsx index 67fbfc4f..3420506f 100644 --- a/src/components/PageComponents/ModuleConfig/Telemetry.tsx +++ b/src/components/PageComponents/ModuleConfig/Telemetry.tsx @@ -1,7 +1,7 @@ import type { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js"; import { useDevice } from "@core/stores/deviceStore.js"; import { Protobuf } from "@meshtastic/meshtasticjs"; -import { DynamicForm } from "@components/DynamicForm.js"; +import { DynamicForm } from "@components/Form/DynamicForm.js"; export const Telemetry = (): JSX.Element => { const { moduleConfig, setWorkingModuleConfig } = useDevice(); @@ -29,15 +29,20 @@ export const Telemetry = (): JSX.Element => { { type: "number", name: "deviceUpdateInterval", - label: "Interval to get telemetry data", - suffix: "seconds" + label: "Query Interval", + description: "Interval to get telemetry data", + properties: { + suffix: "seconds" + } }, { type: "number", name: "environmentUpdateInterval", label: "Update Interval", description: "How often to send Metrics over the mesh", - suffix: "seconds" + properties: { + suffix: "seconds" + } }, { type: "toggle", diff --git a/src/components/PageLayout.tsx b/src/components/PageLayout.tsx index e68ef8be..2472e674 100644 --- a/src/components/PageLayout.tsx +++ b/src/components/PageLayout.tsx @@ -1,7 +1,9 @@ +import { cn } from "@app/core/utils/cn.js"; import { LucideIcon, AlignLeftIcon } from "lucide-react"; export interface PageLayoutProps { label: string; + noPadding?: boolean; children: React.ReactNode; actions?: { icon: LucideIcon; @@ -10,7 +12,8 @@ export interface PageLayoutProps { } export const PageLayout = ({ - label: title, + label, + noPadding, actions, children }: PageLayoutProps): JSX.Element => { @@ -22,7 +25,7 @@ export const PageLayout = ({
- {title} + {label}
{actions?.map((action, index) => (
- {children} + {/* relative flex h-full w-full flex-col */} +
+ {children} +
); };