21 changed files with 802 additions and 397 deletions
File diff suppressed because it is too large
@ -0,0 +1,116 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { useForm } from 'react-hook-form'; |
||||
|
|
||||
|
import { Loading } from '@components/generic/Loading'; |
||||
|
import { Protobuf } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
import { connection } from '../core/connection'; |
||||
|
import { Card } from './generic/Card'; |
||||
|
import { Checkbox } from './generic/form/Checkbox'; |
||||
|
import { Input } from './generic/form/Input'; |
||||
|
|
||||
|
export interface LoraConfigProps { |
||||
|
channel: Protobuf.Channel; |
||||
|
} |
||||
|
|
||||
|
export const LoraConfig = ({ channel }: LoraConfigProps): JSX.Element => { |
||||
|
const [loading, setLoading] = React.useState(false); |
||||
|
|
||||
|
const { register, handleSubmit } = useForm<{ |
||||
|
enabled: boolean; |
||||
|
settings: { |
||||
|
name: string; |
||||
|
bandwidth?: number; |
||||
|
codingRate?: number; |
||||
|
spreadFactor?: number; |
||||
|
downlinkEnabled?: boolean; |
||||
|
uplinkEnabled?: boolean; |
||||
|
txPower?: number; |
||||
|
psk?: string; |
||||
|
}; |
||||
|
}>({ |
||||
|
defaultValues: { |
||||
|
enabled: |
||||
|
channel.role === |
||||
|
(Protobuf.Channel_Role.PRIMARY || Protobuf.Channel_Role.SECONDARY) |
||||
|
? true |
||||
|
: false, |
||||
|
settings: { |
||||
|
name: channel.settings?.name, |
||||
|
bandwidth: channel.settings?.bandwidth, |
||||
|
codingRate: channel.settings?.codingRate, |
||||
|
spreadFactor: channel.settings?.spreadFactor, |
||||
|
downlinkEnabled: channel.settings?.downlinkEnabled, |
||||
|
uplinkEnabled: channel.settings?.uplinkEnabled, |
||||
|
txPower: channel.settings?.txPower, |
||||
|
psk: new TextDecoder().decode(channel.settings?.psk), |
||||
|
}, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const onSubmit = handleSubmit(async (data) => { |
||||
|
setLoading(true); |
||||
|
const adminChannel = Protobuf.Channel.create({ |
||||
|
role: data.enabled |
||||
|
? Protobuf.Channel_Role.SECONDARY |
||||
|
: Protobuf.Channel_Role.DISABLED, |
||||
|
index: channel.index, |
||||
|
settings: { |
||||
|
...data.settings, |
||||
|
psk: new TextEncoder().encode(data.settings.psk), |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
await connection.setChannel(adminChannel, (): Promise<void> => { |
||||
|
setLoading(false); |
||||
|
return Promise.resolve(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<Card> |
||||
|
{loading && <Loading />} |
||||
|
<div className="w-full max-w-3xl p-10 md:max-w-xl"> |
||||
|
{/* TODO: get gap working */} |
||||
|
<form onSubmit={onSubmit}> |
||||
|
<Input |
||||
|
label="Bandwidth" |
||||
|
type="number" |
||||
|
suffix="MHz" |
||||
|
{...register('settings.bandwidth', { valueAsNumber: true })} |
||||
|
/> |
||||
|
<Input |
||||
|
label="Spread Factor" |
||||
|
type="number" |
||||
|
suffix="CPS" |
||||
|
min={7} |
||||
|
max={12} |
||||
|
{...register('settings.spreadFactor', { |
||||
|
valueAsNumber: true, |
||||
|
})} |
||||
|
/> |
||||
|
<Input |
||||
|
label="Coding Rate" |
||||
|
type="number" |
||||
|
{...register('settings.codingRate', { valueAsNumber: true })} |
||||
|
/> |
||||
|
<Input |
||||
|
label="Transmit Power" |
||||
|
type="number" |
||||
|
suffix="dBm" |
||||
|
{...register('settings.txPower', { valueAsNumber: true })} |
||||
|
/> |
||||
|
<Checkbox |
||||
|
label="Uplink Enabled" |
||||
|
{...register('settings.uplinkEnabled')} |
||||
|
/> |
||||
|
<Checkbox |
||||
|
label="Downlink Enabled" |
||||
|
{...register('settings.downlinkEnabled')} |
||||
|
/> |
||||
|
</form> |
||||
|
</div> |
||||
|
</Card> |
||||
|
); |
||||
|
}; |
||||
@ -1,14 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import { Card } from '@components/generic/Card'; |
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; |
|
||||
|
|
||||
export const About = (): JSX.Element => { |
|
||||
return ( |
|
||||
<PrimaryTemplate title="meshtastic-web" tagline="About"> |
|
||||
<Card title="Project desc" description="..."> |
|
||||
<p className="p-10">Content</p> |
|
||||
</Card> |
|
||||
</PrimaryTemplate> |
|
||||
); |
|
||||
}; |
|
||||
@ -0,0 +1,117 @@ |
|||||
|
import type React from 'react'; |
||||
|
|
||||
|
import { useForm, useWatch } from 'react-hook-form'; |
||||
|
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 { Card } from '@components/generic/Card'; |
||||
|
import { Checkbox } from '@components/generic/form/Checkbox'; |
||||
|
import { Input } from '@components/generic/form/Input'; |
||||
|
import { IconButton } from '@components/generic/IconButton'; |
||||
|
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; |
||||
|
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated'; |
||||
|
|
||||
|
export interface ExternalNotificationProps { |
||||
|
navOpen?: boolean; |
||||
|
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>; |
||||
|
} |
||||
|
|
||||
|
export const ExternalNotification = ({ |
||||
|
navOpen, |
||||
|
setNavOpen, |
||||
|
}: ExternalNotificationProps): JSX.Element => { |
||||
|
const preferences = useAppSelector((state) => state.meshtastic.preferences); |
||||
|
|
||||
|
const { register, handleSubmit, formState, reset, control } = |
||||
|
useForm<RadioConfig_UserPreferences>({ |
||||
|
defaultValues: { |
||||
|
extNotificationPluginActive: preferences.extNotificationPluginActive, |
||||
|
extNotificationPluginAlertBell: |
||||
|
preferences.extNotificationPluginAlertBell, |
||||
|
extNotificationPluginAlertMessage: |
||||
|
preferences.extNotificationPluginAlertMessage, |
||||
|
extNotificationPluginEnabled: preferences.extNotificationPluginEnabled, |
||||
|
extNotificationPluginOutput: preferences.extNotificationPluginOutput, |
||||
|
extNotificationPluginOutputMs: |
||||
|
preferences.extNotificationPluginOutputMs, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const onSubmit = handleSubmit((data) => { |
||||
|
void connection.setPreferences(data); |
||||
|
}); |
||||
|
|
||||
|
const watchExternalNotificationPluginEnabled = useWatch({ |
||||
|
control, |
||||
|
name: 'extNotificationPluginEnabled', |
||||
|
defaultValue: false, |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<PrimaryTemplate |
||||
|
title="External Notification" |
||||
|
tagline="Plugin" |
||||
|
leftButton={ |
||||
|
<IconButton |
||||
|
icon={<FiMenu className="w-5 h-5" />} |
||||
|
onClick={(): void => { |
||||
|
setNavOpen && setNavOpen(!navOpen); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
footer={ |
||||
|
<FormFooter |
||||
|
dirty={formState.isDirty} |
||||
|
saveAction={onSubmit} |
||||
|
clearAction={reset} |
||||
|
/> |
||||
|
} |
||||
|
> |
||||
|
<div className="w-full space-y-4"> |
||||
|
<Card> |
||||
|
<div className="w-full max-w-3xl p-10 md:max-w-xl"> |
||||
|
<form onSubmit={onSubmit}> |
||||
|
<Checkbox |
||||
|
label="Plugin Enabled" |
||||
|
{...register('extNotificationPluginEnabled')} |
||||
|
/> |
||||
|
<Checkbox |
||||
|
label="Active" |
||||
|
disabled={!watchExternalNotificationPluginEnabled} |
||||
|
{...register('extNotificationPluginActive')} |
||||
|
/> |
||||
|
<Checkbox |
||||
|
label="Bell" |
||||
|
disabled={!watchExternalNotificationPluginEnabled} |
||||
|
{...register('extNotificationPluginAlertBell')} |
||||
|
/> |
||||
|
<Checkbox |
||||
|
label="Message" |
||||
|
disabled={!watchExternalNotificationPluginEnabled} |
||||
|
{...register('extNotificationPluginAlertMessage')} |
||||
|
/> |
||||
|
<Input |
||||
|
type="number" |
||||
|
label="Output" |
||||
|
disabled={!watchExternalNotificationPluginEnabled} |
||||
|
{...register('extNotificationPluginOutput', { |
||||
|
valueAsNumber: true, |
||||
|
})} |
||||
|
/> |
||||
|
<Input |
||||
|
type="number" |
||||
|
label="Output MS" |
||||
|
disabled={!watchExternalNotificationPluginEnabled} |
||||
|
{...register('extNotificationPluginOutputMs', { |
||||
|
valueAsNumber: true, |
||||
|
})} |
||||
|
/> |
||||
|
</form> |
||||
|
</div> |
||||
|
</Card> |
||||
|
</div> |
||||
|
</PrimaryTemplate> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,118 @@ |
|||||
|
import type React from 'react'; |
||||
|
|
||||
|
import { useForm, useWatch } from 'react-hook-form'; |
||||
|
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 { Card } from '@components/generic/Card'; |
||||
|
import { Checkbox } from '@components/generic/form/Checkbox'; |
||||
|
import { Input } from '@components/generic/form/Input'; |
||||
|
import { IconButton } from '@components/generic/IconButton'; |
||||
|
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate'; |
||||
|
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated'; |
||||
|
|
||||
|
export interface SerialProps { |
||||
|
navOpen?: boolean; |
||||
|
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>; |
||||
|
} |
||||
|
|
||||
|
export const Serial = ({ navOpen, setNavOpen }: SerialProps): JSX.Element => { |
||||
|
const preferences = useAppSelector((state) => state.meshtastic.preferences); |
||||
|
|
||||
|
const { register, handleSubmit, formState, reset, control } = |
||||
|
useForm<RadioConfig_UserPreferences>({ |
||||
|
defaultValues: { |
||||
|
serialpluginEnabled: preferences.serialpluginEnabled, |
||||
|
serialpluginEcho: preferences.serialpluginEcho, |
||||
|
serialpluginMode: preferences.serialpluginMode, |
||||
|
serialpluginRxd: preferences.serialpluginRxd, |
||||
|
serialpluginTimeout: preferences.serialpluginTimeout, |
||||
|
serialpluginTxd: preferences.serialpluginTxd, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const onSubmit = handleSubmit((data) => { |
||||
|
void connection.setPreferences(data); |
||||
|
}); |
||||
|
|
||||
|
const watchSerialPluginEnabled = useWatch({ |
||||
|
control, |
||||
|
name: 'serialpluginEnabled', |
||||
|
defaultValue: false, |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<PrimaryTemplate |
||||
|
title="Serial" |
||||
|
tagline="Plugin" |
||||
|
leftButton={ |
||||
|
<IconButton |
||||
|
icon={<FiMenu className="w-5 h-5" />} |
||||
|
onClick={(): void => { |
||||
|
setNavOpen && setNavOpen(!navOpen); |
||||
|
}} |
||||
|
/> |
||||
|
} |
||||
|
footer={ |
||||
|
<FormFooter |
||||
|
dirty={formState.isDirty} |
||||
|
saveAction={onSubmit} |
||||
|
clearAction={reset} |
||||
|
/> |
||||
|
} |
||||
|
> |
||||
|
<div className="w-full space-y-4"> |
||||
|
<Card> |
||||
|
<div className="w-full max-w-3xl p-10 md:max-w-xl"> |
||||
|
<form onSubmit={onSubmit}> |
||||
|
<Checkbox |
||||
|
label="Plugin Enabled" |
||||
|
{...register('serialpluginEnabled')} |
||||
|
/> |
||||
|
<Checkbox |
||||
|
label="Echo" |
||||
|
disabled={!watchSerialPluginEnabled} |
||||
|
{...register('serialpluginEcho')} |
||||
|
/> |
||||
|
|
||||
|
<Input |
||||
|
type="number" |
||||
|
label="RX" |
||||
|
disabled={!watchSerialPluginEnabled} |
||||
|
{...register('serialpluginRxd', { |
||||
|
valueAsNumber: true, |
||||
|
})} |
||||
|
/> |
||||
|
<Input |
||||
|
type="number" |
||||
|
label="TX" |
||||
|
disabled={!watchSerialPluginEnabled} |
||||
|
{...register('serialpluginTxd', { |
||||
|
valueAsNumber: true, |
||||
|
})} |
||||
|
/> |
||||
|
<Input |
||||
|
type="number" |
||||
|
label="Mode" |
||||
|
disabled={!watchSerialPluginEnabled} |
||||
|
{...register('serialpluginMode', { |
||||
|
valueAsNumber: true, |
||||
|
})} |
||||
|
/> |
||||
|
<Input |
||||
|
type="number" |
||||
|
label="Timeout" |
||||
|
disabled={!watchSerialPluginEnabled} |
||||
|
{...register('serialpluginTimeout', { |
||||
|
valueAsNumber: true, |
||||
|
})} |
||||
|
/> |
||||
|
</form> |
||||
|
</div> |
||||
|
</Card> |
||||
|
</div> |
||||
|
</PrimaryTemplate> |
||||
|
); |
||||
|
}; |
||||
Loading…
Reference in new issue