Internal rework
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FiCheck } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { useAppDispatch, 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 { Modal } from '@components/generic/Modal';
|
||||
import {
|
||||
ble,
|
||||
connection,
|
||||
connectionUrl,
|
||||
serial,
|
||||
setConnection,
|
||||
} from '@core/connection';
|
||||
import { closeConnectionModal } from '@core/slices/appSlice';
|
||||
import {
|
||||
IBLEConnection,
|
||||
IHTTPConnection,
|
||||
ISerialConnection,
|
||||
Protobuf,
|
||||
SettingsManager,
|
||||
} from '@meshtastic/meshtasticjs';
|
||||
import type {
|
||||
BLEConnectionParameters,
|
||||
HTTPConnectionParameters,
|
||||
SerialConnectionParameters,
|
||||
} from '@meshtastic/meshtasticjs/dist/types';
|
||||
|
||||
import { DeviceStatus } from './menu/buttons/DeviceStatus';
|
||||
|
||||
enum connType {
|
||||
HTTP,
|
||||
BLE,
|
||||
SERIAL,
|
||||
}
|
||||
|
||||
export const Connection = (): 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 hostOverrideEnabled = useAppSelector(
|
||||
(state) => state.meshtastic.hostOverrideEnabled,
|
||||
);
|
||||
const hostOverride = useAppSelector((state) => state.meshtastic.hostOverride);
|
||||
const connectionModalOpen = useAppSelector(
|
||||
(state) => state.app.connectionModalOpen,
|
||||
);
|
||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
||||
const connect = async (
|
||||
connectionType: connType,
|
||||
params:
|
||||
| HTTPConnectionParameters
|
||||
| SerialConnectionParameters
|
||||
| BLEConnectionParameters,
|
||||
): Promise<void> => {
|
||||
connection.complete();
|
||||
await 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();
|
||||
setSerialDevices(devices);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (ready) {
|
||||
dispatch(closeConnectionModal());
|
||||
}
|
||||
}, [ready, dispatch]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selectedConnType === connType.BLE) {
|
||||
void updateBleDeviceList();
|
||||
}
|
||||
if (selectedConnType === connType.SERIAL) {
|
||||
void updateSerialDeviceList();
|
||||
}
|
||||
}, [selectedConnType, updateBleDeviceList, updateSerialDeviceList]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const connectionMethod = localStorage.getItem('connectionMethod');
|
||||
|
||||
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]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={connectionModalOpen}
|
||||
onClose={(): void => {
|
||||
dispatch(closeConnectionModal());
|
||||
}}
|
||||
>
|
||||
<Card>
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
{ready ? (
|
||||
<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> => {
|
||||
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> => {
|
||||
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>
|
||||
) : (
|
||||
<div>
|
||||
<DeviceStatus />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -2,14 +2,13 @@ import React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { Loading } from '@components/generic/Loading';
|
||||
import { connection } from '@core/connection';
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,30 +3,42 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiSend } from 'react-icons/fi';
|
||||
|
||||
import { ackMessage } from '@app/core/slices/meshtasticSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { connection } from '@core/connection';
|
||||
import { ackMessage } from '@core/slices/meshtasticSlice';
|
||||
|
||||
import { Select } from '../generic/form/Select';
|
||||
import { IconButton } from '../generic/IconButton';
|
||||
|
||||
export const MessageBar = (): JSX.Element => {
|
||||
export interface MessageBarProps {
|
||||
channelIndex: number;
|
||||
}
|
||||
|
||||
export const MessageBar = ({ channelIndex }: MessageBarProps): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||
const myNodeNum = useAppSelector(
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
).myNodeNum;
|
||||
|
||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||
const [destinationNode, setDestinationNode] =
|
||||
React.useState<number>(0xffffffff);
|
||||
const sendMessage = (): void => {
|
||||
if (ready) {
|
||||
void connection.sendText(currentMessage, destinationNode, true, (id) => {
|
||||
dispatch(ackMessage(id));
|
||||
void connection.sendText(
|
||||
currentMessage,
|
||||
destinationNode,
|
||||
true,
|
||||
channelIndex--,
|
||||
(id) => {
|
||||
dispatch(ackMessage({ channel: 0, messageId: id }));
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
);
|
||||
setCurrentMessage('');
|
||||
}
|
||||
};
|
||||
@@ -51,14 +63,11 @@ export const MessageBar = (): JSX.Element => {
|
||||
value: 0xffffffff,
|
||||
},
|
||||
...nodes
|
||||
.filter((node) => node.num !== myNodeInfo.myNodeNum)
|
||||
.filter((node) => node.number !== myNodeNum)
|
||||
.map((node) => {
|
||||
const user = users.filter(
|
||||
(user) => user.packet.from === node.num,
|
||||
)[0]?.data;
|
||||
return {
|
||||
name: user ? user.shortName : node.num,
|
||||
value: node.num,
|
||||
name: node.user ? node.user.shortName : node.number,
|
||||
value: node.number,
|
||||
};
|
||||
}),
|
||||
]}
|
||||
|
||||
@@ -15,14 +15,14 @@ export const Blur = ({
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 z-20 w-full h-full transition-opacity ${
|
||||
disableOnMd ? 'md:hidden' : 'test'
|
||||
disableOnMd ? 'md:hidden' : ''
|
||||
} ${className}`}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`absolute inset-0 w-full h-full backdrop-filter backdrop-blur-sm ${
|
||||
disableOnMd ? 'md:hidden' : 'test'
|
||||
disableOnMd ? 'md:hidden' : ''
|
||||
}`}
|
||||
tabIndex={0}
|
||||
></div>
|
||||
|
||||
@@ -9,6 +9,7 @@ interface ButtonProps extends DefaultButtonProps {
|
||||
circle?: boolean;
|
||||
active?: boolean;
|
||||
border?: boolean;
|
||||
padding?: number;
|
||||
confirmAction?: () => void;
|
||||
}
|
||||
|
||||
@@ -21,6 +22,7 @@ export const Button = ({
|
||||
confirmAction,
|
||||
disabled,
|
||||
children,
|
||||
padding = 3,
|
||||
...props
|
||||
}: ButtonProps): JSX.Element => {
|
||||
const [hasConfirmed, setHasConfirmed] = React.useState(false);
|
||||
@@ -43,7 +45,9 @@ export const Button = ({
|
||||
className={`items-center select-none flex dark:text-white active:scale-95 transition duration-200 ease-in-out ${
|
||||
active && !disabled ? 'bg-gray-100 dark:bg-gray-700' : ''
|
||||
} ${
|
||||
circle ? 'rounded-full h-10 w-10' : 'rounded-md p-3 space-x-3 text-sm'
|
||||
circle
|
||||
? 'rounded-full h-10 w-10'
|
||||
: `rounded-md p-${padding} space-x-3 text-sm`
|
||||
} ${
|
||||
disabled
|
||||
? 'cursor-not-allowed dark:bg-primaryDark bg-white'
|
||||
|
||||
@@ -24,7 +24,7 @@ export const Card = ({
|
||||
}: CardProps): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className={`relative flex flex-col flex-auto dark:text-white border-y md:border shadow-md select-none dark:bg-primaryDark border-gray-300 dark:border-transparent md:rounded-md ${className}`}
|
||||
className={`relative flex flex-col flex-auto dark:text-white border-y md:border shadow-md select-none bg-white dark:bg-primaryDark border-gray-300 dark:border-gray-600 md:rounded-md ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{loading && <Loading />}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { Dialog } from '@headlessui/react';
|
||||
|
||||
export interface ModalProps {
|
||||
children: React.ReactNode;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const Modal = ({ children, open, onClose }: ModalProps): JSX.Element => {
|
||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
as="div"
|
||||
className={`fixed inset-0 z-30 ${darkMode ? 'dark' : ''}`}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Dialog.Overlay className="fixed w-full h-full backdrop-filter backdrop-blur-sm" />
|
||||
<div className="text-center ">
|
||||
<span
|
||||
className="inline-block h-screen align-middle "
|
||||
aria-hidden="true"
|
||||
>
|
||||
​
|
||||
</span>
|
||||
<div className="inline-block w-full max-w-3xl align-middle">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ export interface StatCardProps {
|
||||
|
||||
export const StatCard = ({ title, value }: StatCardProps): JSX.Element => {
|
||||
return (
|
||||
<div className="w-full border-gray-300 shadow-md border-y md:border h-28 md:rounded-3xl dark:bg-primaryDark dark:border-transparent ">
|
||||
<div className="w-full bg-white border-gray-300 shadow-md select-none dark:text-white border-y md:border dark:bg-primaryDark dark:border-gray-600 md:rounded-md ">
|
||||
<div className="m-4">
|
||||
<div className="text-lg font-light">{title}</div>
|
||||
<div className="text-3xl font-bold">{value}</div>
|
||||
|
||||
+22
-16
@@ -2,20 +2,28 @@ import type React from 'react';
|
||||
|
||||
import { FiWifi, FiWifiOff } from 'react-icons/fi';
|
||||
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { Button } from '@components/generic/Button';
|
||||
import { openConnectionModal } from '@core/slices/appSlice';
|
||||
import { Types } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const DeviceStatusDropdown = (): JSX.Element => {
|
||||
export const DeviceStatus = (): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
const deviceStatus = useAppSelector((state) => state.meshtastic.deviceStatus);
|
||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
||||
|
||||
return (
|
||||
<div className="flex bg-gray-100 rounded-md dark:bg-gray-700">
|
||||
<div className="flex pl-2 my-auto space-x-2 dark:text-white">
|
||||
<Button
|
||||
padding={0}
|
||||
active
|
||||
onClick={(): void => {
|
||||
dispatch(dispatch(openConnectionModal()));
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-2 px-2">
|
||||
<div
|
||||
className={`
|
||||
my-auto mx-2 w-2 h-2 rounded-full min-w-[2] ${
|
||||
my-auto w-2 h-2 rounded-full min-w-[2] ${
|
||||
[
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED,
|
||||
Types.DeviceStatusEnum.DEVICE_CONFIGURED,
|
||||
@@ -31,16 +39,14 @@ export const DeviceStatusDropdown = (): JSX.Element => {
|
||||
}`}
|
||||
></div>
|
||||
<div className="my-auto">{Types.DeviceStatusEnum[deviceStatus]}</div>
|
||||
<IconButton
|
||||
icon={
|
||||
ready ? (
|
||||
<FiWifi className="w-5 h-5" />
|
||||
) : (
|
||||
<FiWifiOff className="w-5 h-5 animate-pulse" />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div className="py-2">
|
||||
{ready ? (
|
||||
<FiWifi className="w-5 h-5" />
|
||||
) : (
|
||||
<FiWifiOff className="w-5 h-5 animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import type React from 'react';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { useRegisterSW } from 'virtual:pwa-register/react';
|
||||
|
||||
const ReloadPrompt = (): JSX.Element => {
|
||||
export const ReloadPrompt = (): JSX.Element => {
|
||||
const {
|
||||
offlineReady: [offlineReady, setOfflineReady],
|
||||
needRefresh: [needRefresh, setNeedRefresh],
|
||||
@@ -58,5 +58,3 @@ const ReloadPrompt = (): JSX.Element => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReloadPrompt;
|
||||
|
||||
Reference in New Issue
Block a user