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