Browse Source

Misc fixes

pull/2/head
Sacha Weatherstone 5 years ago
parent
commit
87dcf35547
  1. 9
      package.json
  2. 107
      pnpm-lock.yaml
  3. 39
      src/App.tsx
  4. 1
      src/components/Connection.tsx
  5. 12
      src/components/connection/Serial.tsx
  6. 22
      src/components/generic/Button.tsx
  7. 8
      src/components/menu/Navigation.tsx
  8. 2
      src/components/menu/buttons/DeviceStatus.tsx
  9. 84
      src/components/menu/buttons/Notifications.tsx
  10. 3
      src/core/connection.ts
  11. 26
      src/core/slices/appSlice.ts
  12. 13
      src/pages/settings/Channels.tsx
  13. 1
      src/pages/settings/Interface.tsx
  14. 1
      src/pages/settings/WiFi.tsx

9
package.json

@ -12,12 +12,13 @@
"lint": "eslint 'src/**/*.{ts,tsx}'"
},
"dependencies": {
"@floating-ui/react-dom": "^0.3.3",
"@headlessui/react": "^1.4.2",
"@meshtastic/meshtasticjs": "^0.6.35",
"@reduxjs/toolkit": "^1.7.1",
"base64-js": "^1.5.1",
"boring-avatars": "^1.5.8",
"i18next": "^21.6.0",
"i18next": "^21.6.2",
"i18next-browser-languagedetector": "^6.1.2",
"mapbox-gl": "^2.6.1",
"react": "^17.0.2",
@ -42,7 +43,7 @@
"@types/react-dom": "^17.0.11",
"@types/react-file-icon": "^1.0.1",
"@types/w3c-web-serial": "^1.0.2",
"@types/web-bluetooth": "^0.0.11",
"@types/web-bluetooth": "^0.0.12",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"@verypossible/eslint-config": "^1.6.1",
@ -60,10 +61,10 @@
"gzipper": "^6.0.0",
"postcss": "^8.4.5",
"prettier": "^2.5.1",
"tailwindcss": "^3.0.5",
"tailwindcss": "^3.0.6",
"tar": "^6.1.11",
"typescript": "^4.5.4",
"vite": "^2.7.2",
"vite": "^2.7.3",
"vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-pwa": "^0.11.12",
"workbox-window": "^6.4.2"

107
pnpm-lock.yaml

@ -1,6 +1,7 @@
lockfileVersion: 5.3
specifiers:
'@floating-ui/react-dom': ^0.3.3
'@headlessui/react': ^1.4.2
'@meshtastic/meshtasticjs': ^0.6.35
'@reduxjs/toolkit': ^1.7.1
@ -9,7 +10,7 @@ specifiers:
'@types/react-dom': ^17.0.11
'@types/react-file-icon': ^1.0.1
'@types/w3c-web-serial': ^1.0.2
'@types/web-bluetooth': ^0.0.11
'@types/web-bluetooth': ^0.0.12
'@typescript-eslint/eslint-plugin': ^5.7.0
'@typescript-eslint/parser': ^5.7.0
'@verypossible/eslint-config': ^1.6.1
@ -27,7 +28,7 @@ specifiers:
eslint-plugin-react: ^7.27.1
eslint-plugin-react-hooks: ^4.3.0
gzipper: ^6.0.0
i18next: ^21.6.0
i18next: ^21.6.2
i18next-browser-languagedetector: ^6.1.2
mapbox-gl: ^2.6.1
postcss: ^8.4.5
@ -44,31 +45,32 @@ specifiers:
react-select: ^5.2.1
rfc4648: ^1.5.0
swr: ^1.1.2-beta.0
tailwindcss: ^3.0.5
tailwindcss: ^3.0.6
tar: ^6.1.11
timeago-react: ^3.0.4
type-route: ^0.6.0
typescript: ^4.5.4
use-breakpoint: ^3.0.0
vite: ^2.7.2
vite: ^2.7.3
vite-plugin-cdn-import: ^0.3.5
vite-plugin-pwa: ^0.11.12
workbox-window: ^6.4.2
dependencies:
'@floating-ui/react-dom': 0.3.3_5539cae010396b202b015f3568914e95
'@headlessui/react': 1.4[email protected][email protected]
'@meshtastic/meshtasticjs': 0.6.35
'@meshtastic/meshtasticjs': link:../meshtastic.js
'@reduxjs/toolkit': 1.7[email protected][email protected]
base64-js: 1.5.1
boring-avatars: 1.5.8
i18next: 21.6.0
i18next: 21.6.2
i18next-browser-languagedetector: 6.1.2
mapbox-gl: 2.6.1
react: 17.0.2
react-dom: 17.0[email protected]
react-file-icon: 1.1[email protected][email protected]
react-hook-form: 7.22[email protected]
react-i18next: 11.15[email protected].0[email protected]
react-i18next: 11.15[email protected].2[email protected]
react-icons: 4.3[email protected]
react-json-pretty: 2.2[email protected][email protected]
react-qr-code: 2.0[email protected]
@ -86,7 +88,7 @@ devDependencies:
'@types/react-dom': 17.0.11
'@types/react-file-icon': 1.0.1
'@types/w3c-web-serial': 1.0.2
'@types/web-bluetooth': 0.0.11
'@types/web-bluetooth': 0.0.12
'@typescript-eslint/eslint-plugin': 5.7.0_915acdfead96f701b1277a1a723fc8d4
'@typescript-eslint/parser': 5.7[email protected][email protected]
'@verypossible/eslint-config': 1.6[email protected]
@ -104,12 +106,12 @@ devDependencies:
gzipper: 6.0.0
postcss: 8.4.5
prettier: 2.5.1
tailwindcss: 3.0.5_16a290f6d0e3717bf6d2667234aebd30
tailwindcss: 3.0.6_16a290f6d0e3717bf6d2667234aebd30
tar: 6.1.11
typescript: 4.5.4
vite: 2.7.2
vite: 2.7.3
vite-plugin-cdn-import: 0.3.5
vite-plugin-pwa: 0.11[email protected].2
vite-plugin-pwa: 0.11[email protected].3
workbox-window: 6.4.2
packages:
@ -1410,6 +1412,30 @@ packages:
- supports-color
dev: true
/@floating-ui/core/0.2.1:
resolution: {integrity: sha512-fhFNlxBdiA8RVdVcZUllwvIgwvj3QL1o5MxthjgPmkc6nizu1/qwTVMhpr9QRdkF6DwNQnu0rvgb0r7Ml3i4mw==}
dev: false
/@floating-ui/dom/0.1.7:
resolution: {integrity: sha512-aA0i/ov24v+f6YCobJecoDBcOikn1W7+q9txSXQmezfPEJ6P9QFRrFvuYGaO0EevgexmJ3PlrfSR4dKhNHRP0Q==}
dependencies:
'@floating-ui/core': 0.2.1
dev: false
/@floating-ui/react-dom/0.3.3_5539cae010396b202b015f3568914e95:
resolution: {integrity: sha512-ALPyo9zJkRKsUFHNXtvBVB1B5zOhDfLtq4eJHYMt6DHt4wPiHgCoM8XtMiFz5s5P3cbznkNa6cUUIRn9j2F5/A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@floating-ui/dom': 0.1.7
react: 17.0.2
react-dom: 17.0[email protected]
use-isomorphic-layout-effect: 1.1.1_cfedea9b3ed0faf0dded75c187406c5e
transitivePeerDependencies:
- '@types/react'
dev: false
/@headlessui/react/[email protected][email protected]:
resolution: {integrity: sha512-N8tv7kLhg9qGKBkVdtg572BvKvWhmiudmeEpOCyNwzOsZHCXBtl8AazGikIfUS+vBoub20Fse3BjawXDVPPdug==}
engines: {node: '>=10'}
@ -1491,13 +1517,6 @@ packages:
engines: {node: '>=6.0.0'}
dev: false
/@meshtastic/meshtasticjs/0.6.35:
resolution: {integrity: sha512-vr6t3/VvgVynmVevZnRsJc+G/TvmP5eTeIZHOrC1N+SX0r/CIs4IbZFTqp5frvwJYqyyBXSxixm5JN5+byFS9A==}
dependencies:
'@protobuf-ts/runtime': 2.1.0
sub-events: 1.8.9
dev: false
/@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -1519,10 +1538,6 @@ packages:
fastq: 1.13.0
dev: true
/@protobuf-ts/runtime/2.1.0:
resolution: {integrity: sha512-HZwkgJW9SGiE9+0lWKr1X997tmG01/40j+hr9yBVk+hTQcm7Hsf77XhMNtsDjWUOcspG6GBXu8o3g4i3kD5/zQ==}
dev: false
/@reduxjs/toolkit/[email protected][email protected]:
resolution: {integrity: sha512-wXwXYjBVz/ItxB7SMzEAMmEE/FBiY1ze18N+VVVX7NtVbRUrdOGKhpQMHivIJfkbJvSdLUU923a/yAagJQzY0Q==}
peerDependencies:
@ -1708,8 +1723,8 @@ packages:
resolution: {integrity: sha512-Ftx4BtLxgAnel7V7GbHylCYjSq827A+jeEE3SnTS7huCGUN0pSwUn+CchTCT9TkZj9w+NVMUq4Bk2R0GvUNmAQ==}
dev: true
/@types/web-bluetooth/0.0.11:
resolution: {integrity: sha512-2CF3Kk2Rcvg/c2QzO7mXUhY7eL9CC3aKzrF+dNWNmp7Q8bmlvjmUM1nFPMSngawdJ+CcIdu8eJlQRytBgAZR9w==}
/@types/web-bluetooth/0.0.12:
resolution: {integrity: sha512-Q4sUrowpylCOWjUWFtNwkNBHQUnCL8y44Iq2ZLtVMAuAGIwPSXa3kqIvu2LwXoc2d1jyaaw4Bg354degHrsADw==}
dev: true
/@typescript-eslint/eslint-plugin/4.33.0_3289a875d95a672b97ebf589745c66ef:
@ -2224,7 +2239,7 @@ packages:
hasBin: true
dependencies:
caniuse-lite: 1.0.30001287
electron-to-chromium: 1.4.20
electron-to-chromium: 1.4.22
escalade: 3.1.1
node-releases: 2.0.1
picocolors: 1.0.0
@ -2509,8 +2524,8 @@ packages:
jake: 10.8.2
dev: true
/electron-to-chromium/1.4.20:
resolution: {integrity: sha512-N7ZVNrdzX8NE90OXEFBMsBf3fp8P/vVDUER3WCUZjzC7OkNTXHVoF6W9qVhq8+dA8tGnbDajzUpj2ISNVVyj+Q==}
/electron-to-chromium/1.4.22:
resolution: {integrity: sha512-IiW8cV8eyjMhuWqk9wwHRPOVN+5Fa7NHOTjogrwg2H9TNiLVA8ywjOJnVKoywaqUHryDUOpK7Mg6P1FETisi0g==}
dev: true
/emoji-regex/8.0.0:
@ -3461,8 +3476,8 @@ packages:
'@babel/runtime': 7.16.5
dev: false
/i18next/21.6.0:
resolution: {integrity: sha512-RjNuACL35wWZgtkyMcjcCmK7R72u3P6jTNbGKzrvHGI9M0iK5Vn1DsBIwOByppaXLIbe0viJ79Nz2h8w1UwPoQ==}
/i18next/21.6.2:
resolution: {integrity: sha512-S1zSTrs2lgPH8iG68pMWh3P7OzQMcs3+Qr5F39JTyGbe/xl5cW4cF8MTPKSy6F25tFTcKBq915IoEZUWewkldg==}
dependencies:
'@babel/runtime': 7.16.5
dev: false
@ -4377,7 +4392,7 @@ packages:
react: 17.0.2
dev: false
/react-i18next/[email protected].0[email protected]:
/react-i18next/[email protected].2[email protected]:
resolution: {integrity: sha512-lnje1uKu5XeM5MLvfbt1oygF+nEIZnpOM4Iu8bkx5ECD4XRYgi3SJDmolrp0EDxDHeK2GgFb+vEEK0hsZ9sjeA==}
peerDependencies:
i18next: '>= 19.0.0'
@ -4386,7 +4401,7 @@ packages:
'@babel/runtime': 7.16.5
html-escaper: 2.0.2
html-parse-stringify: 3.0.1
i18next: 21.6.0
i18next: 21.6.2
react: 17.0.2
dev: false
@ -4860,11 +4875,6 @@ packages:
resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}
dev: false
/sub-events/1.8.9:
resolution: {integrity: sha512-RhhA2amqVzL6nO+aiZOqxBCgcA3ZLfp4W9iHFUELwq8132TS7pUReJV+bcRjtNKdqm/Ep1sD/h01eAcTBtgrBQ==}
engines: {node: '>=10.0.0'}
dev: false
/supercluster/7.1.4:
resolution: {integrity: sha512-GhKkRM1jMR6WUwGPw05fs66pOFWhf59lXq+Q3J3SxPvhNcmgOtLRV6aVQPMRsmXdpaeFJGivt+t7QXUPL3ff4g==}
dependencies:
@ -4904,8 +4914,8 @@ packages:
strip-ansi: 6.0.1
dev: true
/tailwindcss/3.0.5_16a290f6d0e3717bf6d2667234aebd30:
resolution: {integrity: sha512-59pNgzx2o+wkAk7IZGIH7H9eNS53gzZGrO3+NPyOEWHDbquHgiLL/c993T5t1vPSAeBxox4X5OgZwNuRvXVf+g==}
/tailwindcss/3.0.6_16a290f6d0e3717bf6d2667234aebd30:
resolution: {integrity: sha512-+CA2f09rbHFDsdQ1iDvsOGbF1tZFmyPoRhUeaF9/5FRT5GYObtp+UjTSCdmeDcu6T90bx4WAaOkddYFPBkjbAA==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
@ -5149,6 +5159,19 @@ packages:
react-dom: 17.0[email protected]
dev: false
/use-isomorphic-layout-effect/1.1.1_cfedea9b3ed0faf0dded75c187406c5e:
resolution: {integrity: sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 17.0.37
react: 17.0.2
dev: false
/util-deprecate/1.0.2:
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
dev: true
@ -5170,7 +5193,7 @@ packages:
- rollup
dev: true
/vite-plugin-pwa/[email protected].2:
/vite-plugin-pwa/[email protected].3:
resolution: {integrity: sha512-XqFmA4y9C4RBb5osSsa26GVwOSwbzf2GNVcT5+06KYYdguqLpuI9FW7iV/akZqg0OUNUpH4tHfme8SnHA4PIXA==}
peerDependencies:
vite: ^2.0.0
@ -5179,7 +5202,7 @@ packages:
fast-glob: 3.2.7
pretty-bytes: 5.6.0
rollup: 2.61.1
vite: 2.7.2
vite: 2.7.3
workbox-build: 6.4.2
workbox-window: 6.4.2
transitivePeerDependencies:
@ -5188,8 +5211,8 @@ packages:
- supports-color
dev: true
/vite/2.7.2:
resolution: {integrity: sha512-wMffVVdKZRZP/HwW3yttKL8X+IJePz7bUcnGm0vqljffpVwHpjWC3duZtJQHAGvy+wrTjmwU7vkULpZ1dVXY6w==}
/vite/2.7.3:
resolution: {integrity: sha512-GAY1P+9fLJOju1SRm8+hykVnEXog+E+KXuqqyMBQDriKCUIKzWnPn142yNNhSdf/ixYGYdUa5ce3A8WaEajzGw==}
engines: {node: '>=12.2.0'}
hasBin: true
peerDependencies:

39
src/App.tsx

@ -1,9 +1,12 @@
import React from 'react';
import { FiBell } from 'react-icons/fi';
import { DeviceStatus } from '@app/components/menu/buttons/DeviceStatus';
import { useAppSelector } from '@app/hooks/redux';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { Connection } from '@components/Connection';
import { MobileNavToggle } from '@components/menu/buttons/MobileNavToggle';
import { Notifications } from '@components/menu/buttons/Notifications';
import { ThemeToggle } from '@components/menu/buttons/ThemeToggle';
import { Logo } from '@components/menu/Logo';
import { MobileNav } from '@components/menu/MobileNav';
@ -16,22 +19,51 @@ import { NotFound } from '@pages/NotFound';
import { Plugins } from '@pages/Plugins/Index';
import { Settings } from '@pages/settings/Index';
import { addNotification, removeNotification } from './core/slices/appSlice.js';
export const App = (): JSX.Element => {
const route = useRoute();
const dispatch = useAppDispatch();
const notifications = useAppSelector((state) => state.app.notifications);
const darkMode = useAppSelector((state) => state.app.darkMode);
React.useEffect(() => {
if (
Notification.permission !== 'granted' &&
notifications.findIndex((n) => n.id === 'notification-permission') === -1
) {
dispatch(
addNotification({
id: 'notification-permission',
icon: <FiBell className="w-4 h-4" />,
read: Notification.permission === 'denied',
title: 'Enable Push Notifications',
action: {
message: 'Enable',
action: async () => await requestNotificationPermission(),
},
}),
);
}
// Notification.permission === ''
requestNotificationPermission().catch((e) => {
console.log(e);
});
}, []);
}, [dispatch, notifications]);
React.useEffect(() => {
if (Notification.permission === 'granted') {
dispatch(removeNotification('notification-permission'));
}
}, [dispatch]);
return (
<div className={`h-screen w-screen ${darkMode ? 'dark' : ''}`}>
<Connection />
<div className="flex flex-col h-full bg-gray-200 dark:bg-primaryDark">
<div className="flex flex-shrink-0 overflow-hidden bg-primary dark:bg-primary">
<div className="w-full overflow-hidden bg-white border-b border-gray-300 md:mt-6 md:mx-6 md:pt-4 md:pb-3 md:rounded-t-3xl dark:border-gray-600 md:shadow-md dark:bg-primaryDark">
<div className="w-full overflow-hidden bg-white border-b border-gray-300 md:mt-6 md:mx-6 md:py-2 md:rounded-t-3xl dark:border-gray-600 md:shadow-md dark:bg-primaryDark">
<div className="flex items-center justify-between h-12 px-4 md:px-6">
<div className="hidden md:flex">
<Logo />
@ -40,6 +72,7 @@ export const App = (): JSX.Element => {
<MobileNavToggle />
<div className="flex items-center space-x-2">
<DeviceStatus />
<Notifications />
<ThemeToggle />
</div>
</div>

1
src/components/Connection.tsx

@ -76,7 +76,6 @@ export const Connection = (): JSX.Element => {
{state.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED && (
<Button
padding={2}
border
onClick={async (): Promise<void> => {
await connection.disconnect();

12
src/components/connection/Serial.tsx

@ -3,13 +3,15 @@ import React from 'react';
import { useForm } from 'react-hook-form';
import { FiCheck } from 'react-icons/fi';
import { useAppDispatch } from '@app/hooks/redux';
import { Button } from '@components/generic/Button';
import { IconButton } from '@components/generic/IconButton';
import { serial, setConnection } from '@core/connection';
import { connType } from '@core/slices/appSlice';
import { connType, setConnectionParams } from '@core/slices/appSlice';
export const Serial = (): JSX.Element => {
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
const dispatch = useAppDispatch();
const { register, handleSubmit, control } = useForm<{
device?: SerialPort;
@ -45,6 +47,14 @@ export const Serial = (): JSX.Element => {
</div>
<IconButton
onClick={async (): Promise<void> => {
dispatch(
setConnectionParams({
type: connType.SERIAL,
params: {
port: device,
},
}),
);
await setConnection(connType.SERIAL);
}}
icon={<FiCheck />}

22
src/components/generic/Button.tsx

@ -6,23 +6,21 @@ type DefaultButtonProps = JSX.IntrinsicElements['button'];
interface ButtonProps extends DefaultButtonProps {
icon?: JSX.Element;
circle?: boolean;
active?: boolean;
border?: boolean;
padding?: number;
padding?: string;
confirmAction?: () => void;
}
export const Button = ({
icon,
circle,
className,
active,
border,
confirmAction,
disabled,
children,
padding = 3,
padding = '2',
...props
}: ButtonProps): JSX.Element => {
const [hasConfirmed, setHasConfirmed] = React.useState(false);
@ -42,27 +40,17 @@ export const Button = ({
return (
<button
onClick={handleConfirm}
className={`items-center select-none flex dark:text-white active:scale-95 transition duration-200 ease-in-out ${
className={`items-center select-none flex border border-transparent dark:text-white active:scale-95 transition duration-200 ease-in-out focus-within:border-primary dark:focus-within:border-primary focus-within:shadow-border rounded-md p-${padding} space-x-3 text-sm ${
active && !disabled ? 'bg-gray-100 dark:bg-gray-700' : ''
} ${
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'
: 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-md'
} ${
border ? 'border border-gray-400 dark:border-gray-200' : ''
} ${className}`}
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${className}`}
{...props}
>
{icon && (
<div
className={`text-gray-500 dark:text-gray-400 ${
circle ? 'mx-auto' : ''
}`}
>
<div className="text-gray-500 dark:text-gray-400">
{hasConfirmed ? <FiCheck /> : icon}
</div>
)}

8
src/components/menu/Navigation.tsx

@ -22,7 +22,7 @@ export const Navigation = ({
>
<div onClick={onClick}>
<Button
icon={<FiMessageSquare className="w-6 h-6" />}
icon={<FiMessageSquare className="w-5 h-5" />}
active={route.name === 'messages'}
className="w-full md:w-auto"
{...routes.messages().link}
@ -32,7 +32,7 @@ export const Navigation = ({
</div>
<div onClick={onClick}>
<Button
icon={<FiGrid className="w-6 h-6" />}
icon={<FiGrid className="w-5 h-5" />}
className="w-full md:w-auto"
active={route.name === 'nodes'}
{...routes.nodes().link}
@ -42,7 +42,7 @@ export const Navigation = ({
</div>
<div onClick={onClick}>
<Button
icon={<FiPackage className="w-6 h-6" />}
icon={<FiPackage className="w-5 h-5" />}
className="w-full md:w-auto"
active={route.name === 'plugins'}
{...routes.plugins().link}
@ -52,7 +52,7 @@ export const Navigation = ({
</div>
<div onClick={onClick}>
<Button
icon={<FiSettings className="w-6 h-6" />}
icon={<FiSettings className="w-5 h-5" />}
className="w-full md:w-auto"
active={route.name === 'settings'}
{...routes.settings().link}

2
src/components/menu/buttons/DeviceStatus.tsx

@ -14,7 +14,7 @@ export const DeviceStatus = (): JSX.Element => {
return (
<Button
padding={0}
padding="0"
active
onClick={(): void => {
dispatch(dispatch(openConnectionModal()));

84
src/components/menu/buttons/Notifications.tsx

@ -0,0 +1,84 @@
import React from 'react';
import { FiBell, FiX } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/redux';
import { Button } from '@components/generic/Button';
import { IconButton } from '@components/generic/IconButton';
import { shift, useFloating } from '@floating-ui/react-dom';
import { Popover } from '@headlessui/react';
export const Notifications = (): JSX.Element => {
const [unreadCount, setUnreadCount] = React.useState(0);
const notifications = useAppSelector((state) => state.app.notifications);
const { x, y, reference, floating, strategy } = useFloating({
placement: 'bottom',
middleware: [shift()],
});
React.useEffect(() => {
setUnreadCount(
notifications.filter((notification) => !notification.read).length,
);
}, [notifications]);
return (
<Popover>
<Popover.Button as="div" className="relative" ref={reference}>
<IconButton icon={<FiBell className="w-5 h-5" />} />
{unreadCount > 0 && (
<div className="absolute pointer-events-none top-1 right-1">
<div className="w-3 h-3 text-xs font-semibold leading-3 text-center text-white bg-orange-500 rounded-full">
{unreadCount}
</div>
</div>
)}
</Popover.Button>
<Popover.Panel
ref={floating}
style={{
position: strategy,
top: y ?? '',
left: x ?? '',
}}
className="fixed z-50 border border-gray-300 rounded-md shadow-md w-72 bg-primaryDark dark:border-gray-600"
>
<div className="divide-y divide-gray-600">
{notifications.map((notification, index) => (
<div
key={index}
className={`p-1 flex text-sm justify-between ${
notification.read
? 'text-gray-600 dark:text-gray-300'
: 'text-gray-900 dark:text-white'
}`}
>
<div className="my-auto">{notification.icon}</div>
<div className="my-auto font-light">{notification.title}</div>
<div className="flex space-x-1">
{notification.action ? (
<div className="my-auto w-18">
<Button
border
padding="0.5"
onClick={notification.action.action}
>
{notification.action.message}
</Button>
</div>
) : (
<div className="w-16" />
)}
<IconButton icon={<FiX />} />
</div>
</div>
))}
</div>
</Popover.Panel>
</Popover>
);
};

3
src/core/connection.ts

@ -34,8 +34,7 @@ export const connectionUrl = state.hostOverrideEnabled
? state.hostOverride
: import.meta.env.PROD
? window.location.hostname
: (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ??
'http://meshtastic.local';
: (import.meta.env.VITE_PUBLIC_DEVICE_IP as string) ?? 'meshtastic.local';
export const ble = new IBLEConnection();
export const serial = new ISerialConnection();

26
src/core/slices/appSlice.ts

@ -10,6 +10,17 @@ export enum connType {
SERIAL,
}
interface Notification {
id: string;
icon: React.ReactNode;
title: string;
action?: {
message: string;
action: () => void;
};
read: boolean;
}
interface AppState {
mobileNavOpen: boolean;
connectionModalOpen: boolean;
@ -21,6 +32,7 @@ interface AppState {
HTTP: Types.HTTPConnectionParameters;
SERIAL: Types.SerialConnectionParameters;
};
notifications: Notification[];
}
const initialState: AppState = {
@ -39,6 +51,7 @@ const initialState: AppState = {
},
SERIAL: {},
},
notifications: [],
};
export const appSlice = createSlice({
@ -78,6 +91,17 @@ export const appSlice = createSlice({
state.connectionParams[connType[action.payload.type]] =
action.payload.params;
},
addNotification(state, action: PayloadAction<Notification>) {
state.notifications.push(action.payload);
},
removeNotification(state, action: PayloadAction<string>) {
state.notifications.splice(
state.notifications.findIndex(
(notification) => notification.id === action.payload,
),
1,
);
},
},
});
@ -90,6 +114,8 @@ export const {
setCurrentPage,
setConnType,
setConnectionParams,
addNotification,
removeNotification,
} = appSlice.actions;
export default appSlice.reducer;

13
src/pages/settings/Channels.tsx

@ -7,7 +7,6 @@ import JSONPretty from 'react-json-pretty';
import { useAppSelector } from '@app/hooks/redux';
import { Channel } from '@components/Channel';
import { FormFooter } from '@components/FormFooter';
import { Button } from '@components/generic/Button';
import { Card } from '@components/generic/Card';
import { Cover } from '@components/generic/Cover';
import { Checkbox } from '@components/generic/form/Checkbox';
@ -249,18 +248,6 @@ export const Channels = ({
isPrimary={channel.channel.index === 0}
/>
))}
<div className="flex justify-between">
<div
onClick={(): Promise<void> => {
return connection.confirmSetChannel();
}}
className="text-sm font-thin text-gray-400 dark:text-gray-300"
>
Please ensure any changes are working before confirming
</div>
<Button active>Confirm</Button>
</div>
</div>
</Card>
</div>

1
src/pages/settings/Interface.tsx

@ -30,7 +30,6 @@ export const Interface = ({
onClick={(): void => {
setNavOpen && setNavOpen(!navOpen);
}}
circle
/>
}
footer={

1
src/pages/settings/WiFi.tsx

@ -91,6 +91,7 @@ export const WiFi = ({ navOpen, setNavOpen }: WiFiProps): JSX.Element => {
/>
<Input
type="password"
autoComplete="off"
label={t('strings.wifi_psk')}
disabled={watchWifiApMode}
{...register('wifiPassword')}

Loading…
Cancel
Save