42 changed files with 1423 additions and 1157 deletions
File diff suppressed because it is too large
@ -1,22 +1,36 @@ |
|||
import type React from 'react'; |
|||
|
|||
import { FiSave } from 'react-icons/fi'; |
|||
|
|||
import { IconButton } from '@components/generic/button/IconButton'; |
|||
import { Loading } from '@components/generic/Loading'; |
|||
|
|||
export interface FormProps { |
|||
loading?: boolean; |
|||
submit: () => Promise<void>; |
|||
loading: boolean; |
|||
dirty: boolean; |
|||
children: React.ReactNode; |
|||
} |
|||
|
|||
export const Form = ({ loading, children }: FormProps): JSX.Element => { |
|||
export const Form = ({ |
|||
submit, |
|||
loading, |
|||
dirty, |
|||
children, |
|||
}: FormProps): JSX.Element => { |
|||
return ( |
|||
<form |
|||
onSubmit={(e): void => { |
|||
e.preventDefault(); |
|||
}} |
|||
className="relative flex-grow gap-3 p-2" |
|||
> |
|||
{loading && <Loading />} |
|||
{children} |
|||
<div className="flex w-full bg-white dark:bg-secondaryDark"> |
|||
<div className="ml-auto p-2"> |
|||
<IconButton disabled={dirty} onClick={submit} icon={<FiSave />} /> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
); |
|||
}; |
|||
|
|||
@ -0,0 +1,56 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm } from 'react-hook-form'; |
|||
|
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { Select } from '@components/generic/form/Select'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const Display = (): JSX.Element => { |
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
const [loading, setLoading] = useState(false); |
|||
const { register, handleSubmit, formState, reset } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit((data) => { |
|||
setLoading(true); |
|||
void connection.setPreferences(data, async () => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Input |
|||
label="Screen Timeout" |
|||
type="number" |
|||
suffix="Seconds" |
|||
{...register('screenOnSecs', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Carousel Delay" |
|||
type="number" |
|||
suffix="Seconds" |
|||
{...register('autoScreenCarouselSecs', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="GPS Display Units" |
|||
optionsEnum={Protobuf.GpsCoordinateFormat} |
|||
{...register('gpsFormat', { valueAsNumber: true })} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -0,0 +1,131 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { Controller, useForm } from 'react-hook-form'; |
|||
import { MultiSelect } from 'react-multi-select-component'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { Label } from '@components/generic/form/Label'; |
|||
import { Select } from '@components/generic/form/Select'; |
|||
import { connection } from '@core/connection'; |
|||
import { bitwiseDecode, bitwiseEncode } from '@core/utils/bitwise'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const GPS = (): JSX.Element => { |
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
const [loading, setLoading] = useState(false); |
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: { |
|||
...preferences, |
|||
positionBroadcastSecs: |
|||
preferences.positionBroadcastSecs === 0 |
|||
? preferences.isRouter |
|||
? 43200 |
|||
: 900 |
|||
: preferences.positionBroadcastSecs, |
|||
}, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit((data) => { |
|||
setLoading(true); |
|||
void connection.setPreferences(data, async () => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Input |
|||
label="Broadcast Interval" |
|||
type="number" |
|||
suffix="Seconds" |
|||
{...register('positionBroadcastSecs', { valueAsNumber: true })} |
|||
/> |
|||
<Checkbox |
|||
label="Use Smart Position" |
|||
{...register('positionBroadcastSmart')} |
|||
/> |
|||
<Checkbox label="Use Fixed Position" {...register('fixedPosition')} /> |
|||
<Select |
|||
label="Location Sharing" |
|||
optionsEnum={Protobuf.LocationSharing} |
|||
{...register('locationShare', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="GPS Mode" |
|||
optionsEnum={Protobuf.GpsOperation} |
|||
{...register('gpsOperation', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="GPS Update Interval" |
|||
type="number" |
|||
suffix="Seconds" |
|||
{...register('gpsUpdateInterval', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Last GPS Attempt" |
|||
disabled |
|||
{...register('gpsAttemptTime', { valueAsNumber: true })} |
|||
/> |
|||
<Checkbox label="Accept 2D Fix" {...register('gpsAccept2D')} /> |
|||
<Input |
|||
label="Max DOP" |
|||
type="number" |
|||
{...register('gpsMaxDop', { valueAsNumber: true })} |
|||
/> |
|||
<Controller |
|||
name="positionFlags" |
|||
control={control} |
|||
render={({ field, fieldState }): JSX.Element => { |
|||
const { value, onChange, ...rest } = field; |
|||
const { error } = fieldState; |
|||
const label = 'Position Flags'; |
|||
return ( |
|||
<div className="w-full"> |
|||
{label && <Label label={label} error={error?.message} />} |
|||
<MultiSelect |
|||
options={Object.entries(Protobuf.PositionFlags) |
|||
.filter((value) => typeof value[1] !== 'number') |
|||
.filter( |
|||
(value) => |
|||
parseInt(value[0]) !== |
|||
Protobuf.PositionFlags.POS_UNDEFINED, |
|||
) |
|||
.map((value) => { |
|||
return { |
|||
value: parseInt(value[0]), |
|||
label: value[1].toString().replace('POS_', ''), |
|||
}; |
|||
})} |
|||
value={bitwiseDecode(value, Protobuf.PositionFlags).map( |
|||
(flag) => { |
|||
return { |
|||
value: flag, |
|||
label: Protobuf.PositionFlags[flag].replace('POS_', ''), |
|||
}; |
|||
}, |
|||
)} |
|||
onChange={(e: { value: number; label: string }[]): void => |
|||
onChange(bitwiseEncode(e.map((v) => v.value))) |
|||
} |
|||
labelledBy="Select" |
|||
/> |
|||
</div> |
|||
); |
|||
}} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -0,0 +1,65 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm } from 'react-hook-form'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { Select } from '@components/generic/form/Select'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const LoRa = (): JSX.Element => { |
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
const [loading, setLoading] = useState(false); |
|||
const { register, handleSubmit, formState, reset } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit((data) => { |
|||
setLoading(true); |
|||
void connection.setPreferences(data, async () => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Input |
|||
label="Hop Count" |
|||
type="number" |
|||
suffix="Hops" |
|||
{...register('hopLimit', { valueAsNumber: true })} |
|||
/> |
|||
<Checkbox label="Transmit Disabled" {...register('isLoraTxDisabled')} /> |
|||
<Checkbox label="Router Mode" {...register('isRouter')} /> |
|||
<Input |
|||
label="Send Owner Interval" |
|||
type="number" |
|||
suffix="Seconds" |
|||
{...register('sendOwnerInterval', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Frequency Offset" |
|||
type="number" |
|||
suffix="Hz" |
|||
{...register('frequencyOffset', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="Region" |
|||
optionsEnum={Protobuf.RegionCode} |
|||
{...register('region', { valueAsNumber: true })} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -1,158 +0,0 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { Controller, useForm } from 'react-hook-form'; |
|||
import { FiSave } from 'react-icons/fi'; |
|||
import { MultiSelect } from 'react-multi-select-component'; |
|||
|
|||
import { IconButton } from '@components/generic/button/IconButton'; |
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { Label } from '@components/generic/form/Label'; |
|||
import { Select } from '@components/generic/form/Select'; |
|||
import { connection } from '@core/connection'; |
|||
import { bitwiseEncode } from '@core/utils/bitwise'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const Position = (): JSX.Element => { |
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
const [loading, setLoading] = useState(false); |
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: { |
|||
...preferences, |
|||
positionBroadcastSecs: |
|||
preferences.positionBroadcastSecs === 0 |
|||
? preferences.isRouter |
|||
? 43200 |
|||
: 900 |
|||
: preferences.positionBroadcastSecs, |
|||
}, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit((data) => { |
|||
setLoading(true); |
|||
void connection.setPreferences(data, async () => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
const encode = (enums: Protobuf.PositionFlags[]): number => { |
|||
return enums.reduce((acc, curr) => acc | curr, 0); |
|||
}; |
|||
|
|||
const decode = (value: number): Protobuf.PositionFlags[] => { |
|||
const enumValues = Object.keys(Protobuf.PositionFlags) |
|||
.map(Number) |
|||
.filter(Boolean); |
|||
|
|||
return enumValues.map((b) => value & b).filter(Boolean); |
|||
}; |
|||
|
|||
return ( |
|||
<> |
|||
<form className="space-y-2" onSubmit={onSubmit}> |
|||
<Input |
|||
label="Broadcast Interval" |
|||
type="number" |
|||
suffix="Seconds" |
|||
{...register('positionBroadcastSecs', { valueAsNumber: true })} |
|||
/> |
|||
|
|||
<Controller |
|||
name="positionFlags" |
|||
control={control} |
|||
render={({ field, fieldState }): JSX.Element => { |
|||
const { value, onChange, ...rest } = field; |
|||
const { error } = fieldState; |
|||
const label = 'Position Flags'; |
|||
return ( |
|||
<div className="w-full"> |
|||
{label && <Label label={label} error={error?.message} />} |
|||
<MultiSelect |
|||
options={Object.entries(Protobuf.PositionFlags) |
|||
.filter((value) => typeof value[1] !== 'number') |
|||
.filter( |
|||
(value) => |
|||
parseInt(value[0]) !== |
|||
Protobuf.PositionFlags.POS_UNDEFINED, |
|||
) |
|||
.map((value) => { |
|||
return { |
|||
value: parseInt(value[0]), |
|||
label: value[1].toString().replace('POS_', ''), |
|||
}; |
|||
})} |
|||
value={decode(value).map((flag) => { |
|||
return { |
|||
value: flag, |
|||
label: Protobuf.PositionFlags[flag].replace('POS_', ''), |
|||
}; |
|||
})} |
|||
onChange={(e: { value: number; label: string }[]): void => |
|||
onChange(bitwiseEncode(e.map((v) => v.value))) |
|||
} |
|||
labelledBy="Select" |
|||
/> |
|||
</div> |
|||
); |
|||
}} |
|||
/> |
|||
|
|||
<Input |
|||
label="Position Type (DEBUG)" |
|||
type="number" |
|||
disabled |
|||
{...register('positionFlags', { valueAsNumber: true })} |
|||
/> |
|||
<Checkbox label="Use Fixed Position" {...register('fixedPosition')} /> |
|||
<Select |
|||
label="Location Sharing" |
|||
optionsEnum={Protobuf.LocationSharing} |
|||
{...register('locationShare', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="GPS Mode" |
|||
optionsEnum={Protobuf.GpsOperation} |
|||
{...register('gpsOperation', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="Display Format" |
|||
optionsEnum={Protobuf.GpsCoordinateFormat} |
|||
{...register('gpsFormat', { valueAsNumber: true })} |
|||
/> |
|||
<Checkbox label="Accept 2D Fix" {...register('gpsAccept2D')} /> |
|||
<Input |
|||
label="Max DOP" |
|||
type="number" |
|||
{...register('gpsMaxDop', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Last GPS Attempt" |
|||
disabled |
|||
{...register('gpsAttemptTime', { valueAsNumber: true })} |
|||
/> |
|||
</form> |
|||
<div className="flex w-full bg-white dark:bg-secondaryDark"> |
|||
<div className="ml-auto p-2"> |
|||
<IconButton |
|||
disabled={!formState.isDirty} |
|||
onClick={async (): Promise<void> => { |
|||
await onSubmit(); |
|||
}} |
|||
icon={<FiSave />} |
|||
/> |
|||
</div> |
|||
</div> |
|||
</> |
|||
); |
|||
}; |
|||
@ -0,0 +1,99 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { Select } from '@components/generic/form/Select'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const CannedMessage = (): JSX.Element => { |
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
const [loading, setLoading] = useState(false); |
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
const moduleEnabled = useWatch({ |
|||
control, |
|||
name: 'rotary1Enabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit((data) => { |
|||
setLoading(true); |
|||
void connection.setPreferences(data, async () => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Checkbox |
|||
label="Module Enabled" |
|||
{...register('cannedMessageModuleEnabled')} |
|||
/> |
|||
<Checkbox |
|||
label="Rotary Encoder #1 Enabled" |
|||
{...register('rotary1Enabled')} |
|||
/> |
|||
<Input |
|||
label="Encoder #1 Pin A" |
|||
type="number" |
|||
disabled={moduleEnabled} |
|||
{...register('rotary1PinA', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Encoder #1 Pin B" |
|||
type="number" |
|||
disabled={moduleEnabled} |
|||
{...register('rotary1PinB', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Endoer #1 Pin Press" |
|||
type="number" |
|||
disabled={moduleEnabled} |
|||
{...register('rotary1PinPress', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="Clockwise event" |
|||
disabled={moduleEnabled} |
|||
optionsEnum={Protobuf.InputEventChar} |
|||
{...register('rotary1EventCw', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="Counter Clockwise event" |
|||
disabled={moduleEnabled} |
|||
optionsEnum={Protobuf.InputEventChar} |
|||
{...register('rotary1EventCcw', { valueAsNumber: true })} |
|||
/> |
|||
<Select |
|||
label="Press event" |
|||
disabled={moduleEnabled} |
|||
optionsEnum={Protobuf.InputEventChar} |
|||
{...register('rotary1EventPress', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Allow Input Source" |
|||
disabled={moduleEnabled} |
|||
{...register('cannedMessageModuleAllowInputSource')} |
|||
/> |
|||
<Checkbox |
|||
label="Send Bell" |
|||
{...register('cannedMessageModuleSendBell')} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -0,0 +1,84 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => { |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
|
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit(async (data) => { |
|||
setLoading(true); |
|||
await connection.setPreferences(data, async (): Promise<void> => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
const moduleEnabled = useWatch({ |
|||
control, |
|||
name: 'extNotificationModuleEnabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Checkbox |
|||
label="Module Enabled" |
|||
{...register('extNotificationModuleEnabled')} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Output MS" |
|||
suffix="ms" |
|||
disabled={!moduleEnabled} |
|||
{...register('extNotificationModuleOutputMs', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Output" |
|||
disabled={!moduleEnabled} |
|||
{...register('extNotificationModuleOutput', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Checkbox |
|||
label="Active" |
|||
disabled={!moduleEnabled} |
|||
{...register('extNotificationModuleActive')} |
|||
/> |
|||
<Checkbox |
|||
label="Message" |
|||
disabled={!moduleEnabled} |
|||
{...register('extNotificationModuleAlertMessage')} |
|||
/> |
|||
<Checkbox |
|||
label="Bell" |
|||
disabled={!moduleEnabled} |
|||
{...register('extNotificationModuleAlertBell')} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -0,0 +1,68 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const MQTT = (): JSX.Element => { |
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
const [loading, setLoading] = useState(false); |
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
const moduleEnabled = useWatch({ |
|||
control, |
|||
name: 'mqttDisabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit((data) => { |
|||
setLoading(true); |
|||
void connection.setPreferences(data, async () => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Checkbox label="Module Disabled" {...register('mqttDisabled')} /> |
|||
<Input |
|||
label="MQTT Server Address" |
|||
disabled={moduleEnabled} |
|||
{...register('mqttServer')} |
|||
/> |
|||
<Input |
|||
label="MQTT Username" |
|||
disabled={moduleEnabled} |
|||
{...register('mqttUsername')} |
|||
/> |
|||
<Input |
|||
label="MQTT Password" |
|||
type="password" |
|||
autoComplete="off" |
|||
disabled={moduleEnabled} |
|||
{...register('mqttPassword')} |
|||
/> |
|||
<Checkbox |
|||
label="Encryption Enabled" |
|||
disabled={moduleEnabled} |
|||
{...register('mqttEncryptionEnabled')} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -0,0 +1,95 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const SerialSettingsPanel = (): JSX.Element => { |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
|
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit(async (data) => { |
|||
setLoading(true); |
|||
await connection.setPreferences(data, async (): Promise<void> => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
const moduleEnabled = useWatch({ |
|||
control, |
|||
name: 'serialmoduleEnabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Checkbox label="Module Enabled" {...register('serialmoduleEnabled')} /> |
|||
<Checkbox |
|||
label="Echo" |
|||
disabled={!moduleEnabled} |
|||
{...register('serialmoduleEcho')} |
|||
/> |
|||
|
|||
<Input |
|||
type="number" |
|||
label="RX" |
|||
disabled={!moduleEnabled} |
|||
{...register('serialmoduleRxd', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="TX" |
|||
disabled={!moduleEnabled} |
|||
{...register('serialmoduleTxd', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="TX" |
|||
disabled={!moduleEnabled} |
|||
{...register('serialmoduleBaud', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Timeout" |
|||
disabled={!moduleEnabled} |
|||
{...register('serialmoduleTimeout', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Mode" |
|||
disabled={!moduleEnabled} |
|||
{...register('serialmoduleMode', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -0,0 +1,82 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const StoreForwardSettingsPanel = (): JSX.Element => { |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
|
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit(async (data) => { |
|||
setLoading(true); |
|||
await connection.setPreferences(data, async (): Promise<void> => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
const moduleEnabled = useWatch({ |
|||
control, |
|||
name: 'storeForwardModuleEnabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Checkbox |
|||
label="Module Enabled" |
|||
{...register('storeForwardModuleEnabled')} |
|||
/> |
|||
<Checkbox |
|||
label="Heartbeat Enabled" |
|||
disabled={!moduleEnabled} |
|||
{...register('storeForwardModuleHeartbeat')} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Number of records" |
|||
suffix="Records" |
|||
disabled={!moduleEnabled} |
|||
{...register('storeForwardModuleRecords', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="History return max" |
|||
disabled={!moduleEnabled} |
|||
{...register('storeForwardModuleHistoryReturnMax', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="History return window" |
|||
disabled={!moduleEnabled} |
|||
{...register('storeForwardModuleHistoryReturnWindow', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -0,0 +1,83 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm } from 'react-hook-form'; |
|||
|
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { Select } from '@components/generic/form/Select'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const Telemetry = (): JSX.Element => { |
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
const [loading, setLoading] = useState(false); |
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit((data) => { |
|||
setLoading(true); |
|||
void connection.setPreferences(data, async () => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
return ( |
|||
<Form loading={loading} dirty={!formState.isDirty} submit={onSubmit}> |
|||
<Checkbox |
|||
label="Measurement Enabled" |
|||
{...register('telemetryModuleMeasurementEnabled')} |
|||
/> |
|||
<Checkbox |
|||
label="Displayed on Screen" |
|||
{...register('telemetryModuleScreenEnabled')} |
|||
/> |
|||
<Input |
|||
label="Read Error Count Threshold" |
|||
type="number" |
|||
{...register('telemetryModuleReadErrorCountThreshold', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
label="Update Interval" |
|||
type="number" |
|||
{...register('telemetryModuleUpdateInterval', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
label="Recovery Interval" |
|||
type="number" |
|||
{...register('telemetryModuleRecoveryInterval', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Checkbox |
|||
label="Display Farenheit" |
|||
{...register('telemetryModuleDisplayFarenheit')} |
|||
/> |
|||
<Select |
|||
label="Sensor Type" |
|||
optionsEnum={Protobuf.RadioConfig_UserPreferences_TelemetrySensorType} |
|||
{...register('telemetryModuleSensorType', { valueAsNumber: true })} |
|||
/> |
|||
<Input |
|||
label="Sensor Pin" |
|||
type="number" |
|||
{...register('telemetryModuleSensorPin', { valueAsNumber: true })} |
|||
/> |
|||
</Form> |
|||
); |
|||
}; |
|||
@ -1,99 +0,0 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
import { FiSave } from 'react-icons/fi'; |
|||
|
|||
import { IconButton } from '@components/generic/button/IconButton'; |
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => { |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
|
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit(async (data) => { |
|||
setLoading(true); |
|||
await connection.setPreferences(data, async (): Promise<void> => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
const pluginEnabled = useWatch({ |
|||
control, |
|||
name: 'extNotificationPluginEnabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
return ( |
|||
<> |
|||
<Form loading={loading}> |
|||
<Checkbox |
|||
label="Plugin Enabled" |
|||
{...register('extNotificationPluginEnabled')} |
|||
/> |
|||
<Checkbox |
|||
label="Active" |
|||
disabled={!pluginEnabled} |
|||
{...register('extNotificationPluginActive')} |
|||
/> |
|||
<Checkbox |
|||
label="Bell" |
|||
disabled={!pluginEnabled} |
|||
{...register('extNotificationPluginAlertBell')} |
|||
/> |
|||
<Checkbox |
|||
label="Message" |
|||
disabled={!pluginEnabled} |
|||
{...register('extNotificationPluginAlertMessage')} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Output" |
|||
disabled={!pluginEnabled} |
|||
{...register('extNotificationPluginOutput', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Output MS" |
|||
suffix="ms" |
|||
disabled={!pluginEnabled} |
|||
{...register('extNotificationPluginOutputMs', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
</Form> |
|||
<div className="flex w-full bg-white dark:bg-secondaryDark"> |
|||
<div className="ml-auto p-2"> |
|||
<IconButton |
|||
disabled={!formState.isDirty} |
|||
onClick={async (): Promise<void> => { |
|||
await onSubmit(); |
|||
}} |
|||
icon={<FiSave />} |
|||
/> |
|||
</div> |
|||
</div> |
|||
</> |
|||
); |
|||
}; |
|||
@ -1,102 +0,0 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
import { FiSave } from 'react-icons/fi'; |
|||
|
|||
import { IconButton } from '@components/generic/button/IconButton'; |
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const SerialSettingsPanel = (): JSX.Element => { |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
|
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit(async (data) => { |
|||
setLoading(true); |
|||
await connection.setPreferences(data, async (): Promise<void> => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
const pluginEnabled = useWatch({ |
|||
control, |
|||
name: 'serialpluginEnabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
return ( |
|||
<> |
|||
<Form loading={loading}> |
|||
<Checkbox label="Plugin Enabled" {...register('serialpluginEnabled')} /> |
|||
<Checkbox |
|||
label="Echo" |
|||
disabled={!pluginEnabled} |
|||
{...register('serialpluginEcho')} |
|||
/> |
|||
|
|||
<Input |
|||
type="number" |
|||
label="RX" |
|||
disabled={!pluginEnabled} |
|||
{...register('serialpluginRxd', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="TX" |
|||
disabled={!pluginEnabled} |
|||
{...register('serialpluginTxd', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Mode" |
|||
disabled={!pluginEnabled} |
|||
{...register('serialpluginMode', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Timeout" |
|||
disabled={!pluginEnabled} |
|||
{...register('serialpluginTimeout', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
</Form> |
|||
<div className="flex w-full bg-white dark:bg-secondaryDark"> |
|||
<div className="ml-auto p-2"> |
|||
<IconButton |
|||
disabled={!formState.isDirty} |
|||
onClick={async (): Promise<void> => { |
|||
await onSubmit(); |
|||
}} |
|||
icon={<FiSave />} |
|||
/> |
|||
</div> |
|||
</div> |
|||
</> |
|||
); |
|||
}; |
|||
@ -1,97 +0,0 @@ |
|||
import type React from 'react'; |
|||
import { useEffect, useState } from 'react'; |
|||
|
|||
import { useForm, useWatch } from 'react-hook-form'; |
|||
import { FiSave } from 'react-icons/fi'; |
|||
|
|||
import { IconButton } from '@components/generic/button/IconButton'; |
|||
import { Checkbox } from '@components/generic/form/Checkbox'; |
|||
import { Form } from '@components/generic/form/Form'; |
|||
import { Input } from '@components/generic/form/Input'; |
|||
import { connection } from '@core/connection'; |
|||
import { useAppSelector } from '@hooks/useAppSelector'; |
|||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|||
|
|||
export const StoreForwardSettingsPanel = (): JSX.Element => { |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
const preferences = useAppSelector( |
|||
(state) => state.meshtastic.radio.preferences, |
|||
); |
|||
|
|||
const { register, handleSubmit, formState, reset, control } = |
|||
useForm<Protobuf.RadioConfig_UserPreferences>({ |
|||
defaultValues: preferences, |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
reset(preferences); |
|||
}, [reset, preferences]); |
|||
|
|||
const onSubmit = handleSubmit(async (data) => { |
|||
setLoading(true); |
|||
await connection.setPreferences(data, async (): Promise<void> => { |
|||
reset({ ...data }); |
|||
setLoading(false); |
|||
await Promise.resolve(); |
|||
}); |
|||
}); |
|||
|
|||
const pluginEnabled = useWatch({ |
|||
control, |
|||
name: 'storeForwardPluginEnabled', |
|||
defaultValue: false, |
|||
}); |
|||
|
|||
return ( |
|||
<> |
|||
<Form loading={loading}> |
|||
<Checkbox |
|||
label="Plugin Enabled" |
|||
{...register('storeForwardPluginEnabled')} |
|||
/> |
|||
<Checkbox |
|||
label="Heartbeat Enabled" |
|||
disabled={!pluginEnabled} |
|||
{...register('storeForwardPluginHeartbeat')} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="Number of records" |
|||
suffix="Records" |
|||
disabled={!pluginEnabled} |
|||
{...register('storeForwardPluginRecords', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="History return max" |
|||
disabled={!pluginEnabled} |
|||
{...register('storeForwardPluginHistoryReturnMax', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
<Input |
|||
type="number" |
|||
label="History return window" |
|||
disabled={!pluginEnabled} |
|||
{...register('storeForwardPluginHistoryReturnWindow', { |
|||
valueAsNumber: true, |
|||
})} |
|||
/> |
|||
</Form> |
|||
<div className="flex w-full bg-white dark:bg-secondaryDark"> |
|||
<div className="ml-auto p-2"> |
|||
<IconButton |
|||
disabled={!formState.isDirty} |
|||
onClick={async (): Promise<void> => { |
|||
await onSubmit(); |
|||
}} |
|||
icon={<FiSave />} |
|||
/> |
|||
</div> |
|||
</div> |
|||
</> |
|||
); |
|||
}; |
|||
Loading…
Reference in new issue