21 changed files with 559 additions and 258 deletions
@ -0,0 +1,36 @@ |
|||||
|
import type React from 'react'; |
||||
|
|
||||
|
import { FiSave, FiXCircle } from 'react-icons/fi'; |
||||
|
|
||||
|
import { IconButton } from './generic/IconButton'; |
||||
|
|
||||
|
export interface FormFooterProps { |
||||
|
dirty: boolean; |
||||
|
clearAction: () => void; |
||||
|
saveAction: () => void; |
||||
|
} |
||||
|
|
||||
|
export const FormFooter = ({ |
||||
|
dirty, |
||||
|
clearAction, |
||||
|
saveAction, |
||||
|
}: FormFooterProps): JSX.Element => { |
||||
|
return ( |
||||
|
<div className="flex float-right gap-2"> |
||||
|
<IconButton |
||||
|
icon={<FiXCircle className="w-5 h-5" />} |
||||
|
disabled={!dirty} |
||||
|
onClick={(): void => { |
||||
|
clearAction(); |
||||
|
}} |
||||
|
/> |
||||
|
<IconButton |
||||
|
disabled={!dirty} |
||||
|
onClick={(): void => { |
||||
|
saveAction(); |
||||
|
}} |
||||
|
icon={<FiSave className="w-5 h-5" />} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,127 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { useForm } from 'react-hook-form'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
import { FiCode, FiMenu } from 'react-icons/fi'; |
||||
|
import JSONPretty from 'react-json-pretty'; |
||||
|
|
||||
|
import { FormFooter } from '@app/components/FormFooter'; |
||||
|
import { connection } from '@app/core/connection'; |
||||
|
import { useAppSelector } from '@app/hooks/redux'; |
||||
|
import { Card } from '@components/generic/Card'; |
||||
|
import { Cover } from '@components/generic/Cover'; |
||||
|
import { Checkbox } from '@components/generic/form/Checkbox'; |
||||
|
import { Input } from '@components/generic/form/Input'; |
||||
|
import { Select } from '@components/generic/form/Select'; |
||||
|
import { IconButton } from '@components/generic/IconButton'; |
||||
|
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; |
||||
|
import { Protobuf } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
export interface PositionProps { |
||||
|
navOpen?: boolean; |
||||
|
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>; |
||||
|
} |
||||
|
|
||||
|
export const Position = ({ |
||||
|
navOpen, |
||||
|
setNavOpen, |
||||
|
}: PositionProps): JSX.Element => { |
||||
|
const { t } = useTranslation(); |
||||
|
const radioConfig = useAppSelector((state) => state.meshtastic.preferences); |
||||
|
const [debug, setDebug] = React.useState(false); |
||||
|
|
||||
|
const { register, handleSubmit, formState, reset } = |
||||
|
useForm<Protobuf.RadioConfig_UserPreferences>({ |
||||
|
defaultValues: { |
||||
|
...radioConfig, |
||||
|
positionBroadcastSecs: |
||||
|
radioConfig.positionBroadcastSecs === 0 |
||||
|
? radioConfig.isRouter |
||||
|
? 43200 |
||||
|
: 900 |
||||
|
: radioConfig.positionBroadcastSecs, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const onSubmit = handleSubmit((data) => { |
||||
|
void connection.setPreferences(data); |
||||
|
}); |
||||
|
return ( |
||||
|
<PrimaryTemplate |
||||
|
title="Position" |
||||
|
tagline="Settings" |
||||
|
leftButton={ |
||||
|
<IconButton |
||||
|
icon={<FiMenu className="w-5 h-5" />} |
||||
|
onClick={(): void => { |
||||
|
setNavOpen && setNavOpen(!navOpen); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
rightButton={ |
||||
|
<IconButton |
||||
|
icon={<FiCode className="w-5 h-5" />} |
||||
|
active={debug} |
||||
|
onClick={(): void => { |
||||
|
setDebug(!debug); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
footer={ |
||||
|
<FormFooter |
||||
|
dirty={formState.isDirty} |
||||
|
saveAction={onSubmit} |
||||
|
clearAction={reset} |
||||
|
/> |
||||
|
} |
||||
|
> |
||||
|
<Card> |
||||
|
<Cover enabled={debug} content={<JSONPretty data={radioConfig} />} /> |
||||
|
<div className="w-full max-w-3xl p-10 md:max-w-xl"> |
||||
|
<form className="space-y-2" onSubmit={onSubmit}> |
||||
|
<Input |
||||
|
label={'Broadcast Interval (seconds)'} |
||||
|
type="number" |
||||
|
{...register('positionBroadcastSecs', { valueAsNumber: true })} |
||||
|
/> |
||||
|
<Select |
||||
|
label="Position Type" |
||||
|
optionsEnum={Protobuf.PositionFlags} |
||||
|
{...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> |
||||
|
</Card> |
||||
|
</PrimaryTemplate> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,92 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { useForm } from 'react-hook-form'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
import { FiCode, FiMenu } from 'react-icons/fi'; |
||||
|
import JSONPretty from 'react-json-pretty'; |
||||
|
|
||||
|
import { FormFooter } from '@app/components/FormFooter'; |
||||
|
import { Select } from '@app/components/generic/form/Select'; |
||||
|
import { connection } from '@app/core/connection'; |
||||
|
import { useAppSelector } from '@app/hooks/redux'; |
||||
|
import { Card } from '@components/generic/Card'; |
||||
|
import { Cover } from '@components/generic/Cover'; |
||||
|
import { Checkbox } from '@components/generic/form/Checkbox'; |
||||
|
import { IconButton } from '@components/generic/IconButton'; |
||||
|
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; |
||||
|
import { Protobuf } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
export interface PowerProps { |
||||
|
navOpen?: boolean; |
||||
|
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>; |
||||
|
} |
||||
|
|
||||
|
export const Power = ({ navOpen, setNavOpen }: PowerProps): JSX.Element => { |
||||
|
const { t } = useTranslation(); |
||||
|
const radioConfig = useAppSelector((state) => state.meshtastic.preferences); |
||||
|
const [debug, setDebug] = React.useState(false); |
||||
|
|
||||
|
const { register, handleSubmit, formState, reset } = |
||||
|
useForm<Protobuf.RadioConfig_UserPreferences>({ |
||||
|
defaultValues: { |
||||
|
...radioConfig, |
||||
|
isLowPower: radioConfig.isRouter ? true : radioConfig.isLowPower, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const onSubmit = handleSubmit((data) => { |
||||
|
void connection.setPreferences(data); |
||||
|
}); |
||||
|
return ( |
||||
|
<PrimaryTemplate |
||||
|
title="Power" |
||||
|
tagline="Settings" |
||||
|
leftButton={ |
||||
|
<IconButton |
||||
|
icon={<FiMenu className="w-5 h-5" />} |
||||
|
onClick={(): void => { |
||||
|
setNavOpen && setNavOpen(!navOpen); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
rightButton={ |
||||
|
<IconButton |
||||
|
icon={<FiCode className="w-5 h-5" />} |
||||
|
active={debug} |
||||
|
onClick={(): void => { |
||||
|
setDebug(!debug); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
footer={ |
||||
|
<FormFooter |
||||
|
dirty={formState.isDirty} |
||||
|
saveAction={onSubmit} |
||||
|
clearAction={reset} |
||||
|
/> |
||||
|
} |
||||
|
> |
||||
|
<Card> |
||||
|
<Cover enabled={debug} content={<JSONPretty data={radioConfig} />} /> |
||||
|
<div className="w-full max-w-3xl p-10 md:max-w-xl"> |
||||
|
<form className="space-y-2" onSubmit={onSubmit}> |
||||
|
<Select |
||||
|
label={'Charge current'} |
||||
|
optionsEnum={Protobuf.ChargeCurrent} |
||||
|
{...register('chargeCurrent', { valueAsNumber: true })} |
||||
|
/> |
||||
|
<Checkbox label="Always powered" {...register('isAlwaysPowered')} /> |
||||
|
<Checkbox |
||||
|
label="Powered by low power source (solar)" |
||||
|
disabled={radioConfig.isRouter} |
||||
|
validationMessage={ |
||||
|
radioConfig.isRouter ? 'Enabled by default in router mode' : '' |
||||
|
} |
||||
|
{...register('isLowPower')} |
||||
|
/> |
||||
|
</form> |
||||
|
</div> |
||||
|
</Card> |
||||
|
</PrimaryTemplate> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,93 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { useForm, useWatch } from 'react-hook-form'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
import { FiCode, FiMenu } from 'react-icons/fi'; |
||||
|
import JSONPretty from 'react-json-pretty'; |
||||
|
|
||||
|
import { FormFooter } from '@app/components/FormFooter'; |
||||
|
import { Checkbox } from '@app/components/generic/form/Checkbox'; |
||||
|
import { connection } from '@app/core/connection'; |
||||
|
import { useAppSelector } from '@app/hooks/redux'; |
||||
|
import { Card } from '@components/generic/Card'; |
||||
|
import { Cover } from '@components/generic/Cover'; |
||||
|
import { Input } from '@components/generic/form/Input'; |
||||
|
import { IconButton } from '@components/generic/IconButton'; |
||||
|
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; |
||||
|
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
export interface WiFiProps { |
||||
|
navOpen?: boolean; |
||||
|
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>; |
||||
|
} |
||||
|
|
||||
|
export const WiFi = ({ navOpen, setNavOpen }: WiFiProps): JSX.Element => { |
||||
|
const { t } = useTranslation(); |
||||
|
const radioConfig = useAppSelector((state) => state.meshtastic.preferences); |
||||
|
const [debug, setDebug] = React.useState(false); |
||||
|
|
||||
|
const { register, handleSubmit, formState, reset, control } = |
||||
|
useForm<Protobuf.RadioConfig_UserPreferences>({ |
||||
|
defaultValues: radioConfig, |
||||
|
}); |
||||
|
|
||||
|
const watchWifiApMode = useWatch({ |
||||
|
control, |
||||
|
name: 'wifiApMode', |
||||
|
defaultValue: false, |
||||
|
}); |
||||
|
|
||||
|
const onSubmit = handleSubmit((data) => { |
||||
|
void connection.setPreferences(data); |
||||
|
}); |
||||
|
return ( |
||||
|
<PrimaryTemplate |
||||
|
title="WiFi" |
||||
|
tagline="Settings" |
||||
|
leftButton={ |
||||
|
<IconButton |
||||
|
icon={<FiMenu className="w-5 h-5" />} |
||||
|
onClick={(): void => { |
||||
|
setNavOpen && setNavOpen(!navOpen); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
rightButton={ |
||||
|
<IconButton |
||||
|
icon={<FiCode className="w-5 h-5" />} |
||||
|
active={debug} |
||||
|
onClick={(): void => { |
||||
|
setDebug(!debug); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
footer={ |
||||
|
<FormFooter |
||||
|
dirty={formState.isDirty} |
||||
|
saveAction={onSubmit} |
||||
|
clearAction={reset} |
||||
|
/> |
||||
|
} |
||||
|
> |
||||
|
<Card> |
||||
|
<Cover enabled={debug} content={<JSONPretty data={radioConfig} />} /> |
||||
|
<div className="w-full max-w-3xl p-10 md:max-w-xl"> |
||||
|
<form className="space-y-2" onSubmit={onSubmit}> |
||||
|
<Checkbox label="Enable WiFi AP" {...register('wifiApMode')} /> |
||||
|
<Input |
||||
|
label={t('strings.wifi_ssid')} |
||||
|
disabled={watchWifiApMode} |
||||
|
{...register('wifiSsid')} |
||||
|
/> |
||||
|
<Input |
||||
|
type="password" |
||||
|
label={t('strings.wifi_psk')} |
||||
|
disabled={watchWifiApMode} |
||||
|
{...register('wifiPassword')} |
||||
|
/> |
||||
|
</form> |
||||
|
</div> |
||||
|
</Card> |
||||
|
</PrimaryTemplate> |
||||
|
); |
||||
|
}; |
||||
Loading…
Reference in new issue