diff --git a/package.json b/package.json index 005d0fbd..81618a26 100644 --- a/package.json +++ b/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" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9ee0aa7..c0667016 100644 --- a/pnpm-lock.yaml +++ b/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.2_react-dom@17.0.2+react@17.0.2 - '@meshtastic/meshtasticjs': 0.6.35 + '@meshtastic/meshtasticjs': link:../meshtastic.js '@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 + i18next: 21.6.2 i18next-browser-languagedetector: 6.1.2 mapbox-gl: 2.6.1 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 react-file-icon: 1.1.0_react-dom@17.0.2+react@17.0.2 react-hook-form: 7.22.1_react@17.0.2 - react-i18next: 11.15.1_i18next@21.6.0+react@17.0.2 + react-i18next: 11.15.1_i18next@21.6.2+react@17.0.2 react-icons: 4.3.1_react@17.0.2 react-json-pretty: 2.2.0_react-dom@17.0.2+react@17.0.2 react-qr-code: 2.0.3_react@17.0.2 @@ -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.0_eslint@8.4.1+typescript@4.5.4 '@verypossible/eslint-config': 1.6.1_typescript@4.5.4 @@ -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.12_vite@2.7.2 + vite-plugin-pwa: 0.11.12_vite@2.7.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.2_react@17.0.2 + use-isomorphic-layout-effect: 1.1.1_cfedea9b3ed0faf0dded75c187406c5e + transitivePeerDependencies: + - '@types/react' + dev: false + /@headlessui/react/1.4.2_react-dom@17.0.2+react@17.0.2: 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/1.7.1_react-redux@7.2.6+react@17.0.2: 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/11.15.1_i18next@21.6.0+react@17.0.2: + /react-i18next/11.15.1_i18next@21.6.2+react@17.0.2: 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.2_react@17.0.2 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/0.11.12_vite@2.7.2: + /vite-plugin-pwa/0.11.12_vite@2.7.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: diff --git a/src/App.tsx b/src/App.tsx index 45c4ff0b..87acada9 100644 --- a/src/App.tsx +++ b/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: , + 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 (
-
+
@@ -40,6 +72,7 @@ export const App = (): JSX.Element => {
+
diff --git a/src/components/Connection.tsx b/src/components/Connection.tsx index be9cdca3..cc07f2c4 100644 --- a/src/components/Connection.tsx +++ b/src/components/Connection.tsx @@ -76,7 +76,6 @@ export const Connection = (): JSX.Element => { {state.deviceStatus === Types.DeviceStatusEnum.DEVICE_CONNECTED && (
=> { + dispatch( + setConnectionParams({ + type: connType.SERIAL, + params: { + port: device, + }, + }), + ); await setConnection(connType.SERIAL); }} icon={} diff --git a/src/components/generic/Button.tsx b/src/components/generic/Button.tsx index 51dad900..8e662a76 100644 --- a/src/components/generic/Button.tsx +++ b/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 (
+
+ ) : ( +
+ )} + } /> +
+
+ ))} +
+ + + ); +}; diff --git a/src/core/connection.ts b/src/core/connection.ts index 7d2d54ae..646ed8ce 100644 --- a/src/core/connection.ts +++ b/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(); diff --git a/src/core/slices/appSlice.ts b/src/core/slices/appSlice.ts index 2c53b6b3..799aeda8 100644 --- a/src/core/slices/appSlice.ts +++ b/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) { + state.notifications.push(action.payload); + }, + removeNotification(state, action: PayloadAction) { + 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; diff --git a/src/pages/settings/Channels.tsx b/src/pages/settings/Channels.tsx index c82b867a..80f6837b 100644 --- a/src/pages/settings/Channels.tsx +++ b/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} /> ))} - -
-
=> { - return connection.confirmSetChannel(); - }} - className="text-sm font-thin text-gray-400 dark:text-gray-300" - > - Please ensure any changes are working before confirming -
- -
diff --git a/src/pages/settings/Interface.tsx b/src/pages/settings/Interface.tsx index 0c774baa..1ae9dac9 100644 --- a/src/pages/settings/Interface.tsx +++ b/src/pages/settings/Interface.tsx @@ -30,7 +30,6 @@ export const Interface = ({ onClick={(): void => { setNavOpen && setNavOpen(!navOpen); }} - circle /> } footer={ diff --git a/src/pages/settings/WiFi.tsx b/src/pages/settings/WiFi.tsx index 23c8ec0b..6a8280b5 100644 --- a/src/pages/settings/WiFi.tsx +++ b/src/pages/settings/WiFi.tsx @@ -91,6 +91,7 @@ export const WiFi = ({ navOpen, setNavOpen }: WiFiProps): JSX.Element => { />