COnnection modal fix & notification basics

This commit is contained in:
Sacha Weatherstone
2021-12-17 00:13:58 +11:00
parent c3a4f81b1c
commit 8b221e28c5
10 changed files with 160 additions and 93 deletions

View File

@@ -1 +1,2 @@
VITE_PUBLIC_DEVICE_IP=
VITE_PUBLIC_DEVICE_IP=
VITE_PUBLIC_HOSTED=

View File

@@ -14,7 +14,7 @@
"dependencies": {
"@headlessui/react": "^1.4.2",
"@meshtastic/meshtasticjs": "^0.6.35",
"@reduxjs/toolkit": "^1.7.0",
"@reduxjs/toolkit": "^1.7.1",
"base64-js": "^1.5.1",
"boring-avatars": "^1.5.8",
"i18next": "^21.6.0",
@@ -31,7 +31,7 @@
"react-redux": "^7.2.6",
"react-select": "^5.2.1",
"rfc4648": "^1.5.0",
"swr": "^1.1.1",
"swr": "^1.1.2-beta.0",
"timeago-react": "^3.0.4",
"type-route": "^0.6.0",
"use-breakpoint": "^3.0.0"
@@ -60,12 +60,12 @@
"gzipper": "^6.0.0",
"postcss": "^8.4.5",
"prettier": "^2.5.1",
"tailwindcss": "^3.0.2",
"tailwindcss": "^3.0.5",
"tar": "^6.1.11",
"typescript": "^4.5.4",
"vite": "^2.7.2",
"vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-pwa": "^0.11.11",
"vite-plugin-pwa": "^0.11.12",
"workbox-window": "^6.4.2"
}
}

62
pnpm-lock.yaml generated
View File

@@ -3,7 +3,7 @@ lockfileVersion: 5.3
specifiers:
'@headlessui/react': ^1.4.2
'@meshtastic/meshtasticjs': ^0.6.35
'@reduxjs/toolkit': ^1.7.0
'@reduxjs/toolkit': ^1.7.1
'@types/mapbox-gl': ^2.6.0
'@types/react': ^17.0.37
'@types/react-dom': ^17.0.11
@@ -43,8 +43,8 @@ specifiers:
react-redux: ^7.2.6
react-select: ^5.2.1
rfc4648: ^1.5.0
swr: ^1.1.1
tailwindcss: ^3.0.2
swr: ^1.1.2-beta.0
tailwindcss: ^3.0.5
tar: ^6.1.11
timeago-react: ^3.0.4
type-route: ^0.6.0
@@ -52,13 +52,13 @@ specifiers:
use-breakpoint: ^3.0.0
vite: ^2.7.2
vite-plugin-cdn-import: ^0.3.5
vite-plugin-pwa: ^0.11.11
vite-plugin-pwa: ^0.11.12
workbox-window: ^6.4.2
dependencies:
'@headlessui/react': 1.4.2_react-dom@17.0.2+react@17.0.2
'@meshtastic/meshtasticjs': 0.6.35
'@reduxjs/toolkit': 1.7.0_react-redux@7.2.6+react@17.0.2
'@reduxjs/toolkit': 1.7.1_react-redux@7.2.6+react@17.0.2
base64-js: 1.5.1
boring-avatars: 1.5.8
i18next: 21.6.0
@@ -75,7 +75,7 @@ dependencies:
react-redux: 7.2.6_react-dom@17.0.2+react@17.0.2
react-select: 5.2.1_5539cae010396b202b015f3568914e95
rfc4648: 1.5.0
swr: 1.1.1_react@17.0.2
swr: 1.1.2-beta.0_react@17.0.2
timeago-react: 3.0.4_react@17.0.2
type-route: 0.6.0
use-breakpoint: 3.0.0_react-dom@17.0.2+react@17.0.2
@@ -104,12 +104,12 @@ devDependencies:
gzipper: 6.0.0
postcss: 8.4.5
prettier: 2.5.1
tailwindcss: 3.0.2_16a290f6d0e3717bf6d2667234aebd30
tailwindcss: 3.0.5_16a290f6d0e3717bf6d2667234aebd30
tar: 6.1.11
typescript: 4.5.4
vite: 2.7.2
vite-plugin-cdn-import: 0.3.5
vite-plugin-pwa: 0.11.11_vite@2.7.2
vite-plugin-pwa: 0.11.12_vite@2.7.2
workbox-window: 6.4.2
packages:
@@ -1249,7 +1249,7 @@ packages:
babel-plugin-polyfill-corejs2: 0.3.0_@babel+core@7.16.5
babel-plugin-polyfill-corejs3: 0.4.0_@babel+core@7.16.5
babel-plugin-polyfill-regenerator: 0.3.0_@babel+core@7.16.5
core-js-compat: 3.19.3
core-js-compat: 3.20.0
semver: 6.3.0
transitivePeerDependencies:
- supports-color
@@ -1523,8 +1523,8 @@ packages:
resolution: {integrity: sha512-HZwkgJW9SGiE9+0lWKr1X997tmG01/40j+hr9yBVk+hTQcm7Hsf77XhMNtsDjWUOcspG6GBXu8o3g4i3kD5/zQ==}
dev: false
/@reduxjs/toolkit/1.7.0_react-redux@7.2.6+react@17.0.2:
resolution: {integrity: sha512-iApo4zS+8kWnIn4xucTDWpqRjDNkXruFIyJQWwThIEIbMj5kwqvbMaQcEgd2a305B68Z+4bvZqAqJSATeddaJA==}
/@reduxjs/toolkit/1.7.1_react-redux@7.2.6+react@17.0.2:
resolution: {integrity: sha512-wXwXYjBVz/ItxB7SMzEAMmEE/FBiY1ze18N+VVVX7NtVbRUrdOGKhpQMHivIJfkbJvSdLUU923a/yAagJQzY0Q==}
peerDependencies:
react: ^16.9.0 || ^17.0.0 || 18.0.0-beta
react-redux: ^7.2.1 || ^8.0.0-beta
@@ -1646,8 +1646,8 @@ packages:
'@types/geojson': 7946.0.8
dev: true
/@types/node/16.11.13:
resolution: {integrity: sha512-eUXZzHLHoZqj1frtUetNkUetYoJ6X55UmrVnFD4DMhVeAmwLjniZhtBmsRiemQh4uq4G3vUra/Ws/hs9vEvL3Q==}
/@types/node/17.0.0:
resolution: {integrity: sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw==}
dev: true
/@types/parse-json/4.0.0:
@@ -1694,7 +1694,7 @@ packages:
/@types/resolve/1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
'@types/node': 16.11.13
'@types/node': 17.0.0
dev: true
/@types/scheduler/0.16.2:
@@ -2121,7 +2121,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.19.1
caniuse-lite: 1.0.30001286
caniuse-lite: 1.0.30001287
fraction.js: 4.1.2
normalize-range: 0.1.2
picocolors: 1.0.0
@@ -2171,7 +2171,7 @@ packages:
dependencies:
'@babel/core': 7.16.5
'@babel/helper-define-polyfill-provider': 0.3.0_@babel+core@7.16.5
core-js-compat: 3.19.3
core-js-compat: 3.20.0
transitivePeerDependencies:
- supports-color
dev: true
@@ -2223,8 +2223,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001286
electron-to-chromium: 1.4.18
caniuse-lite: 1.0.30001287
electron-to-chromium: 1.4.20
escalade: 3.1.1
node-releases: 2.0.1
picocolors: 1.0.0
@@ -2256,8 +2256,8 @@ packages:
engines: {node: '>= 6'}
dev: true
/caniuse-lite/1.0.30001286:
resolution: {integrity: sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ==}
/caniuse-lite/1.0.30001287:
resolution: {integrity: sha512-4udbs9bc0hfNrcje++AxBuc6PfLNHwh3PO9kbwnfCQWyqtlzg3py0YgFu8jyRTTo85VAz4U+VLxSlID09vNtWA==}
dev: true
/chalk/2.4.2:
@@ -2342,8 +2342,8 @@ packages:
safe-buffer: 5.1.2
dev: true
/core-js-compat/3.19.3:
resolution: {integrity: sha512-59tYzuWgEEVU9r+SRgceIGXSSUn47JknoiXW6Oq7RW8QHjXWz3/vp8pa7dbtuVu40sewz3OP3JmQEcDdztrLhA==}
/core-js-compat/3.20.0:
resolution: {integrity: sha512-relrah5h+sslXssTTOkvqcC/6RURifB0W5yhYBdBkaPYa5/2KBMiog3XiD+s3TwEHWxInWVv4Jx2/Lw0vng+IQ==}
dependencies:
browserslist: 4.19.1
semver: 7.0.0
@@ -2509,8 +2509,8 @@ packages:
jake: 10.8.2
dev: true
/electron-to-chromium/1.4.18:
resolution: {integrity: sha512-i7nKjGGBE1+YUIbfLObA1EZPmN7J1ITEllbhusDk+KIk6V6gUxN9PFe36v+Sd+8Cg0k3cgUv9lQhQZalr8rggw==}
/electron-to-chromium/1.4.20:
resolution: {integrity: sha512-N7ZVNrdzX8NE90OXEFBMsBf3fp8P/vVDUER3WCUZjzC7OkNTXHVoF6W9qVhq8+dA8tGnbDajzUpj2ISNVVyj+Q==}
dev: true
/emoji-regex/8.0.0:
@@ -3730,7 +3730,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 16.11.13
'@types/node': 17.0.0
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
@@ -4885,8 +4885,8 @@ packages:
has-flag: 4.0.0
dev: true
/swr/1.1.1_react@17.0.2:
resolution: {integrity: sha512-ZpUHyU3N3snj2QGFeE2Fd3BXl1CVS6YQIQGb1ttPAkTmvwZqDyV3GRMNPsaeAYCBM74tfn4XbKx28FVQR0mS7Q==}
/swr/1.1.2-beta.0_react@17.0.2:
resolution: {integrity: sha512-e7ZcPCrEvGX+s4jDtBwV9c+HKqHzG1secuwU8bYjnnGkSJMneyn7OaabJKtRh922DH5a3k2vvBdhftoSL8bHJQ==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
@@ -4904,8 +4904,8 @@ packages:
strip-ansi: 6.0.1
dev: true
/tailwindcss/3.0.2_16a290f6d0e3717bf6d2667234aebd30:
resolution: {integrity: sha512-i1KpjYnGYftjzdAth6jA5iMPjhxpUkk5L6DafhfnQs+KiiWaThYxmk47Weh4oFH1mZqP6MuiQNHxtoRVPOraLg==}
/tailwindcss/3.0.5_16a290f6d0e3717bf6d2667234aebd30:
resolution: {integrity: sha512-59pNgzx2o+wkAk7IZGIH7H9eNS53gzZGrO3+NPyOEWHDbquHgiLL/c993T5t1vPSAeBxox4X5OgZwNuRvXVf+g==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
@@ -5170,8 +5170,8 @@ packages:
- rollup
dev: true
/vite-plugin-pwa/0.11.11_vite@2.7.2:
resolution: {integrity: sha512-/nSLS7VfGN5UrL4a1ALGEQAyga/H0hYZjEkwPehiEFW1PM1DTi1A8GkPCsmevKwR6vt10P+5wS1wrvSgwQemzw==}
/vite-plugin-pwa/0.11.12_vite@2.7.2:
resolution: {integrity: sha512-XqFmA4y9C4RBb5osSsa26GVwOSwbzf2GNVcT5+06KYYdguqLpuI9FW7iV/akZqg0OUNUpH4tHfme8SnHA4PIXA==}
peerDependencies:
vite: ^2.0.0
dependencies:

View File

@@ -1,4 +1,4 @@
import type React from 'react';
import React from 'react';
import { DeviceStatus } from '@app/components/menu/buttons/DeviceStatus';
import { useAppSelector } from '@app/hooks/redux';
@@ -9,17 +9,23 @@ import { Logo } from '@components/menu/Logo';
import { MobileNav } from '@components/menu/MobileNav';
import { Navigation } from '@components/menu/Navigation';
import { useRoute } from '@core/router';
import { requestNotificationPermission } from '@core/utils/notifications';
import { Messages } from '@pages/Messages';
import { Nodes } from '@pages/Nodes/Index';
import { NotFound } from '@pages/NotFound';
import { Plugins } from '@pages/Plugins/Index';
import { Settings } from '@pages/settings/Index';
import { NotFound } from './pages/NotFound';
import { Plugins } from './pages/Plugins/Index';
export const App = (): JSX.Element => {
const route = useRoute();
const darkMode = useAppSelector((state) => state.app.darkMode);
React.useEffect(() => {
requestNotificationPermission().catch((e) => {
console.log(e);
});
}, []);
return (
<div className={`h-screen w-screen ${darkMode ? 'dark' : ''}`}>
<Connection />

View File

@@ -1,12 +1,13 @@
import React from 'react';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { BLE } from '@components/connection/BLE';
import { HTTP } from '@components/connection/HTTP';
import { Serial } from '@components/connection/Serial';
import { Button } from '@components/generic/Button';
import { Card } from '@components/generic/Card';
import { Select } from '@components/generic/form/Select';
import { Modal } from '@components/generic/Modal';
import { DeviceStatus } from '@components/menu/buttons/DeviceStatus';
import { connection, connectionUrl, setConnection } from '@core/connection';
import {
closeConnectionModal,
@@ -16,9 +17,6 @@ import {
} from '@core/slices/appSlice';
import { Types } from '@meshtastic/meshtasticjs';
import { BLE } from './connection/BLE';
import { HTTP } from './connection/HTTP';
export const Connection = (): JSX.Element => {
const dispatch = useAppDispatch();
@@ -58,23 +56,25 @@ export const Connection = (): JSX.Element => {
>
<Card>
<div className="w-full max-w-3xl p-10">
<div className="flex justify-between w-full border rounded-md">
<div className="p-2">
<DeviceStatus />
{state.deviceStatus === Types.DeviceStatusEnum.DEVICE_DISCONNECTED ? (
<div className="space-y-2">
<Select
label="Connection Method"
optionsEnum={connType}
value={appState.connType}
onChange={(e): void => {
dispatch(setConnType(parseInt(e.target.value)));
}}
/>
{appState.connType === connType.HTTP && <HTTP />}
{appState.connType === connType.BLE && <BLE />}
{appState.connType === connType.SERIAL && <Serial />}
</div>
<div className="p-2 my-auto">
) : (
<div>
<span>Connecting...</span>
{state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_DISCONNECTED ? (
<Button
padding={2}
border
onClick={async (): Promise<void> => {
await setConnection(appState.connType);
}}
>
Connect
</Button>
) : (
Types.DeviceStatusEnum.DEVICE_CONNECTED && (
<Button
padding={2}
border
@@ -86,22 +86,6 @@ export const Connection = (): JSX.Element => {
</Button>
)}
</div>
</div>
{state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_DISCONNECTED && (
<form className="space-y-2">
<Select
label="Method"
optionsEnum={connType}
value={appState.connType}
onChange={(e): void => {
dispatch(setConnType(parseInt(e.target.value)));
}}
/>
{appState.connType === connType.HTTP && <HTTP />}
{appState.connType === connType.BLE && <BLE />}
{appState.connType === connType.SERIAL && <Serial />}
</form>
)}
</div>
</Card>

View File

@@ -1,14 +1,20 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { FiCheck } from 'react-icons/fi';
import { connType } from '@app/core/slices/appSlice';
import { Button } from '@components/generic/Button';
import { IconButton } from '@components/generic/IconButton';
import { ble, setConnection } from '@core/connection';
export const BLE = (): JSX.Element => {
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
const { register, handleSubmit, control } = useForm<{
device?: BluetoothDevice;
}>();
const updateBleDeviceList = React.useCallback(async (): Promise<void> => {
const devices = await ble.getDevices();
setBleDevices(devices);
@@ -18,8 +24,12 @@ export const BLE = (): JSX.Element => {
void updateBleDeviceList();
}, [updateBleDeviceList]);
const onSubmit = handleSubmit(async (data) => {
await setConnection(connType.BLE);
});
return (
<div className="space-y-2">
<form onSubmit={onSubmit} className="space-y-2">
{bleDevices.map((device, index) => (
<div
onClick={async (): Promise<void> => {
@@ -37,6 +47,9 @@ export const BLE = (): JSX.Element => {
/>
</div>
))}
</div>
<Button type="submit" className="mt-2 ml-auto" border>
Connect
</Button>
</form>
);
};

View File

@@ -1,17 +1,52 @@
import React from 'react';
import type React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useAppDispatch } from '@app/hooks/redux.js';
import { Button } from '@components/generic/Button';
import { Checkbox } from '@components/generic/form/Checkbox';
import { Input } from '@components/generic/form/Input';
import { Select } from '@components/generic/form/Select';
import { connectionUrl } from '@core/connection';
import { connectionUrl, setConnection } from '@core/connection';
import { connType, setConnectionParams } from '@core/slices/appSlice';
export const HTTP = (): JSX.Element => {
const [httpIpSource, setHttpIpSource] = React.useState<'local' | 'remote'>(
'local',
);
const dispatch = useAppDispatch();
const { register, handleSubmit, control } = useForm<{
ipSource: 'local' | 'remote';
ip?: string;
tls: boolean;
}>({
defaultValues: {
ipSource: 'local',
ip: connectionUrl,
tls: false,
},
});
const watchIpSource = useWatch({
control,
name: 'ipSource',
defaultValue: 'local',
});
const onSubmit = handleSubmit(async (data) => {
dispatch(
setConnectionParams({
type: connType.HTTP,
params: {
address: data.ip ?? connectionUrl,
tls: data.tls,
fetchInterval: 2000,
},
}),
);
await setConnection(connType.HTTP);
});
return (
<div>
<form onSubmit={onSubmit}>
<Select
label="Host Source"
options={[
@@ -24,17 +59,17 @@ export const HTTP = (): JSX.Element => {
value: 'remote',
},
]}
value={httpIpSource}
onChange={(e): void => {
setHttpIpSource(e.target.value as 'local' | 'remote');
}}
{...register('ipSource')}
/>
{httpIpSource === 'local' ? (
{watchIpSource === 'local' ? (
<Input label="Host" value={connectionUrl} disabled />
) : (
<Input label="Host" />
<Input label="Host" {...register('ip')} />
)}
<Checkbox label="Use TLS?" />
</div>
<Checkbox label="Use TLS?" {...register('tls')} />
<Button type="submit" className="mt-2 ml-auto" border>
Connect
</Button>
</form>
);
};

View File

@@ -1,7 +1,9 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { FiCheck } from 'react-icons/fi';
import { Button } from '@components/generic/Button';
import { IconButton } from '@components/generic/IconButton';
import { serial, setConnection } from '@core/connection';
import { connType } from '@core/slices/appSlice';
@@ -9,6 +11,10 @@ import { connType } from '@core/slices/appSlice';
export const Serial = (): JSX.Element => {
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
const { register, handleSubmit, control } = useForm<{
device?: SerialPort;
}>();
const updateSerialDeviceList = React.useCallback(async (): Promise<void> => {
const devices = await serial.getPorts();
setSerialDevices(devices);
@@ -18,8 +24,12 @@ export const Serial = (): JSX.Element => {
void updateSerialDeviceList();
}, [updateSerialDeviceList]);
const onSubmit = handleSubmit(async (data) => {
await setConnection(connType.SERIAL);
});
return (
<div className="space-y-2">
<form onSubmit={onSubmit} className="space-y-2">
{serialDevices.map((device, index) => (
<div
className="flex justify-between p-2 bg-gray-700 rounded-md"
@@ -41,6 +51,9 @@ export const Serial = (): JSX.Element => {
/>
</div>
))}
</div>
<Button type="submit" className="mt-2 ml-auto" border>
Connect
</Button>
</form>
);
};

View File

@@ -23,6 +23,8 @@ import {
Types,
} from '@meshtastic/meshtasticjs';
import { showNotification } from './utils/notifications.js';
type connectionType = IBLEConnection | IHTTPConnection | ISerialConnection;
export let connection: connectionType = new IHTTPConnection();
@@ -151,6 +153,7 @@ const registerListeners = (): void => {
connection.onTextPacket.subscribe((message) => {
const myNodeNum = store.getState().meshtastic.radio.hardware.myNodeNum;
showNotification('New message', message.data);
store.dispatch(
addMessage({

View File

@@ -0,0 +1,12 @@
export const requestNotificationPermission = async (): Promise<void> => {
if (window.Notification && Notification.permission !== 'denied') {
await Notification.requestPermission();
}
};
export const showNotification = (title: string, body: string): void => {
new Notification(title, {
body,
icon: 'android-512.png',
});
};