Switch to Vite + improvements
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
SNOWPACK_PUBLIC_DEVICE_IP=
|
VITE_PUBLIC_DEVICE_IP=
|
||||||
@@ -33,7 +33,7 @@ cp ./.env.example ./.env
|
|||||||
And define the device IP address in the `.env` file.
|
And define the device IP address in the `.env` file.
|
||||||
|
|
||||||
```
|
```
|
||||||
SNOWPACK_PUBLIC_DEVICE_IP=xxx.xxx.xxx.xxx
|
VITE_PUBLIC_DEVICE_IP=xxx.xxx.xxx.xxx
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the dependencies.
|
Install the dependencies.
|
||||||
@@ -45,5 +45,5 @@ pnpm i
|
|||||||
Start the developtment server:
|
Start the developtment server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm start
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|||||||
+8
-5
@@ -2,7 +2,13 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
<link rel="shortcut icon" href="/favicon.ico" type="image/svg+xml" />
|
||||||
|
<link
|
||||||
|
rel="alternate icon"
|
||||||
|
href="/safari-tab.svg"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
/>
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/touch-icon.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="/touch-icon.png" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
@@ -14,10 +20,7 @@
|
|||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta name="description" content="Meshtastic Web App" />
|
||||||
name="description"
|
|
||||||
content="Web site created using create-snowpack-app"
|
|
||||||
/>
|
|
||||||
<title>Meshtastic Web</title>
|
<title>Meshtastic Web</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+9
-8
@@ -7,13 +7,13 @@
|
|||||||
"dev": "NODE_ENV=development vite",
|
"dev": "NODE_ENV=development vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,woff,woff2 dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)",
|
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)",
|
||||||
"format": "prettier --write 'src/**/*.{ts,tsx}'",
|
"format": "prettier --write 'src/**/*.{ts,tsx}'",
|
||||||
"lint": "eslint 'src/**/*.{ts,tsx}'"
|
"lint": "eslint 'src/**/*.{ts,tsx}'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.4.2",
|
"@headlessui/react": "^1.4.2",
|
||||||
"@meshtastic/meshtasticjs": "^0.6.25",
|
"@meshtastic/meshtasticjs": "^0.6.26",
|
||||||
"@reduxjs/toolkit": "^1.6.2",
|
"@reduxjs/toolkit": "^1.6.2",
|
||||||
"boring-avatars": "^1.5.8",
|
"boring-avatars": "^1.5.8",
|
||||||
"i18next": "^21.4.2",
|
"i18next": "^21.4.2",
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
"react-apexcharts": "^1.3.9",
|
"react-apexcharts": "^1.3.9",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-file-icon": "^1.1.0",
|
"react-file-icon": "^1.1.0",
|
||||||
"react-hook-form": "^7.19.1",
|
"react-hook-form": "^7.19.4",
|
||||||
"react-i18next": "^11.14.0",
|
"react-i18next": "^11.14.1",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-json-pretty": "^2.2.0",
|
"react-json-pretty": "^2.2.0",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
"@types/react-file-icon": "^1.0.1",
|
"@types/react-file-icon": "^1.0.1",
|
||||||
"@types/react-redux": "^7.1.20",
|
"@types/react-redux": "^7.1.20",
|
||||||
"@types/react-timeago": "^4.1.3",
|
"@types/react-timeago": "^4.1.3",
|
||||||
"@types/snowpack-env": "^2.3.4",
|
|
||||||
"@types/w3c-web-serial": "^1.0.2",
|
"@types/w3c-web-serial": "^1.0.2",
|
||||||
"@types/web-bluetooth": "^0.0.11",
|
"@types/web-bluetooth": "^0.0.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.3.1",
|
"@typescript-eslint/eslint-plugin": "^5.3.1",
|
||||||
@@ -51,8 +50,8 @@
|
|||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-import-resolver-babel-module": "^5.3.1",
|
"eslint-import-resolver-babel-module": "^5.3.1",
|
||||||
"eslint-import-resolver-typescript": "^2.5.0",
|
"eslint-import-resolver-typescript": "^2.5.0",
|
||||||
"eslint-plugin-import": "^2.25.2",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-react": "^7.26.1",
|
"eslint-plugin-react": "^7.27.0",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"gzipper": "^6.0.0",
|
"gzipper": "^6.0.0",
|
||||||
"postcss": "^8.3.11",
|
"postcss": "^8.3.11",
|
||||||
@@ -60,6 +59,8 @@
|
|||||||
"tailwindcss": "^3.0.0-alpha.2",
|
"tailwindcss": "^3.0.0-alpha.2",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
"typescript": "^4.4.4",
|
"typescript": "^4.4.4",
|
||||||
"vite": "^2.6.14"
|
"vite": "^2.6.14",
|
||||||
|
"vite-plugin-pwa": "^0.11.3",
|
||||||
|
"workbox-window": "^6.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1578
-74
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/touch-icon.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
|
||||||
<link rel="mask-icon" href="/safari-tab.svg" color="#67ea94" />
|
|
||||||
<link href="https://rsms.me/inter/inter.css" rel="stylesheet" />
|
|
||||||
|
|
||||||
<link href="https://fonts.gstatic.com" rel="preconnect" />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
|
||||||
/>
|
|
||||||
<meta name="theme-color" content="#67ea94" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Web site created using create-snowpack-app"
|
|
||||||
/>
|
|
||||||
<title>Meshtastic Web</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<script type="module" src="/index.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
+5
-2
@@ -13,6 +13,7 @@ import {
|
|||||||
addChannel,
|
addChannel,
|
||||||
addMessage,
|
addMessage,
|
||||||
addNode,
|
addNode,
|
||||||
|
addPosition,
|
||||||
addUser,
|
addUser,
|
||||||
setDeviceStatus,
|
setDeviceStatus,
|
||||||
setLastMeshInterraction,
|
setLastMeshInterraction,
|
||||||
@@ -82,9 +83,11 @@ const App = (): JSX.Element => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
connection.onUserPacket.subscribe((user) => {
|
connection.onUserPacket.subscribe((user) => {
|
||||||
console.log('got user packet');
|
dispatch(addUser(user));
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(addUser(user.data));
|
connection.onPositionPacket.subscribe((position) => {
|
||||||
|
dispatch(addPosition(position));
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.onNodeInfoPacket.subscribe(
|
connection.onNodeInfoPacket.subscribe(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import Avatar from 'boring-avatars';
|
import Avatar from 'boring-avatars';
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const MessageBar = (): JSX.Element => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
const ready = useAppSelector((state) => state.meshtastic.ready);
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
|
const users = useAppSelector((state) => state.meshtastic.users);
|
||||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||||
const [destinationNode, setDestinationNode] =
|
const [destinationNode, setDestinationNode] =
|
||||||
@@ -52,8 +53,11 @@ export const MessageBar = (): JSX.Element => {
|
|||||||
...nodes
|
...nodes
|
||||||
.filter((node) => node.num !== myNodeInfo.myNodeNum)
|
.filter((node) => node.num !== myNodeInfo.myNodeNum)
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
|
const user = users.filter(
|
||||||
|
(user) => user.packet.from === node.num,
|
||||||
|
)[0]?.data;
|
||||||
return {
|
return {
|
||||||
name: node.user?.shortName ?? node.num,
|
name: user ? user.shortName : node.num,
|
||||||
value: node.num,
|
value: node.num,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||||
|
|
||||||
|
|||||||
@@ -67,49 +67,3 @@ export const Button = ({
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// import React from 'react';
|
|
||||||
|
|
||||||
// type DefaultButtonProps = JSX.IntrinsicElements['button'];
|
|
||||||
|
|
||||||
// export interface ButtonProps extends DefaultButtonProps {
|
|
||||||
// icon?: JSX.Element;
|
|
||||||
// circle?: boolean;
|
|
||||||
// active?: boolean;
|
|
||||||
// border?: boolean;
|
|
||||||
// confirmAction?: () => void;
|
|
||||||
// rightIcon?: React.ReactNode;
|
|
||||||
// leftIcon?: React.ReactNode;
|
|
||||||
// nested?: boolean;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export const Button = ({
|
|
||||||
// rightIcon,
|
|
||||||
// leftIcon,
|
|
||||||
// children,
|
|
||||||
// nested,
|
|
||||||
// ...props
|
|
||||||
// }: ButtonProps): JSX.Element => {
|
|
||||||
// return (
|
|
||||||
// <button
|
|
||||||
// className={`select-none flex hover:bg-gray-300 dark:hover:bg-gray-500 rounded-md cursor-pointer active:scale-95 dark:text-white ${
|
|
||||||
// nested
|
|
||||||
// ? 'hover:bg-gray-300 dark:hover:bg-gray-500 dark:bg-gray-600 bg-gray-200'
|
|
||||||
// : 'dark:bg-gray-700 bg-gray-200'
|
|
||||||
// }`}
|
|
||||||
// {...props}
|
|
||||||
// >
|
|
||||||
// {leftIcon && (
|
|
||||||
// <div className="flex py-1 bg-gray-00 rounded-l-md">
|
|
||||||
// <div className="mx-2 my-auto">{leftIcon}</div>
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// <div className="flex px-4 py-2 space-x-2 leading-4">{children}</div>
|
|
||||||
// {rightIcon && (
|
|
||||||
// <div className="flex py-2 bg-gray-200 rounded-r-md">
|
|
||||||
// <div className="mx-2 my-auto">{rightIcon}</div>
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// </button>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
export interface CoverProps {
|
export interface CoverProps {
|
||||||
content: JSX.Element;
|
content: JSX.Element;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { Blur } from '@components/generic/Blur';
|
import { Blur } from '@components/generic/Blur';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
export interface StatCardProps {
|
export interface StatCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|||||||
? 'border-red-500'
|
? 'border-red-500'
|
||||||
: props.disabled
|
: props.disabled
|
||||||
? 'border-gray-200'
|
? 'border-gray-200'
|
||||||
: ' focus-within:border-primary hover:border-primary'
|
: 'focus-within:border-primary dark:focus-within:border-primary hover:border-primary dark:hover:border-primary'
|
||||||
}`}
|
}`}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
export interface LabelProps {
|
export interface LabelProps {
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -21,7 +21,7 @@ export const InputWrapper = ({
|
|||||||
? 'border-red-500'
|
? 'border-red-500'
|
||||||
: disabled
|
: disabled
|
||||||
? 'border-gray-200'
|
? 'border-gray-200'
|
||||||
: ' focus-within:border-primary hover:border-primary'
|
: ' focus-within:border-primary dark:focus-within:border-primary hover:border-primary dark:hover:border-primary'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
export interface LabelProps {
|
export interface LabelProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|||||||
<InputWrapper>
|
<InputWrapper>
|
||||||
<select
|
<select
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={`w-full bg-white dark:bg-transparent focus:outline-none focus:border-primary ${
|
className={`w-full rounded-md bg-white dark:bg-transparent focus:outline-none focus:border-primary ${
|
||||||
small ? 'py-1 mx-1' : 'h-10 mx-2'
|
small ? 'p-1' : 'h-10 px-2'
|
||||||
}`}
|
}`}
|
||||||
disabled={
|
disabled={
|
||||||
props.disabled
|
props.disabled
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
export const Logo = (): JSX.Element => {
|
export const Logo = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||||
import { Drawer } from '@components/generic/Drawer';
|
import { Drawer } from '@components/generic/Drawer';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FiGrid,
|
FiGrid,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiWifi, FiWifiOff } from 'react-icons/fi';
|
import { FiWifi, FiWifiOff } from 'react-icons/fi';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiMenu } from 'react-icons/fi';
|
import { FiMenu } from 'react-icons/fi';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiMoon, FiSun } from 'react-icons/fi';
|
import { FiMoon, FiSun } from 'react-icons/fi';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
.ReloadPrompt-container {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.ReloadPrompt-toast {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #8885;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: left;
|
||||||
|
box-shadow: 3px 4px 5px 0 #8885;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.ReloadPrompt-toast-message {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.ReloadPrompt-toast-button {
|
||||||
|
border: 1px solid #8885;
|
||||||
|
outline: none;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import './ReloadPrompt.css';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
import { useRegisterSW } from 'virtual:pwa-register/react';
|
||||||
|
|
||||||
|
const ReloadPrompt = (): JSX.Element => {
|
||||||
|
const {
|
||||||
|
offlineReady: [offlineReady, setOfflineReady],
|
||||||
|
needRefresh: [needRefresh, setNeedRefresh],
|
||||||
|
updateServiceWorker,
|
||||||
|
} = useRegisterSW({
|
||||||
|
onRegistered(r) {
|
||||||
|
// eslint-disable-next-line prefer-template
|
||||||
|
console.log(`SW Registered:`, r);
|
||||||
|
},
|
||||||
|
onRegisterError(error) {
|
||||||
|
console.log('SW registration error', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const close = (): void => {
|
||||||
|
setOfflineReady(false);
|
||||||
|
setNeedRefresh(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ReloadPrompt-container">
|
||||||
|
{(offlineReady || needRefresh) && (
|
||||||
|
<div className="ReloadPrompt-toast">
|
||||||
|
<div className="ReloadPrompt-message">
|
||||||
|
{offlineReady ? (
|
||||||
|
<span>App ready to work offline</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
New content available, click on reload button to update.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{needRefresh && (
|
||||||
|
<button
|
||||||
|
className="ReloadPrompt-toast-button"
|
||||||
|
onClick={(): Promise<void> => updateServiceWorker(true)}
|
||||||
|
>
|
||||||
|
Reload
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="ReloadPrompt-toast-button"
|
||||||
|
onClick={(): void => close()}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReloadPrompt;
|
||||||
@@ -5,8 +5,7 @@ import { FiXCircle } from 'react-icons/fi';
|
|||||||
import { useBreakpoint } from '@app/hooks/breakpoint';
|
import { useBreakpoint } from '@app/hooks/breakpoint';
|
||||||
import { Drawer } from '@components/generic/Drawer';
|
import { Drawer } from '@components/generic/Drawer';
|
||||||
import { IconButton } from '@components/generic/IconButton';
|
import { IconButton } from '@components/generic/IconButton';
|
||||||
import type { SidebarItemProps } from '@components/generic/SidebarItem';
|
import { SidebarItem, SidebarItemProps } from '@components/generic/SidebarItem';
|
||||||
import { SidebarItem } from '@components/generic/SidebarItem';
|
|
||||||
import { Tab } from '@headlessui/react';
|
import { Tab } from '@headlessui/react';
|
||||||
|
|
||||||
export interface PageLayoutProps {
|
export interface PageLayoutProps {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
export interface PrimaryTemplateProps {
|
export interface PrimaryTemplateProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ interface MeshtasticState {
|
|||||||
ready: boolean;
|
ready: boolean;
|
||||||
myNodeInfo: Protobuf.MyNodeInfo;
|
myNodeInfo: Protobuf.MyNodeInfo;
|
||||||
myNode: Protobuf.NodeInfo;
|
myNode: Protobuf.NodeInfo;
|
||||||
users: Protobuf.User[];
|
users: Types.UserPacket[];
|
||||||
myUser: Protobuf.User;
|
|
||||||
positionPackets: Types.PositionPacket[];
|
positionPackets: Types.PositionPacket[];
|
||||||
nodes: Protobuf.NodeInfo[];
|
nodes: Protobuf.NodeInfo[];
|
||||||
channels: Protobuf.Channel[];
|
channels: Protobuf.Channel[];
|
||||||
@@ -41,7 +40,6 @@ const initialState: MeshtasticState = {
|
|||||||
myNodeInfo: Protobuf.MyNodeInfo.create(),
|
myNodeInfo: Protobuf.MyNodeInfo.create(),
|
||||||
myNode: Protobuf.NodeInfo.create(),
|
myNode: Protobuf.NodeInfo.create(),
|
||||||
users: [],
|
users: [],
|
||||||
myUser: Protobuf.User.create(),
|
|
||||||
positionPackets: [],
|
positionPackets: [],
|
||||||
nodes: [],
|
nodes: [],
|
||||||
channels: [],
|
channels: [],
|
||||||
@@ -69,21 +67,22 @@ export const meshtasticSlice = createSlice({
|
|||||||
setMyNodeInfo: (state, action: PayloadAction<Protobuf.MyNodeInfo>) => {
|
setMyNodeInfo: (state, action: PayloadAction<Protobuf.MyNodeInfo>) => {
|
||||||
state.myNodeInfo = action.payload;
|
state.myNodeInfo = action.payload;
|
||||||
},
|
},
|
||||||
addUser: (state, action: PayloadAction<Protobuf.User>) => {
|
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
|
||||||
if (action.payload.id === state.myNode.user?.id) {
|
|
||||||
state.myUser = action.payload;
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
state.users.findIndex((user) => user.id === action.payload.id) !== -1
|
state.users.findIndex(
|
||||||
|
(user) => user.data.id === action.payload.data.id,
|
||||||
|
) !== -1
|
||||||
) {
|
) {
|
||||||
state.users = state.users.map((user) => {
|
state.users = state.users.map((user) => {
|
||||||
return user.id === action.payload.id ? action.payload : user;
|
return user.data.id === action.payload.data.id
|
||||||
|
? action.payload
|
||||||
|
: user;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
state.users.push(action.payload);
|
state.users.push(action.payload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addPositionPacket: (state, action: PayloadAction<Types.PositionPacket>) => {
|
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||||
state.positionPackets.push(action.payload);
|
state.positionPackets.push(action.payload);
|
||||||
},
|
},
|
||||||
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
||||||
@@ -161,7 +160,7 @@ export const {
|
|||||||
setReady,
|
setReady,
|
||||||
setMyNodeInfo,
|
setMyNodeInfo,
|
||||||
addUser,
|
addUser,
|
||||||
addPositionPacket,
|
addPosition,
|
||||||
addNode,
|
addNode,
|
||||||
addChannel,
|
addChannel,
|
||||||
setPreferences,
|
setPreferences,
|
||||||
|
|||||||
+2
-6
@@ -10,20 +10,16 @@ import { RouteProvider } from '@core/router';
|
|||||||
import { store } from '@core/store';
|
import { store } from '@core/store';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import ReloadPrompt from './components/pwa/ReloadPrompt';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<RouteProvider>
|
<RouteProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App />
|
||||||
|
<ReloadPrompt />
|
||||||
</Provider>
|
</Provider>
|
||||||
</RouteProvider>
|
</RouteProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
|
|
||||||
// Learn more: https://www.snowpack.dev/#hot-module-replacement
|
|
||||||
if (import.meta.hot) {
|
|
||||||
import.meta.hot.accept();
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { Card } from '@components/generic/Card';
|
import { Card } from '@components/generic/Card';
|
||||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiHash } from 'react-icons/fi';
|
import { FiHash } from 'react-icons/fi';
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ import { useAppSelector } from '../hooks/redux';
|
|||||||
export const Messages = (): JSX.Element => {
|
export const Messages = (): JSX.Element => {
|
||||||
const messages = useAppSelector((state) => state.meshtastic.messages);
|
const messages = useAppSelector((state) => state.meshtastic.messages);
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
|
const users = useAppSelector((state) => state.meshtastic.users);
|
||||||
const channels = useAppSelector((state) => state.meshtastic.channels);
|
const channels = useAppSelector((state) => state.meshtastic.channels);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,8 +50,9 @@ export const Messages = (): JSX.Element => {
|
|||||||
ack={message.ack}
|
ack={message.ack}
|
||||||
rxTime={new Date()}
|
rxTime={new Date()}
|
||||||
senderName={
|
senderName={
|
||||||
nodes.find((node) => node.num === message.message.packet.from)
|
users.find(
|
||||||
?.user?.longName ?? 'UNK'
|
(user) => user.packet.from === message.message.packet.from,
|
||||||
|
)?.data.longName ?? 'UNK'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import Avatar from 'boring-avatars';
|
import Avatar from 'boring-avatars';
|
||||||
|
|
||||||
@@ -10,21 +10,22 @@ import { Node } from './Node';
|
|||||||
|
|
||||||
export const Nodes = (): JSX.Element => {
|
export const Nodes = (): JSX.Element => {
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
|
const users = useAppSelector((state) => state.meshtastic.users);
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
title="Nodes"
|
title="Nodes"
|
||||||
emptyMessage="No nodes discovered yet..."
|
emptyMessage="No nodes discovered yet..."
|
||||||
sidebarItems={nodes.map((node) => {
|
sidebarItems={nodes.map((node) => {
|
||||||
|
const user = users.find((user) => user.packet.from === node.num)?.data;
|
||||||
return {
|
return {
|
||||||
title: node.user?.longName ?? node.num.toString(),
|
title: user ? user.longName : node.num.toString(),
|
||||||
description: node.user?.hwModel
|
description: user
|
||||||
? Protobuf.HardwareModel[node.user.hwModel]
|
? Protobuf.HardwareModel[user.hwModel]
|
||||||
: 'Unknown Hardware',
|
: 'Unknown Hardware',
|
||||||
icon: (
|
icon: (
|
||||||
<Avatar
|
<Avatar
|
||||||
size={30}
|
size={30}
|
||||||
name={node.user?.longName ?? node.num.toString()}
|
name={user ? user.longName : node.num.toString()}
|
||||||
variant="beam"
|
variant="beam"
|
||||||
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
|
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import 'react-json-pretty/themes/acai.css';
|
import 'react-json-pretty/themes/acai.css';
|
||||||
|
|
||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiMenu, FiTerminal } from 'react-icons/fi';
|
import { FiMenu, FiTerminal } from 'react-icons/fi';
|
||||||
import JSONPretty from 'react-json-pretty';
|
import JSONPretty from 'react-json-pretty';
|
||||||
import TimeAgo from 'react-timeago';
|
import TimeAgo from 'react-timeago';
|
||||||
|
|
||||||
|
import { useAppSelector } from '@app/hooks/redux';
|
||||||
import { Card } from '@components/generic/Card';
|
import { Card } from '@components/generic/Card';
|
||||||
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';
|
||||||
@@ -21,9 +22,15 @@ export interface NodeProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||||
|
const user = useAppSelector((state) => state.meshtastic.users).find(
|
||||||
|
(user) => user.packet.from === node.num,
|
||||||
|
)?.data;
|
||||||
|
const position = useAppSelector(
|
||||||
|
(state) => state.meshtastic.positionPackets,
|
||||||
|
).find((position) => position.packet.from === node.num)?.data;
|
||||||
return (
|
return (
|
||||||
<PrimaryTemplate
|
<PrimaryTemplate
|
||||||
title={node.user?.longName ?? node.num.toString()}
|
title={user ? user.longName : node.num.toString()}
|
||||||
tagline="Node"
|
tagline="Node"
|
||||||
button={
|
button={
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -47,7 +54,7 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
|||||||
description={new Date(node.lastHeard * 1000).toLocaleString()}
|
description={new Date(node.lastHeard * 1000).toLocaleString()}
|
||||||
>
|
>
|
||||||
<div className="p-10">
|
<div className="p-10">
|
||||||
<JSONPretty data={node.position} />
|
<JSONPretty data={position} />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { Card } from '@components/generic/Card';
|
import { Card } from '@components/generic/Card';
|
||||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
// import { DefaultExtensionType, defaultStyles, FileIcon } from 'react-file-icon';
|
// import { DefaultExtensionType, defaultStyles, FileIcon } from 'react-file-icon';
|
||||||
import { FiMenu, FiTrash, FiUploadCloud } from 'react-icons/fi';
|
import { FiMenu, FiTrash, FiUploadCloud } from 'react-icons/fi';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiFileText, FiRss } from 'react-icons/fi';
|
import { FiFileText, FiRss } from 'react-icons/fi';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [debug, setDebug] = React.useState(false);
|
const [debug, setDebug] = React.useState(false);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const user = useAppSelector((state) => state.meshtastic.myUser);
|
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||||
|
const user = useAppSelector((state) => state.meshtastic.users).find(
|
||||||
|
(user) => user.packet.from === myNodeInfo.myNodeNum,
|
||||||
|
);
|
||||||
const { register, handleSubmit, formState } = useForm<{
|
const { register, handleSubmit, formState } = useForm<{
|
||||||
longName: string;
|
longName: string;
|
||||||
shortName: string;
|
shortName: string;
|
||||||
@@ -35,17 +38,19 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
|||||||
team: Protobuf.Team;
|
team: Protobuf.Team;
|
||||||
}>({
|
}>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
longName: user.longName,
|
longName: user?.data.longName,
|
||||||
shortName: user.shortName,
|
shortName: user?.data.shortName,
|
||||||
isLicensed: user.isLicensed,
|
isLicensed: user?.data.isLicensed,
|
||||||
team: user.team,
|
team: user?.data.team,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = handleSubmit((data) => {
|
const onSubmit = handleSubmit((data) => {
|
||||||
void connection.setOwner({ ...user, ...data });
|
|
||||||
// TODO: can be remove once getUser is implemented
|
// TODO: can be remove once getUser is implemented
|
||||||
dispatch(addUser({ ...user, ...data }));
|
if (user) {
|
||||||
|
void connection.setOwner({ ...user.data, ...data });
|
||||||
|
dispatch(addUser({ ...user, ...{ data: { ...user.data, ...data } } }));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -92,10 +97,14 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
|||||||
<Cover enabled={debug} content={<JSONPretty data={user} />} />
|
<Cover enabled={debug} content={<JSONPretty data={user} />} />
|
||||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||||
<form className="space-y-2" onSubmit={onSubmit}>
|
<form className="space-y-2" onSubmit={onSubmit}>
|
||||||
<Input label={'Device ID'} value={user.id} disabled />
|
<Input label={'Device ID'} value={user?.data.id} disabled />
|
||||||
<Input
|
<Input
|
||||||
label={'Hardware'}
|
label={'Hardware'}
|
||||||
value={Protobuf.HardwareModel[user.hwModel]}
|
value={
|
||||||
|
Protobuf.HardwareModel[
|
||||||
|
user?.data.hwModel ?? Protobuf.HardwareModel.UNSET
|
||||||
|
]
|
||||||
|
}
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<Input label={'Device Name'} {...register('longName')} />
|
<Input label={'Device Name'} {...register('longName')} />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FiLayers,
|
FiLayers,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FiMenu, FiSave } from 'react-icons/fi';
|
import { FiMenu, FiSave } from 'react-icons/fi';
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
Add desctiptions to form elements (below on mobile, to the right on desktop)
|
||||||
|
full width form elements on channel manager, don't use deprecated `modemConfig`
|
||||||
|
add default value to undefined protobufs, (omit if default to keep them small (only for ota packets))
|
||||||
|
add input validation min,max etc
|
||||||
|
change ch type select to disable, make primary and delete buttons
|
||||||
|
maybe make channel editor acordion?
|
||||||
|
|
||||||
|
meshtastic.js
|
||||||
|
- either extrapolate user and position out of nodeInfo, or fire off events for all position and user packets even when contained in a nodeInfo packet, decide how to fire events for nodeInfo, wether we merge it or do something else
|
||||||
+16
-18
@@ -1,34 +1,32 @@
|
|||||||
{
|
{
|
||||||
"include": ["src", "types"],
|
"include": ["src", "types"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "esnext",
|
"target": "ESNext",
|
||||||
"target": "esnext",
|
"useDefineForClassFields": true,
|
||||||
"moduleResolution": "node",
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"jsx": "preserve",
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
/* paths - import rewriting/resolving */
|
|
||||||
"paths": {
|
"paths": {
|
||||||
// If you configured any Snowpack aliases, add them here.
|
|
||||||
// Add this line to get types for streaming imports (packageOptions.source="remote"):
|
|
||||||
// "*": [".snowpack/types/*"]
|
|
||||||
// More info: https://www.snowpack.dev/guides/streaming-imports
|
|
||||||
"@app/*": ["./src/*"],
|
"@app/*": ["./src/*"],
|
||||||
"@pages/*": ["./src/pages/*"],
|
"@pages/*": ["./src/pages/*"],
|
||||||
"@components/*": ["./src/components/*"],
|
"@components/*": ["./src/components/*"],
|
||||||
"@core/*": ["./src/core/*"]
|
"@core/*": ["./src/core/*"]
|
||||||
},
|
},
|
||||||
/* noEmit - Snowpack builds (emits) files, not tsc. */
|
|
||||||
"noEmit": true,
|
|
||||||
/* Additional Options */
|
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"skipLibCheck": true,
|
"types": ["vite/client", "vite-plugin-pwa/client"],
|
||||||
"types": ["snowpack-env"],
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"importsNotUsedAsValues": "error"
|
"importsNotUsedAsValues": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-1
@@ -1,11 +1,52 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
VitePWA({
|
||||||
|
mode: 'production',
|
||||||
|
|
||||||
|
includeAssets: [
|
||||||
|
'favicon.svg',
|
||||||
|
'favicon.ico',
|
||||||
|
'robots.txt',
|
||||||
|
'touch-icon.png',
|
||||||
|
],
|
||||||
|
manifest: {
|
||||||
|
name: 'Meshtastic Web',
|
||||||
|
short_name: 'Meshtastic',
|
||||||
|
description: 'Meshtastic Web App',
|
||||||
|
// theme_color: '#2C2D3C',
|
||||||
|
theme_color: '#67ea94',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: 'android-192.png',
|
||||||
|
sizes: '192x192',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'android-512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'android-512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
purpose: 'any maskable',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
workbox: {
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
build: {
|
build: {
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
assetsDir: '',
|
assetsDir: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user