Internal cleanup
This commit is contained in:
+13
-10
@@ -9,15 +9,16 @@
|
||||
"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/)",
|
||||
"format": "prettier --write 'src/**/*.{ts,tsx}'",
|
||||
"check": "unimported",
|
||||
"lint": "eslint 'src/**/*.{ts,tsx}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emeraldpay/hashicon-react": "^0.5.2",
|
||||
"@floating-ui/react-dom": "^0.4.3",
|
||||
"@meshtastic/components": "^1.0.23",
|
||||
"@meshtastic/meshtasticjs": "^0.6.45",
|
||||
"@reduxjs/toolkit": "^1.7.2",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"base64-js": "^1.5.1",
|
||||
"cuid": "^2.1.8",
|
||||
"framer-motion": "^6.2.6",
|
||||
"graphql-request": "^4.0.0",
|
||||
"mapbox-gl": "^2.7.0",
|
||||
@@ -28,25 +29,25 @@
|
||||
"react-hook-form": "^7.27.0",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-json-pretty": "^2.2.0",
|
||||
"react-multi-select-component": "^4.2.2",
|
||||
"react-qr-code": "^2.0.3",
|
||||
"react-multi-select-component": "^4.2.3",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-use-clipboard": "^1.0.7",
|
||||
"rfc4648": "^1.5.1",
|
||||
"rollup-plugin-visualizer": "^5.5.4",
|
||||
"swr": "^1.2.1",
|
||||
"swr": "^1.2.2",
|
||||
"timeago-react": "^3.0.4",
|
||||
"tippy.js": "^6.3.7",
|
||||
"type-route": "^0.6.0",
|
||||
"vite-plugin-environment": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hookform/devtools": "^4.0.2",
|
||||
"@types/mapbox-gl": "^2.6.1",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/w3c-web-serial": "^1.0.2",
|
||||
"@types/web-bluetooth": "^0.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
||||
"@typescript-eslint/parser": "^5.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
||||
"@typescript-eslint/parser": "^5.12.0",
|
||||
"@verypossible/eslint-config": "^1.6.1",
|
||||
"@vitejs/plugin-react": "^1.2.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
@@ -63,10 +64,12 @@
|
||||
"postcss": "^8.4.6",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-tailwindcss": "^0.1.7",
|
||||
"tailwindcss": "^3.0.22",
|
||||
"rollup-plugin-visualizer": "^5.5.4",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"tar": "^6.1.11",
|
||||
"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-pwa": "^0.11.13",
|
||||
"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 { Connection } from '@components/Connection';
|
||||
import { ContextMenu } from '@components/generic/ContextMenu';
|
||||
import { BottomNav } from '@components/menu/BottomNav';
|
||||
import { useRoute } from '@core/router';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
|
||||
import { ContextMenu } from './components/generic/ContextMenu';
|
||||
import { BottomNav } from './components/menu/BottomNav';
|
||||
import { Extensions } from './pages/Extensions/Index';
|
||||
import { Messages } from './pages/Messages';
|
||||
import { Nodes } from './pages/Nodes';
|
||||
import { NotFound } from './pages/NotFound';
|
||||
import { Extensions } from '@pages/Extensions/Index';
|
||||
import { Messages } from '@pages/Messages';
|
||||
import { Nodes } from '@pages/Nodes';
|
||||
import { NotFound } from '@pages/NotFound';
|
||||
|
||||
export const App = (): JSX.Element => {
|
||||
const route = useRoute();
|
||||
|
||||
@@ -2,11 +2,13 @@ import React from 'react';
|
||||
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
|
||||
import { Card } from '@app/components/generic/Card';
|
||||
import { BLE } from '@components/connection/BLE';
|
||||
import { HTTP } from '@components/connection/HTTP';
|
||||
import { Serial } from '@components/connection/Serial';
|
||||
import { Select } from '@components/generic/form/Select';
|
||||
import { Modal } from '@components/generic/Modal';
|
||||
import { connection, connectionUrl, setConnection } from '@core/connection';
|
||||
import { connectionUrl, setConnection } from '@core/connection';
|
||||
import {
|
||||
closeConnectionModal,
|
||||
connType,
|
||||
@@ -15,7 +17,6 @@ import {
|
||||
} from '@core/slices/appSlice';
|
||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Button, Card, Select } from '@meshtastic/components';
|
||||
import { Types } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const Connection = (): JSX.Element => {
|
||||
@@ -56,50 +57,65 @@ export const Connection = (): JSX.Element => {
|
||||
dispatch(closeConnectionModal());
|
||||
}}
|
||||
>
|
||||
<Card>
|
||||
<div className="flex w-full max-w-3xl gap-2 p-2">
|
||||
<Card className="relative">
|
||||
<div className="flex max-w-3xl flex-grow gap-4 p-2">
|
||||
<div className="w-1/2">
|
||||
{state.deviceStatus ===
|
||||
Types.DeviceStatusEnum.DEVICE_DISCONNECTED ? (
|
||||
<div className="space-y-2">
|
||||
<Select
|
||||
label="Connection Method"
|
||||
optionsEnum={connType}
|
||||
value={appState.connType}
|
||||
onChange={(e): void => {
|
||||
dispatch(setConnType(parseInt(e.target.value)));
|
||||
}}
|
||||
<div className="space-y-2">
|
||||
<Select
|
||||
label="Connection Method"
|
||||
optionsEnum={connType}
|
||||
value={appState.connType}
|
||||
onChange={(e): void => {
|
||||
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.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>
|
||||
)}
|
||||
)}
|
||||
{appState.connType === connType.BLE && (
|
||||
<BLE
|
||||
connecting={
|
||||
state.deviceStatus ===
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{appState.connType === connType.SERIAL && (
|
||||
<Serial
|
||||
connecting={
|
||||
state.deviceStatus ===
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/2 ">
|
||||
<div className="h-96 overflow-y-auto rounded-md bg-secondaryDark p-2">
|
||||
{state.logs.map((log, index) => (
|
||||
<div key={index} className="flex">
|
||||
<div className="truncate font-mono text-sm">
|
||||
{log.message}
|
||||
<div className="w-1/2">
|
||||
<div className="h-96 overflow-y-auto rounded-md bg-gray-200 p-2 dark:bg-secondaryDark dark:text-gray-400">
|
||||
{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 className="truncate font-mono text-sm">
|
||||
{log.message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
|
||||
import { MapboxContext } from '@components/MapBox/mapboxContext';
|
||||
import {
|
||||
setBearing,
|
||||
setLatLng,
|
||||
@@ -14,7 +15,6 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { useCreateMapbox } from '@hooks/useCreateMapbox';
|
||||
|
||||
import { MapStyles } from '../../pages/Map/styles';
|
||||
import { MapboxContext } from './mapboxContext';
|
||||
|
||||
export type MapboxProviderProps = {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -3,12 +3,17 @@ import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FiCheck } from 'react-icons/fi';
|
||||
|
||||
import { connType } from '@app/core/slices/appSlice';
|
||||
import { setConnection } from '@core/connection';
|
||||
import { Button, IconButton } from '@meshtastic/components';
|
||||
import { Button } from '@components/generic/button/Button';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { connection, setConnection } from '@core/connection';
|
||||
import { connType } from '@core/slices/appSlice';
|
||||
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 { handleSubmit } = useForm<{
|
||||
@@ -45,11 +50,22 @@ export const BLE = (): JSX.Element => {
|
||||
await setConnection(connType.BLE);
|
||||
}}
|
||||
icon={<FiCheck />}
|
||||
disabled={connecting}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Button type="submit" className="mt-2 ml-auto" border>
|
||||
Connect
|
||||
<Button
|
||||
className="mt-2 ml-auto"
|
||||
onClick={async (): Promise<void> => {
|
||||
if (connecting) {
|
||||
await connection.disconnect();
|
||||
} else {
|
||||
await onSubmit();
|
||||
}
|
||||
}}
|
||||
border
|
||||
>
|
||||
{connecting ? 'Disconnect' : 'Connect'}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,19 @@ import type React from 'react';
|
||||
|
||||
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 { 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 { register, handleSubmit, control } = useForm<{
|
||||
@@ -56,16 +63,27 @@ export const HTTP = (): JSX.Element => {
|
||||
value: 'remote',
|
||||
},
|
||||
]}
|
||||
disabled={connecting}
|
||||
{...register('ipSource')}
|
||||
/>
|
||||
{watchIpSource === 'local' ? (
|
||||
<Input label="Host" value={connectionUrl} disabled />
|
||||
) : (
|
||||
<Input label="Host" {...register('ip')} />
|
||||
<Input label="Host" disabled={connecting} {...register('ip')} />
|
||||
)}
|
||||
<Checkbox label="Use TLS?" {...register('tls')} />
|
||||
<Button type="submit" className="mt-2 ml-auto" border>
|
||||
Connect
|
||||
<Checkbox label="Use TLS?" disabled={connecting} {...register('tls')} />
|
||||
<Button
|
||||
className="mt-2 ml-auto"
|
||||
onClick={async (): Promise<void> => {
|
||||
if (connecting) {
|
||||
await connection.disconnect();
|
||||
} else {
|
||||
await onSubmit();
|
||||
}
|
||||
}}
|
||||
border
|
||||
>
|
||||
{connecting ? 'Disconnect' : 'Connect'}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -3,13 +3,18 @@ import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
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 { useAppDispatch } from '@hooks/useAppDispatch';
|
||||
import { Button, IconButton } from '@meshtastic/components';
|
||||
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 dispatch = useAppDispatch();
|
||||
|
||||
@@ -36,7 +41,7 @@ export const Serial = (): JSX.Element => {
|
||||
{serialDevices.length > 0 ? (
|
||||
serialDevices.map((device, index) => (
|
||||
<div
|
||||
className="flex justify-between rounded-md bg-gray-700 p-2"
|
||||
className="flex justify-between rounded-md bg-secondaryDark p-2"
|
||||
key={index}
|
||||
>
|
||||
<div className="my-auto flex gap-4">
|
||||
@@ -59,6 +64,7 @@ export const Serial = (): JSX.Element => {
|
||||
);
|
||||
await setConnection(connType.SERIAL);
|
||||
}}
|
||||
disabled={connecting}
|
||||
icon={<FiCheck />}
|
||||
/>
|
||||
</div>
|
||||
@@ -68,8 +74,18 @@ export const Serial = (): JSX.Element => {
|
||||
<p>No previously connected devices found</p>
|
||||
</div>
|
||||
)}
|
||||
<Button type="submit" className="mt-2 ml-auto" border>
|
||||
Connect
|
||||
<Button
|
||||
className="mt-2 ml-auto"
|
||||
onClick={async (): Promise<void> => {
|
||||
if (connecting) {
|
||||
await connection.disconnect();
|
||||
} else {
|
||||
await onSubmit();
|
||||
}
|
||||
}}
|
||||
border
|
||||
>
|
||||
{connecting ? 'Disconnect' : 'Connect'}
|
||||
</Button>
|
||||
</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 { ContextItem } from './ContextItem';
|
||||
import { ContextItem } from '@components/generic/ContextItem';
|
||||
|
||||
export interface ContextMenuProps {
|
||||
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 { FiArrowLeft } from 'react-icons/fi';
|
||||
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
|
||||
export interface SidebarOverlayProps {
|
||||
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 { Loading } from '@meshtastic/components';
|
||||
import { Loading } from '@components/generic/Loading';
|
||||
|
||||
export interface FormProps {
|
||||
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 { VscExtensions } from 'react-icons/vsc';
|
||||
|
||||
import { toggleMobileNav } from '@app/core/slices/appSlice';
|
||||
import { useAppDispatch } from '@app/hooks/useAppDispatch';
|
||||
import { NavLinkButton } from '@components/layout/Sidebar/NavLinkButton';
|
||||
import { routes, useRoute } from '@core/router';
|
||||
|
||||
import { NavLinkButton } from './NavLinkButton';
|
||||
import { toggleMobileNav } from '@core/slices/appSlice';
|
||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||
|
||||
export interface ButtonNavProps {
|
||||
toggleSettingsOpen: () => void;
|
||||
|
||||
@@ -3,9 +3,12 @@ import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
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 { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const Channels = (): JSX.Element => {
|
||||
|
||||
@@ -15,22 +15,21 @@ import {
|
||||
FiZap,
|
||||
} from 'react-icons/fi';
|
||||
|
||||
import { CollapsibleSection } from '@app/components/layout/Sidebar/sections/CollapsibleSection';
|
||||
import { ExternalSection } from '@app/components/layout/Sidebar/sections/ExternalSection';
|
||||
import { SidebarOverlay } from '@app/components/layout/Sidebar/sections/SidebarOverlay';
|
||||
import { Channels } from '@app/components/layout/Sidebar/Settings/Channels';
|
||||
import { ExternalNotificationsSettingsPlanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/ExternalNotifications/SettingsPlanel';
|
||||
import { RangeTestSettingsPanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/RangeTest/SettingsPanel';
|
||||
import { SerialSettingsPanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/Serial/SettingsPanel';
|
||||
import { StoreForwardSettingsPanel } from '@app/components/layout/Sidebar/Settings/plugins/panels/StoreForward/SettingsPanel';
|
||||
import { Position } from '@app/components/layout/Sidebar/Settings/Position';
|
||||
import { Power } from '@app/components/layout/Sidebar/Settings/Power';
|
||||
import { Radio } from '@app/components/layout/Sidebar/Settings/Radio';
|
||||
import { User } from '@app/components/layout/Sidebar/Settings/User';
|
||||
import { WiFi } from '@app/components/layout/Sidebar/Settings/WiFi';
|
||||
|
||||
import { Interface } from './Interface';
|
||||
import { ChannelsGroup } from './radio/channels/panels/ChannelsGroup';
|
||||
import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection';
|
||||
import { ExternalSection } from '@components/generic/Sidebar/ExternalSection';
|
||||
import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay';
|
||||
import { Channels } from '@components/layout/Sidebar/Settings/Channels';
|
||||
import { ChannelsGroup } from '@components/layout/Sidebar/Settings/channels/ChannelsGroup';
|
||||
import { Interface } from '@components/layout/Sidebar/Settings/Interface';
|
||||
import { ExternalNotificationsSettingsPlanel } from '@components/layout/Sidebar/Settings/plugins/ExternalNotifications';
|
||||
import { RangeTestSettingsPanel } from '@components/layout/Sidebar/Settings/plugins/RangeTest';
|
||||
import { SerialSettingsPanel } from '@components/layout/Sidebar/Settings/plugins/Serial';
|
||||
import { StoreForwardSettingsPanel } from '@components/layout/Sidebar/Settings/plugins/StoreForward';
|
||||
import { Position } from '@components/layout/Sidebar/Settings/Position';
|
||||
import { Power } from '@components/layout/Sidebar/Settings/Power';
|
||||
import { Radio } from '@components/layout/Sidebar/Settings/Radio';
|
||||
import { User } from '@components/layout/Sidebar/Settings/User';
|
||||
import { WiFi } from '@components/layout/Sidebar/Settings/WiFi';
|
||||
|
||||
export interface SettingsProps {
|
||||
open: boolean;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { Select } from '@meshtastic/components';
|
||||
import { Select } from '@components/generic/form/Select';
|
||||
|
||||
export const Interface = (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -4,11 +4,14 @@ import { Controller, useForm } from 'react-hook-form';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
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 { Select } from '@components/generic/form/Select';
|
||||
import { connection } from '@core/connection';
|
||||
import { bitwiseEncode } from '@core/utils/bitwise';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const Position = (): JSX.Element => {
|
||||
|
||||
@@ -3,9 +3,11 @@ import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
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 { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Select } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const Power = (): JSX.Element => {
|
||||
|
||||
@@ -3,9 +3,11 @@ import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
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 { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Select } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const Radio = (): JSX.Element => {
|
||||
|
||||
@@ -4,9 +4,12 @@ import { useForm } from 'react-hook-form';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
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 { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const User = (): JSX.Element => {
|
||||
|
||||
@@ -3,9 +3,11 @@ import React from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
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 { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const WiFi = (): JSX.Element => {
|
||||
|
||||
+6
-3
@@ -5,9 +5,12 @@ import { useForm } from 'react-hook-form';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
import { MdRefresh, MdVisibility, MdVisibilityOff } from 'react-icons/md';
|
||||
|
||||
import { Form } from '@app/components/generic/form/Form';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { Checkbox, IconButton, Input, Select } from '@meshtastic/components';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
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';
|
||||
|
||||
export interface SettingsPanelProps {
|
||||
+4
-5
@@ -3,13 +3,12 @@ import type React from 'react';
|
||||
import { FaQrcode } from 'react-icons/fa';
|
||||
import { FiCode, FiSave } from 'react-icons/fi';
|
||||
|
||||
import { CollapsibleSection } from '@app/components/layout/Sidebar/sections/CollapsibleSection';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection';
|
||||
import { SettingsPanel } from '@components/layout/Sidebar/Settings/channels/Channels';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import { SettingsPanel } from './SettingsPanel';
|
||||
|
||||
export const ChannelsGroup = (): JSX.Element => {
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
|
||||
import { Form } from '@app/components/generic/form/Form';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Form } from '@components/generic/form/Form';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
|
||||
+6
-4
@@ -3,10 +3,12 @@ import React from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
|
||||
import { Form } from '@app/components/generic/form/Form';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Form } from '@components/generic/form/Form';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const RangeTestSettingsPanel = (): JSX.Element => {
|
||||
+6
-4
@@ -3,10 +3,12 @@ import React from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
|
||||
import { Form } from '@app/components/generic/form/Form';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Form } from '@components/generic/form/Form';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const SerialSettingsPanel = (): JSX.Element => {
|
||||
+6
-4
@@ -3,10 +3,12 @@ import React from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { FiSave } from 'react-icons/fi';
|
||||
|
||||
import { Form } from '@app/components/generic/form/Form';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { Checkbox, IconButton, Input } from '@meshtastic/components';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Form } from '@components/generic/form/Form';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { connection } from '@core/connection';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
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 { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
|
||||
import { ButtonNav } from './ButtonNav';
|
||||
import { Settings } from './Settings/Index';
|
||||
import { ButtonNav } from '@components/layout/Sidebar/ButtonNav';
|
||||
import { Settings } from '@components/layout/Sidebar/Settings/Index';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
|
||||
export interface SidebarProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -2,10 +2,10 @@ import type React from 'react';
|
||||
|
||||
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 { Sidebar } from './Sidebar';
|
||||
|
||||
export interface LayoutProps {
|
||||
title: string;
|
||||
|
||||
@@ -16,15 +16,15 @@ import {
|
||||
RiArrowUpLine,
|
||||
} from 'react-icons/ri';
|
||||
|
||||
import { Tooltip } from '@components/generic/Tooltip';
|
||||
import {
|
||||
connType,
|
||||
openConnectionModal,
|
||||
setDarkModeEnabled,
|
||||
toggleMobileNav,
|
||||
} from '@app/core/slices/appSlice';
|
||||
} from '@core/slices/appSlice';
|
||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Tooltip } from '@meshtastic/components';
|
||||
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import { VersionInfo } from '../modals/VersionInfo';
|
||||
|
||||
@@ -3,8 +3,8 @@ import type React from 'react';
|
||||
import { FiCheck, FiClipboard } from 'react-icons/fi';
|
||||
import useCopyClipboard from 'react-use-clipboard';
|
||||
|
||||
import type { ButtonProps } from '@meshtastic/components';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
import type { ButtonProps } from '@components/generic/button/Button';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
|
||||
export interface CopyButtonProps extends ButtonProps {
|
||||
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 { Modal } from '@components/generic/Modal';
|
||||
import { Card } from '@meshtastic/components';
|
||||
|
||||
import { Card } from '../generic/Card';
|
||||
|
||||
export interface VersionInfoProps {
|
||||
visible: boolean;
|
||||
@@ -49,7 +50,7 @@ export const VersionInfo = ({
|
||||
onclose();
|
||||
}}
|
||||
>
|
||||
<Card>
|
||||
<Card className="relative">
|
||||
<div className="w-full max-w-3xl p-10">Version Info</div>
|
||||
{/* {data?.sha} */}
|
||||
</Card>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
|
||||
@@ -85,8 +85,6 @@ export const meshtasticSlice = createSlice({
|
||||
initialState,
|
||||
reducers: {
|
||||
addLogEvent: (state, action: PayloadAction<Types.LogEventPacket>) => {
|
||||
console.log(action.payload.packet);
|
||||
|
||||
state.logs.push(action.payload);
|
||||
},
|
||||
setDeviceStatus: (state, action: PayloadAction<Types.DeviceStatusEnum>) => {
|
||||
@@ -155,8 +153,6 @@ export const meshtasticSlice = createSlice({
|
||||
);
|
||||
|
||||
if (node) {
|
||||
console.log('node exists');
|
||||
|
||||
node.lastHeard = new Date(action.payload.lastHeard * 1000);
|
||||
node.snr.push(action.payload.snr);
|
||||
} else {
|
||||
@@ -204,23 +200,14 @@ export const meshtasticSlice = createSlice({
|
||||
state.radio.preferences = action.payload;
|
||||
},
|
||||
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 =
|
||||
new Date();
|
||||
|
||||
if (action.payload.message.packet.to === 0xffffffff) {
|
||||
console.log('boradcast');
|
||||
|
||||
state.chats[action.payload.message.packet.channel].messages.push(
|
||||
action.payload,
|
||||
);
|
||||
} else {
|
||||
console.log('dm');
|
||||
|
||||
const dmIndex =
|
||||
action.payload.message.packet.from === state.radio.hardware.myNodeNum
|
||||
? action.payload.message.packet.to
|
||||
@@ -233,8 +220,6 @@ export const meshtasticSlice = createSlice({
|
||||
state,
|
||||
action: PayloadAction<{ chatIndex: number; messageId: number }>,
|
||||
) => {
|
||||
console.log(action.payload);
|
||||
|
||||
state.chats[action.payload.chatIndex].messages.map((message) => {
|
||||
if (message.message.packet.id === action.payload.messageId) {
|
||||
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 appReducer from './slices/appSlice';
|
||||
import mapReducer from './slices/mapSlice';
|
||||
import meshtasticReducer from './slices/meshtasticSlice';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
app: appReducer,
|
||||
|
||||
+2
-4
@@ -1,4 +1,3 @@
|
||||
import '@meshtastic/components/dist/style.css';
|
||||
import '@app/index.css';
|
||||
|
||||
import React from 'react';
|
||||
@@ -9,12 +8,11 @@ import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { App } from '@app/App';
|
||||
import { ErrorFallback } from '@components/ErrorFallback';
|
||||
import { ReloadPrompt } from '@components/pwa/ReloadPrompt';
|
||||
import { RouteProvider } from '@core/router';
|
||||
import { store } from '@core/store';
|
||||
|
||||
import { ErrorFallback } from './components/ErrorFallback';
|
||||
import { RouteProvider } from './core/router';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
|
||||
@@ -3,8 +3,9 @@ import React from 'react';
|
||||
import { AnimatePresence, m } from 'framer-motion';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import fetcher from '@app/core/utils/fetcher';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { Card } from '@app/components/generic/Card';
|
||||
import fetcher from '@core/utils/fetcher';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
|
||||
export interface File {
|
||||
nameModified: string;
|
||||
@@ -38,8 +39,8 @@ export const FileBrowser = (): JSX.Element => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full select-none flex-col gap-4 p-4">
|
||||
<div className="w-full flex-grow rounded-md bg-white dark:bg-primaryDark">
|
||||
<div className="flex h-full p-4">
|
||||
<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="my-auto w-1/3">FileName</div>
|
||||
<div className="my-auto w-1/3">Actions</div>
|
||||
@@ -80,7 +81,7 @@ export const FileBrowser = (): JSX.Element => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,12 +5,11 @@ import { MdSubject } from 'react-icons/md';
|
||||
import { RiPinDistanceFill } from 'react-icons/ri';
|
||||
import { VscExtensions } from 'react-icons/vsc';
|
||||
|
||||
import { Layout } from '@app/components/layout';
|
||||
import { ExternalSection } from '@app/components/layout/Sidebar/sections/ExternalSection';
|
||||
|
||||
import { FileBrowser } from './FileBrowser';
|
||||
import { Info } from './Info';
|
||||
import { Logs } from './Logs';
|
||||
import { ExternalSection } from '@components/generic/Sidebar/ExternalSection';
|
||||
import { Layout } from '@components/layout';
|
||||
import { FileBrowser } from '@pages/Extensions/FileBrowser';
|
||||
import { Info } from '@pages/Extensions/Info';
|
||||
import { Logs } from '@pages/Extensions/Logs';
|
||||
|
||||
export const Extensions = (): JSX.Element => {
|
||||
const [selectedExtension, setSelectedExtension] = React.useState<
|
||||
|
||||
@@ -2,8 +2,9 @@ import React from 'react';
|
||||
|
||||
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 { useAppSelector } from '@hooks/useAppSelector';
|
||||
|
||||
export const Info = (): JSX.Element => {
|
||||
const hardwareInfo = useAppSelector(
|
||||
@@ -17,18 +18,18 @@ export const Info = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<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">
|
||||
<div className="m-auto">
|
||||
<Card className="md:w-1/4">
|
||||
<div className="m-auto flex flex-col gap-2">
|
||||
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
|
||||
<div className="text-center text-lg font-medium dark:text-white">
|
||||
{node?.user?.longName || 'Unknown'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center text-lg font-medium dark:text-white">
|
||||
{node?.user?.longName || 'Unknown'}
|
||||
</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} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+101
-98
@@ -3,7 +3,8 @@ import type React from 'react';
|
||||
import { AnimatePresence, m } from 'framer-motion';
|
||||
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';
|
||||
|
||||
export const Logs = (): JSX.Element => {
|
||||
@@ -13,28 +14,27 @@ export const Logs = (): JSX.Element => {
|
||||
type lookupType = { [key: number]: string };
|
||||
|
||||
const emitterLookup: lookupType = {
|
||||
[Types.Emitter.sendPacket]: 'text-blue-500',
|
||||
[Types.Emitter.sendText]: 'text-blue-500',
|
||||
[Types.Emitter.sendPacket]: 'text-blue-500',
|
||||
[Types.Emitter.sendRaw]: 'text-blue-500',
|
||||
[Types.Emitter.setPreferences]: 'text-blue-500',
|
||||
[Types.Emitter.confirmSetPreferences]: 'text-blue-500',
|
||||
[Types.Emitter.setOwner]: 'text-blue-500',
|
||||
[Types.Emitter.sendText]: 'text-rose-500',
|
||||
[Types.Emitter.sendPacket]: 'text-pink-500',
|
||||
[Types.Emitter.sendRaw]: 'text-fuchsia-500',
|
||||
[Types.Emitter.setPreferences]: 'text-purple-500',
|
||||
[Types.Emitter.confirmSetPreferences]: 'text-violet-500',
|
||||
[Types.Emitter.setOwner]: 'text-indigo-500',
|
||||
[Types.Emitter.setChannel]: 'text-blue-500',
|
||||
[Types.Emitter.confirmSetChannel]: 'text-blue-500',
|
||||
[Types.Emitter.deleteChannel]: 'text-blue-500',
|
||||
[Types.Emitter.getChannel]: 'text-blue-500',
|
||||
[Types.Emitter.getAllChannels]: 'text-blue-500',
|
||||
[Types.Emitter.getPreferences]: 'text-blue-500',
|
||||
[Types.Emitter.getOwner]: 'text-blue-500',
|
||||
[Types.Emitter.configure]: 'text-blue-500',
|
||||
[Types.Emitter.handleFromRadio]: 'text-blue-500',
|
||||
[Types.Emitter.handleMeshPacket]: 'text-blue-500',
|
||||
[Types.Emitter.connect]: 'text-blue-500',
|
||||
[Types.Emitter.ping]: 'text-blue-500',
|
||||
[Types.Emitter.readFromRadio]: 'text-blue-500',
|
||||
[Types.Emitter.writeToRadio]: 'text-blue-500',
|
||||
[Types.Emitter.setDebugMode]: 'text-blue-500',
|
||||
[Types.Emitter.confirmSetChannel]: 'text-sky-500',
|
||||
[Types.Emitter.deleteChannel]: 'text-cyan-500',
|
||||
[Types.Emitter.getChannel]: 'text-teal-500',
|
||||
[Types.Emitter.getAllChannels]: 'text-emerald-500',
|
||||
[Types.Emitter.getPreferences]: 'text-green-500',
|
||||
[Types.Emitter.getOwner]: 'text-lime-500',
|
||||
[Types.Emitter.configure]: 'text-yellow-500',
|
||||
[Types.Emitter.handleFromRadio]: 'text-amber-500',
|
||||
[Types.Emitter.handleMeshPacket]: 'text-orange-500',
|
||||
[Types.Emitter.connect]: 'text-red-500',
|
||||
[Types.Emitter.ping]: 'text-stone-500',
|
||||
[Types.Emitter.readFromRadio]: 'text-zinc-500',
|
||||
[Types.Emitter.writeToRadio]: 'text-gray-500',
|
||||
[Types.Emitter.setDebugMode]: 'text-slate-500',
|
||||
};
|
||||
|
||||
const levelLookup: lookupType = {
|
||||
@@ -48,84 +48,87 @@ export const Logs = (): JSX.Element => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full p-4">
|
||||
<table className="table-cell h-full w-full select-none rounded-md bg-white dark:bg-primaryDark">
|
||||
<tbody
|
||||
className="
|
||||
block h-full flex-col overflow-y-auto py-4 px-2 font-mono text-xs dark:text-gray-400"
|
||||
>
|
||||
<AnimatePresence>
|
||||
{logs.length === 0 && (
|
||||
<div className="flex h-full w-full">
|
||||
<m.img
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="m-auto h-64 w-64 text-green-500"
|
||||
src={`/placeholders/${
|
||||
darkMode ? 'View Code Dark.svg' : 'View Code.svg'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{logs.map((log, index) => (
|
||||
// <ContextMenu
|
||||
// key={index}
|
||||
// items={
|
||||
// <>
|
||||
// <ContextItem title="Test" icon={<FiGitBranch />} />
|
||||
// </>
|
||||
// }
|
||||
// >
|
||||
<tr
|
||||
key={index}
|
||||
className="group hover:bg-gray-200 dark:hover:bg-secondaryDark"
|
||||
>
|
||||
<m.td
|
||||
className="w-6 cursor-pointer"
|
||||
whileHover={{ scale: 1.01 }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
>
|
||||
<div className="m-auto pl-2 text-white group-hover:text-black dark:text-primaryDark dark:group-hover:text-gray-400">
|
||||
<FiArrowRight />
|
||||
<div className="flex h-full flex-col gap-4 p-4">
|
||||
<Card className="flex-grow">
|
||||
<table className="table-cell flex-grow">
|
||||
<tbody
|
||||
className="
|
||||
block h-full flex-col overflow-y-auto font-mono text-xs dark:text-gray-400"
|
||||
>
|
||||
<AnimatePresence>
|
||||
{logs.length === 0 && (
|
||||
<div className="flex h-full w-full">
|
||||
<m.img
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="m-auto h-64 w-64 text-green-500"
|
||||
src={`/placeholders/${
|
||||
darkMode ? 'View Code Dark.svg' : 'View Code.svg'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</m.td>
|
||||
<Wrapper>
|
||||
{log.date
|
||||
.toLocaleString(undefined, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{logs.map((log, index) => (
|
||||
// <ContextMenu
|
||||
// key={index}
|
||||
// items={
|
||||
// <>
|
||||
// <ContextItem title="Test" icon={<FiGitBranch />} />
|
||||
// </>
|
||||
// }
|
||||
// >
|
||||
<tr
|
||||
key={index}
|
||||
className="group hover:bg-gray-200 dark:hover:bg-secondaryDark"
|
||||
>
|
||||
<m.td
|
||||
className="w-6 cursor-pointer"
|
||||
whileHover={{ scale: 1.01 }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
>
|
||||
<div className="m-auto pl-2 text-white group-hover:text-black dark:text-primaryDark dark:group-hover:text-gray-400">
|
||||
<FiArrowRight />
|
||||
</div>
|
||||
</m.td>
|
||||
<Wrapper>
|
||||
{log.date
|
||||
.toLocaleString(undefined, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})
|
||||
.replaceAll('/', '-')
|
||||
.replace(',', '')}
|
||||
</Wrapper>
|
||||
<Wrapper>
|
||||
<div className={emitterLookup[log.emitter]}>
|
||||
[{Types.EmitterScope[log.scope]}.{Types.Emitter[log.emitter]}]
|
||||
</div>
|
||||
</Wrapper>
|
||||
<Wrapper className={levelLookup[log.level]}>
|
||||
[{Protobuf.LogRecord_Level[log.level]}]{/* </div> */}
|
||||
</Wrapper>
|
||||
<td
|
||||
className={`m-auto ${
|
||||
log.packet ? '' : 'dark:text-secondaryDark'
|
||||
}`}
|
||||
>
|
||||
<FiPaperclip />
|
||||
</td>
|
||||
<td className="truncate pl-1">{log.message}</td>
|
||||
</tr>
|
||||
// </ContextMenu>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})
|
||||
.replaceAll('/', '-')
|
||||
.replace(',', '')}
|
||||
</Wrapper>
|
||||
<Wrapper>
|
||||
<div className={emitterLookup[log.emitter]}>
|
||||
[{Types.EmitterScope[log.scope]}.
|
||||
{Types.Emitter[log.emitter]}]
|
||||
</div>
|
||||
</Wrapper>
|
||||
<Wrapper className={levelLookup[log.level]}>
|
||||
[{Protobuf.LogRecord_Level[log.level]}]{/* </div> */}
|
||||
</Wrapper>
|
||||
<td
|
||||
className={`m-auto ${
|
||||
log.packet ? '' : 'dark:text-secondaryDark'
|
||||
}`}
|
||||
>
|
||||
<FiPaperclip />
|
||||
</td>
|
||||
<td className="w-full truncate pl-1">{log.message}</td>
|
||||
</tr>
|
||||
// </ContextMenu>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import { FaDirections, FaGlobeAfrica, FaMountain } from 'react-icons/fa';
|
||||
import { MdFullscreen, MdRadar, MdWbShade } from 'react-icons/md';
|
||||
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import {
|
||||
setExaggeration,
|
||||
setHillShade,
|
||||
@@ -11,10 +12,8 @@ import {
|
||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { useMapbox } from '@hooks/useMapbox';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
|
||||
import type { MapStyle } from './styles';
|
||||
import { MapStyles } from './styles';
|
||||
import type { MapStyle } from '@pages/Map/styles';
|
||||
import { MapStyles } from '@pages/Map/styles';
|
||||
|
||||
export const MapContainer = (): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -4,14 +4,13 @@ import mapboxgl from 'mapbox-gl';
|
||||
import { FiMapPin } from 'react-icons/fi';
|
||||
import { RiRoadMapLine } from 'react-icons/ri';
|
||||
|
||||
import { Layout } from '@app/components/layout';
|
||||
import { MapboxProvider } from '@app/components/MapBox/MapboxProvider';
|
||||
import type { Node } from '@app/core/slices/meshtasticSlice';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
|
||||
import { NodeCard } from '../Nodes/NodeCard';
|
||||
import { MapContainer } from './MapContainer';
|
||||
import { Marker } from './Marker';
|
||||
import { Layout } from '@components/layout';
|
||||
import { MapboxProvider } from '@components/MapBox/MapboxProvider';
|
||||
import type { Node } from '@core/slices/meshtasticSlice';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { MapContainer } from '@pages/Map/MapContainer';
|
||||
import { Marker } from '@pages/Map/Marker';
|
||||
import { NodeCard } from '@pages/Nodes/NodeCard';
|
||||
|
||||
export const Map = (): JSX.Element => {
|
||||
const [selectedNode, setSelectedNode] = React.useState<Node>();
|
||||
|
||||
@@ -5,10 +5,11 @@ import { FiSettings } from 'react-icons/fi';
|
||||
import { MdPublic } from 'react-icons/md';
|
||||
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 { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { IconButton, Tooltip } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface ChannelChatProps {
|
||||
|
||||
@@ -2,10 +2,10 @@ import React from 'react';
|
||||
|
||||
import { FiSettings } from 'react-icons/fi';
|
||||
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||
import type { Node } from '@core/slices/meshtasticSlice';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
|
||||
export interface DmChatProps {
|
||||
node: Node;
|
||||
|
||||
@@ -2,9 +2,9 @@ import type React from 'react';
|
||||
|
||||
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 { Tooltip } from '@meshtastic/components';
|
||||
|
||||
export interface MessageProps {
|
||||
lastMsgSameUser: boolean;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { connection } from '@core/connection';
|
||||
import { ackMessage } from '@core/slices/meshtasticSlice';
|
||||
import { useAppDispatch } from '@hooks/useAppDispatch';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Input } from '@meshtastic/components';
|
||||
|
||||
export interface MessageBarProps {
|
||||
chatIndex: number;
|
||||
|
||||
@@ -2,15 +2,14 @@ import React from 'react';
|
||||
|
||||
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 { IconButton } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import { ChannelChat } from './ChannelChat';
|
||||
import { DmChat } from './DmChat';
|
||||
import { Message } from './Message';
|
||||
import { MessageBar } from './MessageBar';
|
||||
import { ChannelChat } from '@pages/Messages/ChannelChat';
|
||||
import { DmChat } from '@pages/Messages/DmChat';
|
||||
import { Message } from '@pages/Messages/Message';
|
||||
import { MessageBar } from '@pages/Messages/MessageBar';
|
||||
|
||||
export const Messages = (): JSX.Element => {
|
||||
const [selectedChatIndex, setSelectedChatIndex] = React.useState<number>(0);
|
||||
@@ -105,11 +104,9 @@ export const Messages = (): JSX.Element => {
|
||||
: chats[selectedChatIndex].messages[index - 1].message
|
||||
.packet.from === message.message.packet.from
|
||||
}
|
||||
sender={nodes.find((node) => {
|
||||
console.log(message);
|
||||
|
||||
return node.number === message.message.packet.from;
|
||||
})}
|
||||
sender={nodes.find(
|
||||
(node) => node.number === message.message.packet.from,
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -12,14 +12,14 @@ import { IoTelescope } from 'react-icons/io5';
|
||||
import { MdGpsFixed, MdGpsNotFixed, MdGpsOff } from 'react-icons/md';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { CollapsibleSection } from '@app/components/layout/Sidebar/sections/CollapsibleSection';
|
||||
import { SidebarOverlay } from '@app/components/layout/Sidebar/sections/SidebarOverlay';
|
||||
import { SidebarItem } from '@app/components/layout/Sidebar/SidebarItem';
|
||||
import { CopyButton } from '@app/components/menu/buttons/CopyButton';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { CollapsibleSection } from '@components/generic/Sidebar/CollapsibleSection';
|
||||
import { SidebarOverlay } from '@components/generic/Sidebar/SidebarOverlay';
|
||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton';
|
||||
import type { Node } from '@core/slices/meshtasticSlice';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { useMapbox } from '@hooks/useMapbox';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
|
||||
type PositionConfidence = 'high' | 'low' | 'none';
|
||||
|
||||
@@ -126,7 +126,25 @@ export const NodeCard = ({
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
<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 title="Line of Sight" icon={<IoTelescope />}>
|
||||
<div>Info</div>
|
||||
|
||||
@@ -5,11 +5,11 @@ import ReactFlow, { Background, Controls, MiniMap } from 'react-flow-renderer';
|
||||
import { FiSettings } from 'react-icons/fi';
|
||||
import { RiMindMap } from 'react-icons/ri';
|
||||
|
||||
import { Layout } from '@app/components/layout';
|
||||
import { SidebarItem } from '@app/components/layout/Sidebar/SidebarItem';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { IconButton } from '@components/generic/button/IconButton';
|
||||
import { Layout } from '@components/layout';
|
||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
|
||||
export const Nodes = (): JSX.Element => {
|
||||
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 { Card } from '@meshtastic/components';
|
||||
import { Card } from '@app/components/generic/Card';
|
||||
|
||||
export const NotFound = (): JSX.Element => {
|
||||
return (
|
||||
<Card
|
||||
title="The requested file or directory could not be found"
|
||||
description="Better luck next time"
|
||||
>
|
||||
<Card>
|
||||
<h3>The requested file or directory could not be found</h3>
|
||||
<h4>Better luck next time</h4>
|
||||
<br />
|
||||
<br />
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user