Internal rework

This commit is contained in:
Sacha Weatherstone
2021-11-21 00:28:24 +11:00
parent 1f8170130d
commit 0cea0b2165
34 changed files with 903 additions and 803 deletions
+269
View File
@@ -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>
);
};
+4 -5
View File
@@ -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;
}
+23 -14
View File
@@ -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,
};
}),
]}
+2 -2
View File
@@ -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>
+5 -1
View File
@@ -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'
+1 -1
View File
@@ -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 />}
+37
View File
@@ -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"
>
&#8203;
</span>
<div className="inline-block w-full max-w-3xl align-middle">
{children}
</div>
</div>
</Dialog>
</>
);
};
+1 -1
View File
@@ -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>
@@ -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>
);
};
+1 -3
View File
@@ -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;