Browse Source

Switch to Vite + improvements

pull/2/head
Sacha Weatherstone 5 years ago
parent
commit
02c0e22fe4
  1. 2
      .env.example
  2. 4
      README.md
  3. 13
      index.html
  4. 17
      package.json
  5. 1830
      pnpm-lock.yaml
  6. 38
      public/index.html
  7. 2
      public/robots.txt
  8. 7
      src/App.tsx
  9. 2
      src/components/chat/Message.tsx
  10. 6
      src/components/chat/MessageBar.tsx
  11. 2
      src/components/generic/Blur.tsx
  12. 46
      src/components/generic/Button.tsx
  13. 2
      src/components/generic/Card.tsx
  14. 2
      src/components/generic/Cover.tsx
  15. 2
      src/components/generic/Drawer.tsx
  16. 2
      src/components/generic/IconButton.tsx
  17. 2
      src/components/generic/SidebarItem.tsx
  18. 2
      src/components/generic/StatCard.tsx
  19. 2
      src/components/generic/form/Checkbox.tsx
  20. 4
      src/components/generic/form/InputWrapper.tsx
  21. 2
      src/components/generic/form/Label.tsx
  22. 4
      src/components/generic/form/Select.tsx
  23. 2
      src/components/menu/Logo.tsx
  24. 2
      src/components/menu/MobileNav.tsx
  25. 2
      src/components/menu/Navigation.tsx
  26. 2
      src/components/menu/buttons/DeviceStatusDropdown.tsx
  27. 2
      src/components/menu/buttons/MobileNavToggle.tsx
  28. 2
      src/components/menu/buttons/ThemeToggle.tsx
  29. 29
      src/components/pwa/ReloadPrompt.css
  30. 61
      src/components/pwa/ReloadPrompt.tsx
  31. 3
      src/components/templates/PageLayout.tsx
  32. 2
      src/components/templates/PrimaryTemplate.tsx
  33. 21
      src/core/slices/meshtasticSlice.ts
  34. 8
      src/index.tsx
  35. 2
      src/pages/About.tsx
  36. 8
      src/pages/Messages.tsx
  37. 13
      src/pages/Nodes/Index.tsx
  38. 13
      src/pages/Nodes/Node.tsx
  39. 2
      src/pages/NotFound.tsx
  40. 2
      src/pages/Plugins/Files.tsx
  41. 2
      src/pages/Plugins/Index.tsx
  42. 2
      src/pages/Plugins/RangeTest.tsx
  43. 27
      src/pages/settings/Device.tsx
  44. 2
      src/pages/settings/Index.tsx
  45. 2
      src/pages/settings/Interface.tsx
  46. 9
      todo.txt
  47. 34
      tsconfig.json
  48. 43
      vite.config.ts

2
.env.example

@ -1 +1 @@
SNOWPACK_PUBLIC_DEVICE_IP=
VITE_PUBLIC_DEVICE_IP=

4
README.md

@ -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
```

13
index.html

@ -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>

17
package.json

@ -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"
}
}

1830
pnpm-lock.yaml

File diff suppressed because it is too large

38
public/index.html

@ -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>

2
public/robots.txt

@ -0,0 +1,2 @@
User-agent: *
Allow: /

7
src/App.tsx

@ -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(

2
src/components/chat/Message.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import Avatar from 'boring-avatars';

6
src/components/chat/MessageBar.tsx

@ -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,
};
}),

2
src/components/generic/Blur.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
type DefaultDivProps = JSX.IntrinsicElements['div'];

46
src/components/generic/Button.tsx

@ -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>
// );
// };

2
src/components/generic/Card.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
type DefaultDivProps = JSX.IntrinsicElements['div'];

2
src/components/generic/Cover.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
export interface CoverProps {
content: JSX.Element;

2
src/components/generic/Drawer.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { Blur } from '@components/generic/Blur';

2
src/components/generic/IconButton.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
type DefaulButtonProps = JSX.IntrinsicElements['button'];

2
src/components/generic/SidebarItem.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
type DefaultDivProps = JSX.IntrinsicElements['div'];

2
src/components/generic/StatCard.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
export interface StatCardProps {
title: string;

2
src/components/generic/form/Checkbox.tsx

@ -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}
/>

4
src/components/generic/form/InputWrapper.tsx

@ -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}

2
src/components/generic/form/Label.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
export interface LabelProps {
label: string;

4
src/components/generic/form/Select.tsx

@ -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

2
src/components/menu/Logo.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
export const Logo = (): JSX.Element => {
return (

2
src/components/menu/MobileNav.tsx

@ -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';

2
src/components/menu/Navigation.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import {
FiGrid,

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

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { FiWifi, FiWifiOff } from 'react-icons/fi';

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

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { FiMenu } from 'react-icons/fi';

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

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { FiMoon, FiSun } from 'react-icons/fi';

29
src/components/pwa/ReloadPrompt.css

@ -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;
}

61
src/components/pwa/ReloadPrompt.tsx

@ -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;

3
src/components/templates/PageLayout.tsx

@ -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 {

2
src/components/templates/PrimaryTemplate.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
export interface PrimaryTemplateProps {
children: React.ReactNode;

21
src/core/slices/meshtasticSlice.ts

@ -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,

8
src/index.tsx

@ -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();
}

2
src/pages/About.tsx

@ -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';

8
src/pages/Messages.tsx

@ -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'
}
/>
))}

13
src/pages/Nodes/Index.tsx

@ -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']}
/>

13
src/pages/Nodes/Node.tsx

@ -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

2
src/pages/NotFound.tsx

@ -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';

2
src/pages/Plugins/Files.tsx

@ -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';

2
src/pages/Plugins/Index.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { FiFileText, FiRss } from 'react-icons/fi';

2
src/pages/Plugins/RangeTest.tsx

@ -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
src/pages/settings/Device.tsx

@ -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')} />

2
src/pages/settings/Index.tsx

@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import {
FiLayers,

2
src/pages/settings/Interface.tsx

@ -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';

9
todo.txt

@ -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

34
tsconfig.json

@ -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"
}
}

43
vite.config.ts

@ -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: '',

Loading…
Cancel
Save