Form improvements & ui cleanup
This commit is contained in:
@@ -11,7 +11,6 @@ import { useAppSelector } from '../hooks/redux';
|
||||
|
||||
export const Messages = (): JSX.Element => {
|
||||
const messages = useAppSelector((state) => state.meshtastic.messages);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const channels = useAppSelector((state) => state.meshtastic.channels);
|
||||
|
||||
|
||||
+25
-15
@@ -1,11 +1,12 @@
|
||||
import 'react-json-pretty/themes/acai.css';
|
||||
|
||||
import type React from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { FiMenu, FiTerminal } from 'react-icons/fi';
|
||||
import { FiCode, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
import { Cover } from '@app/components/generic/Cover';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
@@ -28,11 +29,12 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
const position = useAppSelector(
|
||||
(state) => state.meshtastic.positionPackets,
|
||||
).find((position) => position.packet.from === node.num)?.data;
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title={user ? user.longName : node.num.toString()}
|
||||
tagline="Node"
|
||||
button={
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
@@ -40,12 +42,28 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rightButton={
|
||||
<IconButton
|
||||
icon={<FiCode className="w-5 h-5" />}
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
footer={<></>}
|
||||
>
|
||||
<div className="w-full space-y-4">
|
||||
<div className="justify-between space-y-2 md:space-y-0 md:space-x-2 md:flex">
|
||||
<div className="flex flex-col justify-between gap-4 md:flex-row">
|
||||
<StatCard
|
||||
title="Last heard"
|
||||
value={<TimeAgo date={new Date(node.lastHeard * 1000)} />}
|
||||
value={
|
||||
node.lastHeard ? (
|
||||
<TimeAgo date={new Date(node.lastHeard * 1000)} />
|
||||
) : (
|
||||
'Never'
|
||||
)
|
||||
}
|
||||
/>
|
||||
<StatCard title="SNR" value={node.snr.toString()} />
|
||||
</div>
|
||||
@@ -53,20 +71,12 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
title="Position"
|
||||
description={new Date(node.lastHeard * 1000).toLocaleString()}
|
||||
>
|
||||
<Cover enabled={debug} content={<JSONPretty data={node} />} />
|
||||
<div className="p-10">
|
||||
<JSONPretty data={position} />
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
title="Settings"
|
||||
description="Remote node settings"
|
||||
lgPlaceholder={
|
||||
<div className="w-full h-full text-black dark:text-white">
|
||||
<FiTerminal className="w-24 h-24 m-auto" />
|
||||
<div className="text-center">Placeholder</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Card title="Settings" description="Remote node settings">
|
||||
<div className="p-10">
|
||||
<form className="space-y-4">
|
||||
<Input label={'Device Name'} />
|
||||
|
||||
@@ -54,7 +54,7 @@ export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => {
|
||||
<PrimaryTemplate
|
||||
title="File Browser"
|
||||
tagline="Plugin"
|
||||
button={
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiMenu, FiSave } from 'react-icons/fi';
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { Button } from '@components/generic/Button';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
@@ -26,7 +26,7 @@ export const RangeTest = ({
|
||||
const { t } = useTranslation();
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
|
||||
const { register, handleSubmit, formState } =
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<RadioConfig_UserPreferences>({
|
||||
defaultValues: {
|
||||
rangeTestPluginEnabled: preferences.rangeTestPluginEnabled,
|
||||
@@ -36,16 +36,20 @@ export const RangeTest = ({
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
console.log(data);
|
||||
|
||||
void connection.setPreferences(data);
|
||||
});
|
||||
|
||||
const watchRangeTestPluginEnabled = useWatch({
|
||||
control,
|
||||
name: 'rangeTestPluginEnabled',
|
||||
defaultValue: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title="Range Test"
|
||||
tagline="Plugin"
|
||||
button={
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
@@ -54,16 +58,11 @@ export const RangeTest = ({
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<Button
|
||||
className="px-10 ml-auto"
|
||||
icon={<FiSave className="w-5 h-5" />}
|
||||
disabled={!formState.isDirty}
|
||||
onClick={onSubmit}
|
||||
active
|
||||
border
|
||||
>
|
||||
{t('strings.save_changes')}
|
||||
</Button>
|
||||
<FormFooter
|
||||
dirty={formState.isDirty}
|
||||
saveAction={onSubmit}
|
||||
clearAction={reset}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="w-full space-y-4">
|
||||
@@ -76,11 +75,13 @@ export const RangeTest = ({
|
||||
/>
|
||||
<Checkbox
|
||||
label="Range Test Plugin Save?"
|
||||
disabled={!watchRangeTestPluginEnabled}
|
||||
{...register('rangeTestPluginSave')}
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
label="Message Interval"
|
||||
disabled={!watchRangeTestPluginEnabled}
|
||||
{...register('rangeTestPluginSender', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const Channels = ({
|
||||
<PrimaryTemplate
|
||||
title="Channels"
|
||||
tagline="Settings"
|
||||
button={
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
@@ -38,6 +38,15 @@ export const Channels = ({
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rightButton={
|
||||
<IconButton
|
||||
icon={<FiCode className="w-5 h-5" />}
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<Button
|
||||
className="px-10 ml-auto"
|
||||
@@ -50,22 +59,7 @@ export const Channels = ({
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Card
|
||||
title="Manage Channels"
|
||||
description="Edit channel throughput and other settings"
|
||||
buttons={
|
||||
<Button
|
||||
border
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
icon={<FiCode />}
|
||||
>
|
||||
Debug
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Card>
|
||||
<Cover enabled={debug} content={<JSONPretty data={channels} />} />
|
||||
<div className="w-full p-4 space-y-2 md:p-10">
|
||||
{channels.map((channel) => (
|
||||
|
||||
@@ -2,8 +2,9 @@ import React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiCheck, FiMenu, FiSave } from 'react-icons/fi';
|
||||
import { FiCheck, FiMenu } from 'react-icons/fi';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { connection, setConnection } from '@app/core/connection';
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { Button } from '@components/generic/Button';
|
||||
@@ -52,7 +53,7 @@ export const Connection = ({
|
||||
);
|
||||
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<{
|
||||
const { register, handleSubmit, formState, reset } = useForm<{
|
||||
method: connType;
|
||||
}>({
|
||||
defaultValues: {
|
||||
@@ -121,7 +122,7 @@ export const Connection = ({
|
||||
<PrimaryTemplate
|
||||
title="Connection"
|
||||
tagline="Settings"
|
||||
button={
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
@@ -130,21 +131,14 @@ export const Connection = ({
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<Button
|
||||
className="px-10 ml-auto"
|
||||
icon={<FiSave className="w-5 h-5" />}
|
||||
disabled={!formState.isDirty}
|
||||
active
|
||||
border
|
||||
>
|
||||
{t('strings.save_changes')}
|
||||
</Button>
|
||||
<FormFooter
|
||||
dirty={formState.isDirty}
|
||||
saveAction={onSubmit}
|
||||
clearAction={reset}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Card
|
||||
title="Connection Settings"
|
||||
description="HTTP, BLE and Serial Options"
|
||||
>
|
||||
<Card>
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<form className="space-y-2" onSubmit={onSubmit}>
|
||||
<Select
|
||||
|
||||
@@ -4,55 +4,80 @@ import {
|
||||
FiLayers,
|
||||
FiLayout,
|
||||
FiLink2,
|
||||
FiRss,
|
||||
FiSmartphone,
|
||||
FiMapPin,
|
||||
FiRadio,
|
||||
FiUser,
|
||||
FiWifi,
|
||||
FiZap,
|
||||
} from 'react-icons/fi';
|
||||
|
||||
import { PageLayout } from '@components/templates/PageLayout';
|
||||
|
||||
import { Channels } from './Channels';
|
||||
import { Connection } from './Connection';
|
||||
import { Device } from './Device';
|
||||
import { Interface } from './Interface';
|
||||
import { Position } from './Position';
|
||||
import { Power } from './Power';
|
||||
import { Radio } from './Radio';
|
||||
import { User } from './User';
|
||||
import { WiFi } from './WiFi';
|
||||
|
||||
export const Settings = (): JSX.Element => {
|
||||
const sidebarItems = [
|
||||
{
|
||||
title: 'Connection',
|
||||
description: 'Connection method and parameters',
|
||||
icon: <FiLink2 className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'WiFi',
|
||||
description: 'WiFi credentials and mode',
|
||||
icon: <FiWifi className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Position',
|
||||
description: 'Position settings and flags',
|
||||
icon: <FiMapPin className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'User',
|
||||
description: 'Device name and details',
|
||||
icon: <FiUser className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Power',
|
||||
description: 'Power and sleep settings',
|
||||
icon: <FiZap className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Radio',
|
||||
description: 'LoRa settings',
|
||||
icon: <FiRadio className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Interface',
|
||||
description: 'Language and UI settings',
|
||||
icon: <FiLayout className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Channels',
|
||||
description: 'Manage channels',
|
||||
icon: <FiLayers className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageLayout
|
||||
title="Settings"
|
||||
sidebarItems={[
|
||||
{
|
||||
title: 'Connection',
|
||||
description: 'Method and peramaters for connecting to the device',
|
||||
icon: <FiLink2 className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Device',
|
||||
description: 'Device settings, such as device name and wifi settings',
|
||||
icon: <FiSmartphone className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Radio',
|
||||
description: 'Adjust radio power and frequency settings',
|
||||
icon: <FiRss className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Interface',
|
||||
description: 'Change language and other UI settings',
|
||||
icon: <FiLayout className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Channels',
|
||||
description: 'Manage channels',
|
||||
icon: <FiLayers className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
]}
|
||||
sidebarItems={sidebarItems}
|
||||
panels={[
|
||||
<Connection key={1} />,
|
||||
<Device key={2} />,
|
||||
<Radio key={3} />,
|
||||
<Interface key={4} />,
|
||||
<Channels key={5} />,
|
||||
<WiFi key={2} />,
|
||||
<Position key={3} />,
|
||||
<User key={4} />,
|
||||
<Power key={5} />,
|
||||
<Radio key={6} />,
|
||||
<Interface key={7} />,
|
||||
<Channels key={8} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ export const Interface = ({
|
||||
<PrimaryTemplate
|
||||
title="Interface"
|
||||
tagline="Settings"
|
||||
button={
|
||||
leftButton={
|
||||
<Button
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
+19
-100
@@ -2,16 +2,15 @@ import React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiCode, FiMenu, FiSave, FiXCircle } from 'react-icons/fi';
|
||||
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 { Button } from '@components/generic/Button';
|
||||
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';
|
||||
@@ -39,7 +38,7 @@ export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => {
|
||||
<PrimaryTemplate
|
||||
title="Radio"
|
||||
tagline="Settings"
|
||||
button={
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
@@ -47,108 +46,28 @@ export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rightButton={
|
||||
<IconButton
|
||||
icon={<FiCode className="w-5 h-5" />}
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<div className="flex space-x-2">
|
||||
<IconButton
|
||||
icon={<FiXCircle className="w-5 h-5" />}
|
||||
disabled={formState.isDirty}
|
||||
onClick={(): void => {
|
||||
reset();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="px-10 ml-auto"
|
||||
icon={<FiSave className="w-5 h-5" />}
|
||||
disabled={!formState.isDirty}
|
||||
onClick={onSubmit}
|
||||
active
|
||||
border
|
||||
>
|
||||
{t('strings.save_changes')}
|
||||
</Button>
|
||||
</div>
|
||||
<FormFooter
|
||||
dirty={formState.isDirty}
|
||||
saveAction={onSubmit}
|
||||
clearAction={reset}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Card
|
||||
title="Basic settings"
|
||||
description="Device name and user parameters"
|
||||
buttons={
|
||||
<Button
|
||||
border
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
icon={<FiCode />}
|
||||
>
|
||||
Debug
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<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}>
|
||||
<div>Power</div>
|
||||
<hr />
|
||||
<Input
|
||||
label={'Charge current'}
|
||||
disabled
|
||||
{...register('chargeCurrent', { valueAsNumber: true })}
|
||||
/>
|
||||
<Checkbox label="Always powered" {...register('isAlwaysPowered')} />
|
||||
<Checkbox label="Low Power" {...register('isLowPower')} />
|
||||
<hr />
|
||||
<div>WiFi</div>
|
||||
<Input label={t('strings.wifi_ssid')} {...register('wifiSsid')} />
|
||||
<Input
|
||||
type="password"
|
||||
label={t('strings.wifi_psk')}
|
||||
{...register('wifiPassword')}
|
||||
/>
|
||||
<hr />
|
||||
<div>Position</div>
|
||||
<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 })}
|
||||
/>
|
||||
<hr />
|
||||
<div>Other</div>
|
||||
<Checkbox label="Is Router" {...register('isRouter')} />
|
||||
<Select
|
||||
label="Region"
|
||||
optionsEnum={Protobuf.RegionCode}
|
||||
|
||||
@@ -2,13 +2,13 @@ import React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiCode, FiMenu, FiSave } from 'react-icons/fi';
|
||||
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 { addUser } from '@app/core/slices/meshtasticSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { Button } from '@components/generic/Button';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Cover } from '@components/generic/Cover';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
@@ -18,12 +18,12 @@ import { IconButton } from '@components/generic/IconButton';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface DeviceProps {
|
||||
export interface UserProps {
|
||||
navOpen?: boolean;
|
||||
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -31,7 +31,7 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
const user = useAppSelector((state) => state.meshtastic.users).find(
|
||||
(user) => user.packet.from === myNodeInfo.myNodeNum,
|
||||
);
|
||||
const { register, handleSubmit, formState } = useForm<{
|
||||
const { register, handleSubmit, formState, reset } = useForm<{
|
||||
longName: string;
|
||||
shortName: string;
|
||||
isLicensed: boolean;
|
||||
@@ -46,7 +46,7 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
// TODO: can be remove once getUser is implemented
|
||||
// TODO: can be removed once getUser is implemented
|
||||
if (user) {
|
||||
void connection.setOwner({ ...user.data, ...data });
|
||||
dispatch(addUser({ ...user, ...{ data: { ...user.data, ...data } } }));
|
||||
@@ -55,9 +55,9 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title="Device"
|
||||
title="User"
|
||||
tagline="Settings"
|
||||
button={
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
@@ -65,35 +65,24 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rightButton={
|
||||
<IconButton
|
||||
icon={<FiCode className="w-5 h-5" />}
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<Button
|
||||
className="px-10 ml-auto"
|
||||
icon={<FiSave className="w-5 h-5" />}
|
||||
disabled={!formState.isDirty}
|
||||
onClick={onSubmit}
|
||||
active
|
||||
border
|
||||
>
|
||||
{t('strings.save_changes')}
|
||||
</Button>
|
||||
<FormFooter
|
||||
dirty={formState.isDirty}
|
||||
saveAction={onSubmit}
|
||||
clearAction={reset}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Card
|
||||
title="Basic settings"
|
||||
description="Device name and user parameters"
|
||||
buttons={
|
||||
<Button
|
||||
border
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
icon={<FiCode />}
|
||||
>
|
||||
Debug
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Card>
|
||||
<Cover enabled={debug} content={<JSONPretty data={user} />} />
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<form className="space-y-2" onSubmit={onSubmit}>
|
||||
@@ -117,7 +106,7 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
<Select
|
||||
label="Team"
|
||||
optionsEnum={Protobuf.Team}
|
||||
{...register('team')}
|
||||
{...register('team', { valueAsNumber: true })}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user