Move plugins to settings page
This commit is contained in:
@@ -1,135 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
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.radio.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,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
extNotificationPluginActive: preferences.extNotificationPluginActive,
|
||||
extNotificationPluginAlertBell:
|
||||
preferences.extNotificationPluginAlertBell,
|
||||
extNotificationPluginAlertMessage:
|
||||
preferences.extNotificationPluginAlertMessage,
|
||||
extNotificationPluginEnabled: preferences.extNotificationPluginEnabled,
|
||||
extNotificationPluginOutput: preferences.extNotificationPluginOutput,
|
||||
extNotificationPluginOutputMs: preferences.extNotificationPluginOutputMs,
|
||||
});
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setPreferences(data, async (): Promise<void> => {
|
||||
//add loading indicator
|
||||
reset({ ...data });
|
||||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
//todo, add loading indicator
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,128 +0,0 @@
|
||||
import type React from 'react';
|
||||
|
||||
// import { DefaultExtensionType, defaultStyles, FileIcon } from 'react-file-icon';
|
||||
import { FiMenu, FiTrash, FiUploadCloud } from 'react-icons/fi';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connectionUrl } from '@core/connection';
|
||||
import fetcher from '@core/utils/fetcher';
|
||||
import { Card, IconButton } from '@meshtastic/components';
|
||||
|
||||
export interface RangeTestProps {
|
||||
navOpen?: boolean;
|
||||
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
interface IFile {
|
||||
name: string;
|
||||
nameModified: string;
|
||||
size: number;
|
||||
}
|
||||
interface IFiles {
|
||||
data: {
|
||||
files: IFile[];
|
||||
filesystem: {
|
||||
free: number;
|
||||
total: number;
|
||||
used: number;
|
||||
};
|
||||
};
|
||||
|
||||
status: boolean;
|
||||
}
|
||||
|
||||
export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => {
|
||||
const { data } = useSWR<IFiles>(
|
||||
`http://${connectionUrl}/json/spiffs/browse/static`,
|
||||
fetcher,
|
||||
);
|
||||
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title="File Browser"
|
||||
tagline="Plugin"
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
setNavOpen && setNavOpen(!navOpen);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col justify-between w-full gap-4 md:flex-row-reverse">
|
||||
<Card title="SPIFFS" description="Statistics">
|
||||
{data ? (
|
||||
<div className="flex">
|
||||
<div className="mx-auto my-4 bg-gray-500 rounded-3xl">
|
||||
<div></div>
|
||||
{JSON.stringify(data.data.filesystem.used)} bytes total
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>Loading...</div>
|
||||
)}
|
||||
</Card>
|
||||
<Card
|
||||
title="Files"
|
||||
description="SPIFFS Contents"
|
||||
buttons={<IconButton icon={<FiUploadCloud className="w-8 h-8" />} />}
|
||||
className="md:w-1/3"
|
||||
>
|
||||
{data ? (
|
||||
<div className="flex flex-col my-4 space-y-2">
|
||||
{data.data.files.map((file: IFile) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className="flex justify-between mx-4 bg-gray-300 rounded-md dark:bg-secondaryDark "
|
||||
>
|
||||
<div className="flex p-2 max-h-12">
|
||||
<div className="flex w-12">
|
||||
{/* <FileIcon
|
||||
extension={
|
||||
(file.nameModified ?? file.name).split('.')[
|
||||
(file.nameModified ?? file.name).split('.').length -
|
||||
1
|
||||
]
|
||||
}
|
||||
{...defaultStyles[
|
||||
(file.nameModified ?? file.name).split('.')[
|
||||
(file.nameModified ?? file.name).split('.').length -
|
||||
1
|
||||
] as DefaultExtensionType
|
||||
]}
|
||||
/> */}
|
||||
</div>
|
||||
<a
|
||||
href={`http://${connectionUrl}/${file.name.replace(
|
||||
'static/',
|
||||
'',
|
||||
)}`}
|
||||
className="my-auto font-semibold"
|
||||
>
|
||||
{file.nameModified ?? file.name}
|
||||
</a>
|
||||
</div>
|
||||
<IconButton
|
||||
className="mx-2 my-auto"
|
||||
// confirmAction={async (): Promise<void> => {
|
||||
// await fetch(
|
||||
// `http://${connectionUrl}/json/spiffs/delete/static?remove=${file.name}`,
|
||||
// {
|
||||
// method: 'DELETE',
|
||||
// },
|
||||
// );
|
||||
// }}
|
||||
icon={<FiTrash className="w-5 h-5" />}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div>Loading...</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</PrimaryTemplate>
|
||||
);
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
import type React from 'react';
|
||||
|
||||
import {
|
||||
FiAlignLeft,
|
||||
FiBell,
|
||||
FiFastForward,
|
||||
FiFileText,
|
||||
FiRss,
|
||||
} from 'react-icons/fi';
|
||||
|
||||
import { PageLayout } from '@components/templates/PageLayout';
|
||||
|
||||
import { ExternalNotification } from './ExternalNotification';
|
||||
import { Files } from './Files';
|
||||
import { RangeTest } from './RangeTest';
|
||||
import { Serial } from './Serial';
|
||||
import { StoreAndForward } from './StoreAndForward';
|
||||
|
||||
export const Plugins = (): JSX.Element => {
|
||||
return (
|
||||
<PageLayout
|
||||
title="Plugins"
|
||||
sidebarItems={[
|
||||
{
|
||||
title: 'Range Test',
|
||||
description: 'Test the range of your Meshtastic node',
|
||||
icon: <FiRss className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'File Browser',
|
||||
description: 'HTTP only file browser',
|
||||
icon: <FiFileText className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'External Notification',
|
||||
description: 'External hardware alerts',
|
||||
icon: <FiBell className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Serial',
|
||||
description: 'Send serial data over the mesh',
|
||||
icon: <FiAlignLeft className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Store & Forward',
|
||||
description: 'Retrive message history',
|
||||
icon: <FiFastForward className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
]}
|
||||
panels={[
|
||||
<RangeTest key={1} />,
|
||||
<Files key={2} />,
|
||||
<ExternalNotification key={3} />,
|
||||
<Serial key={4} />,
|
||||
<StoreAndForward key={5} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,106 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
|
||||
|
||||
export interface RangeTestProps {
|
||||
navOpen?: boolean;
|
||||
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const RangeTest = ({
|
||||
navOpen,
|
||||
setNavOpen,
|
||||
}: RangeTestProps): JSX.Element => {
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<RadioConfig_UserPreferences>({
|
||||
defaultValues: {
|
||||
rangeTestPluginEnabled: preferences.rangeTestPluginEnabled,
|
||||
rangeTestPluginSave: preferences.rangeTestPluginSave,
|
||||
rangeTestPluginSender: preferences.rangeTestPluginSender,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
rangeTestPluginEnabled: preferences.rangeTestPluginEnabled,
|
||||
rangeTestPluginSave: preferences.rangeTestPluginSave,
|
||||
rangeTestPluginSender: preferences.rangeTestPluginSender,
|
||||
});
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setPreferences(data, async (): Promise<void> => {
|
||||
//add loading indicator
|
||||
reset({ ...data });
|
||||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
//todo, add loading indicator
|
||||
|
||||
const watchRangeTestPluginEnabled = useWatch({
|
||||
control,
|
||||
name: 'rangeTestPluginEnabled',
|
||||
defaultValue: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title="Range Test"
|
||||
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="Range Test Plugin Enabled?"
|
||||
{...register('rangeTestPluginEnabled')}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Range Test Plugin Save?"
|
||||
disabled={!watchRangeTestPluginEnabled}
|
||||
{...register('rangeTestPluginSave')}
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
label="Message Interval"
|
||||
disabled={!watchRangeTestPluginEnabled}
|
||||
{...register('rangeTestPluginSender', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</PrimaryTemplate>
|
||||
);
|
||||
};
|
||||
@@ -1,133 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
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.radio.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,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
serialpluginEnabled: preferences.serialpluginEnabled,
|
||||
serialpluginEcho: preferences.serialpluginEcho,
|
||||
serialpluginMode: preferences.serialpluginMode,
|
||||
serialpluginRxd: preferences.serialpluginRxd,
|
||||
serialpluginTimeout: preferences.serialpluginTimeout,
|
||||
serialpluginTxd: preferences.serialpluginTxd,
|
||||
});
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setPreferences(data, async (): Promise<void> => {
|
||||
//add loading indicator
|
||||
reset({ ...data });
|
||||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
//todo, add loading indicator
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Card, Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
|
||||
|
||||
export interface StoreAndForwardProps {
|
||||
navOpen?: boolean;
|
||||
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const StoreAndForward = ({
|
||||
navOpen,
|
||||
setNavOpen,
|
||||
}: StoreAndForwardProps): JSX.Element => {
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<RadioConfig_UserPreferences>({
|
||||
defaultValues: {
|
||||
storeForwardPluginEnabled: preferences.storeForwardPluginEnabled,
|
||||
storeForwardPluginRecords: preferences.storeForwardPluginRecords,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
storeForwardPluginEnabled: preferences.storeForwardPluginEnabled,
|
||||
storeForwardPluginRecords: preferences.storeForwardPluginRecords,
|
||||
});
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setPreferences(data, async (): Promise<void> => {
|
||||
//add loading indicator
|
||||
reset({ ...data });
|
||||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
//todo, add loading indicator
|
||||
|
||||
const watchStoreForwardPluginEnabled = useWatch({
|
||||
control,
|
||||
name: 'storeForwardPluginEnabled',
|
||||
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('storeForwardPluginEnabled')}
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
label="Number of records"
|
||||
disabled={!watchStoreForwardPluginEnabled}
|
||||
{...register('storeForwardPluginRecords', {
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</PrimaryTemplate>
|
||||
);
|
||||
};
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { Tooltip } from '@app/components/generic/Tooltip';
|
||||
import type { ChannelData } from '@app/core/slices/meshtasticSlice';
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { Loading } from '@components/generic/Loading';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
@@ -21,11 +20,12 @@ import {
|
||||
Checkbox,
|
||||
IconButton,
|
||||
Input,
|
||||
Loading,
|
||||
Select,
|
||||
} from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import { ChannelsSidebar } from '../../components/pages/settings/channels/ChannelsSidebar';
|
||||
import { ChannelsSidebar } from '../../components/pages/settings/radio/channels/ChannelsSidebar';
|
||||
|
||||
export interface ChannelsProps {
|
||||
navOpen?: boolean;
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
FiLayers,
|
||||
FiLayout,
|
||||
FiMapPin,
|
||||
FiPackage,
|
||||
FiRadio,
|
||||
FiUser,
|
||||
FiWifi,
|
||||
@@ -15,6 +16,7 @@ import { PageLayout } from '@components/templates/PageLayout';
|
||||
|
||||
import { Channels } from './Channels';
|
||||
import { Interface } from './Interface';
|
||||
import { Plugins } from './Plugins';
|
||||
import { Position } from './Position';
|
||||
import { Power } from './Power';
|
||||
import { Radio } from './Radio';
|
||||
@@ -31,8 +33,9 @@ export const Settings = (): JSX.Element => {
|
||||
<User key={4} />,
|
||||
<Power key={5} />,
|
||||
<Radio key={6} />,
|
||||
<Channels key={8} />,
|
||||
<Interface key={7} />,
|
||||
<Channels key={7} />,
|
||||
<Plugins key={8} />,
|
||||
<Interface key={9} />,
|
||||
];
|
||||
|
||||
const sidebarItems: SidebarItemProps[] = [
|
||||
@@ -56,6 +59,11 @@ export const Settings = (): JSX.Element => {
|
||||
description: 'Manage channels',
|
||||
icon: <FiLayers className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Plugins',
|
||||
description: 'Plugins',
|
||||
icon: <FiPackage className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'Interface',
|
||||
description: 'Language and UI settings',
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
FiAlignLeft,
|
||||
FiBell,
|
||||
FiExternalLink,
|
||||
FiFastForward,
|
||||
FiMenu,
|
||||
FiRss,
|
||||
} from 'react-icons/fi';
|
||||
|
||||
import { PluginsSidebar } from '@app/components/pages/settings/plugins/PluginsSidebar';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { Button, Card, IconButton } from '@meshtastic/components';
|
||||
|
||||
export type Plugin =
|
||||
| 'Range Test'
|
||||
| 'External Notifications'
|
||||
| 'Serial'
|
||||
| 'Store & Forward';
|
||||
|
||||
export interface PluginsProps {
|
||||
navOpen?: boolean;
|
||||
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const Plugins = ({ navOpen, setNavOpen }: PluginsProps): JSX.Element => {
|
||||
const [sidebarOpen, setSidebarOpen] = React.useState(false);
|
||||
const [selectedPlugin, setSelectedPlugin] = React.useState<
|
||||
Plugin | undefined
|
||||
>();
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
|
||||
const plugins: {
|
||||
name: Plugin;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
icon: JSX.Element;
|
||||
}[] = [
|
||||
{
|
||||
name: 'Range Test',
|
||||
description: 'Test the range of your Meshtastic node',
|
||||
enabled: preferences.rangeTestPluginEnabled,
|
||||
icon: <FiRss />,
|
||||
},
|
||||
{
|
||||
name: 'External Notifications',
|
||||
description: 'External hardware alerts',
|
||||
enabled: preferences.extNotificationPluginEnabled,
|
||||
icon: <FiBell />,
|
||||
},
|
||||
{
|
||||
name: 'Serial',
|
||||
description: 'Send serial data over the mesh',
|
||||
enabled: preferences.serialpluginEnabled,
|
||||
icon: <FiAlignLeft />,
|
||||
},
|
||||
{
|
||||
name: 'Store & Forward',
|
||||
description: 'Retrive message history',
|
||||
enabled: preferences.storeForwardPluginEnabled,
|
||||
icon: <FiFastForward />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<PrimaryTemplate
|
||||
title="Plugins"
|
||||
tagline="Settings"
|
||||
leftButton={
|
||||
<Button
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
setNavOpen && setNavOpen(!navOpen);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Card
|
||||
title="Basic settings"
|
||||
description="Device name and user parameters"
|
||||
>
|
||||
<div className="w-full max-w-3xl p-10 space-y-2 md:max-w-xl">
|
||||
{plugins.map((plugin, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={(): void => {
|
||||
setSelectedPlugin(plugin.name);
|
||||
setSidebarOpen(true);
|
||||
}}
|
||||
className={`flex justify-between p-2 border border-gray-300 dark:border-gray-600 bg-gray-100 rounded-md dark:bg-secondaryDark shadow-md ${
|
||||
selectedPlugin === plugin.name
|
||||
? 'border-primary dark:border-primary'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex my-auto space-x-2">
|
||||
<div
|
||||
className={`h-3 my-auto w-3 rounded-full ${
|
||||
plugin.enabled ? 'bg-green-500' : 'bg-gray-400'
|
||||
}`}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<div className="my-auto">{plugin.icon}</div>
|
||||
{plugin.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<IconButton active icon={<FiExternalLink />} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</PrimaryTemplate>
|
||||
{sidebarOpen && (
|
||||
<PluginsSidebar
|
||||
closeSidebar={(): void => {
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
plugin={selectedPlugin}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user