Updated channel editor + fixes

This commit is contained in:
Sacha Weatherstone
2021-11-16 23:15:41 +11:00
parent 053c015198
commit 1f8170130d
21 changed files with 802 additions and 397 deletions
+7 -7
View File
@@ -13,10 +13,10 @@
},
"dependencies": {
"@headlessui/react": "^1.4.2",
"@meshtastic/meshtasticjs": "^0.6.27",
"@meshtastic/meshtasticjs": "^0.6.28",
"@reduxjs/toolkit": "^1.6.2",
"boring-avatars": "^1.5.8",
"i18next": "^21.4.2",
"i18next": "^21.5.1",
"i18next-browser-languagedetector": "^6.1.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -33,13 +33,13 @@
"use-breakpoint": "^2.0.2"
},
"devDependencies": {
"@types/react": "^17.0.34",
"@types/react": "^17.0.35",
"@types/react-dom": "^17.0.11",
"@types/react-file-icon": "^1.0.1",
"@types/w3c-web-serial": "^1.0.2",
"@types/web-bluetooth": "^0.0.11",
"@typescript-eslint/eslint-plugin": "^5.3.1",
"@typescript-eslint/parser": "^5.3.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@verypossible/eslint-config": "^1.6.1",
"@vitejs/plugin-react": "^1.0.9",
"autoprefixer": "^10.4.0",
@@ -58,7 +58,7 @@
"tar": "^6.1.11",
"typescript": "^4.4.4",
"vite": "^2.6.14",
"vite-plugin-pwa": "^0.11.3",
"workbox-window": "^6.3.0"
"vite-plugin-pwa": "^0.11.5",
"workbox-window": "^6.4.1"
}
}
+285 -252
View File
File diff suppressed because it is too large Load Diff
+23 -10
View File
@@ -22,12 +22,13 @@ import {
setReady,
} from '@core/slices/meshtasticSlice';
import {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
Protobuf,
SettingsManager,
Types,
} from '@meshtastic/meshtasticjs';
import { About } from '@pages/About';
import { Messages } from '@pages/Messages';
import { Nodes } from '@pages/Nodes/Index';
import { Settings } from '@pages/settings/Index';
@@ -55,15 +56,28 @@ const App = (): JSX.Element => {
'http://meshtastic.local';
React.useEffect(() => {
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
const connectionMethod = localStorage.getItem('connectionMethod');
setConnection(new IHTTPConnection());
void connection.connect({
address: connectionURL,
tls: false,
receiveBatchRequests: false,
fetchInterval: 2000,
});
switch (connectionMethod) {
case 'serial':
setConnection(new ISerialConnection());
//show connection dialogue
break;
case 'bluetooth':
setConnection(new IBLEConnection());
//show connection dialogue
break;
default:
setConnection(new IHTTPConnection());
void connection.connect({
address: connectionURL,
tls: false,
receiveBatchRequests: false,
fetchInterval: 2000,
});
break;
}
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
}, [hostOverrideEnabled, hostOverride, connectionURL]);
React.useEffect(() => {
@@ -168,7 +182,6 @@ const App = (): JSX.Element => {
{route.name === 'nodes' && <Nodes />}
{route.name === 'plugins' && <Plugins />}
{route.name === 'settings' && <Settings />}
{route.name === 'about' && <About />}
{route.name === false && <NotFound />}
</div>
</div>
+32 -38
View File
@@ -13,13 +13,17 @@ import { IconButton } from './generic/IconButton';
export interface ChannelProps {
channel: Protobuf.Channel;
hideEnabled?: boolean;
}
export const Channel = ({ channel }: ChannelProps): JSX.Element => {
export const Channel = ({
channel,
hideEnabled,
}: ChannelProps): JSX.Element => {
const [edit, setEdit] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const { register, handleSubmit, formState } = useForm<{
const { register, handleSubmit } = useForm<{
enabled: boolean;
settings: {
name: string;
@@ -33,11 +37,12 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
};
}>({
defaultValues: {
enabled:
channel.role ===
(Protobuf.Channel_Role.PRIMARY || Protobuf.Channel_Role.SECONDARY)
? true
: false,
enabled: [
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY,
].find((role) => role === channel.role)
? true
: false,
settings: {
name: channel.settings?.name,
bandwidth: channel.settings?.bandwidth,
@@ -54,9 +59,12 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
const onSubmit = handleSubmit(async (data) => {
setLoading(true);
const adminChannel = Protobuf.Channel.create({
role: data.enabled
? Protobuf.Channel_Role.SECONDARY
: Protobuf.Channel_Role.DISABLED,
role:
channel.role === Protobuf.Channel_Role.PRIMARY
? Protobuf.Channel_Role.PRIMARY
: data.enabled
? Protobuf.Channel_Role.SECONDARY
: Protobuf.Channel_Role.DISABLED,
index: channel.index,
settings: {
...data.settings,
@@ -75,41 +83,24 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
{edit ? (
<>
{loading && <Loading />}
<div className="my-auto space-x-2">
<form>
<div className="flex space-x-2">
{/* @todo: change to disable & make primary buttons */}
<div className="flex my-auto">
{/* TODO: get gap working */}
<form className="gap-3">
{/* @todo: change to disable & make primary buttons */}
{!hideEnabled && (
<Checkbox
label="Enabled"
{...register('enabled', { valueAsNumber: true })}
/>
</div>
)}
<Input label="Name" {...register('settings.name')} />
<Input label="Pre-Shared Key" {...register('settings.psk')} />
<Input
label="Bandwidth"
type="number"
{...register('settings.bandwidth', { valueAsNumber: true })}
/>
<Input
label="Spread Factor"
type="number"
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"
{...register('settings.txPower', { valueAsNumber: true })}
label="Pre-Shared Key"
type="password"
{...register('settings.psk')}
/>
<Checkbox
label="Upling Enabled"
label="Uplink Enabled"
{...register('settings.uplinkEnabled')}
/>
<Checkbox
@@ -132,7 +123,10 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
<div className="flex my-auto space-x-2">
<div
className={`h-3 my-auto w-3 rounded-full ${
channel.role === Protobuf.Channel_Role.SECONDARY
[
Protobuf.Channel_Role.SECONDARY,
Protobuf.Channel_Role.PRIMARY,
].find((role) => role === channel.role)
? 'bg-green-500'
: 'bg-gray-400'
}`}
+116
View File
@@ -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>
);
};
+8 -1
View File
@@ -9,10 +9,12 @@ interface InputProps extends DefaultInputProps {
label?: string;
error?: string;
action?: JSX.Element;
prefix?: string;
suffix?: string;
}
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
function Input({ label, error, action, ...props }: InputProps, ref) {
function Input({ label, error, action, suffix, ...props }: InputProps, ref) {
return (
<div className="w-full">
{label && <Label label={label} error={error} />}
@@ -22,6 +24,11 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
className="w-full h-10 px-3 py-2 bg-transparent focus:outline-none"
{...props}
/>
{suffix && (
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400">
{suffix}
</span>
)}
{action && <div className="flex mr-1">{action}</div>}
</InputWrapper>
</div>
+1 -1
View File
@@ -30,7 +30,7 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
<select
ref={ref}
className={`w-full rounded-md bg-transparent focus:outline-none focus:border-primary ${
small ? 'p-1' : 'h-10 px-2'
small ? 'm-1' : 'h-10 mx-2'
}`}
disabled={
props.disabled
+1 -17
View File
@@ -1,12 +1,6 @@
import type React from 'react';
import {
FiGrid,
FiInfo,
FiMessageSquare,
FiPackage,
FiSettings,
} from 'react-icons/fi';
import { FiGrid, FiMessageSquare, FiPackage, FiSettings } from 'react-icons/fi';
import { Button } from '@components/generic/Button';
import { routes, useRoute } from '@core/router';
@@ -66,16 +60,6 @@ export const Navigation = ({
Settings
</Button>
</div>
<div onClick={onClick}>
<Button
icon={<FiInfo className="w-6 h-6" />}
className="w-full md:w-auto"
active={route.name === 'about'}
{...routes.about().link}
>
About
</Button>
</div>
</div>
);
};
+3
View File
@@ -8,6 +8,9 @@ type connectionType = IBLEConnection | IHTTPConnection | ISerialConnection;
export let connection: connectionType = new IHTTPConnection();
export const ble = new IBLEConnection();
export const serial = new ISerialConnection();
export const setConnection = (conn: connectionType): void => {
connection = conn;
};
-1
View File
@@ -5,5 +5,4 @@ export const { RouteProvider, useRoute, routes } = createRouter({
nodes: defineRoute('/nodes'),
plugins: defineRoute('/plugins'),
settings: defineRoute('/settings'),
about: defineRoute('/about'),
});
+15 -6
View File
@@ -21,7 +21,6 @@ interface MeshtasticState {
lastMeshInterraction: number;
ready: boolean;
myNodeInfo: Protobuf.MyNodeInfo;
myNode: Protobuf.NodeInfo;
users: Types.UserPacket[];
positionPackets: Types.PositionPacket[];
nodes: Protobuf.NodeInfo[];
@@ -38,13 +37,14 @@ const initialState: MeshtasticState = {
lastMeshInterraction: 0,
ready: false,
myNodeInfo: Protobuf.MyNodeInfo.create(),
myNode: Protobuf.NodeInfo.create(),
users: [],
positionPackets: [],
nodes: [],
channels: [],
preferences: Protobuf.RadioConfig_UserPreferences.create(),
messages: [],
//todo implement
// connectionMethod: localStorage.getItem('connectionMethod'),
hostOverrideEnabled:
localStorage.getItem('hostOverrideEnabled') === 'true' ?? false,
hostOverride: localStorage.getItem('hostOverride') ?? '',
@@ -83,12 +83,21 @@ export const meshtasticSlice = createSlice({
}
},
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
state.positionPackets.push(action.payload);
if (
state.positionPackets.findIndex(
(position) => position.packet.from === action.payload.packet.from,
) !== -1
) {
state.positionPackets = state.positionPackets.map((position) => {
return position.packet.from === action.payload.packet.from
? action.payload
: position;
});
} else {
state.positionPackets.push(action.payload);
}
},
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
if (action.payload.num === state.myNodeInfo.myNodeNum) {
state.myNode = action.payload;
}
if (
state.nodes.findIndex((node) => node.num === action.payload.num) !== -1
) {
-14
View File
@@ -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>
);
};
+117
View File
@@ -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>
);
};
+19 -2
View File
@@ -1,11 +1,13 @@
import type React from 'react';
import { FiFileText, FiRss } from 'react-icons/fi';
import { FiAlignLeft, FiBell, 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';
export const Plugins = (): JSX.Element => {
return (
@@ -22,8 +24,23 @@ export const Plugins = (): JSX.Element => {
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" />,
},
]}
panels={[
<RangeTest key={1} />,
<Files key={2} />,
<ExternalNotification key={3} />,
<Serial key={4} />,
]}
panels={[<RangeTest key={1} />, <Files key={2} />]}
/>
);
};
+1 -3
View File
@@ -1,7 +1,6 @@
import type React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { FiMenu } from 'react-icons/fi';
import { FormFooter } from '@app/components/FormFooter';
@@ -23,7 +22,6 @@ export const RangeTest = ({
navOpen,
setNavOpen,
}: RangeTestProps): JSX.Element => {
const { t } = useTranslation();
const preferences = useAppSelector((state) => state.meshtastic.preferences);
const { register, handleSubmit, formState, reset, control } =
@@ -66,7 +64,7 @@ export const RangeTest = ({
}
>
<div className="w-full space-y-4">
<Card title="Range Test" description="Settings">
<Card>
<div className="w-full max-w-3xl p-10 md:max-w-xl">
<form onSubmit={onSubmit}>
<Checkbox
+118
View File
@@ -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>
);
};
+8 -6
View File
@@ -1,9 +1,9 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FiCode, FiMenu, FiSave } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { LoraConfig } from '@app/components/LoraConfig';
import { connection } from '@app/core/connection';
import { useAppSelector } from '@app/hooks/redux';
import { Channel } from '@components/Channel';
@@ -22,10 +22,7 @@ export const Channels = ({
navOpen,
setNavOpen,
}: ChannelsProps): JSX.Element => {
const { t } = useTranslation();
const channels = useAppSelector((state) => state.meshtastic.channels).filter(
(channel) => channel.index !== 0,
);
const channels = useAppSelector((state) => state.meshtastic.channels);
const [debug, setDebug] = React.useState(false);
return (
@@ -61,11 +58,16 @@ export const Channels = ({
}
>
<div className="space-y-4">
{channels[0] && <LoraConfig channel={channels[0]} />}
<Card>
<Cover enabled={debug} content={<JSONPretty data={channels} />} />
<div className="w-full p-4 space-y-2 md:p-10">
{channels.map((channel) => (
<Channel key={channel.index} channel={channel} />
<Channel
key={channel.index}
channel={channel}
hideEnabled={channel.index === 0}
/>
))}
<div className="flex justify-between">
+37 -35
View File
@@ -1,12 +1,12 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { FiCheck, FiMenu } from 'react-icons/fi';
import JSONPretty from 'react-json-pretty';
import { FormFooter } from '@app/components/FormFooter';
import { connection, setConnection } from '@app/core/connection';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { ble, connection, serial, setConnection } 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';
@@ -40,20 +40,18 @@ export const Connection = ({
navOpen,
setNavOpen,
}: ConnectionProps): JSX.Element => {
const dispatch = useAppDispatch();
const [selectedConnType, setSelectedConnType] = React.useState(connType.HTTP);
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
const [httpIpSource, setHttpIpSource] = React.useState<'local' | 'remote'>(
'local',
);
const { t } = useTranslation();
const hostOverrideEnabled = useAppSelector(
(state) => state.meshtastic.hostOverrideEnabled,
);
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
const { register, handleSubmit, formState, reset } = useForm<{
const { formState, reset } = useForm<{
method: connType;
}>({
defaultValues: {
@@ -83,30 +81,26 @@ export const Connection = ({
await connection.connect(params);
};
const updateBleDeviceList = async (): Promise<void> => {
const updateBleDeviceList = React.useCallback(async (): Promise<void> => {
const devices = await ble.getDevices();
setBleDevices(devices);
};
}, []);
const updateSerialDeviceList = async (): Promise<void> => {
const updateSerialDeviceList = React.useCallback(async (): Promise<void> => {
const devices = await serial.getPorts();
console.log(devices);
setSerialDevices(devices);
};
}, []);
// React.useEffect(() => {
// if (selectedConnType === connType.BLE) {
// void updateBleDeviceList();
// }
// if (selectedConnType === connType.SERIAL) {
// void updateSerialDeviceList();
// }
// }, [selectedConnType]);
const onSubmit = handleSubmit((data) => {
// void connection.setOwner(data);
});
React.useEffect(() => {
if (selectedConnType === connType.BLE) {
void updateBleDeviceList();
}
if (selectedConnType === connType.SERIAL) {
void updateSerialDeviceList();
}
}, [selectedConnType, updateBleDeviceList, updateSerialDeviceList]);
const connectionURL: string = hostOverrideEnabled
? hostOverride
@@ -115,9 +109,6 @@ export const Connection = ({
: (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ??
'http://meshtastic.local';
const ble = new IBLEConnection();
const serial = new ISerialConnection();
return (
<PrimaryTemplate
title="Connection"
@@ -133,14 +124,16 @@ export const Connection = ({
footer={
<FormFooter
dirty={formState.isDirty}
saveAction={onSubmit}
saveAction={(): void => {
return;
}}
clearAction={reset}
/>
}
>
<Card>
<div className="w-full max-w-3xl p-10 md:max-w-xl">
<form className="space-y-2" onSubmit={onSubmit}>
<form className="space-y-2">
<Select
label="Method"
optionsEnum={connType}
@@ -184,7 +177,7 @@ export const Connection = ({
</Button>
<Button
border
onClick={async () => {
onClick={async (): Promise<void> => {
await ble.getDevice();
}}
>
@@ -193,7 +186,7 @@ export const Connection = ({
</div>
<div className="space-y-2">
<div>Previously connected devices</div>
{bleDevices.map((device) => (
{bleDevices.map((device, index) => (
<div
onClick={async (): Promise<void> => {
console.log('clicked');
@@ -203,7 +196,7 @@ export const Connection = ({
});
}}
className="flex justify-between p-2 bg-gray-700 rounded-md"
key={device.id}
key={index}
>
<div className="my-auto">{device.name}</div>
<IconButton
@@ -224,13 +217,13 @@ export const Connection = ({
{selectedConnType === connType.SERIAL && (
<div>
<div className="flex space-x-2">
<Button border onClick={updateBleDeviceList}>
<Button border onClick={updateSerialDeviceList}>
Refresh List
</Button>
<Button
border
onClick={async () => {
await serial.getPort();
onClick={async (): Promise<void> => {
console.log(await serial.getPort());
}}
>
New Device
@@ -238,10 +231,10 @@ export const Connection = ({
</div>
<div className="space-y-2">
<div>Previously connected devices</div>
{serialDevices.map((device) => (
{serialDevices.map((device, index) => (
<div
className="flex justify-between p-2 bg-gray-700 rounded-md"
key={device.getInfo().usbProductId}
key={index}
>
<div className="my-auto">
{device.getInfo().usbProductId}
@@ -256,12 +249,21 @@ export const Connection = ({
}}
icon={<FiCheck />}
/>
<JSONPretty data={device.getInfo()} />
</div>
))}
</div>
</div>
)}
</form>
<Button
border
onClick={(): void => {
connection.disconnect();
}}
>
Disconnect
</Button>
</div>
</Card>
</PrimaryTemplate>
+2 -1
View File
@@ -82,8 +82,9 @@ export const Position = ({
<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)"
label="Broadcast Interval"
type="number"
suffix="Seconds"
{...register('positionBroadcastSecs', { valueAsNumber: true })}
/>
<Select
-2
View File
@@ -1,7 +1,6 @@
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 { base16 } from 'rfc4648';
@@ -25,7 +24,6 @@ export interface UserProps {
}
export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => {
const { t } = useTranslation();
const [debug, setDebug] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const dispatch = useAppDispatch();
+9 -1
View File
@@ -4,6 +4,14 @@ add default value to undefined protobufs, (omit if default to keep them small (o
add input validation min,max etc
change ch type select to disable, make primary and delete buttons
maybe make channel editor acordion?
add url routing for settings tabs
form not updated if rendered before store populated, populated on remount, also disabled fields will remain disabled even if there disable props provided by redux are true, required toggling
add loading blur to card (prop)
form still considered dirty after save
form prefix should be located in the input (absolute?)
form suffix should focus input
reset store on new connection
meshtastic.js
- either extrapolate user and position out of nodeInfo, or fire off events for all position and user packets even when contained in a nodeInfo packet, decide how to fire events for nodeInfo, wether we merge it or do something else
- either extrapolate user and position out of nodeInfo, or fire off events for all position and user packets even when contained in a nodeInfo packet, decide how to fire events for nodeInfo, wether we merge it or do something else
- fix entering device-reconnecting state and not re-connecting despite packets being received