Move plugins to settings page

This commit is contained in:
Sacha Weatherstone
2022-01-18 00:39:55 +11:00
parent b929ca0136
commit 161d432330
32 changed files with 834 additions and 1016 deletions
@@ -0,0 +1,76 @@
import type React from 'react';
import { FiCode, FiSliders } from 'react-icons/fi';
import { TabButton } from '@app/components/TabButton';
import type { Plugin } from '@app/pages/settings/Plugins';
import { Sidebar } from '@components/generic/Sidebar';
import { Tab } from '@headlessui/react';
import { ExternalNotificationsDebugPanel } from './panels/ExternalNotifications/DebugPanel';
import { ExternalNotificationsSettingsPlanel } from './panels/ExternalNotifications/SettingsPlanel';
import { RangeTestDebugPanel } from './panels/RangeTest/DebugPanel';
import { RangeTestSettingsPanel } from './panels/RangeTest/SettingsPanel';
import { SerialDebugPanel } from './panels/Serial/DebugPanel';
import { SerialSettingsPanel } from './panels/Serial/SettingsPanel';
import { StoreForwardDebugPanel } from './panels/StoreForward/DebugPanel';
import { StoreForwardSettingsPanel } from './panels/StoreForward/SettingsPanel';
export interface PluginsSidebarProps {
plugin?: Plugin;
closeSidebar: () => void;
}
export const PluginsSidebar = ({
plugin,
closeSidebar,
}: PluginsSidebarProps): JSX.Element => {
return (
<Sidebar
title={plugin ?? 'Please select plugin'}
tagline={plugin ? 'settings' : '...'}
closeSidebar={closeSidebar}
>
{plugin && (
<Tab.Group>
<div className="shadow-md">
<Tab.List className="flex justify-between border-b border-gray-300 dark:border-gray-600">
<TabButton>
<FiSliders />
</TabButton>
<TabButton>
<FiCode />
</TabButton>
</Tab.List>
</div>
<Tab.Panels className="flex flex-grow overflow-y-auto bg-gray-100 dark:bg-primaryDark">
{plugin === 'Range Test' && (
<>
<RangeTestSettingsPanel />
<RangeTestDebugPanel />
</>
)}
{plugin === 'External Notifications' && (
<>
<ExternalNotificationsSettingsPlanel />
<ExternalNotificationsDebugPanel />
</>
)}
{plugin === 'Serial' && (
<>
<SerialSettingsPanel />
<SerialDebugPanel />
</>
)}
{plugin === 'Store & Forward' && (
<>
<StoreForwardSettingsPanel />
<StoreForwardDebugPanel />
</>
)}
</Tab.Panels>
</Tab.Group>
)}
</Sidebar>
);
};
@@ -0,0 +1,32 @@
import type React from 'react';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { CopyButton } from '@components/menu/buttons/CopyButton';
import { Tab } from '@headlessui/react';
export const ExternalNotificationsDebugPanel = (): JSX.Element => {
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const debugData = {
extNotificationPluginActive: preferences.extNotificationPluginActive,
extNotificationPluginAlertBell: preferences.extNotificationPluginAlertBell,
extNotificationPluginAlertMessage:
preferences.extNotificationPluginAlertMessage,
extNotificationPluginEnabled: preferences.extNotificationPluginEnabled,
extNotificationPluginOutput: preferences.extNotificationPluginOutput,
extNotificationPluginOutputMs: preferences.extNotificationPluginOutputMs,
};
return (
<Tab.Panel className="relative">
<div className="fixed right-0 m-2">
<CopyButton data={JSON.stringify(debugData)} />
</div>
<JSONPretty className="max-w-sm" data={debugData} />
</Tab.Panel>
);
};
@@ -0,0 +1,97 @@
import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiSave } from 'react-icons/fi';
import { Form } from '@app/components/generic/form/Form';
import { connection } from '@app/core/connection';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { Tab } from '@headlessui/react';
import { Checkbox, IconButton, Input } from '@meshtastic/components';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
const [loading, setLoading] = React.useState(false);
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: preferences,
});
React.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 (
<Tab.Panel className="flex flex-col w-full">
<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="p-2 ml-auto">
<IconButton
disabled={!formState.isDirty}
onClick={async (): Promise<void> => {
await onSubmit();
}}
icon={<FiSave />}
/>
</div>
</div>
</Tab.Panel>
);
};
@@ -0,0 +1,28 @@
import type React from 'react';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { CopyButton } from '@components/menu/buttons/CopyButton';
import { Tab } from '@headlessui/react';
export const RangeTestDebugPanel = (): JSX.Element => {
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const debugData = {
rangeTestPluginEnabled: preferences.rangeTestPluginEnabled,
rangeTestPluginSave: preferences.rangeTestPluginSave,
rangeTestPluginSender: preferences.rangeTestPluginSender,
};
return (
<Tab.Panel className="relative">
<div className="fixed right-0 m-2">
<CopyButton data={JSON.stringify(debugData)} />
</div>
<JSONPretty className="max-w-sm" data={debugData} />
</Tab.Panel>
);
};
@@ -0,0 +1,79 @@
import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiSave } from 'react-icons/fi';
import { Form } from '@app/components/generic/form/Form';
import { connection } from '@app/core/connection';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { Tab } from '@headlessui/react';
import { Checkbox, IconButton, Input } from '@meshtastic/components';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export const RangeTestSettingsPanel = (): JSX.Element => {
const [loading, setLoading] = React.useState(false);
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: preferences,
});
React.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: 'rangeTestPluginEnabled',
defaultValue: false,
});
return (
<Tab.Panel className="flex flex-col w-full">
<Form loading={loading}>
<Checkbox
label="Range Test Plugin Enabled?"
{...register('rangeTestPluginEnabled')}
/>
<Checkbox
label="Range Test Plugin Save?"
disabled={!pluginEnabled}
{...register('rangeTestPluginSave')}
/>
<Input
type="number"
label="Message Interval"
disabled={!pluginEnabled}
suffix="Seconds"
{...register('rangeTestPluginSender', {
valueAsNumber: true,
})}
/>
</Form>
<div className="flex w-full bg-white dark:bg-secondaryDark">
<div className="p-2 ml-auto">
<IconButton
disabled={!formState.isDirty}
onClick={async (): Promise<void> => {
await onSubmit();
}}
icon={<FiSave />}
/>
</div>
</div>
</Tab.Panel>
);
};
@@ -0,0 +1,31 @@
import type React from 'react';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { CopyButton } from '@components/menu/buttons/CopyButton';
import { Tab } from '@headlessui/react';
export const SerialDebugPanel = (): JSX.Element => {
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const debugData = {
serialpluginEnabled: preferences.serialpluginEnabled,
serialpluginEcho: preferences.serialpluginEcho,
serialpluginMode: preferences.serialpluginMode,
serialpluginRxd: preferences.serialpluginRxd,
serialpluginTxd: preferences.serialpluginTxd,
serialpluginTimeout: preferences.serialpluginTimeout,
};
return (
<Tab.Panel className="relative">
<div className="fixed right-0 m-2">
<CopyButton data={JSON.stringify(debugData)} />
</div>
<JSONPretty className="max-w-sm" data={debugData} />
</Tab.Panel>
);
};
@@ -0,0 +1,100 @@
import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiSave } from 'react-icons/fi';
import { Form } from '@app/components/generic/form/Form';
import { connection } from '@app/core/connection';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { Tab } from '@headlessui/react';
import { Checkbox, IconButton, Input } from '@meshtastic/components';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export const SerialSettingsPanel = (): JSX.Element => {
const [loading, setLoading] = React.useState(false);
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: preferences,
});
React.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 (
<Tab.Panel className="flex flex-col w-full">
<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="p-2 ml-auto">
<IconButton
disabled={!formState.isDirty}
onClick={async (): Promise<void> => {
await onSubmit();
}}
icon={<FiSave />}
/>
</div>
</div>
</Tab.Panel>
);
};
@@ -0,0 +1,32 @@
import type React from 'react';
import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { CopyButton } from '@components/menu/buttons/CopyButton';
import { Tab } from '@headlessui/react';
export const StoreForwardDebugPanel = (): JSX.Element => {
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const debugData = {
storeForwardPluginEnabled: preferences.storeForwardPluginEnabled,
storeForwardPluginHeartbeat: preferences.storeForwardPluginHeartbeat,
storeForwardPluginRecords: preferences.storeForwardPluginRecords,
storeForwardPluginHistoryReturnMax:
preferences.storeForwardPluginHistoryReturnMax,
storeForwardPluginHistoryReturnWindow:
preferences.storeForwardPluginHistoryReturnWindow,
};
return (
<Tab.Panel className="relative">
<div className="fixed right-0 m-2">
<CopyButton data={JSON.stringify(debugData)} />
</div>
<JSONPretty className="max-w-sm" data={debugData} />
</Tab.Panel>
);
};
@@ -0,0 +1,95 @@
import React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { FiSave } from 'react-icons/fi';
import { Form } from '@app/components/generic/form/Form';
import { connection } from '@app/core/connection';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { Tab } from '@headlessui/react';
import { Checkbox, IconButton, Input } from '@meshtastic/components';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export const StoreForwardSettingsPanel = (): JSX.Element => {
const [loading, setLoading] = React.useState(false);
const preferences = useAppSelector(
(state) => state.meshtastic.radio.preferences,
);
const { register, handleSubmit, formState, reset, control } =
useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: preferences,
});
React.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 (
<Tab.Panel className="flex flex-col w-full">
<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="p-2 ml-auto">
<IconButton
disabled={!formState.isDirty}
onClick={async (): Promise<void> => {
await onSubmit();
}}
icon={<FiSave />}
/>
</div>
</div>
</Tab.Panel>
);
};
@@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form';
import { FiSave } from 'react-icons/fi';
import { MdRefresh, MdVisibility, MdVisibilityOff } from 'react-icons/md';
import { Loading } from '@app/components/generic/Loading';
import { Form } from '@app/components/generic/form/Form';
import { connection } from '@app/core/connection';
import { Tab } from '@headlessui/react';
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
@@ -73,8 +73,7 @@ export const SettingsPanel = ({ channel }: SettingsPanelProps): JSX.Element => {
return (
<Tab.Panel className="flex flex-col w-full">
{loading && <Loading />}
<form className="flex-grow gap-3 p-2">
<Form loading={loading}>
{channel?.index !== 0 && (
<>
<Checkbox
@@ -122,7 +121,7 @@ export const SettingsPanel = ({ channel }: SettingsPanelProps): JSX.Element => {
/>
<Checkbox label="Uplink Enabled" {...register('uplinkEnabled')} />
<Checkbox label="Downlink Enabled" {...register('downlinkEnabled')} />
</form>
</Form>
<div className="flex w-full bg-white dark:bg-secondaryDark">
<div className="p-2 ml-auto">
<IconButton