WIP
This commit is contained in:
+2
-1
@@ -26,11 +26,12 @@
|
|||||||
"@reduxjs/toolkit": "^1.7.2",
|
"@reduxjs/toolkit": "^1.7.2",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"framer-motion": "^6.2.6",
|
"framer-motion": "^6.2.7",
|
||||||
"mapbox-gl": "^2.7.0",
|
"mapbox-gl": "^2.7.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-draggable": "^4.4.4",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-flow-renderer": "^10.0.0-next.39",
|
"react-flow-renderer": "^10.0.0-next.39",
|
||||||
"react-hook-form": "^7.27.1",
|
"react-hook-form": "^7.27.1",
|
||||||
|
|||||||
Generated
+6
-4
@@ -15,13 +15,14 @@ specifiers:
|
|||||||
'@vitejs/plugin-react': ^1.2.0
|
'@vitejs/plugin-react': ^1.2.0
|
||||||
autoprefixer: ^10.4.2
|
autoprefixer: ^10.4.2
|
||||||
base64-js: ^1.5.1
|
base64-js: ^1.5.1
|
||||||
framer-motion: ^6.2.6
|
framer-motion: ^6.2.7
|
||||||
gzipper: ^7.0.0
|
gzipper: ^7.0.0
|
||||||
mapbox-gl: ^2.7.0
|
mapbox-gl: ^2.7.0
|
||||||
postcss: ^8.4.6
|
postcss: ^8.4.6
|
||||||
prettier: ^2.5.1
|
prettier: ^2.5.1
|
||||||
react: ^17.0.2
|
react: ^17.0.2
|
||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
|
react-draggable: ^4.4.4
|
||||||
react-error-boundary: ^3.1.4
|
react-error-boundary: ^3.1.4
|
||||||
react-flow-renderer: ^10.0.0-next.39
|
react-flow-renderer: ^10.0.0-next.39
|
||||||
react-hook-form: ^7.27.1
|
react-hook-form: ^7.27.1
|
||||||
@@ -53,11 +54,12 @@ dependencies:
|
|||||||
'@reduxjs/toolkit': 1.7.2_react-redux@7.2.6+react@17.0.2
|
'@reduxjs/toolkit': 1.7.2_react-redux@7.2.6+react@17.0.2
|
||||||
'@tippyjs/react': 4.2.6_react-dom@17.0.2+react@17.0.2
|
'@tippyjs/react': 4.2.6_react-dom@17.0.2+react@17.0.2
|
||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
framer-motion: 6.2.6_react-dom@17.0.2+react@17.0.2
|
framer-motion: 6.2.7_react-dom@17.0.2+react@17.0.2
|
||||||
mapbox-gl: 2.7.0
|
mapbox-gl: 2.7.0
|
||||||
prettier: 2.5.1
|
prettier: 2.5.1
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
|
react-draggable: 4.4.4_react-dom@17.0.2+react@17.0.2
|
||||||
react-error-boundary: 3.1.4_react@17.0.2
|
react-error-boundary: 3.1.4_react@17.0.2
|
||||||
react-flow-renderer: 10.0.0-next.39_react-dom@17.0.2+react@17.0.2
|
react-flow-renderer: 10.0.0-next.39_react-dom@17.0.2+react@17.0.2
|
||||||
react-hook-form: 7.27.1_react@17.0.2
|
react-hook-form: 7.27.1_react@17.0.2
|
||||||
@@ -3448,8 +3450,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==}
|
resolution: {integrity: sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/framer-motion/6.2.6_react-dom@17.0.2+react@17.0.2:
|
/framer-motion/6.2.7_react-dom@17.0.2+react@17.0.2:
|
||||||
resolution: {integrity: sha512-7eGav5MxEEzDHozQTDY6+psTIOw2i2kM1QVoJOC3bCp9VOKoo+mKR5n7aT5JPh7ksEKFYJYz0GJDils/9S+oLA==}
|
resolution: {integrity: sha512-RExmZCFpJ3OCakoXmZz8iW8ZI5MoaHmVadydetvTSrNaKmZ7ZC/JDQpNyw1NoDG+fchRGP86lXoiTFSQuin+Cg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.8 || ^17.0.0 || ^18.0.0'
|
react: '>=16.8 || ^17.0.0 || ^18.0.0'
|
||||||
react-dom: '>=16.8 || ^17.0.0 || ^18.0.0'
|
react-dom: '>=16.8 || ^17.0.0 || ^18.0.0'
|
||||||
|
|||||||
+2
-2
@@ -13,10 +13,10 @@ import { NotFound } from '@pages/NotFound';
|
|||||||
|
|
||||||
export const App = (): JSX.Element => {
|
export const App = (): JSX.Element => {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`h-screen w-screen ${darkMode ? 'dark' : ''}`}>
|
<div className={`h-screen w-screen ${appState.darkMode ? 'dark' : ''}`}>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<Connection />
|
<Connection />
|
||||||
<div className="flex h-full flex-col bg-gray-200 dark:bg-secondaryDark">
|
<div className="flex h-full flex-col bg-gray-200 dark:bg-secondaryDark">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence, m } from 'framer-motion';
|
||||||
|
|
||||||
import { BLE } from '@components/connection/BLE';
|
import { BLE } from '@components/connection/BLE';
|
||||||
import { HTTP } from '@components/connection/HTTP';
|
import { HTTP } from '@components/connection/HTTP';
|
||||||
@@ -21,10 +22,10 @@ import { Types } from '@meshtastic/meshtasticjs';
|
|||||||
export const Connection = (): JSX.Element => {
|
export const Connection = (): JSX.Element => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const state = useAppSelector((state) => state.meshtastic);
|
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||||
const appState = useAppSelector((state) => state.app);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (!import.meta.env.VITE_PUBLIC_HOSTED) {
|
if (!import.meta.env.VITE_PUBLIC_HOSTED) {
|
||||||
dispatch(
|
dispatch(
|
||||||
setConnectionParams({
|
setConnectionParams({
|
||||||
@@ -41,11 +42,11 @@ export const Connection = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.ready) {
|
if (meshtasticState.ready) {
|
||||||
dispatch(closeConnectionModal());
|
dispatch(closeConnectionModal());
|
||||||
}
|
}
|
||||||
}, [state.ready, dispatch]);
|
}, [meshtasticState.ready, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -67,14 +68,14 @@ export const Connection = (): JSX.Element => {
|
|||||||
dispatch(setConnType(parseInt(e.target.value)));
|
dispatch(setConnType(parseInt(e.target.value)));
|
||||||
}}
|
}}
|
||||||
disabled={
|
disabled={
|
||||||
state.deviceStatus ===
|
meshtasticState.deviceStatus ===
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{appState.connType === connType.HTTP && (
|
{appState.connType === connType.HTTP && (
|
||||||
<HTTP
|
<HTTP
|
||||||
connecting={
|
connecting={
|
||||||
state.deviceStatus ===
|
meshtasticState.deviceStatus ===
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -82,7 +83,7 @@ export const Connection = (): JSX.Element => {
|
|||||||
{appState.connType === connType.BLE && (
|
{appState.connType === connType.BLE && (
|
||||||
<BLE
|
<BLE
|
||||||
connecting={
|
connecting={
|
||||||
state.deviceStatus ===
|
meshtasticState.deviceStatus ===
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -90,7 +91,7 @@ export const Connection = (): JSX.Element => {
|
|||||||
{appState.connType === connType.SERIAL && (
|
{appState.connType === connType.SERIAL && (
|
||||||
<Serial
|
<Serial
|
||||||
connecting={
|
connecting={
|
||||||
state.deviceStatus ===
|
meshtasticState.deviceStatus ===
|
||||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -98,8 +99,23 @@ export const Connection = (): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:w-1/2">
|
<div className="md:w-1/2">
|
||||||
<div className="h-96 overflow-y-auto rounded-md bg-gray-200 p-2 dark:bg-secondaryDark dark:text-gray-400">
|
<div className="h-96 overflow-y-auto rounded-md border border-gray-300 bg-gray-200 p-2 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400">
|
||||||
{state.logs
|
{meshtasticState.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-40 w-40 text-green-500"
|
||||||
|
src={`/placeholders/${
|
||||||
|
appState.darkMode
|
||||||
|
? 'View Code Dark.svg'
|
||||||
|
: 'View Code.svg'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{meshtasticState.logs
|
||||||
.filter((log) => {
|
.filter((log) => {
|
||||||
return ![
|
return ![
|
||||||
Types.Emitter.handleFromRadio,
|
Types.Emitter.handleFromRadio,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import mapboxgl from 'mapbox-gl';
|
import { ScaleControl } from 'mapbox-gl';
|
||||||
|
|
||||||
import { MapboxContext } from '@components/MapBox/mapboxContext';
|
import { MapboxContext } from '@components/MapBox/mapboxContext';
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +27,7 @@ export const MapboxProvider = ({
|
|||||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
const darkMode = useAppSelector((state) => state.app.darkMode);
|
||||||
const mapState = useAppSelector((state) => state.map);
|
const mapState = useAppSelector((state) => state.map);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const ref = React.useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const map = useCreateMapbox({
|
const map = useCreateMapbox({
|
||||||
ref,
|
ref,
|
||||||
@@ -41,9 +42,9 @@ export const MapboxProvider = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
map?.on('load', () => {
|
map?.on('load', () => {
|
||||||
map.addControl(new mapboxgl.ScaleControl());
|
map.addControl(new ScaleControl());
|
||||||
});
|
});
|
||||||
map?.on('styledata', () => {
|
map?.on('styledata', () => {
|
||||||
if (!map.getSource('mapbox-dem')) {
|
if (!map.getSource('mapbox-dem')) {
|
||||||
@@ -73,14 +74,14 @@ export const MapboxProvider = ({
|
|||||||
});
|
});
|
||||||
}, [dispatch, map, mapState.exaggeration]);
|
}, [dispatch, map, mapState.exaggeration]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const center = map?.getCenter();
|
const center = map?.getCenter();
|
||||||
if (center !== mapState.latLng) {
|
if (center !== mapState.latLng) {
|
||||||
map?.setCenter(mapState.latLng);
|
map?.setCenter(mapState.latLng);
|
||||||
}
|
}
|
||||||
}, [map, mapState.latLng]);
|
}, [map, mapState.latLng]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (['Light', 'Dark'].includes(mapState.style)) {
|
if (['Light', 'Dark'].includes(mapState.style)) {
|
||||||
dispatch(setMapStyle(darkMode ? 'Dark' : 'Light'));
|
dispatch(setMapStyle(darkMode ? 'Dark' : 'Light'));
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ export const MapboxProvider = ({
|
|||||||
/**
|
/**
|
||||||
* Hill Shading
|
* Hill Shading
|
||||||
*/
|
*/
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (map?.loaded()) {
|
if (map?.loaded()) {
|
||||||
if (mapState.hillShade) {
|
if (mapState.hillShade) {
|
||||||
map.addLayer(
|
map.addLayer(
|
||||||
@@ -111,7 +112,7 @@ export const MapboxProvider = ({
|
|||||||
/**
|
/**
|
||||||
* Exaggeration
|
* Exaggeration
|
||||||
*/
|
*/
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (map?.loaded()) {
|
if (map?.loaded()) {
|
||||||
map.setTerrain({
|
map.setTerrain({
|
||||||
source: 'mapbox-dem',
|
source: 'mapbox-dem',
|
||||||
@@ -123,7 +124,7 @@ export const MapboxProvider = ({
|
|||||||
/**
|
/**
|
||||||
* Map Style
|
* Map Style
|
||||||
*/
|
*/
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (map?.loaded()) {
|
if (map?.loaded()) {
|
||||||
map.setStyle(MapStyles[mapState.style].data);
|
map.setStyle(MapStyles[mapState.style].data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
import type mapbox from 'mapbox-gl';
|
import type { Map } from 'mapbox-gl';
|
||||||
|
|
||||||
export interface MapboxContextValue {
|
export interface MapboxContextValue {
|
||||||
ref: React.Ref<HTMLDivElement>;
|
ref: React.Ref<HTMLDivElement>;
|
||||||
map?: mapbox.Map;
|
map?: Map;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MapboxContext = React.createContext<MapboxContextValue>(
|
export const MapboxContext = createContext<MapboxContextValue>(
|
||||||
{} as MapboxContextValue,
|
{} as MapboxContextValue,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FiArrowRightCircle } from 'react-icons/fi';
|
import { FiArrowRightCircle } from 'react-icons/fi';
|
||||||
@@ -14,19 +15,19 @@ export interface BLEProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BLE = ({ connecting }: BLEProps): JSX.Element => {
|
export const BLE = ({ connecting }: BLEProps): JSX.Element => {
|
||||||
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
|
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
|
||||||
|
|
||||||
const { handleSubmit } = useForm<{
|
const { handleSubmit } = useForm<{
|
||||||
device?: BluetoothDevice;
|
device?: BluetoothDevice;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const updateBleDeviceList = React.useCallback(async (): Promise<void> => {
|
const updateBleDeviceList = useCallback(async (): Promise<void> => {
|
||||||
const ble = new IBLEConnection();
|
const ble = new IBLEConnection();
|
||||||
const devices = await ble.getDevices();
|
const devices = await ble.getDevices();
|
||||||
setBleDevices(devices);
|
setBleDevices(devices);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
void updateBleDeviceList();
|
void updateBleDeviceList();
|
||||||
}, [updateBleDeviceList]);
|
}, [updateBleDeviceList]);
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ export const BLE = ({ connecting }: BLEProps): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<div className="my-auto">{device.name}</div>
|
<div className="my-auto">{device.name}</div>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
nested
|
||||||
onClick={async (): Promise<void> => {
|
onClick={async (): Promise<void> => {
|
||||||
await setConnection(connType.BLE);
|
await setConnection(connType.BLE);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FiArrowRightCircle } from 'react-icons/fi';
|
import { FiArrowRightCircle } from 'react-icons/fi';
|
||||||
@@ -15,20 +16,20 @@ export interface SerialProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Serial = ({ connecting }: SerialProps): JSX.Element => {
|
export const Serial = ({ connecting }: SerialProps): JSX.Element => {
|
||||||
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
|
const [serialDevices, setSerialDevices] = useState<SerialPort[]>([]);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { handleSubmit } = useForm<{
|
const { handleSubmit } = useForm<{
|
||||||
device?: SerialPort;
|
device?: SerialPort;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const updateSerialDeviceList = React.useCallback(async (): Promise<void> => {
|
const updateSerialDeviceList = useCallback(async (): Promise<void> => {
|
||||||
const serial = new ISerialConnection();
|
const serial = new ISerialConnection();
|
||||||
const devices = await serial.getPorts();
|
const devices = await serial.getPorts();
|
||||||
setSerialDevices(devices);
|
setSerialDevices(devices);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
void updateSerialDeviceList();
|
void updateSerialDeviceList();
|
||||||
}, [updateSerialDeviceList]);
|
}, [updateSerialDeviceList]);
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ export const Serial = ({ connecting }: SerialProps): JSX.Element => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
nested
|
||||||
onClick={async (): Promise<void> => {
|
onClick={async (): Promise<void> => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setConnectionParams({
|
setConnectionParams({
|
||||||
|
|||||||
@@ -1,21 +1,56 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { m } from 'framer-motion';
|
import { m } from 'framer-motion';
|
||||||
|
import Draggable from 'react-draggable';
|
||||||
|
|
||||||
export interface CardProps {
|
export interface CardProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
title?: string;
|
||||||
|
actions?: React.ReactNode;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
border?: boolean;
|
||||||
|
draggable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Card = ({ className, children }: CardProps): JSX.Element => {
|
export const Card = ({
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
actions,
|
||||||
|
draggable,
|
||||||
|
border,
|
||||||
|
children,
|
||||||
|
}: CardProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<m.div
|
<Draggable handle=".handle" disabled={!draggable}>
|
||||||
className={`flex select-none rounded-md bg-white p-4 shadow-md dark:bg-primaryDark ${className}`}
|
<div
|
||||||
initial={{ opacity: 0 }}
|
className={`flex h-full w-full flex-col rounded-md shadow-md ${
|
||||||
animate={{ opacity: 1 }}
|
border ? 'border border-gray-300 dark:border-gray-600' : ''
|
||||||
exit={{ opacity: 0 }}
|
} ${className ?? ''}`}
|
||||||
>
|
>
|
||||||
{children}
|
{(title || actions) && (
|
||||||
</m.div>
|
<div
|
||||||
|
className={`w-full select-none justify-between rounded-t-md border-b border-gray-300 bg-gray-200 p-2 px-2 text-lg font-medium dark:border-gray-600 dark:bg-primaryDark dark:text-white ${
|
||||||
|
draggable ? 'cursor-move' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="handle flex h-8 justify-between">
|
||||||
|
<div className="my-auto ml-2 truncate">{title}</div>
|
||||||
|
{actions}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<m.div
|
||||||
|
className={`flex flex-grow select-none flex-col gap-4 p-4 ${
|
||||||
|
title || actions ? 'rounded-b-md backdrop-blur-xl' : 'rounded-md'
|
||||||
|
} ${draggable ? '' : 'bg-white dark:bg-primaryDark'}`}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</m.div>
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { FiActivity, FiAperture, FiTag } from 'react-icons/fi';
|
import { FiActivity, FiAperture, FiTag } from 'react-icons/fi';
|
||||||
|
|
||||||
@@ -13,8 +14,8 @@ export const ContextMenu = ({
|
|||||||
items,
|
items,
|
||||||
children,
|
children,
|
||||||
}: ContextMenuProps): JSX.Element => {
|
}: ContextMenuProps): JSX.Element => {
|
||||||
const [visible, setVisible] = React.useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [position, setPosition] = React.useState({ x: 0, y: 0 });
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -37,16 +37,18 @@ export const Modal = ({
|
|||||||
​
|
​
|
||||||
</span>
|
</span>
|
||||||
<div className="inline-block w-full max-w-3xl align-middle">
|
<div className="inline-block w-full max-w-3xl align-middle">
|
||||||
<Card className="relative flex-col gap-4 border border-gray-300 dark:border-gray-600">
|
<Card
|
||||||
<div className="flex justify-between">
|
border
|
||||||
<div className="text-2xl font-medium dark:text-white">
|
draggable
|
||||||
{title}
|
title={title}
|
||||||
</div>
|
actions={
|
||||||
<div className="flex gap-2">
|
<>
|
||||||
{actions}
|
{actions}
|
||||||
<IconButton tooltip="Close" icon={<FiX />} onClick={onClose} />
|
<IconButton tooltip="Close" icon={<FiX />} onClick={onClose} />
|
||||||
</div>
|
</>
|
||||||
</div>
|
}
|
||||||
|
className="relative flex-col gap-4 "
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { AnimatePresence, m } from 'framer-motion';
|
import { AnimatePresence, m } from 'framer-motion';
|
||||||
import { FiArrowUp } from 'react-icons/fi';
|
import { FiArrowUp } from 'react-icons/fi';
|
||||||
@@ -14,13 +15,15 @@ export const CollapsibleSection = ({
|
|||||||
icon,
|
icon,
|
||||||
children,
|
children,
|
||||||
}: CollapsibleSectionProps): JSX.Element => {
|
}: CollapsibleSectionProps): JSX.Element => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const toggleOpen = (): void => setOpen(!open);
|
const toggleOpen = (): void => setOpen(!open);
|
||||||
return (
|
return (
|
||||||
<m.div>
|
<m.div>
|
||||||
<m.div
|
<m.div
|
||||||
layout
|
layout
|
||||||
className="w-full cursor-pointer select-none overflow-hidden border-b border-gray-300 p-2 text-sm font-medium dark:border-primaryDark dark:bg-secondaryDark dark:text-gray-400"
|
className={`w-full cursor-pointer select-none overflow-hidden border-l-4 border-b border-gray-300 p-2 text-sm font-medium dark:border-primaryDark dark:bg-secondaryDark dark:text-gray-400 ${
|
||||||
|
open ? 'dark:border-l-primary' : 'dark:border-secondaryDark'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<m.div
|
<m.div
|
||||||
layout
|
layout
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { m } from 'framer-motion';
|
import { m } from 'framer-motion';
|
||||||
import { FiExternalLink } from 'react-icons/fi';
|
import { FiChevronRight } from 'react-icons/fi';
|
||||||
|
|
||||||
export interface ExternalSectionProps {
|
export interface ExternalSectionProps {
|
||||||
title: string;
|
title: string;
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
|
active?: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalSection = ({
|
export const ExternalSection = ({
|
||||||
title,
|
title,
|
||||||
icon,
|
icon,
|
||||||
|
active,
|
||||||
onClick,
|
onClick,
|
||||||
}: ExternalSectionProps): JSX.Element => {
|
}: ExternalSectionProps): JSX.Element => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const toggleOpen = (): void => setOpen(!open);
|
const toggleOpen = (): void => setOpen(!open);
|
||||||
return (
|
return (
|
||||||
<m.div
|
<m.div
|
||||||
@@ -24,7 +27,9 @@ export const ExternalSection = ({
|
|||||||
>
|
>
|
||||||
<m.div
|
<m.div
|
||||||
layout
|
layout
|
||||||
className="w-full cursor-pointer select-none overflow-hidden dark:bg-secondaryDark dark:text-gray-400"
|
className={`w-full cursor-pointer select-none overflow-hidden border-l-4 dark:bg-secondaryDark dark:text-gray-400 ${
|
||||||
|
active ? 'border-primary' : 'dark:border-secondaryDark'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<m.div
|
<m.div
|
||||||
layout
|
layout
|
||||||
@@ -38,7 +43,7 @@ export const ExternalSection = ({
|
|||||||
{title}
|
{title}
|
||||||
</m.div>
|
</m.div>
|
||||||
<m.div className="my-auto">
|
<m.div className="my-auto">
|
||||||
<FiExternalLink />
|
<FiChevronRight />
|
||||||
</m.div>
|
</m.div>
|
||||||
</m.div>
|
</m.div>
|
||||||
</m.div>
|
</m.div>
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { m } from 'framer-motion';
|
||||||
import { FiCheck } from 'react-icons/fi';
|
import { FiCheck } from 'react-icons/fi';
|
||||||
|
|
||||||
type DefaultButtonProps = JSX.IntrinsicElements['button'];
|
|
||||||
|
|
||||||
export enum ButtonSize {
|
export enum ButtonSize {
|
||||||
Small = 'small',
|
Small = 'small',
|
||||||
Medium = 'medium',
|
Medium = 'medium',
|
||||||
Large = 'large',
|
Large = 'large',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ButtonProps extends DefaultButtonProps {
|
export interface ButtonProps {
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
active?: boolean;
|
|
||||||
border?: boolean;
|
border?: boolean;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
children?: React.ReactNode;
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
|
onClick?: () => void;
|
||||||
confirmAction?: () => void;
|
confirmAction?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Button = ({
|
export const Button = ({
|
||||||
icon,
|
icon,
|
||||||
className,
|
className,
|
||||||
active,
|
|
||||||
border,
|
border,
|
||||||
size = ButtonSize.Medium,
|
size = ButtonSize.Medium,
|
||||||
confirmAction,
|
confirmAction,
|
||||||
|
onClick,
|
||||||
disabled,
|
disabled,
|
||||||
children,
|
children,
|
||||||
...props
|
|
||||||
}: ButtonProps): JSX.Element => {
|
}: ButtonProps): JSX.Element => {
|
||||||
const [hasConfirmed, setHasConfirmed] = React.useState(false);
|
const [hasConfirmed, setHasConfirmed] = useState(false);
|
||||||
|
|
||||||
const handleConfirm = (): void => {
|
const handleConfirm = (): void => {
|
||||||
if (typeof confirmAction == 'function') {
|
if (typeof confirmAction == 'function') {
|
||||||
@@ -44,9 +46,11 @@ export const Button = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<m.button
|
||||||
|
whileHover={{ scale: 1.01 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
onClick={handleConfirm}
|
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
|
className={`flex select-none items-center space-x-3 rounded-md border border-transparent text-sm focus-within:border-primary focus-within:shadow-border dark:text-white dark:focus-within:border-primary
|
||||||
${
|
${
|
||||||
size === ButtonSize.Small
|
size === ButtonSize.Small
|
||||||
? 'p-0'
|
? 'p-0'
|
||||||
@@ -58,8 +62,10 @@ export const Button = ({
|
|||||||
disabled
|
disabled
|
||||||
? 'cursor-not-allowed bg-white dark:bg-primaryDark'
|
? 'cursor-not-allowed bg-white dark:bg-primaryDark'
|
||||||
: 'cursor-pointer hover:bg-gray-100 hover:shadow-md dark:hover:bg-secondaryDark'
|
: 'cursor-pointer hover:bg-gray-100 hover:shadow-md dark:hover:bg-secondaryDark'
|
||||||
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${className}`}
|
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${
|
||||||
{...props}
|
className ?? ''
|
||||||
|
}`}
|
||||||
|
onClickCapture={onClick}
|
||||||
>
|
>
|
||||||
{icon && (
|
{icon && (
|
||||||
<div className="text-gray-500 dark:text-gray-400">
|
<div className="text-gray-500 dark:text-gray-400">
|
||||||
@@ -68,6 +74,6 @@ export const Button = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
</button>
|
</m.button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
|
import { m } from 'framer-motion';
|
||||||
|
|
||||||
import { Tooltip } from '@components/generic/Tooltip';
|
import { Tooltip } from '@components/generic/Tooltip';
|
||||||
|
|
||||||
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
||||||
@@ -7,26 +9,32 @@ type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
|||||||
export interface IconButtonProps extends DefaulButtonProps {
|
export interface IconButtonProps extends DefaulButtonProps {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
|
nested?: boolean;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconButton = ({
|
export const IconButton = ({
|
||||||
icon,
|
icon,
|
||||||
active,
|
|
||||||
tooltip,
|
tooltip,
|
||||||
|
nested,
|
||||||
|
active,
|
||||||
disabled,
|
disabled,
|
||||||
...props
|
...props
|
||||||
}: IconButtonProps): JSX.Element => {
|
}: IconButtonProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Tooltip disabled={!tooltip} content={tooltip}>
|
<Tooltip disabled={!tooltip} content={tooltip}>
|
||||||
<div className="my-auto text-gray-500 dark:text-gray-400">
|
<m.div
|
||||||
|
whileHover={{ scale: 1.01 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="my-auto text-gray-500 dark:text-gray-400"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={`rounded-md p-2 transition duration-200 ease-in-out active:scale-95 ${
|
className={`rounded-md p-2 hover:bg-gray-200 ${
|
||||||
active
|
active ? 'bg-gray-200 dark:bg-secondaryDark' : ''
|
||||||
? 'bg-gray-200 dark:bg-gray-600'
|
} ${
|
||||||
: 'hover:bg-gray-200 dark:hover:bg-gray-600'
|
nested ? 'dark:hover:bg-primaryDark' : 'dark:hover:bg-secondaryDark'
|
||||||
} ${
|
} ${
|
||||||
disabled
|
disabled
|
||||||
? 'cursor-not-allowed text-gray-400 dark:text-gray-700'
|
? 'cursor-not-allowed text-gray-400 dark:text-gray-700'
|
||||||
@@ -37,7 +45,7 @@ export const IconButton = ({
|
|||||||
{icon}
|
{icon}
|
||||||
<span className="sr-only">Refresh</span>
|
<span className="sr-only">Refresh</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</m.div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
import { Label } from '@components/generic/form/Label';
|
import { Label } from '@components/generic/form/Label';
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ export interface CheckboxProps extends DefaultInputProps {
|
|||||||
error?: boolean;
|
error?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
function Input(
|
function Input(
|
||||||
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps,
|
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps,
|
||||||
ref,
|
ref,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
import { InputWrapper } from '@components/generic/form/InputWrapper';
|
import { InputWrapper } from '@components/generic/form/InputWrapper';
|
||||||
import { Label } from '@components/generic/form/Label';
|
import { Label } from '@components/generic/form/Label';
|
||||||
@@ -13,25 +14,26 @@ export interface InputProps extends DefaultInputProps {
|
|||||||
suffix?: string;
|
suffix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||||
function Input({ label, error, action, suffix, ...props }: InputProps, ref) {
|
{ label, error, action, suffix, ...props }: InputProps,
|
||||||
return (
|
ref,
|
||||||
<div className="w-full">
|
) {
|
||||||
{label && <Label label={label} error={error} />}
|
return (
|
||||||
<InputWrapper error={error} disabled={props.disabled}>
|
<div className="w-full">
|
||||||
<input
|
{label && <Label label={label} error={error} />}
|
||||||
ref={ref}
|
<InputWrapper error={error} disabled={props.disabled}>
|
||||||
className="h-10 w-full bg-transparent px-3 py-2 focus:outline-none disabled:cursor-not-allowed dark:text-white"
|
<input
|
||||||
{...props}
|
ref={ref}
|
||||||
/>
|
className="h-10 w-full bg-transparent px-3 py-2 focus:outline-none disabled:cursor-not-allowed dark:text-white"
|
||||||
{suffix && (
|
{...props}
|
||||||
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400">
|
/>
|
||||||
{suffix}
|
{suffix && (
|
||||||
</span>
|
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||||
)}
|
{suffix}
|
||||||
{action && <div className="mr-1 flex">{action}</div>}
|
</span>
|
||||||
</InputWrapper>
|
)}
|
||||||
</div>
|
{action && <div className="mr-1 flex">{action}</div>}
|
||||||
);
|
</InputWrapper>
|
||||||
},
|
</div>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
import { InputWrapper } from '@components/generic/form/InputWrapper';
|
import { InputWrapper } from '@components/generic/form/InputWrapper';
|
||||||
import { Label } from '@components/generic/form/Label';
|
import { Label } from '@components/generic/form/Label';
|
||||||
@@ -16,7 +17,7 @@ export interface SelectProps extends DefaultSelectProps {
|
|||||||
small?: boolean;
|
small?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
||||||
({ options, optionsEnum, label, error, small, ...props }, ref) => {
|
({ options, optionsEnum, label, error, small, ...props }, ref) => {
|
||||||
const optionsEnumValues = optionsEnum
|
const optionsEnumValues = optionsEnum
|
||||||
? Object.entries(optionsEnum).filter(
|
? Object.entries(optionsEnum).filter(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiMessageCircle, FiSettings } from 'react-icons/fi';
|
import { FiMessageCircle, FiSettings } from 'react-icons/fi';
|
||||||
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
import { RiMindMap, RiRoadMapLine } from 'react-icons/ri';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } 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';
|
||||||
@@ -17,9 +18,9 @@ export const Channels = (): JSX.Element => {
|
|||||||
channels.find(
|
channels.find(
|
||||||
(channel) => channel.role === Protobuf.Channel_Role.PRIMARY,
|
(channel) => channel.role === Protobuf.Channel_Role.PRIMARY,
|
||||||
) ?? channels[0];
|
) ?? channels[0];
|
||||||
const [usePreset, setUsePreset] = React.useState(true);
|
const [usePreset, setUsePreset] = useState(true);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedChannel, setSelectedChannel] = React.useState<
|
const [selectedChannel, setSelectedChannel] = useState<
|
||||||
Protobuf.Channel | undefined
|
Protobuf.Channel | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FiAlignLeft,
|
FiAlignLeft,
|
||||||
@@ -38,8 +39,8 @@ export interface SettingsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
|
export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
|
||||||
const [pluginsOpen, setPluginsOpen] = React.useState(false);
|
const [pluginsOpen, setPluginsOpen] = useState(false);
|
||||||
const [channelsOpen, setChannelsOpen] = React.useState(false);
|
const [channelsOpen, setChannelsOpen] = useState(false);
|
||||||
const {
|
const {
|
||||||
rangeTestPluginEnabled,
|
rangeTestPluginEnabled,
|
||||||
extNotificationPluginEnabled,
|
extNotificationPluginEnabled,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { FiSave } from 'react-icons/fi';
|
import { FiSave } from 'react-icons/fi';
|
||||||
@@ -18,7 +19,7 @@ export const Position = (): JSX.Element => {
|
|||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { register, handleSubmit, formState, reset, control } =
|
const { register, handleSubmit, formState, reset, control } =
|
||||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -32,7 +33,7 @@ export const Position = (): JSX.Element => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -14,7 +15,7 @@ export const Power = (): JSX.Element => {
|
|||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { register, handleSubmit, formState, reset } =
|
const { register, handleSubmit, formState, reset } =
|
||||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -23,7 +24,7 @@ export const Power = (): JSX.Element => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -14,13 +15,13 @@ export const Radio = (): JSX.Element => {
|
|||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { register, handleSubmit, formState, reset } =
|
const { register, handleSubmit, formState, reset } =
|
||||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||||
defaultValues: preferences,
|
defaultValues: preferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -13,7 +14,7 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
|||||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||||
|
|
||||||
export const User = (): JSX.Element => {
|
export const User = (): JSX.Element => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const myNodeNum = useAppSelector(
|
const myNodeNum = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.hardware,
|
(state) => state.meshtastic.radio.hardware,
|
||||||
).myNodeNum;
|
).myNodeNum;
|
||||||
@@ -40,7 +41,7 @@ export const User = (): JSX.Element => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset({
|
reset({
|
||||||
longName: node?.user?.longName,
|
longName: node?.user?.longName,
|
||||||
shortName: node?.user?.shortName,
|
shortName: node?.user?.shortName,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -14,7 +15,7 @@ export const WiFi = (): JSX.Element => {
|
|||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { register, handleSubmit, formState, reset, control } =
|
const { register, handleSubmit, formState, reset, control } =
|
||||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||||
defaultValues: preferences,
|
defaultValues: preferences,
|
||||||
@@ -32,7 +33,7 @@ export const WiFi = (): JSX.Element => {
|
|||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { fromByteArray, toByteArray } from 'base64-js';
|
import { fromByteArray, toByteArray } from 'base64-js';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@@ -18,9 +19,9 @@ export interface SettingsPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsPanel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
export const SettingsPanel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [keySize, setKeySize] = React.useState<128 | 256>(256);
|
const [keySize, setKeySize] = useState<128 | 256>(256);
|
||||||
const [pskHidden, setPskHidden] = React.useState(true);
|
const [pskHidden, setPskHidden] = useState(true);
|
||||||
|
|
||||||
const { register, handleSubmit, setValue, formState, reset } = useForm<
|
const { register, handleSubmit, setValue, formState, reset } = useForm<
|
||||||
Omit<Protobuf.ChannelSettings, 'psk'> & { psk: string; enabled: boolean }
|
Omit<Protobuf.ChannelSettings, 'psk'> & { psk: string; enabled: boolean }
|
||||||
@@ -37,7 +38,7 @@ export const SettingsPanel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset({
|
reset({
|
||||||
enabled: [
|
enabled: [
|
||||||
Protobuf.Channel_Role.SECONDARY,
|
Protobuf.Channel_Role.SECONDARY,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -12,7 +13,7 @@ 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 => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
@@ -23,7 +24,7 @@ export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
|
|||||||
defaultValues: preferences,
|
defaultValues: preferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -12,7 +13,7 @@ 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 => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
@@ -23,7 +24,7 @@ export const RangeTestSettingsPanel = (): JSX.Element => {
|
|||||||
defaultValues: preferences,
|
defaultValues: preferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -12,7 +13,7 @@ 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 => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
@@ -23,7 +24,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
|
|||||||
defaultValues: preferences,
|
defaultValues: preferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } 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';
|
||||||
@@ -12,7 +13,7 @@ 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 => {
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const preferences = useAppSelector(
|
const preferences = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.preferences,
|
(state) => state.meshtastic.radio.preferences,
|
||||||
@@ -23,7 +24,7 @@ export const StoreForwardSettingsPanel = (): JSX.Element => {
|
|||||||
defaultValues: preferences,
|
defaultValues: preferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
reset(preferences);
|
reset(preferences);
|
||||||
}, [reset, preferences]);
|
}, [reset, preferences]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { ButtonNav } from '@components/layout/Sidebar/ButtonNav';
|
import { ButtonNav } from '@components/layout/Sidebar/ButtonNav';
|
||||||
import { Settings } from '@components/layout/Sidebar/Settings/Index';
|
import { Settings } from '@components/layout/Sidebar/Settings/Index';
|
||||||
@@ -9,7 +10,7 @@ export interface SidebarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
||||||
const [settingsOpen, setSettingsOpen] = React.useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
|
||||||
const appState = useAppSelector((state) => state.app);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FiBluetooth,
|
FiBluetooth,
|
||||||
@@ -31,7 +32,7 @@ import { VersionInfo } from '../modals/VersionInfo';
|
|||||||
import { BottomNavItem } from './BottomNavItem';
|
import { BottomNavItem } from './BottomNavItem';
|
||||||
|
|
||||||
export const BottomNav = (): JSX.Element => {
|
export const BottomNav = (): JSX.Element => {
|
||||||
const [showVersionInfo, setShowVersionInfo] = React.useState(false);
|
const [showVersionInfo, setShowVersionInfo] = useState(false);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||||
const appState = useAppSelector((state) => state.app);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import { MdUpgrade } from 'react-icons/md';
|
import { MdUpgrade } from 'react-icons/md';
|
||||||
@@ -56,7 +57,7 @@ export const VersionInfo = ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const index = data.findIndex(
|
const index = data.findIndex(
|
||||||
(commit) => commit.sha.substring(0, 7) === process.env.COMMIT_HASH,
|
(commit) => commit.sha.substring(0, 7) === process.env.COMMIT_HASH,
|
||||||
@@ -90,7 +91,7 @@ export const VersionInfo = ({
|
|||||||
data.map((commit) => (
|
data.map((commit) => (
|
||||||
<div
|
<div
|
||||||
key={commit.sha}
|
key={commit.sha}
|
||||||
className={`flex gap-2 rounded-md py-1 px-2 ${
|
className={`flex gap-2 rounded-md border border-transparent py-1 px-2 hover:border-primary ${
|
||||||
commit.sha.substring(0, 7) === process.env.COMMIT_HASH
|
commit.sha.substring(0, 7) === process.env.COMMIT_HASH
|
||||||
? 'bg-primary'
|
? 'bg-primary'
|
||||||
: 'dark:bg-secondaryDark'
|
: 'dark:bg-secondaryDark'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import mapboxgl from 'mapbox-gl';
|
import { LngLat } from 'mapbox-gl';
|
||||||
|
|
||||||
import type { MapStyleName } from '@pages/Map/styles';
|
import type { MapStyleName } from '@pages/Map/styles';
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
@@ -6,7 +6,7 @@ import { createSlice } from '@reduxjs/toolkit';
|
|||||||
|
|
||||||
interface MapState {
|
interface MapState {
|
||||||
firstLoad: boolean;
|
firstLoad: boolean;
|
||||||
latLng: mapboxgl.LngLat;
|
latLng: LngLat;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
bearing: number;
|
bearing: number;
|
||||||
pitch: number;
|
pitch: number;
|
||||||
@@ -18,7 +18,7 @@ interface MapState {
|
|||||||
|
|
||||||
const initialState: MapState = {
|
const initialState: MapState = {
|
||||||
firstLoad: true,
|
firstLoad: true,
|
||||||
latLng: new mapboxgl.LngLat(0, 0),
|
latLng: new LngLat(0, 0),
|
||||||
zoom: 2,
|
zoom: 2,
|
||||||
bearing: 0,
|
bearing: 0,
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
@@ -33,7 +33,7 @@ export const mapSlice = createSlice({
|
|||||||
name: 'map',
|
name: 'map',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setLatLng: (state, action: PayloadAction<mapboxgl.LngLat>) => {
|
setLatLng: (state, action: PayloadAction<LngLat>) => {
|
||||||
state.latLng = action.payload;
|
state.latLng = action.payload;
|
||||||
},
|
},
|
||||||
setZoom: (state, action: PayloadAction<number>) => {
|
setZoom: (state, action: PayloadAction<number>) => {
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||||
|
|
||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import mapboxgl from 'mapbox-gl';
|
import { Map, MapboxOptions } from 'mapbox-gl';
|
||||||
|
|
||||||
type AccessToken = string;
|
|
||||||
|
|
||||||
export interface useMapboxProps {
|
export interface useMapboxProps {
|
||||||
ref: React.RefObject<HTMLDivElement>;
|
ref: React.RefObject<HTMLDivElement>;
|
||||||
accessToken: AccessToken;
|
accessToken: string;
|
||||||
options?: Partial<mapboxgl.MapboxOptions>;
|
options?: Partial<MapboxOptions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateMapbox({
|
export function useCreateMapbox({
|
||||||
ref,
|
ref,
|
||||||
accessToken,
|
accessToken,
|
||||||
options,
|
options,
|
||||||
}: useMapboxProps): mapboxgl.Map | undefined {
|
}: useMapboxProps): Map | undefined {
|
||||||
const [mapInstance, setMapInstance] = React.useState<mapboxgl.Map>();
|
const [mapInstance, setMapInstance] = useState<Map>();
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const container = ref.current as HTMLDivElement;
|
const container = ref.current as HTMLDivElement;
|
||||||
if (mapInstance || !container) {
|
if (mapInstance || !container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mapboxgl.accessToken = accessToken;
|
const map = new Map({
|
||||||
const map = new mapboxgl.Map({
|
accessToken,
|
||||||
container,
|
container,
|
||||||
antialias: true,
|
antialias: true,
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import type { MapboxContextValue } from '@components/MapBox/mapboxContext';
|
import type { MapboxContextValue } from '@components/MapBox/mapboxContext';
|
||||||
import { MapboxContext } from '@components/MapBox/mapboxContext';
|
import { MapboxContext } from '@components/MapBox/mapboxContext';
|
||||||
|
|
||||||
export const useMapbox = (): MapboxContextValue => {
|
export const useMapbox = (): MapboxContextValue => {
|
||||||
return React.useContext(MapboxContext);
|
return useContext(MapboxContext);
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,7 @@
|
|||||||
import '@app/index.css';
|
import '@app/index.css';
|
||||||
|
|
||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { StrictMode } from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
|
|
||||||
import { domAnimation, LazyMotion } from 'framer-motion';
|
import { domAnimation, LazyMotion } from 'framer-motion';
|
||||||
@@ -14,7 +15,7 @@ import { RouteProvider } from '@core/router';
|
|||||||
import { store } from '@core/store';
|
import { store } from '@core/store';
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<React.StrictMode>
|
<StrictMode>
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
<RouteProvider>
|
<RouteProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@@ -25,6 +26,6 @@ render(
|
|||||||
</Provider>
|
</Provider>
|
||||||
</RouteProvider>
|
</RouteProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</React.StrictMode>,
|
</StrictMode>,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { Button } from '@app/components/generic/button/Button';
|
import { Button } from '@app/components/generic/button/Button';
|
||||||
import { Card } from '@app/components/generic/Card';
|
import { Card } from '@app/components/generic/Card';
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { AnimatePresence, m } from 'framer-motion';
|
import { AnimatePresence, m } from 'framer-motion';
|
||||||
|
import { FiFilePlus } from 'react-icons/fi';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
import { Button } from '@app/components/generic/button/Button';
|
||||||
import { Card } from '@app/components/generic/Card';
|
import { Card } from '@app/components/generic/Card';
|
||||||
import { fetcher } from '@core/utils/fetcher';
|
import { fetcher } from '@core/utils/fetcher';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
@@ -29,22 +31,27 @@ export const FileBrowser = (): JSX.Element => {
|
|||||||
const connectionParams = useAppSelector(
|
const connectionParams = useAppSelector(
|
||||||
(state) => state.app.connectionParams,
|
(state) => state.app.connectionParams,
|
||||||
);
|
);
|
||||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||||
|
|
||||||
const { data } = useSWR<Files>(
|
const { data } = useSWR<Files>(
|
||||||
`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
|
`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
|
||||||
connectionParams.HTTP.address
|
connectionParams.HTTP.address
|
||||||
}/json/spiffs/browse/static`,
|
}${
|
||||||
|
meshtasticState.radio.hardware.firmwareVersion.includes('1.2')
|
||||||
|
? '/json/spiffs/browse/static'
|
||||||
|
: '/json/fs/browse/static'
|
||||||
|
}`,
|
||||||
fetcher,
|
fetcher,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full p-4">
|
<div className="flex h-full p-4">
|
||||||
<Card className="flex-grow flex-col">
|
<Card
|
||||||
<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">
|
title="File Browser"
|
||||||
<div className="my-auto w-1/3">FileName</div>
|
actions={<Button icon={<FiFilePlus />}>Upload File</Button>}
|
||||||
<div className="my-auto w-1/3">Actions</div>
|
className="flex-grow flex-col"
|
||||||
</div>
|
>
|
||||||
<div className="h-full px-4">
|
<div className="h-full px-4">
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{(!data || data?.data.files.length === 0) && (
|
{(!data || data?.data.files.length === 0) && (
|
||||||
@@ -55,7 +62,7 @@ export const FileBrowser = (): JSX.Element => {
|
|||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="m-auto h-64 w-64 text-green-500"
|
className="m-auto h-64 w-64 text-green-500"
|
||||||
src={`/placeholders/${
|
src={`/placeholders/${
|
||||||
darkMode ? 'Files Dark.svg' : 'Files.svg'
|
appState.darkMode ? 'Files Dark.svg' : 'Files.svg'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { FiFile, FiInfo } from 'react-icons/fi';
|
import { FiFile, FiInfo } from 'react-icons/fi';
|
||||||
import { MdSubject } from 'react-icons/md';
|
import { MdSubject } from 'react-icons/md';
|
||||||
@@ -14,7 +15,7 @@ import { Logs } from '@pages/Extensions/Logs';
|
|||||||
import { Debug } from './Debug';
|
import { Debug } from './Debug';
|
||||||
|
|
||||||
export const Extensions = (): JSX.Element => {
|
export const Extensions = (): JSX.Element => {
|
||||||
const [selectedExtension, setSelectedExtension] = React.useState<
|
const [selectedExtension, setSelectedExtension] = useState<
|
||||||
'info' | 'logs' | 'fileBrowser' | 'rangeTest' | 'debug'
|
'info' | 'logs' | 'fileBrowser' | 'rangeTest' | 'debug'
|
||||||
>('info');
|
>('info');
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ export const Extensions = (): JSX.Element => {
|
|||||||
setSelectedExtension('info');
|
setSelectedExtension('info');
|
||||||
}}
|
}}
|
||||||
icon={<FiInfo />}
|
icon={<FiInfo />}
|
||||||
|
active={selectedExtension === 'info'}
|
||||||
title="Node Info"
|
title="Node Info"
|
||||||
/>
|
/>
|
||||||
<ExternalSection
|
<ExternalSection
|
||||||
@@ -36,6 +38,7 @@ export const Extensions = (): JSX.Element => {
|
|||||||
setSelectedExtension('logs');
|
setSelectedExtension('logs');
|
||||||
}}
|
}}
|
||||||
icon={<MdSubject />}
|
icon={<MdSubject />}
|
||||||
|
active={selectedExtension === 'logs'}
|
||||||
title="Logs"
|
title="Logs"
|
||||||
/>
|
/>
|
||||||
<ExternalSection
|
<ExternalSection
|
||||||
@@ -43,6 +46,7 @@ export const Extensions = (): JSX.Element => {
|
|||||||
setSelectedExtension('fileBrowser');
|
setSelectedExtension('fileBrowser');
|
||||||
}}
|
}}
|
||||||
icon={<FiFile />}
|
icon={<FiFile />}
|
||||||
|
active={selectedExtension === 'fileBrowser'}
|
||||||
title="File Browser"
|
title="File Browser"
|
||||||
/>
|
/>
|
||||||
<ExternalSection
|
<ExternalSection
|
||||||
@@ -50,6 +54,7 @@ export const Extensions = (): JSX.Element => {
|
|||||||
setSelectedExtension('rangeTest');
|
setSelectedExtension('rangeTest');
|
||||||
}}
|
}}
|
||||||
icon={<RiPinDistanceFill />}
|
icon={<RiPinDistanceFill />}
|
||||||
|
active={selectedExtension === 'rangeTest'}
|
||||||
title="Range Test"
|
title="Range Test"
|
||||||
/>
|
/>
|
||||||
<ExternalSection
|
<ExternalSection
|
||||||
@@ -57,6 +62,7 @@ export const Extensions = (): JSX.Element => {
|
|||||||
setSelectedExtension('debug');
|
setSelectedExtension('debug');
|
||||||
}}
|
}}
|
||||||
icon={<VscDebug />}
|
icon={<VscDebug />}
|
||||||
|
active={selectedExtension === 'debug'}
|
||||||
title="Debug"
|
title="Debug"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
|
import { FiRefreshCw } from 'react-icons/fi';
|
||||||
import JSONPretty from 'react-json-pretty';
|
import JSONPretty from 'react-json-pretty';
|
||||||
|
|
||||||
|
import { Button } from '@app/components/generic/button/Button';
|
||||||
import { Card } from '@app/components/generic/Card';
|
import { Card } from '@app/components/generic/Card';
|
||||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
@@ -17,8 +19,16 @@ 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 xl:flex-row">
|
||||||
<Card className="md:w-1/4">
|
<Card
|
||||||
|
title="Connected Node Details"
|
||||||
|
actions={
|
||||||
|
<Button className="truncate" icon={<FiRefreshCw />}>
|
||||||
|
Refresh Node info
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
className="xl:w-3/5"
|
||||||
|
>
|
||||||
<div className="m-auto flex flex-col gap-2">
|
<div className="m-auto flex flex-col gap-2">
|
||||||
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
|
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
|
||||||
<div className="text-center text-lg font-medium dark:text-white">
|
<div className="text-center text-lg font-medium dark:text-white">
|
||||||
@@ -27,7 +37,7 @@ export const Info = (): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="flex-grow">
|
<Card title="Debug Information" className="flex-grow">
|
||||||
<JSONPretty className="overflow-y-auto" data={hardwareInfo} />
|
<JSONPretty className="overflow-y-auto" data={hardwareInfo} />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import type React from 'react';
|
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, FiX } from 'react-icons/fi';
|
||||||
|
|
||||||
|
import { Button } from '@app/components/generic/button/Button';
|
||||||
import { Card } from '@app/components/generic/Card';
|
import { Card } from '@app/components/generic/Card';
|
||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
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 => {
|
||||||
const logs = useAppSelector((state) => state.meshtastic.logs);
|
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
const appState = useAppSelector((state) => state.app);
|
||||||
|
|
||||||
type lookupType = { [key: number]: string };
|
type lookupType = { [key: number]: string };
|
||||||
|
|
||||||
@@ -49,14 +50,18 @@ export const Logs = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col gap-4 p-4">
|
<div className="flex h-full flex-col gap-4 p-4">
|
||||||
<Card className="flex-grow overflow-y-auto">
|
<Card
|
||||||
|
title="Device Logs"
|
||||||
|
actions={<Button icon={<FiX />}>Clear Logs</Button>}
|
||||||
|
className="flex-grow overflow-y-auto"
|
||||||
|
>
|
||||||
<table className="table-cell flex-grow">
|
<table className="table-cell flex-grow">
|
||||||
<tbody
|
<tbody
|
||||||
className="
|
className="
|
||||||
block h-full flex-col overflow-y-auto 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 && (
|
{meshtasticState.logs.length === 0 && (
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<m.img
|
<m.img
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
@@ -64,13 +69,13 @@ export const Logs = (): JSX.Element => {
|
|||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="m-auto h-64 w-64 text-green-500"
|
className="m-auto h-64 w-64 text-green-500"
|
||||||
src={`/placeholders/${
|
src={`/placeholders/${
|
||||||
darkMode ? 'View Code Dark.svg' : 'View Code.svg'
|
appState.darkMode ? 'View Code Dark.svg' : 'View Code.svg'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
{logs.map((log, index) => (
|
{meshtasticState.logs.map((log, index) => (
|
||||||
// <ContextMenu
|
// <ContextMenu
|
||||||
// key={index}
|
// key={index}
|
||||||
// items={
|
// items={
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useCallback } 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';
|
||||||
@@ -23,7 +24,7 @@ export const MapContainer = (): JSX.Element => {
|
|||||||
|
|
||||||
const { ref } = useMapbox();
|
const { ref } = useMapbox();
|
||||||
|
|
||||||
const ChangeMapStyle = React.useCallback(
|
const ChangeMapStyle = useCallback(
|
||||||
(styleName: string, style: MapStyle) => {
|
(styleName: string, style: MapStyle) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setMapStyle(
|
setMapStyle(
|
||||||
|
|||||||
+13
-11
@@ -1,13 +1,15 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
import mapbox from 'mapbox-gl';
|
import type { LngLatLike, MarkerOptions } from 'mapbox-gl';
|
||||||
|
import { Marker as MapboxMarker } from 'mapbox-gl';
|
||||||
|
|
||||||
import { useMapbox } from '@hooks/useMapbox';
|
import { useMapbox } from '@hooks/useMapbox';
|
||||||
|
|
||||||
export interface MarkerProps extends Omit<mapbox.MarkerOptions, 'element'> {
|
export interface MarkerProps extends Omit<MarkerOptions, 'element'> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
center: mapbox.LngLatLike;
|
center: LngLatLike;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Marker = ({
|
export const Marker = ({
|
||||||
@@ -16,22 +18,22 @@ export const Marker = ({
|
|||||||
...props
|
...props
|
||||||
}: MarkerProps): JSX.Element => {
|
}: MarkerProps): JSX.Element => {
|
||||||
const { map } = useMapbox();
|
const { map } = useMapbox();
|
||||||
const ref = React.useRef<HTMLDivElement>(document.createElement('div'));
|
const ref = useRef<HTMLDivElement>(document.createElement('div'));
|
||||||
|
|
||||||
const addMarker = React.useCallback((): void => {
|
const addMarker = useCallback((): void => {
|
||||||
if (map) {
|
if (map) {
|
||||||
const marker = new mapbox.Marker(ref.current, props).setLngLat(center);
|
const marker = new MapboxMarker(ref.current, props).setLngLat(center);
|
||||||
marker.addTo(map);
|
marker.addTo(map);
|
||||||
}
|
}
|
||||||
}, [map, center, props]);
|
}, [map, center, props]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
map?.on('load', () => {
|
map?.on('load', () => {
|
||||||
addMarker();
|
addMarker();
|
||||||
});
|
});
|
||||||
}, [addMarker, map]);
|
}, [addMarker, map]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (map?.loaded()) {
|
if (map?.loaded()) {
|
||||||
addMarker();
|
addMarker();
|
||||||
}
|
}
|
||||||
@@ -39,5 +41,5 @@ export const Marker = ({
|
|||||||
|
|
||||||
<div ref={ref}>{children}</div>;
|
<div ref={ref}>{children}</div>;
|
||||||
|
|
||||||
return ReactDOM.createPortal(children, ref.current);
|
return createPortal(children, ref.current);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
import { FiMapPin } from 'react-icons/fi';
|
import { FiMapPin } from 'react-icons/fi';
|
||||||
@@ -13,7 +14,7 @@ import { Marker } from '@pages/Map/Marker';
|
|||||||
import { NodeCard } from '@pages/Nodes/NodeCard';
|
import { NodeCard } from '@pages/Nodes/NodeCard';
|
||||||
|
|
||||||
export const Map = (): JSX.Element => {
|
export const Map = (): JSX.Element => {
|
||||||
const [selectedNode, setSelectedNode] = React.useState<Node>();
|
const [selectedNode, setSelectedNode] = useState<Node>();
|
||||||
|
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
const myNodeNum = useAppSelector(
|
const myNodeNum = useAppSelector(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { m } from 'framer-motion';
|
import { m } from 'framer-motion';
|
||||||
import { FiSettings } from 'react-icons/fi';
|
import { FiSettings } from 'react-icons/fi';
|
||||||
@@ -41,7 +41,7 @@ export const ChannelChat = ({
|
|||||||
setSelected={(): void => {
|
setSelected={(): void => {
|
||||||
setSelectedIndex(channel.index);
|
setSelectedIndex(channel.index);
|
||||||
}}
|
}}
|
||||||
actions={<IconButton icon={<FiSettings />} />}
|
actions={<IconButton nested icon={<FiSettings />} />}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import { FiSettings } from 'react-icons/fi';
|
import { FiSettings } from 'react-icons/fi';
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export const DmChat = ({
|
|||||||
setSelected={(): void => {
|
setSelected={(): void => {
|
||||||
setSelectedIndex(node.number);
|
setSelectedIndex(node.number);
|
||||||
}}
|
}}
|
||||||
actions={<IconButton icon={<FiSettings />} />}
|
actions={<IconButton nested icon={<FiSettings />} />}
|
||||||
>
|
>
|
||||||
<div className="flex dark:text-white">
|
<div className="flex dark:text-white">
|
||||||
<div className="m-auto">
|
<div className="m-auto">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { Input } from '@components/generic/form/Input';
|
import { Input } from '@components/generic/form/Input';
|
||||||
import { connection } from '@core/connection';
|
import { connection } from '@core/connection';
|
||||||
@@ -14,9 +15,9 @@ export const MessageBar = ({ chatIndex }: MessageBarProps): JSX.Element => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||||
|
|
||||||
const [isChannel, setIsChannel] = React.useState(false);
|
const [isChannel, setIsChannel] = useState(false);
|
||||||
|
|
||||||
React.useState(() => {
|
useState(() => {
|
||||||
setIsChannel(
|
setIsChannel(
|
||||||
meshtasticState.radio.channels.findIndex(
|
meshtasticState.radio.channels.findIndex(
|
||||||
(channel) => channel.index === chatIndex,
|
(channel) => channel.index === chatIndex,
|
||||||
@@ -24,7 +25,7 @@ export const MessageBar = ({ chatIndex }: MessageBarProps): JSX.Element => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
const [currentMessage, setCurrentMessage] = useState('');
|
||||||
const sendMessage = (): void => {
|
const sendMessage = (): void => {
|
||||||
if (meshtasticState.ready) {
|
if (meshtasticState.ready) {
|
||||||
void connection.sendText(
|
void connection.sendText(
|
||||||
@@ -33,6 +34,13 @@ export const MessageBar = ({ chatIndex }: MessageBarProps): JSX.Element => {
|
|||||||
true,
|
true,
|
||||||
isChannel ? chatIndex-- : 0,
|
isChannel ? chatIndex-- : 0,
|
||||||
(id) => {
|
(id) => {
|
||||||
|
console.log(`Chat Index, ${chatIndex}`);
|
||||||
|
console.log(`Chat Index --, ${chatIndex--}`);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Chat Index computed, ${isChannel ? chatIndex-- : chatIndex}`,
|
||||||
|
);
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
ackMessage({
|
ackMessage({
|
||||||
chatIndex: isChannel ? chatIndex-- : chatIndex,
|
chatIndex: isChannel ? chatIndex-- : chatIndex,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { FiHash, FiMessageCircle } from 'react-icons/fi';
|
import { FiHash, FiMessageCircle } from 'react-icons/fi';
|
||||||
|
|
||||||
@@ -12,9 +13,9 @@ import { Message } from '@pages/Messages/Message';
|
|||||||
import { MessageBar } from '@pages/Messages/MessageBar';
|
import { MessageBar } from '@pages/Messages/MessageBar';
|
||||||
|
|
||||||
export const Messages = (): JSX.Element => {
|
export const Messages = (): JSX.Element => {
|
||||||
const [selectedChatIndex, setSelectedChatIndex] = React.useState<number>(0);
|
const [selectedChatIndex, setSelectedChatIndex] = useState<number>(0);
|
||||||
|
|
||||||
const chatRef = React.useRef<HTMLDivElement>(null);
|
const chatRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const myNodeNum = useAppSelector(
|
const myNodeNum = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.hardware,
|
(state) => state.meshtastic.radio.hardware,
|
||||||
@@ -25,7 +26,7 @@ export const Messages = (): JSX.Element => {
|
|||||||
(state) => state.meshtastic.radio.channels,
|
(state) => state.meshtastic.radio.channels,
|
||||||
).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);
|
).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (chatRef.current) {
|
if (chatRef.current) {
|
||||||
chatRef.current.scrollTop = chatRef.current.scrollHeight;
|
chatRef.current.scrollTop = chatRef.current.scrollHeight;
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ export const Messages = (): JSX.Element => {
|
|||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<div className="flex w-full justify-between border-b border-gray-300 px-2 dark:border-gray-600 dark:text-gray-300">
|
<div className="flex w-full justify-between border-b border-gray-300 px-2 dark:border-gray-600 dark:text-gray-300">
|
||||||
<div className="my-auto flex gap-2 py-2 text-sm">
|
<div className="my-auto flex gap-2 py-2 text-sm">
|
||||||
<IconButton icon={<FiHash className="h-4 w-4" />} />
|
<IconButton nested icon={<FiHash className="h-4 w-4" />} />
|
||||||
<div className="my-auto">
|
<div className="my-auto">
|
||||||
{channels.findIndex((ch) => ch.index === selectedChatIndex) !==
|
{channels.findIndex((ch) => ch.index === selectedChatIndex) !==
|
||||||
-1 ? (
|
-1 ? (
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { m } from 'framer-motion';
|
import { m } from 'framer-motion';
|
||||||
import mapbox from 'mapbox-gl';
|
import { LngLat } from 'mapbox-gl';
|
||||||
import { BiCrown } from 'react-icons/bi';
|
import { BiCrown } from 'react-icons/bi';
|
||||||
import {
|
import {
|
||||||
FiAlignLeft,
|
FiAlignLeft,
|
||||||
@@ -40,11 +41,11 @@ export const NodeCard = ({
|
|||||||
setSelected,
|
setSelected,
|
||||||
}: NodeCardProps): JSX.Element => {
|
}: NodeCardProps): JSX.Element => {
|
||||||
const { map } = useMapbox();
|
const { map } = useMapbox();
|
||||||
const [infoOpen, setInfoOpen] = React.useState(false);
|
const [infoOpen, setInfoOpen] = useState(false);
|
||||||
const [PositionConfidence, setPositionConfidence] =
|
const [PositionConfidence, setPositionConfidence] =
|
||||||
React.useState<PositionConfidence>('none');
|
useState<PositionConfidence>('none');
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
setPositionConfidence(
|
setPositionConfidence(
|
||||||
node.currentPosition
|
node.currentPosition
|
||||||
? new Date(node.currentPosition.posTimestamp * 1000) >
|
? new Date(node.currentPosition.posTimestamp * 1000) >
|
||||||
@@ -62,6 +63,7 @@ export const NodeCard = ({
|
|||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
nested
|
||||||
tooltip={PositionConfidence !== 'none' ? 'Fly to Node' : ''}
|
tooltip={PositionConfidence !== 'none' ? 'Fly to Node' : ''}
|
||||||
disabled={PositionConfidence === 'none'}
|
disabled={PositionConfidence === 'none'}
|
||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
@@ -69,7 +71,7 @@ export const NodeCard = ({
|
|||||||
setSelected();
|
setSelected();
|
||||||
if (PositionConfidence !== 'none' && node.currentPosition) {
|
if (PositionConfidence !== 'none' && node.currentPosition) {
|
||||||
map?.flyTo({
|
map?.flyTo({
|
||||||
center: new mapbox.LngLat(
|
center: new LngLat(
|
||||||
node.currentPosition.longitudeI / 1e7,
|
node.currentPosition.longitudeI / 1e7,
|
||||||
node.currentPosition.latitudeI / 1e7,
|
node.currentPosition.latitudeI / 1e7,
|
||||||
),
|
),
|
||||||
@@ -88,6 +90,7 @@ export const NodeCard = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
nested
|
||||||
tooltip="Show Node Info"
|
tooltip="Show Node Info"
|
||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { m } from 'framer-motion';
|
import { m } from 'framer-motion';
|
||||||
import type { Edge, Node } from 'react-flow-renderer';
|
import type { Edge, Node } from 'react-flow-renderer';
|
||||||
@@ -15,15 +16,15 @@ import { Hashicon } from '@emeraldpay/hashicon-react';
|
|||||||
import { useAppSelector } from '@hooks/useAppSelector';
|
import { useAppSelector } from '@hooks/useAppSelector';
|
||||||
|
|
||||||
export const Nodes = (): JSX.Element => {
|
export const Nodes = (): JSX.Element => {
|
||||||
const [graphNodes, setGraphNodes] = React.useState<Node[]>([]);
|
const [graphNodes, setGraphNodes] = useState<Node[]>([]);
|
||||||
const [graphEdges, setGraphEdges] = React.useState<Edge[]>([]);
|
const [graphEdges, setGraphEdges] = useState<Edge[]>([]);
|
||||||
const [selected, setSelected] = React.useState<number>(0);
|
const [selected, setSelected] = useState<number>(0);
|
||||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||||
const myNodeNum = useAppSelector(
|
const myNodeNum = useAppSelector(
|
||||||
(state) => state.meshtastic.radio.hardware.myNodeNum,
|
(state) => state.meshtastic.radio.hardware.myNodeNum,
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const tmpNodes: Node[] = [];
|
const tmpNodes: Node[] = [];
|
||||||
// User Terminal
|
// User Terminal
|
||||||
tmpNodes.push({
|
tmpNodes.push({
|
||||||
@@ -45,7 +46,7 @@ export const Nodes = (): JSX.Element => {
|
|||||||
setGraphNodes(tmpNodes);
|
setGraphNodes(tmpNodes);
|
||||||
}, [nodes, myNodeNum]);
|
}, [nodes, myNodeNum]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const tmpEdges: Edge[] = [];
|
const tmpEdges: Edge[] = [];
|
||||||
|
|
||||||
nodes.map((node, index) => {
|
nodes.map((node, index) => {
|
||||||
@@ -91,6 +92,7 @@ export const Nodes = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
actions={
|
actions={
|
||||||
<IconButton
|
<IconButton
|
||||||
|
nested
|
||||||
onClick={(e): void => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setSelected(node.number);
|
setSelected(node.number);
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ module.exports = {
|
|||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
primary: '#67ea94',
|
primary: '#67ea94',
|
||||||
// primaryDark: '#1E293B',
|
|
||||||
primaryDark: '#25262C',
|
primaryDark: '#25262C',
|
||||||
// secondaryDark: '#0F172A',
|
|
||||||
secondaryDark: '#1C1D23',
|
secondaryDark: '#1C1D23',
|
||||||
accentDark: '25262C',
|
accentDark: '25262C',
|
||||||
},
|
},
|
||||||
|
|||||||
+6
-6
@@ -1,5 +1,5 @@
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import path from 'path';
|
import { resolve } from 'path';
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import importToCDN from 'vite-plugin-cdn-import';
|
import importToCDN from 'vite-plugin-cdn-import';
|
||||||
@@ -79,11 +79,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@app': path.resolve(__dirname, './src'),
|
'@app': resolve(__dirname, './src'),
|
||||||
'@pages': path.resolve(__dirname, './src/pages'),
|
'@pages': resolve(__dirname, './src/pages'),
|
||||||
'@components': path.resolve(__dirname, './src/components'),
|
'@components': resolve(__dirname, './src/components'),
|
||||||
'@hooks': path.resolve(__dirname, './src/hooks'),
|
'@hooks': resolve(__dirname, './src/hooks'),
|
||||||
'@core': path.resolve(__dirname, './src/core'),
|
'@core': resolve(__dirname, './src/core'),
|
||||||
'@skypack/': 'https://cdn.skypack.dev/',
|
'@skypack/': 'https://cdn.skypack.dev/',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user