Internal cleanup
This commit is contained in:
+13
-10
@@ -9,15 +9,16 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"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/)",
|
"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}'",
|
||||||
|
"check": "unimported",
|
||||||
"lint": "eslint 'src/**/*.{ts,tsx}'"
|
"lint": "eslint 'src/**/*.{ts,tsx}'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emeraldpay/hashicon-react": "^0.5.2",
|
"@emeraldpay/hashicon-react": "^0.5.2",
|
||||||
"@floating-ui/react-dom": "^0.4.3",
|
|
||||||
"@meshtastic/components": "^1.0.23",
|
|
||||||
"@meshtastic/meshtasticjs": "^0.6.45",
|
"@meshtastic/meshtasticjs": "^0.6.45",
|
||||||
"@reduxjs/toolkit": "^1.7.2",
|
"@reduxjs/toolkit": "^1.7.2",
|
||||||
|
"@tippyjs/react": "^4.2.6",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
"cuid": "^2.1.8",
|
||||||
"framer-motion": "^6.2.6",
|
"framer-motion": "^6.2.6",
|
||||||
"graphql-request": "^4.0.0",
|
"graphql-request": "^4.0.0",
|
||||||
"mapbox-gl": "^2.7.0",
|
"mapbox-gl": "^2.7.0",
|
||||||
@@ -28,25 +29,25 @@
|
|||||||
"react-hook-form": "^7.27.0",
|
"react-hook-form": "^7.27.0",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-json-pretty": "^2.2.0",
|
"react-json-pretty": "^2.2.0",
|
||||||
"react-multi-select-component": "^4.2.2",
|
"react-multi-select-component": "^4.2.3",
|
||||||
"react-qr-code": "^2.0.3",
|
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"react-use-clipboard": "^1.0.7",
|
"react-use-clipboard": "^1.0.7",
|
||||||
"rfc4648": "^1.5.1",
|
"rfc4648": "^1.5.1",
|
||||||
"rollup-plugin-visualizer": "^5.5.4",
|
"swr": "^1.2.2",
|
||||||
"swr": "^1.2.1",
|
|
||||||
"timeago-react": "^3.0.4",
|
"timeago-react": "^3.0.4",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
"type-route": "^0.6.0",
|
"type-route": "^0.6.0",
|
||||||
"vite-plugin-environment": "^1.1.0"
|
"vite-plugin-environment": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@hookform/devtools": "^4.0.2",
|
||||||
"@types/mapbox-gl": "^2.6.1",
|
"@types/mapbox-gl": "^2.6.1",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/w3c-web-serial": "^1.0.2",
|
"@types/w3c-web-serial": "^1.0.2",
|
||||||
"@types/web-bluetooth": "^0.0.12",
|
"@types/web-bluetooth": "^0.0.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||||
"@typescript-eslint/parser": "^5.11.0",
|
"@typescript-eslint/parser": "^5.12.0",
|
||||||
"@verypossible/eslint-config": "^1.6.1",
|
"@verypossible/eslint-config": "^1.6.1",
|
||||||
"@vitejs/plugin-react": "^1.2.0",
|
"@vitejs/plugin-react": "^1.2.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
@@ -63,10 +64,12 @@
|
|||||||
"postcss": "^8.4.6",
|
"postcss": "^8.4.6",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"prettier-plugin-tailwindcss": "^0.1.7",
|
"prettier-plugin-tailwindcss": "^0.1.7",
|
||||||
"tailwindcss": "^3.0.22",
|
"rollup-plugin-visualizer": "^5.5.4",
|
||||||
|
"tailwindcss": "^3.0.23",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"vite": "^2.8.1",
|
"unimported": "^1.19.1",
|
||||||
|
"vite": "^2.8.4",
|
||||||
"vite-plugin-cdn-import": "^0.3.5",
|
"vite-plugin-cdn-import": "^0.3.5",
|
||||||
"vite-plugin-pwa": "^0.11.13",
|
"vite-plugin-pwa": "^0.11.13",
|
||||||
"workbox-window": "^6.4.2"
|
"workbox-window": "^6.4.2"
|
||||||
|
|||||||
Generated
+939
-628
File diff suppressed because it is too large
Load Diff
+6
-7
@@ -2,15 +2,14 @@ import React from 'react';
|
|||||||
|
|
||||||
import { Map } from '@app/pages/Map';
|
import { Map } from '@app/pages/Map';
|
||||||
import { Connection } from '@components/Connection';
|
import { Connection } from '@components/Connection';
|
||||||
|
import { ContextMenu } from '@components/generic/ContextMenu';
|
||||||
|
import { BottomNav } from '@components/menu/BottomNav';
|
||||||
import { useRoute } from '@core/router';
|
import { useRoute } from '@core/router';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
|
import { Extensions } from '@pages/Extensions/Index';
|
||||||
import { ContextMenu } from './components/generic/ContextMenu';
|
import { Messages } from '@pages/Messages';
|
||||||
import { BottomNav } from './components/menu/BottomNav';
|
import { Nodes } from '@pages/Nodes';
|
||||||
import { Extensions } from './pages/Extensions/Index';
|
import { NotFound } from '@pages/NotFound';
|
||||||
import { Messages } from './pages/Messages';
|
|
||||||
import { Nodes } from './pages/Nodes';
|
|
||||||
import { NotFound } from './pages/NotFound';
|
|
||||||
|
|
||||||
export const App = (): JSX.Element => {
|
export const App = (): JSX.Element => {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import React from 'react';
|
|||||||
|
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
|
import { Card } from '@app/components/generic/Card';
|
||||||
import { BLE } from '@components/connection/BLE';
|
import { BLE } from '@components/connection/BLE';
|
||||||
import { HTTP } from '@components/connection/HTTP';
|
import { HTTP } from '@components/connection/HTTP';
|
||||||
import { Serial } from '@components/connection/Serial';
|
import { Serial } from '@components/connection/Serial';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
import { Modal } from '@components/generic/Modal';
|
import { Modal } from '@components/generic/Modal';
|
||||||
import { connection, connectionUrl, setConnection } from '@core/connection';
|
import { connectionUrl, setConnection } from '@core/connection';
|
||||||
import {
|
import {
|
||||||
closeConnectionModal,
|
closeConnectionModal,
|
||||||
connType,
|
connType,
|
||||||
@@ -15,7 +17,6 @@ import {
|
|||||||
} from '@core/slices/appSlice';
|
} from '@core/slices/appSlice';
|
||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Button, Card, Select } from '@meshtastic/components';
|
|
||||||
import { Types } from '@meshtastic/meshtasticjs';
|
import { Types } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Connection = (): JSX.Element => {
|
export const Connection = (): JSX.Element => {
|
||||||
@@ -56,11 +57,9 @@ export const Connection = (): JSX.Element => {
|
|||||||
dispatch(closeConnectionModal());
|
dispatch(closeConnectionModal());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card>
|
<Card className="relative">
|
||||||
<div className="flex w-full max-w-3xl gap-2 p-2">
|
<div className="flex max-w-3xl flex-grow gap-4 p-2">
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
{state.deviceStatus ===
|
|
||||||
Types.DeviceStatusEnum.DEVICE_DISCONNECTED ? (
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Select
|
<Select
|
||||||
label="Connection Method"
|
label="Connection Method"
|
||||||
@@ -69,31 +68,48 @@ export const Connection = (): JSX.Element => {
|
|||||||
onChange={(e): void => {
|
onChange={(e): void => {
|
||||||
dispatch(setConnType(parseInt(e.target.value)));
|
dispatch(setConnType(parseInt(e.target.value)));
|
||||||
}}
|
}}
|
||||||
|
disabled={
|
||||||
|
state.deviceStatus ===
|
||||||
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{appState.connType === connType.HTTP && (
|
||||||
|
<HTTP
|
||||||
|
connecting={
|
||||||
|
state.deviceStatus ===
|
||||||
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{appState.connType === connType.BLE && (
|
||||||
|
<BLE
|
||||||
|
connecting={
|
||||||
|
state.deviceStatus ===
|
||||||
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{appState.connType === connType.SERIAL && (
|
||||||
|
<Serial
|
||||||
|
connecting={
|
||||||
|
state.deviceStatus ===
|
||||||
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{appState.connType === connType.HTTP && <HTTP />}
|
|
||||||
{appState.connType === connType.BLE && <BLE />}
|
|
||||||
{appState.connType === connType.SERIAL && <Serial />}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<span>Connecting...</span>
|
|
||||||
{state.deviceStatus ===
|
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED && (
|
|
||||||
<Button
|
|
||||||
border
|
|
||||||
onClick={async (): Promise<void> => {
|
|
||||||
await connection.disconnect();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Disconnect
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<div className="h-96 overflow-y-auto rounded-md bg-secondaryDark p-2">
|
<div className="h-96 overflow-y-auto rounded-md bg-gray-200 p-2 dark:bg-secondaryDark dark:text-gray-400">
|
||||||
{state.logs.map((log, index) => (
|
{state.logs
|
||||||
|
.filter((log) => {
|
||||||
|
return ![
|
||||||
|
Types.Emitter.handleFromRadio,
|
||||||
|
Types.Emitter.handleMeshPacket,
|
||||||
|
Types.Emitter.sendPacket,
|
||||||
|
].includes(log.emitter);
|
||||||
|
})
|
||||||
|
.map((log, index) => (
|
||||||
<div key={index} className="flex">
|
<div key={index} className="flex">
|
||||||
<div className="truncate font-mono text-sm">
|
<div className="truncate font-mono text-sm">
|
||||||
{log.message}
|
{log.message}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
|
|
||||||
|
import { MapboxContext } from '@components/MapBox/mapboxContext';
|
||||||
import {
|
import {
|
||||||
setBearing,
|
setBearing,
|
||||||
setLatLng,
|
setLatLng,
|
||||||
@@ -14,7 +15,6 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
|||||||
import { useCreateMapbox } from '@hooks/useCreateMapbox';
|
import { useCreateMapbox } from '@hooks/useCreateMapbox';
|
||||||
|
|
||||||
import { MapStyles } from '../../pages/Map/styles';
|
import { MapStyles } from '../../pages/Map/styles';
|
||||||
import { MapboxContext } from './mapboxContext';
|
|
||||||
|
|
||||||
export type MapboxProviderProps = {
|
export type MapboxProviderProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ import React from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FiCheck } from 'react-icons/fi';
|
import { FiCheck } from 'react-icons/fi';
|
||||||
|
|
||||||
import { connType } from '@app/core/slices/appSlice';
|
import { Button } from '@components/generic/button/Button';
|
||||||
import { setConnection } from '@core/connection';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { Button, IconButton } from '@meshtastic/components';
|
import { connection, setConnection } from '@core/connection';
|
||||||
|
import { connType } from '@core/slices/appSlice';
|
||||||
import { IBLEConnection } from '@meshtastic/meshtasticjs';
|
import { IBLEConnection } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const BLE = (): JSX.Element => {
|
export interface BLEProps {
|
||||||
|
connecting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BLE = ({ connecting }: BLEProps): JSX.Element => {
|
||||||
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
|
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
|
||||||
|
|
||||||
const { handleSubmit } = useForm<{
|
const { handleSubmit } = useForm<{
|
||||||
@@ -45,11 +50,22 @@ export const BLE = (): JSX.Element => {
|
|||||||
await setConnection(connType.BLE);
|
await setConnection(connType.BLE);
|
||||||
}}
|
}}
|
||||||
icon={<FiCheck />}
|
icon={<FiCheck />}
|
||||||
|
disabled={connecting}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Button type="submit" className="mt-2 ml-auto" border>
|
<Button
|
||||||
Connect
|
className="mt-2 ml-auto"
|
||||||
|
onClick={async (): Promise<void> => {
|
||||||
|
if (connecting) {
|
||||||
|
await connection.disconnect();
|
||||||
|
} else {
|
||||||
|
await onSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
border
|
||||||
|
>
|
||||||
|
{connecting ? 'Disconnect' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,12 +2,19 @@ import type React from 'react';
|
|||||||
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
import { connectionUrl, setConnection } from '@core/connection';
|
import { Button } from '@components/generic/button/Button';
|
||||||
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
|
import { connection, connectionUrl, setConnection } from '@core/connection';
|
||||||
import { connType, setConnectionParams } from '@core/slices/appSlice';
|
import { connType, setConnectionParams } from '@core/slices/appSlice';
|
||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { Button, Checkbox, Input, Select } from '@meshtastic/components';
|
|
||||||
|
|
||||||
export const HTTP = (): JSX.Element => {
|
export interface HTTPProps {
|
||||||
|
connecting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HTTP = ({ connecting }: HTTPProps): JSX.Element => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { register, handleSubmit, control } = useForm<{
|
const { register, handleSubmit, control } = useForm<{
|
||||||
@@ -56,16 +63,27 @@ export const HTTP = (): JSX.Element => {
|
|||||||
value: 'remote',
|
value: 'remote',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
disabled={connecting}
|
||||||
{...register('ipSource')}
|
{...register('ipSource')}
|
||||||
/>
|
/>
|
||||||
{watchIpSource === 'local' ? (
|
{watchIpSource === 'local' ? (
|
||||||
<Input label="Host" value={connectionUrl} disabled />
|
<Input label="Host" value={connectionUrl} disabled />
|
||||||
) : (
|
) : (
|
||||||
<Input label="Host" {...register('ip')} />
|
<Input label="Host" disabled={connecting} {...register('ip')} />
|
||||||
)}
|
)}
|
||||||
<Checkbox label="Use TLS?" {...register('tls')} />
|
<Checkbox label="Use TLS?" disabled={connecting} {...register('tls')} />
|
||||||
<Button type="submit" className="mt-2 ml-auto" border>
|
<Button
|
||||||
Connect
|
className="mt-2 ml-auto"
|
||||||
|
onClick={async (): Promise<void> => {
|
||||||
|
if (connecting) {
|
||||||
|
await connection.disconnect();
|
||||||
|
} else {
|
||||||
|
await onSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
border
|
||||||
|
>
|
||||||
|
{connecting ? 'Disconnect' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ import React from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FiCheck } from 'react-icons/fi';
|
import { FiCheck } from 'react-icons/fi';
|
||||||
|
|
||||||
import { setConnection } from '@core/connection';
|
import { Button } from '@components/generic/button/Button';
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { connection, setConnection } from '@core/connection';
|
||||||
import { connType, setConnectionParams } from '@core/slices/appSlice';
|
import { connType, setConnectionParams } from '@core/slices/appSlice';
|
||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { Button, IconButton } from '@meshtastic/components';
|
|
||||||
import { ISerialConnection } from '@meshtastic/meshtasticjs';
|
import { ISerialConnection } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Serial = (): JSX.Element => {
|
export interface SerialProps {
|
||||||
|
connecting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Serial = ({ connecting }: SerialProps): JSX.Element => {
|
||||||
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
|
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
@@ -36,7 +41,7 @@ export const Serial = (): JSX.Element => {
|
|||||||
{serialDevices.length > 0 ? (
|
{serialDevices.length > 0 ? (
|
||||||
serialDevices.map((device, index) => (
|
serialDevices.map((device, index) => (
|
||||||
<div
|
<div
|
||||||
className="flex justify-between rounded-md bg-gray-700 p-2"
|
className="flex justify-between rounded-md bg-secondaryDark p-2"
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<div className="my-auto flex gap-4">
|
<div className="my-auto flex gap-4">
|
||||||
@@ -59,6 +64,7 @@ export const Serial = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
await setConnection(connType.SERIAL);
|
await setConnection(connType.SERIAL);
|
||||||
}}
|
}}
|
||||||
|
disabled={connecting}
|
||||||
icon={<FiCheck />}
|
icon={<FiCheck />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,8 +74,18 @@ export const Serial = (): JSX.Element => {
|
|||||||
<p>No previously connected devices found</p>
|
<p>No previously connected devices found</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Button type="submit" className="mt-2 ml-auto" border>
|
<Button
|
||||||
Connect
|
className="mt-2 ml-auto"
|
||||||
|
onClick={async (): Promise<void> => {
|
||||||
|
if (connecting) {
|
||||||
|
await connection.disconnect();
|
||||||
|
} else {
|
||||||
|
await onSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
border
|
||||||
|
>
|
||||||
|
{connecting ? 'Disconnect' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
import { m } from 'framer-motion';
|
||||||
|
|
||||||
|
export interface CardProps {
|
||||||
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Card = ({ className, children }: CardProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<m.div
|
||||||
|
className={`flex select-none rounded-md bg-white p-4 shadow-md dark:bg-primaryDark ${className}`}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</m.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { FiActivity, FiAperture, FiTag } from 'react-icons/fi';
|
import { FiActivity, FiAperture, FiTag } from 'react-icons/fi';
|
||||||
|
|
||||||
import { ContextItem } from './ContextItem';
|
import { ContextItem } from '@components/generic/ContextItem';
|
||||||
|
|
||||||
export interface ContextMenuProps {
|
export interface ContextMenuProps {
|
||||||
items?: JSX.Element;
|
items?: JSX.Element;
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
export interface CoverProps {
|
|
||||||
content: JSX.Element;
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Cover = ({ content, enabled }: CoverProps): JSX.Element => {
|
|
||||||
return enabled ? <div className="m-4 ">{content}</div> : <></>;
|
|
||||||
};
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import { IconButton } from '@meshtastic/components';
|
|
||||||
|
|
||||||
export interface ListItemProps {
|
|
||||||
selected: boolean;
|
|
||||||
selectedIcon: JSX.Element;
|
|
||||||
actions?: JSX.Element;
|
|
||||||
status: JSX.Element;
|
|
||||||
onClick?: () => void;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ListItem = ({
|
|
||||||
selected,
|
|
||||||
selectedIcon,
|
|
||||||
actions,
|
|
||||||
status,
|
|
||||||
onClick,
|
|
||||||
children,
|
|
||||||
}: ListItemProps): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onClick={(): void => {
|
|
||||||
onClick && onClick();
|
|
||||||
}}
|
|
||||||
className={`flex select-none rounded-md border bg-gray-100 shadow-md dark:bg-primaryDark ${
|
|
||||||
selected
|
|
||||||
? 'border-primary dark:border-primary'
|
|
||||||
: 'border-gray-100 dark:border-primaryDark'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="w-3 rounded-l-md bg-green-500" />
|
|
||||||
<div className="flex justify-between p-2">
|
|
||||||
<div className="my-auto flex space-x-2">
|
|
||||||
{status}
|
|
||||||
<div className="flex gap-2">{children}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{actions}
|
|
||||||
<IconButton active={selected} icon={selectedIcon} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
export const Loading = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className="absolute top-0 bottom-0 left-0 right-0 z-10 flex rounded-md backdrop-blur-sm backdrop-filter">
|
||||||
|
<div className="m-auto text-lg font-medium text-gray-400">Loading</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import type React from 'react';
|
|||||||
import { AnimatePresence, AnimateSharedLayout, m } from 'framer-motion';
|
import { AnimatePresence, AnimateSharedLayout, m } from 'framer-motion';
|
||||||
import { FiArrowLeft } from 'react-icons/fi';
|
import { FiArrowLeft } from 'react-icons/fi';
|
||||||
|
|
||||||
import { IconButton } from '@meshtastic/components';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
|
||||||
export interface SidebarOverlayProps {
|
export interface SidebarOverlayProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'tippy.js/dist/tippy.css';
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
import cuid from 'cuid';
|
||||||
|
|
||||||
|
import Tippy, { TippyProps } from '@tippyjs/react';
|
||||||
|
|
||||||
|
export const Tooltip = ({
|
||||||
|
children,
|
||||||
|
content,
|
||||||
|
...props
|
||||||
|
}: TippyProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Tippy content={content} {...props}>
|
||||||
|
<div key={cuid()}>{children}</div>
|
||||||
|
</Tippy>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FiCheck } from 'react-icons/fi';
|
||||||
|
|
||||||
|
type DefaultButtonProps = JSX.IntrinsicElements['button'];
|
||||||
|
|
||||||
|
export enum ButtonSize {
|
||||||
|
Small = 'small',
|
||||||
|
Medium = 'medium',
|
||||||
|
Large = 'large',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonProps extends DefaultButtonProps {
|
||||||
|
icon?: JSX.Element;
|
||||||
|
active?: boolean;
|
||||||
|
border?: boolean;
|
||||||
|
size?: ButtonSize;
|
||||||
|
confirmAction?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button = ({
|
||||||
|
icon,
|
||||||
|
className,
|
||||||
|
active,
|
||||||
|
border,
|
||||||
|
size = ButtonSize.Medium,
|
||||||
|
confirmAction,
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: ButtonProps): JSX.Element => {
|
||||||
|
const [hasConfirmed, setHasConfirmed] = React.useState(false);
|
||||||
|
|
||||||
|
const handleConfirm = (): void => {
|
||||||
|
if (typeof confirmAction == 'function') {
|
||||||
|
if (hasConfirmed) {
|
||||||
|
void confirmAction();
|
||||||
|
}
|
||||||
|
setHasConfirmed(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setHasConfirmed(false);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleConfirm}
|
||||||
|
className={`flex select-none items-center space-x-3 rounded-md border border-transparent text-sm transition duration-200 ease-in-out focus-within:border-primary focus-within:shadow-border active:scale-95 dark:text-white dark:focus-within:border-primary
|
||||||
|
${
|
||||||
|
size === ButtonSize.Small
|
||||||
|
? 'p-0'
|
||||||
|
: size === ButtonSize.Medium
|
||||||
|
? 'p-2'
|
||||||
|
: 'p-4'
|
||||||
|
}
|
||||||
|
${
|
||||||
|
disabled
|
||||||
|
? 'cursor-not-allowed bg-white dark:bg-primaryDark'
|
||||||
|
: 'cursor-pointer hover:bg-gray-100 hover:shadow-md dark:hover:bg-secondaryDark'
|
||||||
|
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${className}`}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{icon && (
|
||||||
|
<div className="text-gray-500 dark:text-gray-400">
|
||||||
|
{hasConfirmed ? <FiCheck /> : icon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span>{children}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
||||||
|
|
||||||
|
export interface IconButtonProps extends DefaulButtonProps {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconButton = ({
|
||||||
|
icon,
|
||||||
|
active,
|
||||||
|
disabled,
|
||||||
|
...props
|
||||||
|
}: IconButtonProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className="my-auto text-gray-500 dark:text-gray-400">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={disabled}
|
||||||
|
className={`rounded-md p-2 transition duration-200 ease-in-out active:scale-95 ${
|
||||||
|
active
|
||||||
|
? 'bg-gray-200 dark:bg-gray-600'
|
||||||
|
: 'hover:bg-gray-200 dark:hover:bg-gray-600'
|
||||||
|
} ${
|
||||||
|
disabled ? 'cursor-not-allowed text-gray-400 dark:text-gray-700' : ''
|
||||||
|
}`}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
<span className="sr-only">Refresh</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Label } from '@components/generic/form/Label';
|
||||||
|
|
||||||
|
type DefaultInputProps = JSX.IntrinsicElements['input'];
|
||||||
|
|
||||||
|
export interface CheckboxProps extends DefaultInputProps {
|
||||||
|
action?: (enabled: boolean) => void;
|
||||||
|
label: string;
|
||||||
|
valid?: boolean;
|
||||||
|
validationMessage?: string;
|
||||||
|
error?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
|
function Input(
|
||||||
|
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps,
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-col">
|
||||||
|
<Label label={label} />
|
||||||
|
<div className="ml-auto">
|
||||||
|
<input
|
||||||
|
ref={ref}
|
||||||
|
type="checkbox"
|
||||||
|
id={id}
|
||||||
|
className={`h-8 w-8 appearance-none rounded-md border border-gray-400 transition duration-200 ease-in-out checked:border-transparent checked:bg-primary focus-within:shadow-border focus:outline-none dark:border-gray-200 ${
|
||||||
|
props.disabled
|
||||||
|
? 'border-gray-400 bg-gray-300 text-gray-500 dark:border-gray-700 dark:bg-secondaryDark dark:text-gray-400'
|
||||||
|
: ''
|
||||||
|
} ${
|
||||||
|
error
|
||||||
|
? 'border-red-500'
|
||||||
|
: props.disabled
|
||||||
|
? 'border-gray-200'
|
||||||
|
: 'focus-within:border-primary hover:border-primary dark:focus-within:border-primary dark:hover:border-primary'
|
||||||
|
}`}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!valid && (
|
||||||
|
<div className="text-sm text-gray-600">{validationMessage}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { Loading } from '@meshtastic/components';
|
import { Loading } from '@components/generic/Loading';
|
||||||
|
|
||||||
export interface FormProps {
|
export interface FormProps {
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { InputWrapper } from '@components/generic/form/InputWrapper';
|
||||||
|
import { Label } from '@components/generic/form/Label';
|
||||||
|
|
||||||
|
type DefaultInputProps = JSX.IntrinsicElements['input'];
|
||||||
|
|
||||||
|
export interface InputProps extends DefaultInputProps {
|
||||||
|
label?: string;
|
||||||
|
error?: string;
|
||||||
|
action?: JSX.Element;
|
||||||
|
prefix?: string;
|
||||||
|
suffix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
function Input({ label, error, action, suffix, ...props }: InputProps, ref) {
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
{label && <Label label={label} error={error} />}
|
||||||
|
<InputWrapper error={error} disabled={props.disabled}>
|
||||||
|
<input
|
||||||
|
ref={ref}
|
||||||
|
className="h-10 w-full bg-transparent px-3 py-2 focus:outline-none disabled:cursor-not-allowed dark:text-white"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
{suffix && (
|
||||||
|
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||||
|
{suffix}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{action && <div className="mr-1 flex">{action}</div>}
|
||||||
|
</InputWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
export interface LabelProps {
|
||||||
|
error?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputWrapper = ({
|
||||||
|
error,
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
}: LabelProps): JSX.Element => (
|
||||||
|
<div
|
||||||
|
className={`flex w-full rounded-md border border-gray-400 transition duration-200 ease-in-out dark:border-gray-200 ${
|
||||||
|
disabled
|
||||||
|
? 'border-gray-400 bg-gray-300 text-gray-500 dark:border-gray-700 dark:bg-secondaryDark dark:text-gray-400'
|
||||||
|
: ''
|
||||||
|
} ${
|
||||||
|
error
|
||||||
|
? 'border-red-500 dark:border-red-500'
|
||||||
|
: disabled
|
||||||
|
? ''
|
||||||
|
: ' focus-within:border-primary focus-within:shadow-border hover:border-primary dark:focus-within:border-primary dark:hover:border-primary'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { InputWrapper } from '@components/generic/form/InputWrapper';
|
||||||
|
import { Label } from '@components/generic/form/Label';
|
||||||
|
|
||||||
|
type DefaultSelectProps = JSX.IntrinsicElements['select'];
|
||||||
|
|
||||||
|
export interface SelectProps extends DefaultSelectProps {
|
||||||
|
options?: {
|
||||||
|
name: string | number;
|
||||||
|
value: string | number;
|
||||||
|
}[];
|
||||||
|
optionsEnum?: { [s: string]: string | number };
|
||||||
|
label?: string;
|
||||||
|
error?: string;
|
||||||
|
small?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||||
|
({ options, optionsEnum, label, error, small, ...props }, ref) => {
|
||||||
|
const optionsEnumValues = optionsEnum
|
||||||
|
? Object.entries(optionsEnum).filter(
|
||||||
|
(value) => typeof value[1] === 'number',
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{label && <Label label={label} error={error} />}
|
||||||
|
<InputWrapper error={error} disabled={props.disabled}>
|
||||||
|
<select
|
||||||
|
ref={ref}
|
||||||
|
className={`w-full rounded-md bg-transparent focus:border-primary focus:outline-none disabled:cursor-not-allowed dark:text-white ${
|
||||||
|
small ? 'm-1' : 'mx-2 h-10'
|
||||||
|
}`}
|
||||||
|
disabled={
|
||||||
|
props.disabled
|
||||||
|
? true
|
||||||
|
: !(optionsEnumValues.length || options?.length)
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{!(optionsEnumValues.length || options?.length) && (
|
||||||
|
<option key="loading" className="dark:bg-gray-700">
|
||||||
|
Loading
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
{optionsEnumValues.length &&
|
||||||
|
optionsEnumValues.map(([name, value], index) => (
|
||||||
|
<option key={index} className="dark:bg-gray-700" value={value}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{options &&
|
||||||
|
options.map((option, index) => (
|
||||||
|
<option
|
||||||
|
key={index}
|
||||||
|
className="dark:bg-gray-700"
|
||||||
|
value={option.value}
|
||||||
|
>
|
||||||
|
{option.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</InputWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -4,11 +4,10 @@ import { FiMessageCircle, FiSettings } from 'react-icons/fi';
|
|||||||
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
||||||
import { VscExtensions } from 'react-icons/vsc';
|
import { VscExtensions } from 'react-icons/vsc';
|
||||||
|
|
||||||
import { toggleMobileNav } from '@app/core/slices/appSlice';
|
import { NavLinkButton } from '@components/layout/Sidebar/NavLinkButton';
|
||||||
import { useAppDispatch } from '@app/hooks/useAppDispatch';
|
|
||||||
import { routes, useRoute } from '@core/router';
|
import { routes, useRoute } from '@core/router';
|
||||||
|
import { toggleMobileNav } from '@core/slices/appSlice';
|
||||||
import { NavLinkButton } from './NavLinkButton';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
|
|
||||||
export interface ButtonNavProps {
|
export interface ButtonNavProps {
|
||||||
toggleSettingsOpen: () => void;
|
toggleSettingsOpen: () => void;
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import React from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Channels = (): JSX.Element => {
|
export const Channels = (): JSX.Element => {
|
||||||
|
|||||||
@@ -15,22 +15,21 @@ import {
|
|||||||
FiZap,
|
FiZap,
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
|
|
||||||
import { CollapsibleSection } from '@app/components/layout/Sidebar/sections/CollapsibleSection';
|
import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection';
|
||||||
import { ExternalSection } from '@app/components/layout/Sidebar/sections/ExternalSection';
|
import { ExternalSection } from '@components/generic/Sidebar/ExternalSection';
|
||||||
import { SidebarOverlay } from '@app/components/layout/Sidebar/sections/SidebarOverlay';
|
import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay';
|
||||||
import { Channels } from '@app/components/layout/Sidebar/Settings/Channels';
|
import { Channels } from '@components/layout/Sidebar/Settings/Channels';
|
||||||
import { ExternalNotificationsSettingsPlanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/ExternalNotifications/SettingsPlanel';
|
import { ChannelsGroup } from '@components/layout/Sidebar/Settings/channels/ChannelsGroup';
|
||||||
import { RangeTestSettingsPanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/RangeTest/SettingsPanel';
|
import { Interface } from '@components/layout/Sidebar/Settings/Interface';
|
||||||
import { SerialSettingsPanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/Serial/SettingsPanel';
|
import { ExternalNotificationsSettingsPlanel } from '@components/layout/Sidebar/Settings/plugins/ExternalNotifications';
|
||||||
import { StoreForwardSettingsPanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/StoreForward/SettingsPanel';
|
import { RangeTestSettingsPanel } from '@components/layout/Sidebar/Settings/plugins/RangeTest';
|
||||||
import { Position } from '@app/components/layout/Sidebar/Settings/Position';
|
import { SerialSettingsPanel } from '@components/layout/Sidebar/Settings/plugins/Serial';
|
||||||
import { Power } from '@app/components/layout/Sidebar/Settings/Power';
|
import { StoreForwardSettingsPanel } from '@components/layout/Sidebar/Settings/plugins/StoreForward';
|
||||||
import { Radio } from '@app/components/layout/Sidebar/Settings/Radio';
|
import { Position } from '@components/layout/Sidebar/Settings/Position';
|
||||||
import { User } from '@app/components/layout/Sidebar/Settings/User';
|
import { Power } from '@components/layout/Sidebar/Settings/Power';
|
||||||
import { WiFi } from '@app/components/layout/Sidebar/Settings/WiFi';
|
import { Radio } from '@components/layout/Sidebar/Settings/Radio';
|
||||||
|
import { User } from '@components/layout/Sidebar/Settings/User';
|
||||||
import { Interface } from './Interface';
|
import { WiFi } from '@components/layout/Sidebar/Settings/WiFi';
|
||||||
import { ChannelsGroup } from './radio/channels/panels/ChannelsGroup';
|
|
||||||
|
|
||||||
export interface SettingsProps {
|
export interface SettingsProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { Select } from '@meshtastic/components';
|
import { Select } from '@components/generic/form/Select';
|
||||||
|
|
||||||
export const Interface = (): JSX.Element => {
|
export const Interface = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import { Controller, useForm } from 'react-hook-form';
|
|||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
import { MultiSelect } from 'react-multi-select-component';
|
import { MultiSelect } from 'react-multi-select-component';
|
||||||
|
|
||||||
import { bitwiseEncode } from '@app/core/utils/bitwise';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
|
import { Input } from '@components/generic/form/Input';
|
||||||
import { Label } from '@components/generic/form/Label';
|
import { Label } from '@components/generic/form/Label';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
|
import { bitwiseEncode } from '@core/utils/bitwise';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Position = (): JSX.Element => {
|
export const Position = (): JSX.Element => {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import React from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Checkbox, IconButton, Select } from '@meshtastic/components';
|
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Power = (): JSX.Element => {
|
export const Power = (): JSX.Element => {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import React from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Checkbox, IconButton, Select } from '@meshtastic/components';
|
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Radio = (): JSX.Element => {
|
export const Radio = (): JSX.Element => {
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
import { base16 } from 'rfc4648';
|
import { base16 } from 'rfc4648';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const User = (): JSX.Element => {
|
export const User = (): JSX.Element => {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import React from 'react';
|
|||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
|
import { Input } from '@components/generic/form/Input';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
|
||||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const WiFi = (): JSX.Element => {
|
export const WiFi = (): JSX.Element => {
|
||||||
|
|||||||
+6
-3
@@ -5,9 +5,12 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
import { MdRefresh, MdVisibility, MdVisibilityOff } from 'react-icons/md';
|
import { MdRefresh, MdVisibility, MdVisibilityOff } from 'react-icons/md';
|
||||||
|
|
||||||
import { Form } from '@app/components/generic/form/Form';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { connection } from '@app/core/connection';
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
import { Form } from '@components/generic/form/Form';
|
||||||
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { Select } from '@components/generic/form/Select';
|
||||||
|
import { connection } from '@core/connection';
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export interface SettingsPanelProps {
|
export interface SettingsPanelProps {
|
||||||
+4
-5
@@ -3,13 +3,12 @@ import type React from 'react';
|
|||||||
import { FaQrcode } from 'react-icons/fa';
|
import { FaQrcode } from 'react-icons/fa';
|
||||||
import { FiCode, FiSave } from 'react-icons/fi';
|
import { FiCode, FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
import { CollapsibleSection } from '@app/components/layout/Sidebar/sections/CollapsibleSection';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection';
|
||||||
import { IconButton } from '@meshtastic/components';
|
import { SettingsPanel } from '@components/layout/Sidebar/Settings/channels/Channels';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
import { SettingsPanel } from './SettingsPanel';
|
|
||||||
|
|
||||||
export const ChannelsGroup = (): JSX.Element => {
|
export const ChannelsGroup = (): JSX.Element => {
|
||||||
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
|
const channels = useAppSelector((state) => state.meshtastic.radio.channels);
|
||||||
|
|
||||||
+6
-4
@@ -3,10 +3,12 @@ import React from 'react';
|
|||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
import { Form } from '@app/components/generic/form/Form';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { connection } from '@app/core/connection';
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { Form } from '@components/generic/form/Form';
|
||||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { connection } from '@core/connection';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
|
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
|
||||||
+6
-4
@@ -3,10 +3,12 @@ import React from 'react';
|
|||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
import { Form } from '@app/components/generic/form/Form';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { connection } from '@app/core/connection';
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { Form } from '@components/generic/form/Form';
|
||||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { connection } from '@core/connection';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const RangeTestSettingsPanel = (): JSX.Element => {
|
export const RangeTestSettingsPanel = (): JSX.Element => {
|
||||||
+6
-4
@@ -3,10 +3,12 @@ import React from 'react';
|
|||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
import { Form } from '@app/components/generic/form/Form';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { connection } from '@app/core/connection';
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { Form } from '@components/generic/form/Form';
|
||||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { connection } from '@core/connection';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const SerialSettingsPanel = (): JSX.Element => {
|
export const SerialSettingsPanel = (): JSX.Element => {
|
||||||
+6
-4
@@ -3,10 +3,12 @@ import React from 'react';
|
|||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
|
|
||||||
import { Form } from '@app/components/generic/form/Form';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { connection } from '@app/core/connection';
|
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { Form } from '@components/generic/form/Form';
|
||||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
import { Input } from '@components/generic/form/Input';
|
||||||
|
import { connection } from '@core/connection';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const StoreForwardSettingsPanel = (): JSX.Element => {
|
export const StoreForwardSettingsPanel = (): JSX.Element => {
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import JSONPretty from 'react-json-pretty';
|
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
|
||||||
|
|
||||||
export const ExternalNotificationsDebugPanel = (): JSX.Element => {
|
|
||||||
const preferences = useAppSelector(
|
|
||||||
(state) => state.meshtastic.radio.preferences,
|
|
||||||
);
|
|
||||||
|
|
||||||
const debugData = {
|
|
||||||
extNotificationPluginActive: preferences.extNotificationPluginActive,
|
|
||||||
extNotificationPluginAlertBell: preferences.extNotificationPluginAlertBell,
|
|
||||||
extNotificationPluginAlertMessage:
|
|
||||||
preferences.extNotificationPluginAlertMessage,
|
|
||||||
extNotificationPluginEnabled: preferences.extNotificationPluginEnabled,
|
|
||||||
extNotificationPluginOutput: preferences.extNotificationPluginOutput,
|
|
||||||
extNotificationPluginOutputMs: preferences.extNotificationPluginOutputMs,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="fixed right-0 m-2">
|
|
||||||
<CopyButton data={JSON.stringify(debugData)} />
|
|
||||||
</div>
|
|
||||||
<JSONPretty className="max-w-sm" data={debugData} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import JSONPretty from 'react-json-pretty';
|
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
|
||||||
|
|
||||||
export const RangeTestDebugPanel = (): JSX.Element => {
|
|
||||||
const preferences = useAppSelector(
|
|
||||||
(state) => state.meshtastic.radio.preferences,
|
|
||||||
);
|
|
||||||
|
|
||||||
const debugData = {
|
|
||||||
rangeTestPluginEnabled: preferences.rangeTestPluginEnabled,
|
|
||||||
rangeTestPluginSave: preferences.rangeTestPluginSave,
|
|
||||||
rangeTestPluginSender: preferences.rangeTestPluginSender,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="fixed right-0 m-2">
|
|
||||||
<CopyButton data={JSON.stringify(debugData)} />
|
|
||||||
</div>
|
|
||||||
<JSONPretty className="max-w-sm" data={debugData} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import JSONPretty from 'react-json-pretty';
|
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
|
||||||
|
|
||||||
export const SerialDebugPanel = (): JSX.Element => {
|
|
||||||
const preferences = useAppSelector(
|
|
||||||
(state) => state.meshtastic.radio.preferences,
|
|
||||||
);
|
|
||||||
|
|
||||||
const debugData = {
|
|
||||||
serialpluginEnabled: preferences.serialpluginEnabled,
|
|
||||||
serialpluginEcho: preferences.serialpluginEcho,
|
|
||||||
serialpluginMode: preferences.serialpluginMode,
|
|
||||||
serialpluginRxd: preferences.serialpluginRxd,
|
|
||||||
serialpluginTxd: preferences.serialpluginTxd,
|
|
||||||
serialpluginTimeout: preferences.serialpluginTimeout,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="fixed right-0 m-2">
|
|
||||||
<CopyButton data={JSON.stringify(debugData)} />
|
|
||||||
</div>
|
|
||||||
<JSONPretty className="max-w-sm" data={debugData} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import JSONPretty from 'react-json-pretty';
|
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
|
||||||
|
|
||||||
export const StoreForwardDebugPanel = (): JSX.Element => {
|
|
||||||
const preferences = useAppSelector(
|
|
||||||
(state) => state.meshtastic.radio.preferences,
|
|
||||||
);
|
|
||||||
|
|
||||||
const debugData = {
|
|
||||||
storeForwardPluginEnabled: preferences.storeForwardPluginEnabled,
|
|
||||||
storeForwardPluginHeartbeat: preferences.storeForwardPluginHeartbeat,
|
|
||||||
storeForwardPluginRecords: preferences.storeForwardPluginRecords,
|
|
||||||
storeForwardPluginHistoryReturnMax:
|
|
||||||
preferences.storeForwardPluginHistoryReturnMax,
|
|
||||||
storeForwardPluginHistoryReturnWindow:
|
|
||||||
preferences.storeForwardPluginHistoryReturnWindow,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="fixed right-0 m-2">
|
|
||||||
<CopyButton data={JSON.stringify(debugData)} />
|
|
||||||
</div>
|
|
||||||
<JSONPretty className="max-w-sm" data={debugData} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import JSONPretty from 'react-json-pretty';
|
|
||||||
|
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
|
||||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
|
||||||
|
|
||||||
export interface DebugPanelProps {
|
|
||||||
channel: Protobuf.Channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DebugPanel = ({ channel }: DebugPanelProps): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="fixed right-0 m-2">
|
|
||||||
<CopyButton data={JSON.stringify(channel)} />
|
|
||||||
</div>
|
|
||||||
<JSONPretty className="max-w-sm" data={channel} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import QRCode from 'react-qr-code';
|
|
||||||
|
|
||||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
|
||||||
|
|
||||||
export interface QRCodePanelProps {
|
|
||||||
channel: Protobuf.Channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const QRCodePanel = ({ channel }: QRCodePanelProps): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="m-auto">
|
|
||||||
<QRCode
|
|
||||||
className="rounded-md"
|
|
||||||
value={`https://www.meshtastic.org/d/#${channel.index}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { ButtonNav } from '@components/layout/Sidebar/ButtonNav';
|
||||||
|
import { Settings } from '@components/layout/Sidebar/Settings/Index';
|
||||||
import { ButtonNav } from './ButtonNav';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Settings } from './Settings/Index';
|
|
||||||
|
|
||||||
export interface SidebarProps {
|
export interface SidebarProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type React from 'react';
|
|||||||
|
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
import { IconButton } from '@meshtastic/components';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Sidebar } from '@components/layout/Sidebar';
|
||||||
|
|
||||||
import { ErrorFallback } from '../ErrorFallback';
|
import { ErrorFallback } from '../ErrorFallback';
|
||||||
import { Sidebar } from './Sidebar';
|
|
||||||
|
|
||||||
export interface LayoutProps {
|
export interface LayoutProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -16,15 +16,15 @@ import {
|
|||||||
RiArrowUpLine,
|
RiArrowUpLine,
|
||||||
} from 'react-icons/ri';
|
} from 'react-icons/ri';
|
||||||
|
|
||||||
|
import { Tooltip } from '@components/generic/Tooltip';
|
||||||
import {
|
import {
|
||||||
connType,
|
connType,
|
||||||
openConnectionModal,
|
openConnectionModal,
|
||||||
setDarkModeEnabled,
|
setDarkModeEnabled,
|
||||||
toggleMobileNav,
|
toggleMobileNav,
|
||||||
} from '@app/core/slices/appSlice';
|
} from '@core/slices/appSlice';
|
||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Tooltip } from '@meshtastic/components';
|
|
||||||
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
import { VersionInfo } from '../modals/VersionInfo';
|
import { VersionInfo } from '../modals/VersionInfo';
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import type React from 'react';
|
|||||||
import { FiCheck, FiClipboard } from 'react-icons/fi';
|
import { FiCheck, FiClipboard } from 'react-icons/fi';
|
||||||
import useCopyClipboard from 'react-use-clipboard';
|
import useCopyClipboard from 'react-use-clipboard';
|
||||||
|
|
||||||
import type { ButtonProps } from '@meshtastic/components';
|
import type { ButtonProps } from '@components/generic/button/Button';
|
||||||
import { IconButton } from '@meshtastic/components';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
|
||||||
export interface CopyButtonProps extends ButtonProps {
|
export interface CopyButtonProps extends ButtonProps {
|
||||||
data: string;
|
data: string;
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import { FiBluetooth, FiCpu, FiWifi } from 'react-icons/fi';
|
|
||||||
|
|
||||||
import { connType, openConnectionModal } from '@core/slices/appSlice';
|
|
||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
|
||||||
import { Button } from '@meshtastic/components';
|
|
||||||
import { Types } from '@meshtastic/meshtasticjs';
|
|
||||||
|
|
||||||
export const DeviceStatus = (): JSX.Element => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const appState = useAppSelector((state) => state.app);
|
|
||||||
const state = useAppSelector((state) => state.meshtastic);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
active
|
|
||||||
onClick={(): void => {
|
|
||||||
dispatch(dispatch(openConnectionModal()));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex gap-2 px-2">
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
my-auto h-2 w-2 min-w-[2] rounded-full ${
|
|
||||||
[
|
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED,
|
|
||||||
Types.DeviceStatusEnum.DEVICE_CONFIGURED,
|
|
||||||
].includes(state.deviceStatus)
|
|
||||||
? 'bg-green-400'
|
|
||||||
: [
|
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTING,
|
|
||||||
Types.DeviceStatusEnum.DEVICE_RECONNECTING,
|
|
||||||
Types.DeviceStatusEnum.DEVICE_CONFIGURING,
|
|
||||||
].includes(state.deviceStatus)
|
|
||||||
? 'bg-yellow-400'
|
|
||||||
: 'bg-gray-400'
|
|
||||||
}`}
|
|
||||||
></div>
|
|
||||||
<div className="my-auto">
|
|
||||||
{state.nodes.find(
|
|
||||||
(node) => node.number === state.radio.hardware.myNodeNum,
|
|
||||||
)?.user?.longName ?? 'Disconnected'}
|
|
||||||
</div>
|
|
||||||
{appState.connType === connType.BLE ? (
|
|
||||||
<FiBluetooth className="h-5 w-5" />
|
|
||||||
) : appState.connType === connType.SERIAL ? (
|
|
||||||
<FiCpu className="h-5 w-5" />
|
|
||||||
) : (
|
|
||||||
<FiWifi className="h-5 w-5" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -3,7 +3,8 @@ import React from 'react';
|
|||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
import { Modal } from '@components/generic/Modal';
|
import { Modal } from '@components/generic/Modal';
|
||||||
import { Card } from '@meshtastic/components';
|
|
||||||
|
import { Card } from '../generic/Card';
|
||||||
|
|
||||||
export interface VersionInfoProps {
|
export interface VersionInfoProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -49,7 +50,7 @@ export const VersionInfo = ({
|
|||||||
onclose();
|
onclose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card>
|
<Card className="relative">
|
||||||
<div className="w-full max-w-3xl p-10">Version Info</div>
|
<div className="w-full max-w-3xl p-10">Version Info</div>
|
||||||
{/* {data?.sha} */}
|
{/* {data?.sha} */}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
|
|
||||||
import type { MapStyleName } from '@app/pages/Map/styles';
|
import type { MapStyleName } from '@pages/Map/styles';
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ export const meshtasticSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
addLogEvent: (state, action: PayloadAction<Types.LogEventPacket>) => {
|
addLogEvent: (state, action: PayloadAction<Types.LogEventPacket>) => {
|
||||||
console.log(action.payload.packet);
|
|
||||||
|
|
||||||
state.logs.push(action.payload);
|
state.logs.push(action.payload);
|
||||||
},
|
},
|
||||||
setDeviceStatus: (state, action: PayloadAction<Types.DeviceStatusEnum>) => {
|
setDeviceStatus: (state, action: PayloadAction<Types.DeviceStatusEnum>) => {
|
||||||
@@ -155,8 +153,6 @@ export const meshtasticSlice = createSlice({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
console.log('node exists');
|
|
||||||
|
|
||||||
node.lastHeard = new Date(action.payload.lastHeard * 1000);
|
node.lastHeard = new Date(action.payload.lastHeard * 1000);
|
||||||
node.snr.push(action.payload.snr);
|
node.snr.push(action.payload.snr);
|
||||||
} else {
|
} else {
|
||||||
@@ -204,23 +200,14 @@ export const meshtasticSlice = createSlice({
|
|||||||
state.radio.preferences = action.payload;
|
state.radio.preferences = action.payload;
|
||||||
},
|
},
|
||||||
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
|
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
|
||||||
console.log(action.payload);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`${action.payload.message.packet.from} -> ${action.payload.message.packet.to}`,
|
|
||||||
);
|
|
||||||
state.chats[action.payload.message.packet.channel].lastInterraction =
|
state.chats[action.payload.message.packet.channel].lastInterraction =
|
||||||
new Date();
|
new Date();
|
||||||
|
|
||||||
if (action.payload.message.packet.to === 0xffffffff) {
|
if (action.payload.message.packet.to === 0xffffffff) {
|
||||||
console.log('boradcast');
|
|
||||||
|
|
||||||
state.chats[action.payload.message.packet.channel].messages.push(
|
state.chats[action.payload.message.packet.channel].messages.push(
|
||||||
action.payload,
|
action.payload,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log('dm');
|
|
||||||
|
|
||||||
const dmIndex =
|
const dmIndex =
|
||||||
action.payload.message.packet.from === state.radio.hardware.myNodeNum
|
action.payload.message.packet.from === state.radio.hardware.myNodeNum
|
||||||
? action.payload.message.packet.to
|
? action.payload.message.packet.to
|
||||||
@@ -233,8 +220,6 @@ export const meshtasticSlice = createSlice({
|
|||||||
state,
|
state,
|
||||||
action: PayloadAction<{ chatIndex: number; messageId: number }>,
|
action: PayloadAction<{ chatIndex: number; messageId: number }>,
|
||||||
) => {
|
) => {
|
||||||
console.log(action.payload);
|
|
||||||
|
|
||||||
state.chats[action.payload.chatIndex].messages.map((message) => {
|
state.chats[action.payload.chatIndex].messages.map((message) => {
|
||||||
if (message.message.packet.id === action.payload.messageId) {
|
if (message.message.packet.id === action.payload.messageId) {
|
||||||
message.ack = true;
|
message.ack = true;
|
||||||
|
|||||||
+3
-4
@@ -1,9 +1,8 @@
|
|||||||
|
import appReducer from '@core/slices/appSlice';
|
||||||
|
import mapReducer from '@core/slices/mapSlice';
|
||||||
|
import meshtasticReducer from '@core/slices/meshtasticSlice';
|
||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import appReducer from './slices/appSlice';
|
|
||||||
import mapReducer from './slices/mapSlice';
|
|
||||||
import meshtasticReducer from './slices/meshtasticSlice';
|
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
app: appReducer,
|
app: appReducer,
|
||||||
|
|||||||
+2
-4
@@ -1,4 +1,3 @@
|
|||||||
import '@meshtastic/components/dist/style.css';
|
|
||||||
import '@app/index.css';
|
import '@app/index.css';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -9,12 +8,11 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { App } from '@app/App';
|
import { App } from '@app/App';
|
||||||
|
import { ErrorFallback } from '@components/ErrorFallback';
|
||||||
import { ReloadPrompt } from '@components/pwa/ReloadPrompt';
|
import { ReloadPrompt } from '@components/pwa/ReloadPrompt';
|
||||||
|
import { RouteProvider } from '@core/router';
|
||||||
import { store } from '@core/store';
|
import { store } from '@core/store';
|
||||||
|
|
||||||
import { ErrorFallback } from './components/ErrorFallback';
|
|
||||||
import { RouteProvider } from './core/router';
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import React from 'react';
|
|||||||
import { AnimatePresence, m } from 'framer-motion';
|
import { AnimatePresence, m } from 'framer-motion';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import fetcher from '@app/core/utils/fetcher';
|
import { Card } from '@app/components/generic/Card';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import fetcher from '@core/utils/fetcher';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
|
|
||||||
export interface File {
|
export interface File {
|
||||||
nameModified: string;
|
nameModified: string;
|
||||||
@@ -38,8 +39,8 @@ export const FileBrowser = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full select-none flex-col gap-4 p-4">
|
<div className="flex h-full p-4">
|
||||||
<div className="w-full flex-grow rounded-md bg-white dark:bg-primaryDark">
|
<Card className="flex-grow flex-col">
|
||||||
<div className="flex h-10 w-full rounded-t-md border-b border-gray-300 px-4 text-lg font-semibold shadow-md dark:border-gray-600 dark:bg-zinc-700 dark:text-white">
|
<div className="flex h-10 w-full rounded-t-md border-b border-gray-300 px-4 text-lg font-semibold shadow-md dark:border-gray-600 dark:bg-zinc-700 dark:text-white">
|
||||||
<div className="my-auto w-1/3">FileName</div>
|
<div className="my-auto w-1/3">FileName</div>
|
||||||
<div className="my-auto w-1/3">Actions</div>
|
<div className="my-auto w-1/3">Actions</div>
|
||||||
@@ -80,7 +81,7 @@ export const FileBrowser = (): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import { MdSubject } from 'react-icons/md';
|
|||||||
import { RiPinDistanceFill } from 'react-icons/ri';
|
import { RiPinDistanceFill } from 'react-icons/ri';
|
||||||
import { VscExtensions } from 'react-icons/vsc';
|
import { VscExtensions } from 'react-icons/vsc';
|
||||||
|
|
||||||
import { Layout } from '@app/components/layout';
|
import { ExternalSection } from '@components/generic/Sidebar/ExternalSection';
|
||||||
import { ExternalSection } from '@app/components/layout/Sidebar/sections/ExternalSection';
|
import { Layout } from '@components/layout';
|
||||||
|
import { FileBrowser } from '@pages/Extensions/FileBrowser';
|
||||||
import { FileBrowser } from './FileBrowser';
|
import { Info } from '@pages/Extensions/Info';
|
||||||
import { Info } from './Info';
|
import { Logs } from '@pages/Extensions/Logs';
|
||||||
import { Logs } from './Logs';
|
|
||||||
|
|
||||||
export const Extensions = (): JSX.Element => {
|
export const Extensions = (): JSX.Element => {
|
||||||
const [selectedExtension, setSelectedExtension] = React.useState<
|
const [selectedExtension, setSelectedExtension] = React.useState<
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import React from 'react';
|
|||||||
|
|
||||||
import JSONPretty from 'react-json-pretty';
|
import JSONPretty from 'react-json-pretty';
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { Card } from '@app/components/generic/Card';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
|
|
||||||
export const Info = (): JSX.Element => {
|
export const Info = (): JSX.Element => {
|
||||||
const hardwareInfo = useAppSelector(
|
const hardwareInfo = useAppSelector(
|
||||||
@@ -17,18 +18,18 @@ export const Info = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col gap-4 p-4 md:flex-row">
|
<div className="flex h-full flex-col gap-4 p-4 md:flex-row">
|
||||||
<div className="flex w-full flex-col gap-4 rounded-md bg-white p-8 shadow-md dark:bg-primaryDark md:w-1/4">
|
<Card className="md:w-1/4">
|
||||||
<div className="m-auto">
|
<div className="m-auto flex flex-col gap-2">
|
||||||
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
|
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
|
||||||
</div>
|
|
||||||
<div className="text-center text-lg font-medium dark:text-white">
|
<div className="text-center text-lg font-medium dark:text-white">
|
||||||
{node?.user?.longName || 'Unknown'}
|
{node?.user?.longName || 'Unknown'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<div className="flex-grow rounded-md bg-white p-8 shadow-md dark:bg-primaryDark">
|
<Card className="flex-grow">
|
||||||
<JSONPretty className="overflow-y-auto" data={hardwareInfo} />
|
<JSONPretty className="overflow-y-auto" data={hardwareInfo} />
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import type React from 'react';
|
|||||||
import { AnimatePresence, m } from 'framer-motion';
|
import { AnimatePresence, m } from 'framer-motion';
|
||||||
import { FiArrowRight, FiPaperclip } from 'react-icons/fi';
|
import { FiArrowRight, FiPaperclip } from 'react-icons/fi';
|
||||||
|
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { Card } from '@app/components/generic/Card';
|
||||||
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const Logs = (): JSX.Element => {
|
export const Logs = (): JSX.Element => {
|
||||||
@@ -13,28 +14,27 @@ export const Logs = (): JSX.Element => {
|
|||||||
type lookupType = { [key: number]: string };
|
type lookupType = { [key: number]: string };
|
||||||
|
|
||||||
const emitterLookup: lookupType = {
|
const emitterLookup: lookupType = {
|
||||||
[Types.Emitter.sendPacket]: 'text-blue-500',
|
[Types.Emitter.sendText]: 'text-rose-500',
|
||||||
[Types.Emitter.sendText]: 'text-blue-500',
|
[Types.Emitter.sendPacket]: 'text-pink-500',
|
||||||
[Types.Emitter.sendPacket]: 'text-blue-500',
|
[Types.Emitter.sendRaw]: 'text-fuchsia-500',
|
||||||
[Types.Emitter.sendRaw]: 'text-blue-500',
|
[Types.Emitter.setPreferences]: 'text-purple-500',
|
||||||
[Types.Emitter.setPreferences]: 'text-blue-500',
|
[Types.Emitter.confirmSetPreferences]: 'text-violet-500',
|
||||||
[Types.Emitter.confirmSetPreferences]: 'text-blue-500',
|
[Types.Emitter.setOwner]: 'text-indigo-500',
|
||||||
[Types.Emitter.setOwner]: 'text-blue-500',
|
|
||||||
[Types.Emitter.setChannel]: 'text-blue-500',
|
[Types.Emitter.setChannel]: 'text-blue-500',
|
||||||
[Types.Emitter.confirmSetChannel]: 'text-blue-500',
|
[Types.Emitter.confirmSetChannel]: 'text-sky-500',
|
||||||
[Types.Emitter.deleteChannel]: 'text-blue-500',
|
[Types.Emitter.deleteChannel]: 'text-cyan-500',
|
||||||
[Types.Emitter.getChannel]: 'text-blue-500',
|
[Types.Emitter.getChannel]: 'text-teal-500',
|
||||||
[Types.Emitter.getAllChannels]: 'text-blue-500',
|
[Types.Emitter.getAllChannels]: 'text-emerald-500',
|
||||||
[Types.Emitter.getPreferences]: 'text-blue-500',
|
[Types.Emitter.getPreferences]: 'text-green-500',
|
||||||
[Types.Emitter.getOwner]: 'text-blue-500',
|
[Types.Emitter.getOwner]: 'text-lime-500',
|
||||||
[Types.Emitter.configure]: 'text-blue-500',
|
[Types.Emitter.configure]: 'text-yellow-500',
|
||||||
[Types.Emitter.handleFromRadio]: 'text-blue-500',
|
[Types.Emitter.handleFromRadio]: 'text-amber-500',
|
||||||
[Types.Emitter.handleMeshPacket]: 'text-blue-500',
|
[Types.Emitter.handleMeshPacket]: 'text-orange-500',
|
||||||
[Types.Emitter.connect]: 'text-blue-500',
|
[Types.Emitter.connect]: 'text-red-500',
|
||||||
[Types.Emitter.ping]: 'text-blue-500',
|
[Types.Emitter.ping]: 'text-stone-500',
|
||||||
[Types.Emitter.readFromRadio]: 'text-blue-500',
|
[Types.Emitter.readFromRadio]: 'text-zinc-500',
|
||||||
[Types.Emitter.writeToRadio]: 'text-blue-500',
|
[Types.Emitter.writeToRadio]: 'text-gray-500',
|
||||||
[Types.Emitter.setDebugMode]: 'text-blue-500',
|
[Types.Emitter.setDebugMode]: 'text-slate-500',
|
||||||
};
|
};
|
||||||
|
|
||||||
const levelLookup: lookupType = {
|
const levelLookup: lookupType = {
|
||||||
@@ -48,11 +48,12 @@ export const Logs = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full p-4">
|
<div className="flex h-full flex-col gap-4 p-4">
|
||||||
<table className="table-cell h-full w-full select-none rounded-md bg-white dark:bg-primaryDark">
|
<Card className="flex-grow">
|
||||||
|
<table className="table-cell flex-grow">
|
||||||
<tbody
|
<tbody
|
||||||
className="
|
className="
|
||||||
block h-full flex-col overflow-y-auto py-4 px-2 font-mono text-xs dark:text-gray-400"
|
block h-full flex-col overflow-y-auto font-mono text-xs dark:text-gray-400"
|
||||||
>
|
>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{logs.length === 0 && (
|
{logs.length === 0 && (
|
||||||
@@ -107,7 +108,8 @@ export const Logs = (): JSX.Element => {
|
|||||||
</Wrapper>
|
</Wrapper>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<div className={emitterLookup[log.emitter]}>
|
<div className={emitterLookup[log.emitter]}>
|
||||||
[{Types.EmitterScope[log.scope]}.{Types.Emitter[log.emitter]}]
|
[{Types.EmitterScope[log.scope]}.
|
||||||
|
{Types.Emitter[log.emitter]}]
|
||||||
</div>
|
</div>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
<Wrapper className={levelLookup[log.level]}>
|
<Wrapper className={levelLookup[log.level]}>
|
||||||
@@ -120,12 +122,13 @@ export const Logs = (): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<FiPaperclip />
|
<FiPaperclip />
|
||||||
</td>
|
</td>
|
||||||
<td className="truncate pl-1">{log.message}</td>
|
<td className="w-full truncate pl-1">{log.message}</td>
|
||||||
</tr>
|
</tr>
|
||||||
// </ContextMenu>
|
// </ContextMenu>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { FaDirections, FaGlobeAfrica, FaMountain } from 'react-icons/fa';
|
import { FaDirections, FaGlobeAfrica, FaMountain } from 'react-icons/fa';
|
||||||
import { MdFullscreen, MdRadar, MdWbShade } from 'react-icons/md';
|
import { MdFullscreen, MdRadar, MdWbShade } from 'react-icons/md';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import {
|
import {
|
||||||
setExaggeration,
|
setExaggeration,
|
||||||
setHillShade,
|
setHillShade,
|
||||||
@@ -11,10 +12,8 @@ import {
|
|||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { useMapbox } from '@hooks/useMapbox';
|
import { useMapbox } from '@hooks/useMapbox';
|
||||||
import { IconButton } from '@meshtastic/components';
|
import type { MapStyle } from '@pages/Map/styles';
|
||||||
|
import { MapStyles } from '@pages/Map/styles';
|
||||||
import type { MapStyle } from './styles';
|
|
||||||
import { MapStyles } from './styles';
|
|
||||||
|
|
||||||
export const MapContainer = (): JSX.Element => {
|
export const MapContainer = (): JSX.Element => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ import mapboxgl from 'mapbox-gl';
|
|||||||
import { FiMapPin } from 'react-icons/fi';
|
import { FiMapPin } from 'react-icons/fi';
|
||||||
import { RiRoadMapLine } from 'react-icons/ri';
|
import { RiRoadMapLine } from 'react-icons/ri';
|
||||||
|
|
||||||
import { Layout } from '@app/components/layout';
|
import { Layout } from '@components/layout';
|
||||||
import { MapboxProvider } from '@app/components/MapBox/MapboxProvider';
|
import { MapboxProvider } from '@components/MapBox/MapboxProvider';
|
||||||
import type { Node } from '@app/core/slices/meshtasticSlice';
|
import type { Node } from '@core/slices/meshtasticSlice';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
|
import { MapContainer } from '@pages/Map/MapContainer';
|
||||||
import { NodeCard } from '../Nodes/NodeCard';
|
import { Marker } from '@pages/Map/Marker';
|
||||||
import { MapContainer } from './MapContainer';
|
import { NodeCard } from '@pages/Nodes/NodeCard';
|
||||||
import { Marker } from './Marker';
|
|
||||||
|
|
||||||
export const Map = (): JSX.Element => {
|
export const Map = (): JSX.Element => {
|
||||||
const [selectedNode, setSelectedNode] = React.useState<Node>();
|
const [selectedNode, setSelectedNode] = React.useState<Node>();
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { FiSettings } from 'react-icons/fi';
|
|||||||
import { MdPublic } from 'react-icons/md';
|
import { MdPublic } from 'react-icons/md';
|
||||||
import TimeAgo from 'timeago-react';
|
import TimeAgo from 'timeago-react';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Tooltip } from '@components/generic/Tooltip';
|
||||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { IconButton, Tooltip } from '@meshtastic/components';
|
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export interface ChannelChatProps {
|
export interface ChannelChatProps {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React from 'react';
|
|||||||
|
|
||||||
import { FiSettings } from 'react-icons/fi';
|
import { FiSettings } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
import type { Node } from '@core/slices/meshtasticSlice';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { IconButton } from '@meshtastic/components';
|
|
||||||
|
|
||||||
export interface DmChatProps {
|
export interface DmChatProps {
|
||||||
node: Node;
|
node: Node;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import type React from 'react';
|
|||||||
|
|
||||||
import { FiClock } from 'react-icons/fi';
|
import { FiClock } from 'react-icons/fi';
|
||||||
|
|
||||||
import type { Node } from '@app/core/slices/meshtasticSlice';
|
import { Tooltip } from '@components/generic/Tooltip';
|
||||||
|
import type { Node } from '@core/slices/meshtasticSlice';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { Tooltip } from '@meshtastic/components';
|
|
||||||
|
|
||||||
export interface MessageProps {
|
export interface MessageProps {
|
||||||
lastMsgSameUser: boolean;
|
lastMsgSameUser: boolean;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Input } from '@components/generic/form/Input';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
import { ackMessage } from '@core/slices/meshtasticSlice';
|
import { ackMessage } from '@core/slices/meshtasticSlice';
|
||||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { Input } from '@meshtastic/components';
|
|
||||||
|
|
||||||
export interface MessageBarProps {
|
export interface MessageBarProps {
|
||||||
chatIndex: number;
|
chatIndex: number;
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ import React from 'react';
|
|||||||
|
|
||||||
import { FiHash, FiMessageCircle } from 'react-icons/fi';
|
import { FiHash, FiMessageCircle } from 'react-icons/fi';
|
||||||
|
|
||||||
import { Layout } from '@app/components/layout';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
|
import { Layout } from '@components/layout';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
import { IconButton } from '@meshtastic/components';
|
|
||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
import { ChannelChat } from '@pages/Messages/ChannelChat';
|
||||||
import { ChannelChat } from './ChannelChat';
|
import { DmChat } from '@pages/Messages/DmChat';
|
||||||
import { DmChat } from './DmChat';
|
import { Message } from '@pages/Messages/Message';
|
||||||
import { Message } from './Message';
|
import { MessageBar } from '@pages/Messages/MessageBar';
|
||||||
import { MessageBar } from './MessageBar';
|
|
||||||
|
|
||||||
export const Messages = (): JSX.Element => {
|
export const Messages = (): JSX.Element => {
|
||||||
const [selectedChatIndex, setSelectedChatIndex] = React.useState<number>(0);
|
const [selectedChatIndex, setSelectedChatIndex] = React.useState<number>(0);
|
||||||
@@ -105,11 +104,9 @@ export const Messages = (): JSX.Element => {
|
|||||||
: chats[selectedChatIndex].messages[index - 1].message
|
: chats[selectedChatIndex].messages[index - 1].message
|
||||||
.packet.from === message.message.packet.from
|
.packet.from === message.message.packet.from
|
||||||
}
|
}
|
||||||
sender={nodes.find((node) => {
|
sender={nodes.find(
|
||||||
console.log(message);
|
(node) => node.number === message.message.packet.from,
|
||||||
|
)}
|
||||||
return node.number === message.message.packet.from;
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import { IoTelescope } from 'react-icons/io5';
|
|||||||
import { MdGpsFixed, MdGpsNotFixed, MdGpsOff } from 'react-icons/md';
|
import { MdGpsFixed, MdGpsNotFixed, MdGpsOff } from 'react-icons/md';
|
||||||
import JSONPretty from 'react-json-pretty';
|
import JSONPretty from 'react-json-pretty';
|
||||||
|
|
||||||
import { CollapsibleSection } from '@app/components/layout/Sidebar/sections/CollapsibleSection';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { SidebarOverlay } from '@app/components/layout/Sidebar/sections/SidebarOverlay';
|
import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection';
|
||||||
import { SidebarItem } from '@app/components/layout/Sidebar/SidebarItem';
|
import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay';
|
||||||
import { CopyButton } from '@app/components/menu/buttons/CopyButton';
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
|
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
import type { Node } from '@core/slices/meshtasticSlice';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { useMapbox } from '@hooks/useMapbox';
|
import { useMapbox } from '@hooks/useMapbox';
|
||||||
import { IconButton } from '@meshtastic/components';
|
|
||||||
|
|
||||||
type PositionConfidence = 'high' | 'low' | 'none';
|
type PositionConfidence = 'high' | 'low' | 'none';
|
||||||
|
|
||||||
@@ -126,7 +126,25 @@ export const NodeCard = ({
|
|||||||
</div>
|
</div>
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
<CollapsibleSection title="Location" icon={<FiMapPin />}>
|
<CollapsibleSection title="Location" icon={<FiMapPin />}>
|
||||||
<div>Info</div>
|
<>
|
||||||
|
{node.currentPosition && (
|
||||||
|
<div className="flex h-10 select-none justify-between rounded-md border border-gray-300 bg-transparent bg-gray-200 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 ">
|
||||||
|
<div className="my-auto px-1">
|
||||||
|
{(node.currentPosition.latitudeI / 1e7).toPrecision(6)},
|
||||||
|
{(node.currentPosition?.longitudeI / 1e7).toPrecision(6)}
|
||||||
|
</div>
|
||||||
|
<CopyButton
|
||||||
|
data={
|
||||||
|
node.currentPosition
|
||||||
|
? `${node.currentPosition.latitudeI / 1e7},${
|
||||||
|
node.currentPosition.longitudeI / 1e7
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
<CollapsibleSection title="Line of Sight" icon={<IoTelescope />}>
|
<CollapsibleSection title="Line of Sight" icon={<IoTelescope />}>
|
||||||
<div>Info</div>
|
<div>Info</div>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import ReactFlow, { Background, Controls, MiniMap } from 'react-flow-renderer';
|
|||||||
import { FiSettings } from 'react-icons/fi';
|
import { FiSettings } from 'react-icons/fi';
|
||||||
import { RiMindMap } from 'react-icons/ri';
|
import { RiMindMap } from 'react-icons/ri';
|
||||||
|
|
||||||
import { Layout } from '@app/components/layout';
|
import { IconButton } from '@components/generic/button/IconButton';
|
||||||
import { SidebarItem } from '@app/components/layout/Sidebar/SidebarItem';
|
import { Layout } from '@components/layout';
|
||||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { IconButton } from '@meshtastic/components';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
|
|
||||||
export const Nodes = (): JSX.Element => {
|
export const Nodes = (): JSX.Element => {
|
||||||
const [graphNodes, setGraphNodes] = React.useState<Node[]>([]);
|
const [graphNodes, setGraphNodes] = React.useState<Node[]>([]);
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import JSONPretty from 'react-json-pretty';
|
|
||||||
|
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
|
||||||
|
|
||||||
export interface DebugPanelProps {
|
|
||||||
node: Node;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DebugPanel = ({ node }: DebugPanelProps): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="relative">
|
|
||||||
<div className="fixed right-0 m-2">
|
|
||||||
<CopyButton data={JSON.stringify(node)} />
|
|
||||||
</div>
|
|
||||||
<JSONPretty className="max-w-sm" data={node} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import type React from 'react';
|
|
||||||
|
|
||||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
|
||||||
import type { Node } from '@core/slices/meshtasticSlice';
|
|
||||||
|
|
||||||
export interface PositionPanelProps {
|
|
||||||
node: Node;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PositionPanel = ({ node }: PositionPanelProps): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<div className="p-2">
|
|
||||||
{node.currentPosition && (
|
|
||||||
<div className="flex h-10 select-none justify-between rounded-md border border-gray-300 bg-transparent bg-gray-200 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 ">
|
|
||||||
<div className="my-auto px-1">
|
|
||||||
{(node.currentPosition.latitudeI / 1e7).toPrecision(6)},
|
|
||||||
{(node.currentPosition?.longitudeI / 1e7).toPrecision(6)}
|
|
||||||
</div>
|
|
||||||
<CopyButton
|
|
||||||
data={
|
|
||||||
node.currentPosition
|
|
||||||
? `${node.currentPosition.latitudeI / 1e7},${
|
|
||||||
node.currentPosition.longitudeI / 1e7
|
|
||||||
}`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { Card } from '@meshtastic/components';
|
import { Card } from '@app/components/generic/Card';
|
||||||
|
|
||||||
export const NotFound = (): JSX.Element => {
|
export const NotFound = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card>
|
||||||
title="The requested file or directory could not be found"
|
<h3>The requested file or directory could not be found</h3>
|
||||||
description="Better luck next time"
|
<h4>Better luck next time</h4>
|
||||||
>
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user