Internal rework
This commit is contained in:
+18
-16
@@ -1,4 +1,4 @@
|
||||
import type React from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { FiHash } from 'react-icons/fi';
|
||||
|
||||
@@ -10,9 +10,9 @@ import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
import { useAppSelector } from '../hooks/redux';
|
||||
|
||||
export const Messages = (): JSX.Element => {
|
||||
const messages = useAppSelector((state) => state.meshtastic.messages);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const channels = useAppSelector((state) => state.meshtastic.channels);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
|
||||
const [channelIndex, setChannelIndex] = React.useState(0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
@@ -23,25 +23,28 @@ export const Messages = (): JSX.Element => {
|
||||
options={channels
|
||||
.filter(
|
||||
(channel) =>
|
||||
channel.role !== Protobuf.Channel_Role.DISABLED &&
|
||||
channel.settings?.name !== 'admin',
|
||||
channel.channel.role !== Protobuf.Channel_Role.DISABLED &&
|
||||
channel.channel.settings?.name !== 'admin',
|
||||
)
|
||||
.map((channel) => {
|
||||
return {
|
||||
name: channel.settings?.name.length
|
||||
? channel.settings.name
|
||||
: channel.role === Protobuf.Channel_Role.PRIMARY
|
||||
name: channel.channel.settings?.name.length
|
||||
? channel.channel.settings.name
|
||||
: channel.channel.role === Protobuf.Channel_Role.PRIMARY
|
||||
? 'Primary'
|
||||
: `CH: ${channel.index}`,
|
||||
value: channel.index,
|
||||
: `CH: ${channel.channel.index}`,
|
||||
value: channel.channel.index,
|
||||
};
|
||||
})}
|
||||
onChange={(e): void => {
|
||||
setChannelIndex(parseInt(e.target.value));
|
||||
}}
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow p-6 space-y-2 overflow-y-auto bg-white border-b border-gray-300 md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
|
||||
{messages.map((message, index) => (
|
||||
{channels[channelIndex]?.messages.map((message, index) => (
|
||||
<Message
|
||||
key={index}
|
||||
isSender={message.isSender}
|
||||
@@ -49,14 +52,13 @@ export const Messages = (): JSX.Element => {
|
||||
ack={message.ack}
|
||||
rxTime={new Date()}
|
||||
senderName={
|
||||
users.find(
|
||||
(user) => user.packet.from === message.message.packet.from,
|
||||
)?.data.longName ?? 'UNK'
|
||||
nodes.find((node) => node.number === message.message.packet.from)
|
||||
?.user?.longName ?? 'UNK'
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<MessageBar />
|
||||
<MessageBar channelIndex={channelIndex} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,23 +9,26 @@ import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
import { Node } from './Node';
|
||||
|
||||
export const Nodes = (): JSX.Element => {
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const myNodeNum = useAppSelector(
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
).myNodeNum;
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
|
||||
(node) => node.number !== myNodeNum,
|
||||
);
|
||||
return (
|
||||
<PageLayout
|
||||
title="Nodes"
|
||||
emptyMessage="No nodes discovered yet..."
|
||||
sidebarItems={nodes.map((node) => {
|
||||
const user = users.find((user) => user.packet.from === node.num)?.data;
|
||||
return {
|
||||
title: user ? user.longName : node.num.toString(),
|
||||
description: user
|
||||
? Protobuf.HardwareModel[user.hwModel]
|
||||
title: node.user?.longName ?? node.number.toString(),
|
||||
description: node.user
|
||||
? Protobuf.HardwareModel[node.user.hwModel]
|
||||
: 'Unknown Hardware',
|
||||
icon: (
|
||||
<Avatar
|
||||
size={30}
|
||||
name={user ? user.longName : node.num.toString()}
|
||||
name={node.user?.longName ?? node.number.toString()}
|
||||
variant="beam"
|
||||
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
|
||||
/>
|
||||
|
||||
@@ -6,33 +6,24 @@ import { FiCode, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
import TimeAgo from 'timeago-react';
|
||||
|
||||
import { Cover } from '@app/components/generic/Cover';
|
||||
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 { Cover } from '@components/generic/Cover';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { StatCard } from '@components/generic/StatCard';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
import type { Node as NodeType } from '@core/slices/meshtasticSlice';
|
||||
|
||||
export interface NodeProps {
|
||||
navOpen?: boolean;
|
||||
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
node: Protobuf.NodeInfo;
|
||||
node: NodeType;
|
||||
}
|
||||
|
||||
export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
const user = useAppSelector((state) => state.meshtastic.users).find(
|
||||
(user) => user.packet.from === node.num,
|
||||
)?.data;
|
||||
const position = useAppSelector(
|
||||
(state) => state.meshtastic.positionPackets,
|
||||
).find((position) => position.packet.from === node.num)?.data;
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title={user ? user.longName : node.num.toString()}
|
||||
title={node.user?.longName ?? node.number.toString()}
|
||||
tagline="Node"
|
||||
leftButton={
|
||||
<IconButton
|
||||
@@ -51,38 +42,21 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
footer={<></>}
|
||||
>
|
||||
<div className="w-full space-y-4">
|
||||
<div className="flex flex-col justify-between gap-4 md:flex-row">
|
||||
<StatCard
|
||||
title="Last heard"
|
||||
value={
|
||||
node.lastHeard ? (
|
||||
<TimeAgo datetime={new Date(node.lastHeard * 1000)} />
|
||||
) : (
|
||||
'Never'
|
||||
)
|
||||
node.lastHeard ? <TimeAgo datetime={node.lastHeard} /> : 'Never'
|
||||
}
|
||||
/>
|
||||
<StatCard title="SNR" value={node.snr.toString()} />
|
||||
</div>
|
||||
<Card
|
||||
title="Position"
|
||||
description={new Date(node.lastHeard * 1000).toLocaleString()}
|
||||
>
|
||||
<Card title="Position" description={node.lastHeard.toLocaleString()}>
|
||||
<Cover enabled={debug} content={<JSONPretty data={node} />} />
|
||||
<div className="p-10">
|
||||
<JSONPretty data={position} />
|
||||
</div>
|
||||
</Card>
|
||||
<Card title="Settings" description="Remote node settings">
|
||||
<div className="p-10">
|
||||
<form className="space-y-4">
|
||||
<Input label="Device Name" />
|
||||
<Input label="Short Name" maxLength={3} />
|
||||
<Checkbox label="Licenced Operator?" />
|
||||
</form>
|
||||
<JSONPretty data={node.positions[node.positions.length - 1]} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type React from 'react';
|
||||
import 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 { connection } from '@core/connection';
|
||||
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
|
||||
|
||||
export interface ExternalNotificationProps {
|
||||
@@ -22,7 +22,9 @@ export const ExternalNotification = ({
|
||||
navOpen,
|
||||
setNavOpen,
|
||||
}: ExternalNotificationProps): JSX.Element => {
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<RadioConfig_UserPreferences>({
|
||||
@@ -39,6 +41,19 @@ export const ExternalNotification = ({
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
extNotificationPluginActive: preferences.extNotificationPluginActive,
|
||||
extNotificationPluginAlertBell:
|
||||
preferences.extNotificationPluginAlertBell,
|
||||
extNotificationPluginAlertMessage:
|
||||
preferences.extNotificationPluginAlertMessage,
|
||||
extNotificationPluginEnabled: preferences.extNotificationPluginEnabled,
|
||||
extNotificationPluginOutput: preferences.extNotificationPluginOutput,
|
||||
extNotificationPluginOutputMs: preferences.extNotificationPluginOutputMs,
|
||||
});
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setPreferences(data);
|
||||
});
|
||||
|
||||
@@ -4,11 +4,11 @@ import type React from 'react';
|
||||
import { FiMenu, FiTrash, FiUploadCloud } from 'react-icons/fi';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import fetcher from '@app/core/utils/fetcher';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connectionUrl } from '@core/connection';
|
||||
import fetcher from '@core/utils/fetcher';
|
||||
|
||||
export interface RangeTestProps {
|
||||
navOpen?: boolean;
|
||||
@@ -33,20 +33,8 @@ interface IFiles {
|
||||
}
|
||||
|
||||
export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => {
|
||||
const hostOverrideEnabled = useAppSelector(
|
||||
(state) => state.meshtastic.hostOverrideEnabled,
|
||||
);
|
||||
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
|
||||
|
||||
const connectionURL = hostOverrideEnabled
|
||||
? hostOverride
|
||||
: import.meta.env.PROD
|
||||
? window.location.hostname
|
||||
: (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ??
|
||||
'http://meshtastic.local';
|
||||
|
||||
const { data } = useSWR<IFiles>(
|
||||
`http://${connectionURL}/json/spiffs/browse/static`,
|
||||
`http://${connectionUrl}/json/spiffs/browse/static`,
|
||||
fetcher,
|
||||
);
|
||||
|
||||
@@ -107,7 +95,7 @@ export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => {
|
||||
/> */}
|
||||
</div>
|
||||
<a
|
||||
href={`http://${connectionURL}/${file.name.replace(
|
||||
href={`http://${connectionUrl}/${file.name.replace(
|
||||
'static/',
|
||||
'',
|
||||
)}`}
|
||||
@@ -120,7 +108,7 @@ export const Files = ({ navOpen, setNavOpen }: RangeTestProps): JSX.Element => {
|
||||
className="mx-2 my-auto"
|
||||
// confirmAction={async (): Promise<void> => {
|
||||
// await fetch(
|
||||
// `http://${connectionURL}/json/spiffs/delete/static?remove=${file.name}`,
|
||||
// `http://${connectionUrl}/json/spiffs/delete/static?remove=${file.name}`,
|
||||
// {
|
||||
// method: 'DELETE',
|
||||
// },
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type React from 'react';
|
||||
import 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 { FormFooter } from '@components/FormFooter';
|
||||
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 { connection } from '@core/connection';
|
||||
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
|
||||
|
||||
export interface RangeTestProps {
|
||||
@@ -22,7 +22,9 @@ export const RangeTest = ({
|
||||
navOpen,
|
||||
setNavOpen,
|
||||
}: RangeTestProps): JSX.Element => {
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<RadioConfig_UserPreferences>({
|
||||
@@ -33,6 +35,14 @@ export const RangeTest = ({
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
rangeTestPluginEnabled: preferences.rangeTestPluginEnabled,
|
||||
rangeTestPluginSave: preferences.rangeTestPluginSave,
|
||||
rangeTestPluginSender: preferences.rangeTestPluginSender,
|
||||
});
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setPreferences(data);
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type React from 'react';
|
||||
import 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 { FormFooter } from '@components/FormFooter';
|
||||
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 { connection } from '@core/connection';
|
||||
import type { RadioConfig_UserPreferences } from '@meshtastic/meshtasticjs/dist/generated';
|
||||
|
||||
export interface SerialProps {
|
||||
@@ -19,7 +19,9 @@ export interface SerialProps {
|
||||
}
|
||||
|
||||
export const Serial = ({ navOpen, setNavOpen }: SerialProps): JSX.Element => {
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<RadioConfig_UserPreferences>({
|
||||
@@ -33,6 +35,17 @@ export const Serial = ({ navOpen, setNavOpen }: SerialProps): JSX.Element => {
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
serialpluginEnabled: preferences.serialpluginEnabled,
|
||||
serialpluginEcho: preferences.serialpluginEcho,
|
||||
serialpluginMode: preferences.serialpluginMode,
|
||||
serialpluginRxd: preferences.serialpluginRxd,
|
||||
serialpluginTimeout: preferences.serialpluginTimeout,
|
||||
serialpluginTxd: preferences.serialpluginTxd,
|
||||
});
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setPreferences(data);
|
||||
});
|
||||
|
||||
@@ -3,15 +3,15 @@ import React from 'react';
|
||||
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';
|
||||
import { Button } from '@components/generic/Button';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Cover } from '@components/generic/Cover';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { LoraConfig } from '@components/LoraConfig';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
|
||||
export interface ChannelsProps {
|
||||
navOpen?: boolean;
|
||||
@@ -22,7 +22,7 @@ export const Channels = ({
|
||||
navOpen,
|
||||
setNavOpen,
|
||||
}: ChannelsProps): JSX.Element => {
|
||||
const channels = useAppSelector((state) => state.meshtastic.channels);
|
||||
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
|
||||
return (
|
||||
@@ -58,15 +58,15 @@ export const Channels = ({
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{channels[0] && <LoraConfig channel={channels[0]} />}
|
||||
{channels[0] && <LoraConfig channel={channels[0].channel} />}
|
||||
<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}
|
||||
hideEnabled={channel.index === 0}
|
||||
key={channel.channel.index}
|
||||
channel={channel.channel}
|
||||
hideEnabled={channel.channel.index === 0}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FiCheck, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
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';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { Select } from '@components/generic/form/Select';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import {
|
||||
IBLEConnection,
|
||||
IHTTPConnection,
|
||||
ISerialConnection,
|
||||
} from '@meshtastic/meshtasticjs';
|
||||
import type {
|
||||
BLEConnectionParameters,
|
||||
HTTPConnectionParameters,
|
||||
SerialConnectionParameters,
|
||||
} from '@meshtastic/meshtasticjs/dist/types';
|
||||
|
||||
export interface ConnectionProps {
|
||||
navOpen?: boolean;
|
||||
setNavOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
enum connType {
|
||||
HTTP,
|
||||
BLE,
|
||||
SERIAL,
|
||||
}
|
||||
|
||||
export const Connection = ({
|
||||
navOpen,
|
||||
setNavOpen,
|
||||
}: ConnectionProps): JSX.Element => {
|
||||
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 hostOverrideEnabled = useAppSelector(
|
||||
(state) => state.meshtastic.hostOverrideEnabled,
|
||||
);
|
||||
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
|
||||
|
||||
const { formState, reset } = useForm<{
|
||||
method: connType;
|
||||
}>({
|
||||
defaultValues: {
|
||||
method: connType.HTTP,
|
||||
},
|
||||
});
|
||||
|
||||
const connect = async (
|
||||
connectionType: connType,
|
||||
params:
|
||||
| HTTPConnectionParameters
|
||||
| SerialConnectionParameters
|
||||
| BLEConnectionParameters,
|
||||
): Promise<void> => {
|
||||
connection.complete();
|
||||
connection.disconnect();
|
||||
|
||||
if (connectionType === connType.BLE) {
|
||||
setConnection(new IBLEConnection());
|
||||
} else if (connectionType === connType.HTTP) {
|
||||
setConnection(new IHTTPConnection());
|
||||
} else {
|
||||
setConnection(new ISerialConnection());
|
||||
}
|
||||
|
||||
// @ts-ignore tmp
|
||||
await connection.connect(params);
|
||||
};
|
||||
|
||||
const updateBleDeviceList = React.useCallback(async (): Promise<void> => {
|
||||
const devices = await ble.getDevices();
|
||||
setBleDevices(devices);
|
||||
}, []);
|
||||
|
||||
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, updateBleDeviceList, updateSerialDeviceList]);
|
||||
|
||||
const connectionURL: string = hostOverrideEnabled
|
||||
? hostOverride
|
||||
: import.meta.env.PROD
|
||||
? window.location.hostname
|
||||
: (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ??
|
||||
'http://meshtastic.local';
|
||||
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title="Connection"
|
||||
tagline="Settings"
|
||||
leftButton={
|
||||
<IconButton
|
||||
icon={<FiMenu className="w-5 h-5" />}
|
||||
onClick={(): void => {
|
||||
setNavOpen && setNavOpen(!navOpen);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<FormFooter
|
||||
dirty={formState.isDirty}
|
||||
saveAction={(): void => {
|
||||
return;
|
||||
}}
|
||||
clearAction={reset}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Card>
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<form className="space-y-2">
|
||||
<Select
|
||||
label="Method"
|
||||
optionsEnum={connType}
|
||||
value={selectedConnType}
|
||||
onChange={(e): void => {
|
||||
setSelectedConnType(parseInt(e.target.value));
|
||||
}}
|
||||
/>
|
||||
{selectedConnType === connType.HTTP && (
|
||||
<>
|
||||
<Select
|
||||
label="Host Source"
|
||||
options={[
|
||||
{
|
||||
name: 'Local',
|
||||
value: 'local',
|
||||
},
|
||||
{
|
||||
name: 'Remote',
|
||||
value: 'remote',
|
||||
},
|
||||
]}
|
||||
value={httpIpSource}
|
||||
onChange={(e): void => {
|
||||
setHttpIpSource(e.target.value as 'local' | 'remote');
|
||||
}}
|
||||
/>
|
||||
{httpIpSource === 'local' ? (
|
||||
<Input label="Host" value={connectionURL} disabled />
|
||||
) : (
|
||||
<Input label="Host" />
|
||||
)}
|
||||
<Checkbox label="Use TLS?" />
|
||||
</>
|
||||
)}
|
||||
{selectedConnType === connType.BLE && (
|
||||
<div>
|
||||
<div className="flex space-x-2">
|
||||
<Button border onClick={updateBleDeviceList}>
|
||||
Refresh List
|
||||
</Button>
|
||||
<Button
|
||||
border
|
||||
onClick={async (): Promise<void> => {
|
||||
await ble.getDevice();
|
||||
}}
|
||||
>
|
||||
New Device
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div>Previously connected devices</div>
|
||||
{bleDevices.map((device, index) => (
|
||||
<div
|
||||
onClick={async (): Promise<void> => {
|
||||
console.log('clicked');
|
||||
|
||||
await connect(connType.BLE, {
|
||||
device: device,
|
||||
});
|
||||
}}
|
||||
className="flex justify-between p-2 bg-gray-700 rounded-md"
|
||||
key={index}
|
||||
>
|
||||
<div className="my-auto">{device.name}</div>
|
||||
<IconButton
|
||||
onClick={async (): Promise<void> => {
|
||||
console.log('clicked');
|
||||
|
||||
await connect(connType.BLE, {
|
||||
device: device,
|
||||
});
|
||||
}}
|
||||
icon={<FiCheck />}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{selectedConnType === connType.SERIAL && (
|
||||
<div>
|
||||
<div className="flex space-x-2">
|
||||
<Button border onClick={updateSerialDeviceList}>
|
||||
Refresh List
|
||||
</Button>
|
||||
<Button
|
||||
border
|
||||
onClick={async (): Promise<void> => {
|
||||
console.log(await serial.getPort());
|
||||
}}
|
||||
>
|
||||
New Device
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div>Previously connected devices</div>
|
||||
{serialDevices.map((device, index) => (
|
||||
<div
|
||||
className="flex justify-between p-2 bg-gray-700 rounded-md"
|
||||
key={index}
|
||||
>
|
||||
<div className="my-auto">
|
||||
{device.getInfo().usbProductId}
|
||||
{device.getInfo().usbVendorId}
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={async (): Promise<void> => {
|
||||
await connect(connType.SERIAL, {
|
||||
// @ts-ignore tmp
|
||||
device: device,
|
||||
});
|
||||
}}
|
||||
icon={<FiCheck />}
|
||||
/>
|
||||
<JSONPretty data={device.getInfo()} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
<Button
|
||||
border
|
||||
onClick={(): void => {
|
||||
connection.disconnect();
|
||||
}}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</PrimaryTemplate>
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import type React from 'react';
|
||||
import {
|
||||
FiLayers,
|
||||
FiLayout,
|
||||
FiLink2,
|
||||
FiMapPin,
|
||||
FiRadio,
|
||||
FiUser,
|
||||
@@ -14,7 +13,6 @@ import {
|
||||
import { PageLayout } from '@components/templates/PageLayout';
|
||||
|
||||
import { Channels } from './Channels';
|
||||
import { Connection } from './Connection';
|
||||
import { Interface } from './Interface';
|
||||
import { Position } from './Position';
|
||||
import { Power } from './Power';
|
||||
@@ -24,11 +22,6 @@ import { WiFi } from './WiFi';
|
||||
|
||||
export const Settings = (): JSX.Element => {
|
||||
const sidebarItems = [
|
||||
{
|
||||
title: 'Connection',
|
||||
description: 'Connection method and parameters',
|
||||
icon: <FiLink2 className="flex-shrink-0 w-6 h-6" />,
|
||||
},
|
||||
{
|
||||
title: 'WiFi',
|
||||
description: 'WiFi credentials and mode',
|
||||
@@ -70,7 +63,6 @@ export const Settings = (): JSX.Element => {
|
||||
title="Settings"
|
||||
sidebarItems={sidebarItems}
|
||||
panels={[
|
||||
<Connection key={1} />,
|
||||
<WiFi key={2} />,
|
||||
<Position key={3} />,
|
||||
<User key={4} />,
|
||||
|
||||
@@ -3,11 +3,11 @@ import type React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiMenu, FiSave } from 'react-icons/fi';
|
||||
|
||||
import i18n from '@app/core/translation';
|
||||
import { Button } from '@components/generic/Button';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Select } from '@components/generic/form/Select';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import i18n from '@core/translation';
|
||||
|
||||
export interface InterfaceProps {
|
||||
navOpen?: boolean;
|
||||
|
||||
@@ -4,9 +4,8 @@ import { useForm } from 'react-hook-form';
|
||||
import { FiCode, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Cover } from '@components/generic/Cover';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
@@ -14,6 +13,7 @@ import { Input } from '@components/generic/form/Input';
|
||||
import { Select } from '@components/generic/form/Select';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface PositionProps {
|
||||
@@ -25,7 +25,9 @@ export const Position = ({
|
||||
navOpen,
|
||||
setNavOpen,
|
||||
}: PositionProps): JSX.Element => {
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { register, handleSubmit, formState, reset } =
|
||||
@@ -41,6 +43,10 @@ export const Position = ({
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset(preferences);
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setLoading(true);
|
||||
void connection.setPreferences(data, async () => {
|
||||
|
||||
@@ -4,15 +4,15 @@ import { useForm } from 'react-hook-form';
|
||||
import { FiCode, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { Select } from '@app/components/generic/form/Select';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Cover } from '@components/generic/Cover';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Select } from '@components/generic/form/Select';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface PowerProps {
|
||||
@@ -21,7 +21,9 @@ export interface PowerProps {
|
||||
}
|
||||
|
||||
export const Power = ({ navOpen, setNavOpen }: PowerProps): JSX.Element => {
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { register, handleSubmit, formState, reset } =
|
||||
@@ -32,6 +34,10 @@ export const Power = ({ navOpen, setNavOpen }: PowerProps): JSX.Element => {
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset(preferences);
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setLoading(true);
|
||||
void connection.setPreferences(data, async () => {
|
||||
|
||||
@@ -4,15 +4,15 @@ import { useForm } from 'react-hook-form';
|
||||
import { FiCode, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Cover } from '@components/generic/Cover';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Select } from '@components/generic/form/Select';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface RadioProps {
|
||||
@@ -21,7 +21,9 @@ export interface RadioProps {
|
||||
}
|
||||
|
||||
export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => {
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { register, handleSubmit, formState, reset } =
|
||||
@@ -29,6 +31,10 @@ export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => {
|
||||
defaultValues: preferences,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset(preferences);
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setLoading(true);
|
||||
void connection.setPreferences(data, async () => {
|
||||
|
||||
+31
-19
@@ -5,8 +5,8 @@ import { FiCode, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
import { base16 } from 'rfc4648';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Cover } from '@components/generic/Cover';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
@@ -15,7 +15,6 @@ import { Select } from '@components/generic/form/Select';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import { connection } from '@core/connection';
|
||||
import { addUser } from '@core/slices/meshtasticSlice';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface UserProps {
|
||||
@@ -26,10 +25,11 @@ export interface UserProps {
|
||||
export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => {
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||
const user = useAppSelector((state) => state.meshtastic.users).find(
|
||||
(user) => user.packet.from === myNodeInfo.myNodeNum,
|
||||
const myNodeNum = useAppSelector(
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
).myNodeNum;
|
||||
const node = useAppSelector((state) => state.meshtastic.nodes).find(
|
||||
(node) => node.number === myNodeNum,
|
||||
);
|
||||
const { register, handleSubmit, formState, reset } = useForm<{
|
||||
longName: string;
|
||||
@@ -38,22 +38,34 @@ export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => {
|
||||
team: Protobuf.Team;
|
||||
}>({
|
||||
defaultValues: {
|
||||
longName: user?.data.longName,
|
||||
shortName: user?.data.shortName,
|
||||
isLicensed: user?.data.isLicensed,
|
||||
team: user?.data.team,
|
||||
longName: node?.user?.longName,
|
||||
shortName: node?.user?.shortName,
|
||||
isLicensed: node?.user?.isLicensed,
|
||||
team: node?.user?.team,
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset({
|
||||
longName: node?.user?.longName,
|
||||
shortName: node?.user?.shortName,
|
||||
isLicensed: node?.user?.isLicensed,
|
||||
team: node?.user?.team,
|
||||
});
|
||||
}, [reset, node]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setLoading(true);
|
||||
// TODO: can be removed once getUser is implemented
|
||||
if (user) {
|
||||
void connection.setOwner({ ...user.data, ...data }, async () => {
|
||||
|
||||
if (node?.user) {
|
||||
void connection.setOwner({ ...node.user, ...data }, async () => {
|
||||
await Promise.resolve();
|
||||
setLoading(false);
|
||||
});
|
||||
dispatch(addUser({ ...user, ...{ data: { ...user.data, ...data } } }));
|
||||
// TODO: can be removed once getUser is implemented
|
||||
// dispatch(
|
||||
// addUser({ ...node.user, ...{ data: { ...node.user.data, ...data } } }),
|
||||
// );
|
||||
}
|
||||
});
|
||||
|
||||
@@ -87,15 +99,15 @@ export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => {
|
||||
}
|
||||
>
|
||||
<Card loading={loading}>
|
||||
<Cover enabled={debug} content={<JSONPretty data={user?.data} />} />
|
||||
<Cover enabled={debug} content={<JSONPretty data={node?.user} />} />
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<form className="space-y-2" onSubmit={onSubmit}>
|
||||
<Input label="Device ID" value={user?.data.id} disabled />
|
||||
<Input label="Device ID" value={node?.user?.id} disabled />
|
||||
<Input
|
||||
label="Hardware"
|
||||
value={
|
||||
Protobuf.HardwareModel[
|
||||
user?.data.hwModel ?? Protobuf.HardwareModel.UNSET
|
||||
node?.user?.hwModel ?? Protobuf.HardwareModel.UNSET
|
||||
]
|
||||
}
|
||||
disabled
|
||||
@@ -104,7 +116,7 @@ export const User = ({ navOpen, setNavOpen }: UserProps): JSX.Element => {
|
||||
label="Mac Address"
|
||||
defaultValue={
|
||||
base16
|
||||
.stringify(user?.data.macaddr ?? [])
|
||||
.stringify(node?.user?.macaddr ?? [])
|
||||
.match(/.{1,2}/g)
|
||||
?.join(':') ?? ''
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FiCode, FiMenu } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { FormFooter } from '@app/components/FormFooter';
|
||||
import { Checkbox } from '@app/components/generic/form/Checkbox';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { FormFooter } from '@components/FormFooter';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Cover } from '@components/generic/Cover';
|
||||
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 { connection } from '@core/connection';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface WiFiProps {
|
||||
@@ -23,7 +23,9 @@ export interface WiFiProps {
|
||||
|
||||
export const WiFi = ({ navOpen, setNavOpen }: WiFiProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const preferences = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
@@ -37,6 +39,10 @@ export const WiFi = ({ navOpen, setNavOpen }: WiFiProps): JSX.Element => {
|
||||
defaultValue: false,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
reset(preferences);
|
||||
}, [reset, preferences]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
setLoading(true);
|
||||
void connection.setPreferences(data, async () => {
|
||||
|
||||
Reference in New Issue
Block a user