Browse Source

COnnection modal fix & notification basics

pull/2/head
Sacha Weatherstone 5 years ago
parent
commit
8b221e28c5
  1. 3
      .env.example
  2. 8
      package.json
  3. 62
      pnpm-lock.yaml
  4. 14
      src/App.tsx
  5. 54
      src/components/Connection.tsx
  6. 17
      src/components/connection/BLE.tsx
  7. 63
      src/components/connection/HTTP.tsx
  8. 17
      src/components/connection/Serial.tsx
  9. 3
      src/core/connection.ts
  10. 12
      src/core/utils/notifications.ts

3
.env.example

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

8
package.json

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

62
pnpm-lock.yaml

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

14
src/App.tsx

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

54
src/components/Connection.tsx

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

17
src/components/connection/BLE.tsx

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

63
src/components/connection/HTTP.tsx

@ -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 { Checkbox } from '@components/generic/form/Checkbox';
import { Input } from '@components/generic/form/Input'; import { Input } from '@components/generic/form/Input';
import { Select } from '@components/generic/form/Select'; 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 => { export const HTTP = (): JSX.Element => {
const [httpIpSource, setHttpIpSource] = React.useState<'local' | 'remote'>( const dispatch = useAppDispatch();
'local',
); 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 ( return (
<div> <form onSubmit={onSubmit}>
<Select <Select
label="Host Source" label="Host Source"
options={[ options={[
@ -24,17 +59,17 @@ export const HTTP = (): JSX.Element => {
value: 'remote', value: 'remote',
}, },
]} ]}
value={httpIpSource} {...register('ipSource')}
onChange={(e): void => {
setHttpIpSource(e.target.value as 'local' | 'remote');
}}
/> />
{httpIpSource === 'local' ? ( {watchIpSource === 'local' ? (
<Input label="Host" value={connectionUrl} disabled /> <Input label="Host" value={connectionUrl} disabled />
) : ( ) : (
<Input label="Host" /> <Input label="Host" {...register('ip')} />
)} )}
<Checkbox label="Use TLS?" /> <Checkbox label="Use TLS?" {...register('tls')} />
</div> <Button type="submit" className="mt-2 ml-auto" border>
Connect
</Button>
</form>
); );
}; };

17
src/components/connection/Serial.tsx

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

3
src/core/connection.ts

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

12
src/core/utils/notifications.ts

@ -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',
});
};
Loading…
Cancel
Save