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.
|
||||
|
||||
```
|
||||
SNOWPACK_PUBLIC_DEVICE_IP=xxx.xxx.xxx.xxx
|
||||
VITE_PUBLIC_DEVICE_IP=xxx.xxx.xxx.xxx
|
||||
```
|
||||
|
||||
Install the dependencies.
|
||||
@@ -45,5 +45,5 @@ pnpm i
|
||||
Start the developtment server:
|
||||
|
||||
```bash
|
||||
pnpm start
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
+8
-5
@@ -2,7 +2,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
@@ -14,10 +20,7 @@
|
||||
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"
|
||||
/>
|
||||
<meta name="description" content="Meshtastic Web App" />
|
||||
<title>Meshtastic Web</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+9
-8
@@ -7,13 +7,13 @@
|
||||
"dev": "NODE_ENV=development vite",
|
||||
"build": "tsc && vite build",
|
||||
"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}'",
|
||||
"lint": "eslint 'src/**/*.{ts,tsx}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.4.2",
|
||||
"@meshtastic/meshtasticjs": "^0.6.25",
|
||||
"@meshtastic/meshtasticjs": "^0.6.26",
|
||||
"@reduxjs/toolkit": "^1.6.2",
|
||||
"boring-avatars": "^1.5.8",
|
||||
"i18next": "^21.4.2",
|
||||
@@ -22,8 +22,8 @@
|
||||
"react-apexcharts": "^1.3.9",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-file-icon": "^1.1.0",
|
||||
"react-hook-form": "^7.19.1",
|
||||
"react-i18next": "^11.14.0",
|
||||
"react-hook-form": "^7.19.4",
|
||||
"react-i18next": "^11.14.1",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-json-pretty": "^2.2.0",
|
||||
"react-redux": "^7.2.6",
|
||||
@@ -38,7 +38,6 @@
|
||||
"@types/react-file-icon": "^1.0.1",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"@types/react-timeago": "^4.1.3",
|
||||
"@types/snowpack-env": "^2.3.4",
|
||||
"@types/w3c-web-serial": "^1.0.2",
|
||||
"@types/web-bluetooth": "^0.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.3.1",
|
||||
@@ -51,8 +50,8 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-babel-module": "^5.3.1",
|
||||
"eslint-import-resolver-typescript": "^2.5.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-react": "^7.26.1",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-react": "^7.27.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"gzipper": "^6.0.0",
|
||||
"postcss": "^8.3.11",
|
||||
@@ -60,6 +59,8 @@
|
||||
"tailwindcss": "^3.0.0-alpha.2",
|
||||
"tar": "^6.1.11",
|
||||
"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,
|
||||
addMessage,
|
||||
addNode,
|
||||
addPosition,
|
||||
addUser,
|
||||
setDeviceStatus,
|
||||
setLastMeshInterraction,
|
||||
@@ -82,9 +83,11 @@ const App = (): JSX.Element => {
|
||||
});
|
||||
|
||||
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(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import Avatar from 'boring-avatars';
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const MessageBar = (): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||
const [destinationNode, setDestinationNode] =
|
||||
@@ -52,8 +53,11 @@ export const MessageBar = (): JSX.Element => {
|
||||
...nodes
|
||||
.filter((node) => node.num !== myNodeInfo.myNodeNum)
|
||||
.map((node) => {
|
||||
const user = users.filter(
|
||||
(user) => user.packet.from === node.num,
|
||||
)[0]?.data;
|
||||
return {
|
||||
name: node.user?.shortName ?? node.num,
|
||||
name: user ? user.shortName : node.num,
|
||||
value: node.num,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||
|
||||
|
||||
@@ -67,49 +67,3 @@ export const 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'];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface CoverProps {
|
||||
content: JSX.Element;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { Blur } from '@components/generic/Blur';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface StatCardProps {
|
||||
title: string;
|
||||
|
||||
@@ -34,7 +34,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
? 'border-red-500'
|
||||
: props.disabled
|
||||
? '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}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface LabelProps {
|
||||
error?: string;
|
||||
@@ -21,7 +21,7 @@ export const InputWrapper = ({
|
||||
? 'border-red-500'
|
||||
: disabled
|
||||
? '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}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface LabelProps {
|
||||
label: string;
|
||||
|
||||
@@ -29,8 +29,8 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||
<InputWrapper>
|
||||
<select
|
||||
ref={ref}
|
||||
className={`w-full bg-white dark:bg-transparent focus:outline-none focus:border-primary ${
|
||||
small ? 'py-1 mx-1' : 'h-10 mx-2'
|
||||
className={`w-full rounded-md bg-white dark:bg-transparent focus:outline-none focus:border-primary ${
|
||||
small ? 'p-1' : 'h-10 px-2'
|
||||
}`}
|
||||
disabled={
|
||||
props.disabled
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export const Logo = (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { Drawer } from '@components/generic/Drawer';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import {
|
||||
FiGrid,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
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';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
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 { Drawer } from '@components/generic/Drawer';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import type { SidebarItemProps } from '@components/generic/SidebarItem';
|
||||
import { SidebarItem } from '@components/generic/SidebarItem';
|
||||
import { SidebarItem, SidebarItemProps } from '@components/generic/SidebarItem';
|
||||
import { Tab } from '@headlessui/react';
|
||||
|
||||
export interface PageLayoutProps {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface PrimaryTemplateProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -22,8 +22,7 @@ interface MeshtasticState {
|
||||
ready: boolean;
|
||||
myNodeInfo: Protobuf.MyNodeInfo;
|
||||
myNode: Protobuf.NodeInfo;
|
||||
users: Protobuf.User[];
|
||||
myUser: Protobuf.User;
|
||||
users: Types.UserPacket[];
|
||||
positionPackets: Types.PositionPacket[];
|
||||
nodes: Protobuf.NodeInfo[];
|
||||
channels: Protobuf.Channel[];
|
||||
@@ -41,7 +40,6 @@ const initialState: MeshtasticState = {
|
||||
myNodeInfo: Protobuf.MyNodeInfo.create(),
|
||||
myNode: Protobuf.NodeInfo.create(),
|
||||
users: [],
|
||||
myUser: Protobuf.User.create(),
|
||||
positionPackets: [],
|
||||
nodes: [],
|
||||
channels: [],
|
||||
@@ -69,21 +67,22 @@ export const meshtasticSlice = createSlice({
|
||||
setMyNodeInfo: (state, action: PayloadAction<Protobuf.MyNodeInfo>) => {
|
||||
state.myNodeInfo = action.payload;
|
||||
},
|
||||
addUser: (state, action: PayloadAction<Protobuf.User>) => {
|
||||
if (action.payload.id === state.myNode.user?.id) {
|
||||
state.myUser = action.payload;
|
||||
}
|
||||
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
|
||||
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) => {
|
||||
return user.id === action.payload.id ? action.payload : user;
|
||||
return user.data.id === action.payload.data.id
|
||||
? action.payload
|
||||
: user;
|
||||
});
|
||||
} else {
|
||||
state.users.push(action.payload);
|
||||
}
|
||||
},
|
||||
addPositionPacket: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||
state.positionPackets.push(action.payload);
|
||||
},
|
||||
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
||||
@@ -161,7 +160,7 @@ export const {
|
||||
setReady,
|
||||
setMyNodeInfo,
|
||||
addUser,
|
||||
addPositionPacket,
|
||||
addPosition,
|
||||
addNode,
|
||||
addChannel,
|
||||
setPreferences,
|
||||
|
||||
+2
-6
@@ -10,20 +10,16 @@ import { RouteProvider } from '@core/router';
|
||||
import { store } from '@core/store';
|
||||
|
||||
import App from './App';
|
||||
import ReloadPrompt from './components/pwa/ReloadPrompt';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<RouteProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<ReloadPrompt />
|
||||
</Provider>
|
||||
</RouteProvider>
|
||||
</React.StrictMode>,
|
||||
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 { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiHash } from 'react-icons/fi';
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useAppSelector } from '../hooks/redux';
|
||||
export const Messages = (): JSX.Element => {
|
||||
const messages = useAppSelector((state) => state.meshtastic.messages);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const channels = useAppSelector((state) => state.meshtastic.channels);
|
||||
|
||||
return (
|
||||
@@ -49,8 +50,9 @@ export const Messages = (): JSX.Element => {
|
||||
ack={message.ack}
|
||||
rxTime={new Date()}
|
||||
senderName={
|
||||
nodes.find((node) => node.num === message.message.packet.from)
|
||||
?.user?.longName ?? 'UNK'
|
||||
users.find(
|
||||
(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';
|
||||
|
||||
@@ -10,21 +10,22 @@ import { Node } from './Node';
|
||||
|
||||
export const Nodes = (): JSX.Element => {
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
return (
|
||||
<PageLayout
|
||||
title="Nodes"
|
||||
emptyMessage="No nodes discovered yet..."
|
||||
sidebarItems={nodes.map((node) => {
|
||||
const user = users.find((user) => user.packet.from === node.num)?.data;
|
||||
return {
|
||||
title: node.user?.longName ?? node.num.toString(),
|
||||
description: node.user?.hwModel
|
||||
? Protobuf.HardwareModel[node.user.hwModel]
|
||||
title: user ? user.longName : node.num.toString(),
|
||||
description: user
|
||||
? Protobuf.HardwareModel[user.hwModel]
|
||||
: 'Unknown Hardware',
|
||||
icon: (
|
||||
<Avatar
|
||||
size={30}
|
||||
name={node.user?.longName ?? node.num.toString()}
|
||||
name={user ? user.longName : node.num.toString()}
|
||||
variant="beam"
|
||||
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'react-json-pretty/themes/acai.css';
|
||||
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiMenu, FiTerminal } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
@@ -21,9 +22,15 @@ export interface NodeProps {
|
||||
}
|
||||
|
||||
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 (
|
||||
<PrimaryTemplate
|
||||
title={node.user?.longName ?? node.num.toString()}
|
||||
title={user ? user.longName : node.num.toString()}
|
||||
tagline="Node"
|
||||
button={
|
||||
<IconButton
|
||||
@@ -47,7 +54,7 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
description={new Date(node.lastHeard * 1000).toLocaleString()}
|
||||
>
|
||||
<div className="p-10">
|
||||
<JSONPretty data={node.position} />
|
||||
<JSONPretty data={position} />
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { Card } from '@components/generic/Card';
|
||||
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 { 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';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -27,7 +27,10 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
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<{
|
||||
longName: string;
|
||||
shortName: string;
|
||||
@@ -35,17 +38,19 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
team: Protobuf.Team;
|
||||
}>({
|
||||
defaultValues: {
|
||||
longName: user.longName,
|
||||
shortName: user.shortName,
|
||||
isLicensed: user.isLicensed,
|
||||
team: user.team,
|
||||
longName: user?.data.longName,
|
||||
shortName: user?.data.shortName,
|
||||
isLicensed: user?.data.isLicensed,
|
||||
team: user?.data.team,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setOwner({ ...user, ...data });
|
||||
// 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 (
|
||||
@@ -92,10 +97,14 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
<Cover enabled={debug} content={<JSONPretty data={user} />} />
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<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
|
||||
label={'Hardware'}
|
||||
value={Protobuf.HardwareModel[user.hwModel]}
|
||||
value={
|
||||
Protobuf.HardwareModel[
|
||||
user?.data.hwModel ?? Protobuf.HardwareModel.UNSET
|
||||
]
|
||||
}
|
||||
disabled
|
||||
/>
|
||||
<Input label={'Device Name'} {...register('longName')} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import {
|
||||
FiLayers,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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"],
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "preserve",
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"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": "./",
|
||||
/* paths - import rewriting/resolving */
|
||||
"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/*"],
|
||||
"@pages/*": ["./src/pages/*"],
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@core/*": ["./src/core/*"]
|
||||
},
|
||||
/* noEmit - Snowpack builds (emits) files, not tsc. */
|
||||
"noEmit": true,
|
||||
/* Additional Options */
|
||||
"importHelpers": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["snowpack-env"],
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"types": ["vite/client", "vite-plugin-pwa/client"],
|
||||
"importsNotUsedAsValues": "error"
|
||||
}
|
||||
}
|
||||
|
||||
+42
-1
@@ -1,11 +1,52 @@
|
||||
import path from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
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: {
|
||||
target: 'esnext',
|
||||
assetsDir: '',
|
||||
|
||||
Reference in New Issue
Block a user