WIP
This commit is contained in:
@@ -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 { HTTP } from '@components/connection/HTTP';
|
||||
@@ -21,10 +22,10 @@ import { Types } from '@meshtastic/meshtasticjs';
|
||||
export const Connection = (): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const state = useAppSelector((state) => state.meshtastic);
|
||||
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||
const appState = useAppSelector((state) => state.app);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!import.meta.env.VITE_PUBLIC_HOSTED) {
|
||||
dispatch(
|
||||
setConnectionParams({
|
||||
@@ -41,11 +42,11 @@ export const Connection = (): JSX.Element => {
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (state.ready) {
|
||||
useEffect(() => {
|
||||
if (meshtasticState.ready) {
|
||||
dispatch(closeConnectionModal());
|
||||
}
|
||||
}, [state.ready, dispatch]);
|
||||
}, [meshtasticState.ready, dispatch]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
@@ -67,14 +68,14 @@ export const Connection = (): JSX.Element => {
|
||||
dispatch(setConnType(parseInt(e.target.value)));
|
||||
}}
|
||||
disabled={
|
||||
state.deviceStatus ===
|
||||
meshtasticState.deviceStatus ===
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||
}
|
||||
/>
|
||||
{appState.connType === connType.HTTP && (
|
||||
<HTTP
|
||||
connecting={
|
||||
state.deviceStatus ===
|
||||
meshtasticState.deviceStatus ===
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||
}
|
||||
/>
|
||||
@@ -82,7 +83,7 @@ export const Connection = (): JSX.Element => {
|
||||
{appState.connType === connType.BLE && (
|
||||
<BLE
|
||||
connecting={
|
||||
state.deviceStatus ===
|
||||
meshtasticState.deviceStatus ===
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||
}
|
||||
/>
|
||||
@@ -90,7 +91,7 @@ export const Connection = (): JSX.Element => {
|
||||
{appState.connType === connType.SERIAL && (
|
||||
<Serial
|
||||
connecting={
|
||||
state.deviceStatus ===
|
||||
meshtasticState.deviceStatus ===
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED
|
||||
}
|
||||
/>
|
||||
@@ -98,8 +99,23 @@ export const Connection = (): JSX.Element => {
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
{state.logs
|
||||
<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">
|
||||
{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) => {
|
||||
return ![
|
||||
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 {
|
||||
@@ -26,7 +27,7 @@ export const MapboxProvider = ({
|
||||
const darkMode = useAppSelector((state) => state.app.darkMode);
|
||||
const mapState = useAppSelector((state) => state.map);
|
||||
const dispatch = useAppDispatch();
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const map = useCreateMapbox({
|
||||
ref,
|
||||
@@ -41,9 +42,9 @@ export const MapboxProvider = ({
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
map?.on('load', () => {
|
||||
map.addControl(new mapboxgl.ScaleControl());
|
||||
map.addControl(new ScaleControl());
|
||||
});
|
||||
map?.on('styledata', () => {
|
||||
if (!map.getSource('mapbox-dem')) {
|
||||
@@ -73,14 +74,14 @@ export const MapboxProvider = ({
|
||||
});
|
||||
}, [dispatch, map, mapState.exaggeration]);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
const center = map?.getCenter();
|
||||
if (center !== mapState.latLng) {
|
||||
map?.setCenter(mapState.latLng);
|
||||
}
|
||||
}, [map, mapState.latLng]);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (['Light', 'Dark'].includes(mapState.style)) {
|
||||
dispatch(setMapStyle(darkMode ? 'Dark' : 'Light'));
|
||||
}
|
||||
@@ -89,7 +90,7 @@ export const MapboxProvider = ({
|
||||
/**
|
||||
* Hill Shading
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (map?.loaded()) {
|
||||
if (mapState.hillShade) {
|
||||
map.addLayer(
|
||||
@@ -111,7 +112,7 @@ export const MapboxProvider = ({
|
||||
/**
|
||||
* Exaggeration
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (map?.loaded()) {
|
||||
map.setTerrain({
|
||||
source: 'mapbox-dem',
|
||||
@@ -123,7 +124,7 @@ export const MapboxProvider = ({
|
||||
/**
|
||||
* Map Style
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (map?.loaded()) {
|
||||
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 {
|
||||
ref: React.Ref<HTMLDivElement>;
|
||||
map?: mapbox.Map;
|
||||
map?: Map;
|
||||
}
|
||||
|
||||
export const MapboxContext = React.createContext<MapboxContextValue>(
|
||||
export const MapboxContext = createContext<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 { FiArrowRightCircle } from 'react-icons/fi';
|
||||
@@ -14,19 +15,19 @@ export interface BLEProps {
|
||||
}
|
||||
|
||||
export const BLE = ({ connecting }: BLEProps): JSX.Element => {
|
||||
const [bleDevices, setBleDevices] = React.useState<BluetoothDevice[]>([]);
|
||||
const [bleDevices, setBleDevices] = useState<BluetoothDevice[]>([]);
|
||||
|
||||
const { handleSubmit } = useForm<{
|
||||
device?: BluetoothDevice;
|
||||
}>();
|
||||
|
||||
const updateBleDeviceList = React.useCallback(async (): Promise<void> => {
|
||||
const updateBleDeviceList = useCallback(async (): Promise<void> => {
|
||||
const ble = new IBLEConnection();
|
||||
const devices = await ble.getDevices();
|
||||
setBleDevices(devices);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
void updateBleDeviceList();
|
||||
}, [updateBleDeviceList]);
|
||||
|
||||
@@ -46,6 +47,7 @@ export const BLE = ({ connecting }: BLEProps): JSX.Element => {
|
||||
>
|
||||
<div className="my-auto">{device.name}</div>
|
||||
<IconButton
|
||||
nested
|
||||
onClick={async (): Promise<void> => {
|
||||
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 { FiArrowRightCircle } from 'react-icons/fi';
|
||||
@@ -15,20 +16,20 @@ export interface SerialProps {
|
||||
}
|
||||
|
||||
export const Serial = ({ connecting }: SerialProps): JSX.Element => {
|
||||
const [serialDevices, setSerialDevices] = React.useState<SerialPort[]>([]);
|
||||
const [serialDevices, setSerialDevices] = useState<SerialPort[]>([]);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { handleSubmit } = useForm<{
|
||||
device?: SerialPort;
|
||||
}>();
|
||||
|
||||
const updateSerialDeviceList = React.useCallback(async (): Promise<void> => {
|
||||
const updateSerialDeviceList = useCallback(async (): Promise<void> => {
|
||||
const serial = new ISerialConnection();
|
||||
const devices = await serial.getPorts();
|
||||
setSerialDevices(devices);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
void updateSerialDeviceList();
|
||||
}, [updateSerialDeviceList]);
|
||||
|
||||
@@ -53,6 +54,7 @@ export const Serial = ({ connecting }: SerialProps): JSX.Element => {
|
||||
</p>
|
||||
</div>
|
||||
<IconButton
|
||||
nested
|
||||
onClick={async (): Promise<void> => {
|
||||
dispatch(
|
||||
setConnectionParams({
|
||||
|
||||
@@ -1,21 +1,56 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { m } from 'framer-motion';
|
||||
import Draggable from 'react-draggable';
|
||||
|
||||
export interface CardProps {
|
||||
className?: string;
|
||||
title?: string;
|
||||
actions?: 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 (
|
||||
<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>
|
||||
<Draggable handle=".handle" disabled={!draggable}>
|
||||
<div
|
||||
className={`flex h-full w-full flex-col rounded-md shadow-md ${
|
||||
border ? 'border border-gray-300 dark:border-gray-600' : ''
|
||||
} ${className ?? ''}`}
|
||||
>
|
||||
{(title || actions) && (
|
||||
<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';
|
||||
|
||||
@@ -13,8 +14,8 @@ export const ContextMenu = ({
|
||||
items,
|
||||
children,
|
||||
}: ContextMenuProps): JSX.Element => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [position, setPosition] = React.useState({ x: 0, y: 0 });
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -37,16 +37,18 @@ export const Modal = ({
|
||||
​
|
||||
</span>
|
||||
<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">
|
||||
<div className="flex justify-between">
|
||||
<div className="text-2xl font-medium dark:text-white">
|
||||
{title}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Card
|
||||
border
|
||||
draggable
|
||||
title={title}
|
||||
actions={
|
||||
<>
|
||||
{actions}
|
||||
<IconButton tooltip="Close" icon={<FiX />} onClick={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
className="relative flex-col gap-4 "
|
||||
>
|
||||
{children}
|
||||
</Card>
|
||||
</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 { FiArrowUp } from 'react-icons/fi';
|
||||
@@ -14,13 +15,15 @@ export const CollapsibleSection = ({
|
||||
icon,
|
||||
children,
|
||||
}: CollapsibleSectionProps): JSX.Element => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const toggleOpen = (): void => setOpen(!open);
|
||||
return (
|
||||
<m.div>
|
||||
<m.div
|
||||
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
|
||||
layout
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { m } from 'framer-motion';
|
||||
import { FiExternalLink } from 'react-icons/fi';
|
||||
import { FiChevronRight } from 'react-icons/fi';
|
||||
|
||||
export interface ExternalSectionProps {
|
||||
title: string;
|
||||
icon?: JSX.Element;
|
||||
active?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const ExternalSection = ({
|
||||
title,
|
||||
icon,
|
||||
active,
|
||||
onClick,
|
||||
}: ExternalSectionProps): JSX.Element => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const toggleOpen = (): void => setOpen(!open);
|
||||
return (
|
||||
<m.div
|
||||
@@ -24,7 +27,9 @@ export const ExternalSection = ({
|
||||
>
|
||||
<m.div
|
||||
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
|
||||
layout
|
||||
@@ -38,7 +43,7 @@ export const ExternalSection = ({
|
||||
{title}
|
||||
</m.div>
|
||||
<m.div className="my-auto">
|
||||
<FiExternalLink />
|
||||
<FiChevronRight />
|
||||
</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';
|
||||
|
||||
type DefaultButtonProps = JSX.IntrinsicElements['button'];
|
||||
|
||||
export enum ButtonSize {
|
||||
Small = 'small',
|
||||
Medium = 'medium',
|
||||
Large = 'large',
|
||||
}
|
||||
|
||||
export interface ButtonProps extends DefaultButtonProps {
|
||||
export interface ButtonProps {
|
||||
icon?: JSX.Element;
|
||||
active?: boolean;
|
||||
border?: boolean;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
children?: React.ReactNode;
|
||||
size?: ButtonSize;
|
||||
onClick?: () => void;
|
||||
confirmAction?: () => void;
|
||||
}
|
||||
|
||||
export const Button = ({
|
||||
icon,
|
||||
className,
|
||||
active,
|
||||
border,
|
||||
size = ButtonSize.Medium,
|
||||
confirmAction,
|
||||
onClick,
|
||||
disabled,
|
||||
children,
|
||||
...props
|
||||
}: ButtonProps): JSX.Element => {
|
||||
const [hasConfirmed, setHasConfirmed] = React.useState(false);
|
||||
const [hasConfirmed, setHasConfirmed] = useState(false);
|
||||
|
||||
const handleConfirm = (): void => {
|
||||
if (typeof confirmAction == 'function') {
|
||||
@@ -44,9 +46,11 @@ export const Button = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
<m.button
|
||||
whileHover={{ scale: 1.01 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
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
|
||||
? 'p-0'
|
||||
@@ -58,8 +62,10 @@ export const Button = ({
|
||||
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}
|
||||
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${
|
||||
className ?? ''
|
||||
}`}
|
||||
onClickCapture={onClick}
|
||||
>
|
||||
{icon && (
|
||||
<div className="text-gray-500 dark:text-gray-400">
|
||||
@@ -68,6 +74,6 @@ export const Button = ({
|
||||
)}
|
||||
|
||||
<span>{children}</span>
|
||||
</button>
|
||||
</m.button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { m } from 'framer-motion';
|
||||
|
||||
import { Tooltip } from '@components/generic/Tooltip';
|
||||
|
||||
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
||||
@@ -7,26 +9,32 @@ type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
||||
export interface IconButtonProps extends DefaulButtonProps {
|
||||
icon: React.ReactNode;
|
||||
tooltip?: string;
|
||||
nested?: boolean;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export const IconButton = ({
|
||||
icon,
|
||||
active,
|
||||
tooltip,
|
||||
nested,
|
||||
active,
|
||||
disabled,
|
||||
...props
|
||||
}: IconButtonProps): JSX.Element => {
|
||||
return (
|
||||
<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
|
||||
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'
|
||||
className={`rounded-md p-2 hover:bg-gray-200 ${
|
||||
active ? 'bg-gray-200 dark:bg-secondaryDark' : ''
|
||||
} ${
|
||||
nested ? 'dark:hover:bg-primaryDark' : 'dark:hover:bg-secondaryDark'
|
||||
} ${
|
||||
disabled
|
||||
? 'cursor-not-allowed text-gray-400 dark:text-gray-700'
|
||||
@@ -37,7 +45,7 @@ export const IconButton = ({
|
||||
{icon}
|
||||
<span className="sr-only">Refresh</span>
|
||||
</button>
|
||||
</div>
|
||||
</m.div>
|
||||
</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';
|
||||
|
||||
@@ -12,7 +13,7 @@ export interface CheckboxProps extends DefaultInputProps {
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
function Input(
|
||||
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps,
|
||||
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 { Label } from '@components/generic/form/Label';
|
||||
@@ -13,25 +14,26 @@ export interface InputProps extends DefaultInputProps {
|
||||
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>
|
||||
);
|
||||
},
|
||||
);
|
||||
export const Input = 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>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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 { Label } from '@components/generic/form/Label';
|
||||
@@ -16,7 +17,7 @@ export interface SelectProps extends DefaultSelectProps {
|
||||
small?: boolean;
|
||||
}
|
||||
|
||||
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
||||
({ options, optionsEnum, label, error, small, ...props }, ref) => {
|
||||
const optionsEnumValues = optionsEnum
|
||||
? 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 { 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 { FiSave } from 'react-icons/fi';
|
||||
@@ -17,9 +18,9 @@ export const Channels = (): JSX.Element => {
|
||||
channels.find(
|
||||
(channel) => channel.role === Protobuf.Channel_Role.PRIMARY,
|
||||
) ?? channels[0];
|
||||
const [usePreset, setUsePreset] = React.useState(true);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [selectedChannel, setSelectedChannel] = React.useState<
|
||||
const [usePreset, setUsePreset] = useState(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedChannel, setSelectedChannel] = useState<
|
||||
Protobuf.Channel | undefined
|
||||
>();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
FiAlignLeft,
|
||||
@@ -38,8 +39,8 @@ export interface SettingsProps {
|
||||
}
|
||||
|
||||
export const Settings = ({ open, setOpen }: SettingsProps): JSX.Element => {
|
||||
const [pluginsOpen, setPluginsOpen] = React.useState(false);
|
||||
const [channelsOpen, setChannelsOpen] = React.useState(false);
|
||||
const [pluginsOpen, setPluginsOpen] = useState(false);
|
||||
const [channelsOpen, setChannelsOpen] = useState(false);
|
||||
const {
|
||||
rangeTestPluginEnabled,
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -18,7 +19,7 @@ export const Position = (): JSX.Element => {
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||
defaultValues: {
|
||||
@@ -32,7 +33,7 @@ export const Position = (): JSX.Element => {
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -14,7 +15,7 @@ export const Power = (): JSX.Element => {
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { register, handleSubmit, formState, reset } =
|
||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||
defaultValues: {
|
||||
@@ -23,7 +24,7 @@ export const Power = (): JSX.Element => {
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -14,13 +15,13 @@ export const Radio = (): JSX.Element => {
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { register, handleSubmit, formState, reset } =
|
||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||
defaultValues: preferences,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -13,7 +14,7 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const User = (): JSX.Element => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const myNodeNum = useAppSelector(
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
).myNodeNum;
|
||||
@@ -40,7 +41,7 @@ export const User = (): JSX.Element => {
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
reset({
|
||||
longName: node?.user?.longName,
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -14,7 +15,7 @@ export const WiFi = (): JSX.Element => {
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { register, handleSubmit, formState, reset, control } =
|
||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||
defaultValues: preferences,
|
||||
@@ -32,7 +33,7 @@ export const WiFi = (): JSX.Element => {
|
||||
defaultValue: false,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { useForm } from 'react-hook-form';
|
||||
@@ -18,9 +19,9 @@ export interface SettingsPanelProps {
|
||||
}
|
||||
|
||||
export const SettingsPanel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [keySize, setKeySize] = React.useState<128 | 256>(256);
|
||||
const [pskHidden, setPskHidden] = React.useState(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [keySize, setKeySize] = useState<128 | 256>(256);
|
||||
const [pskHidden, setPskHidden] = useState(true);
|
||||
|
||||
const { register, handleSubmit, setValue, formState, reset } = useForm<
|
||||
Omit<Protobuf.ChannelSettings, 'psk'> & { psk: string; enabled: boolean }
|
||||
@@ -37,7 +38,7 @@ export const SettingsPanel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
reset({
|
||||
enabled: [
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -12,7 +13,7 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
@@ -23,7 +24,7 @@ export const ExternalNotificationsSettingsPlanel = (): JSX.Element => {
|
||||
defaultValues: preferences,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -12,7 +13,7 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const RangeTestSettingsPanel = (): JSX.Element => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
@@ -23,7 +24,7 @@ export const RangeTestSettingsPanel = (): JSX.Element => {
|
||||
defaultValues: preferences,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -12,7 +13,7 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const SerialSettingsPanel = (): JSX.Element => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
@@ -23,7 +24,7 @@ export const SerialSettingsPanel = (): JSX.Element => {
|
||||
defaultValues: preferences,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { FiSave } from 'react-icons/fi';
|
||||
@@ -12,7 +13,7 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export const StoreForwardSettingsPanel = (): JSX.Element => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const preferences = useAppSelector(
|
||||
(state) => state.meshtastic.radio.preferences,
|
||||
@@ -23,7 +24,7 @@ export const StoreForwardSettingsPanel = (): JSX.Element => {
|
||||
defaultValues: preferences,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
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 { Settings } from '@components/layout/Sidebar/Settings/Index';
|
||||
@@ -9,7 +10,7 @@ export interface SidebarProps {
|
||||
}
|
||||
|
||||
export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
||||
const [settingsOpen, setSettingsOpen] = React.useState(false);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
const appState = useAppSelector((state) => state.app);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
FiBluetooth,
|
||||
@@ -31,7 +32,7 @@ import { VersionInfo } from '../modals/VersionInfo';
|
||||
import { BottomNavItem } from './BottomNavItem';
|
||||
|
||||
export const BottomNav = (): JSX.Element => {
|
||||
const [showVersionInfo, setShowVersionInfo] = React.useState(false);
|
||||
const [showVersionInfo, setShowVersionInfo] = useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||
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 { MdUpgrade } from 'react-icons/md';
|
||||
@@ -56,7 +57,7 @@ export const VersionInfo = ({
|
||||
},
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const index = data.findIndex(
|
||||
(commit) => commit.sha.substring(0, 7) === process.env.COMMIT_HASH,
|
||||
@@ -90,7 +91,7 @@ export const VersionInfo = ({
|
||||
data.map((commit) => (
|
||||
<div
|
||||
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
|
||||
? 'bg-primary'
|
||||
: 'dark:bg-secondaryDark'
|
||||
|
||||
Reference in New Issue
Block a user