This commit is contained in:
Sacha Weatherstone
2022-02-23 14:14:33 +11:00
parent 3b85422ab2
commit 4cf326661f
54 changed files with 403 additions and 255 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { Button } from '@app/components/generic/button/Button';
import { Card } from '@app/components/generic/Card';
+16 -9
View File
@@ -1,8 +1,10 @@
import React from 'react';
import type React from 'react';
import { AnimatePresence, m } from 'framer-motion';
import { FiFilePlus } from 'react-icons/fi';
import useSWR from 'swr';
import { Button } from '@app/components/generic/button/Button';
import { Card } from '@app/components/generic/Card';
import { fetcher } from '@core/utils/fetcher';
import { useAppSelector } from '@hooks/useAppSelector';
@@ -29,22 +31,27 @@ export const FileBrowser = (): JSX.Element => {
const connectionParams = useAppSelector(
(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>(
`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
connectionParams.HTTP.address
}/json/spiffs/browse/static`,
}${
meshtasticState.radio.hardware.firmwareVersion.includes('1.2')
? '/json/spiffs/browse/static'
: '/json/fs/browse/static'
}`,
fetcher,
);
return (
<div className="flex h-full p-4">
<Card className="flex-grow flex-col">
<div className="flex h-10 w-full rounded-t-md border-b border-gray-300 px-4 text-lg font-semibold shadow-md dark:border-gray-600 dark:bg-zinc-700 dark:text-white">
<div className="my-auto w-1/3">FileName</div>
<div className="my-auto w-1/3">Actions</div>
</div>
<Card
title="File Browser"
actions={<Button icon={<FiFilePlus />}>Upload File</Button>}
className="flex-grow flex-col"
>
<div className="h-full px-4">
<AnimatePresence>
{(!data || data?.data.files.length === 0) && (
@@ -55,7 +62,7 @@ export const FileBrowser = (): JSX.Element => {
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={`/placeholders/${
darkMode ? 'Files Dark.svg' : 'Files.svg'
appState.darkMode ? 'Files Dark.svg' : 'Files.svg'
}`}
/>
</div>
+8 -2
View File
@@ -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 { MdSubject } from 'react-icons/md';
@@ -14,7 +15,7 @@ import { Logs } from '@pages/Extensions/Logs';
import { Debug } from './Debug';
export const Extensions = (): JSX.Element => {
const [selectedExtension, setSelectedExtension] = React.useState<
const [selectedExtension, setSelectedExtension] = useState<
'info' | 'logs' | 'fileBrowser' | 'rangeTest' | 'debug'
>('info');
@@ -29,6 +30,7 @@ export const Extensions = (): JSX.Element => {
setSelectedExtension('info');
}}
icon={<FiInfo />}
active={selectedExtension === 'info'}
title="Node Info"
/>
<ExternalSection
@@ -36,6 +38,7 @@ export const Extensions = (): JSX.Element => {
setSelectedExtension('logs');
}}
icon={<MdSubject />}
active={selectedExtension === 'logs'}
title="Logs"
/>
<ExternalSection
@@ -43,6 +46,7 @@ export const Extensions = (): JSX.Element => {
setSelectedExtension('fileBrowser');
}}
icon={<FiFile />}
active={selectedExtension === 'fileBrowser'}
title="File Browser"
/>
<ExternalSection
@@ -50,6 +54,7 @@ export const Extensions = (): JSX.Element => {
setSelectedExtension('rangeTest');
}}
icon={<RiPinDistanceFill />}
active={selectedExtension === 'rangeTest'}
title="Range Test"
/>
<ExternalSection
@@ -57,6 +62,7 @@ export const Extensions = (): JSX.Element => {
setSelectedExtension('debug');
}}
icon={<VscDebug />}
active={selectedExtension === 'debug'}
title="Debug"
/>
</div>
+14 -4
View File
@@ -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 { Button } from '@app/components/generic/button/Button';
import { Card } from '@app/components/generic/Card';
import { Hashicon } from '@emeraldpay/hashicon-react';
import { useAppSelector } from '@hooks/useAppSelector';
@@ -17,8 +19,16 @@ export const Info = (): JSX.Element => {
);
return (
<div className="flex h-full flex-col gap-4 p-4 md:flex-row">
<Card className="md:w-1/4">
<div className="flex h-full flex-col gap-4 p-4 xl:flex-row">
<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">
<Hashicon value={hardwareInfo.myNodeNum.toString()} size={180} />
<div className="text-center text-lg font-medium dark:text-white">
@@ -27,7 +37,7 @@ export const Info = (): JSX.Element => {
</div>
</Card>
<Card className="flex-grow">
<Card title="Debug Information" className="flex-grow">
<JSONPretty className="overflow-y-auto" data={hardwareInfo} />
</Card>
</div>
+12 -7
View File
@@ -1,15 +1,16 @@
import type React from 'react';
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 { useAppSelector } from '@hooks/useAppSelector';
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
export const Logs = (): JSX.Element => {
const logs = useAppSelector((state) => state.meshtastic.logs);
const darkMode = useAppSelector((state) => state.app.darkMode);
const meshtasticState = useAppSelector((state) => state.meshtastic);
const appState = useAppSelector((state) => state.app);
type lookupType = { [key: number]: string };
@@ -49,14 +50,18 @@ export const Logs = (): JSX.Element => {
return (
<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">
<tbody
className="
block h-full flex-col overflow-y-auto font-mono text-xs dark:text-gray-400"
>
<AnimatePresence>
{logs.length === 0 && (
{meshtasticState.logs.length === 0 && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
@@ -64,13 +69,13 @@ export const Logs = (): JSX.Element => {
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={`/placeholders/${
darkMode ? 'View Code Dark.svg' : 'View Code.svg'
appState.darkMode ? 'View Code Dark.svg' : 'View Code.svg'
}`}
/>
</div>
)}
</AnimatePresence>
{logs.map((log, index) => (
{meshtasticState.logs.map((log, index) => (
// <ContextMenu
// key={index}
// items={
+3 -2
View File
@@ -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 { MdFullscreen, MdRadar, MdWbShade } from 'react-icons/md';
@@ -23,7 +24,7 @@ export const MapContainer = (): JSX.Element => {
const { ref } = useMapbox();
const ChangeMapStyle = React.useCallback(
const ChangeMapStyle = useCallback(
(styleName: string, style: MapStyle) => {
dispatch(
setMapStyle(
+13 -11
View File
@@ -1,13 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import type React from 'react';
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';
export interface MarkerProps extends Omit<mapbox.MarkerOptions, 'element'> {
export interface MarkerProps extends Omit<MarkerOptions, 'element'> {
children?: React.ReactNode;
center: mapbox.LngLatLike;
center: LngLatLike;
}
export const Marker = ({
@@ -16,22 +18,22 @@ export const Marker = ({
...props
}: MarkerProps): JSX.Element => {
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) {
const marker = new mapbox.Marker(ref.current, props).setLngLat(center);
const marker = new MapboxMarker(ref.current, props).setLngLat(center);
marker.addTo(map);
}
}, [map, center, props]);
React.useEffect(() => {
useEffect(() => {
map?.on('load', () => {
addMarker();
});
}, [addMarker, map]);
React.useEffect(() => {
useEffect(() => {
if (map?.loaded()) {
addMarker();
}
@@ -39,5 +41,5 @@ export const Marker = ({
<div ref={ref}>{children}</div>;
return ReactDOM.createPortal(children, ref.current);
return createPortal(children, ref.current);
};
+3 -2
View File
@@ -1,4 +1,5 @@
import React from 'react';
import type React from 'react';
import { useState } from 'react';
import mapboxgl from 'mapbox-gl';
import { FiMapPin } from 'react-icons/fi';
@@ -13,7 +14,7 @@ import { Marker } from '@pages/Map/Marker';
import { NodeCard } from '@pages/Nodes/NodeCard';
export const Map = (): JSX.Element => {
const [selectedNode, setSelectedNode] = React.useState<Node>();
const [selectedNode, setSelectedNode] = useState<Node>();
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const myNodeNum = useAppSelector(
+2 -2
View File
@@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { m } from 'framer-motion';
import { FiSettings } from 'react-icons/fi';
@@ -41,7 +41,7 @@ export const ChannelChat = ({
setSelected={(): void => {
setSelectedIndex(channel.index);
}}
actions={<IconButton icon={<FiSettings />} />}
actions={<IconButton nested icon={<FiSettings />} />}
>
<Tooltip
content={
+2 -2
View File
@@ -1,4 +1,4 @@
import React from 'react';
import type React from 'react';
import { FiSettings } from 'react-icons/fi';
@@ -25,7 +25,7 @@ export const DmChat = ({
setSelected={(): void => {
setSelectedIndex(node.number);
}}
actions={<IconButton icon={<FiSettings />} />}
actions={<IconButton nested icon={<FiSettings />} />}
>
<div className="flex dark:text-white">
<div className="m-auto">
+12 -4
View File
@@ -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 { connection } from '@core/connection';
@@ -14,9 +15,9 @@ export const MessageBar = ({ chatIndex }: MessageBarProps): JSX.Element => {
const dispatch = useAppDispatch();
const meshtasticState = useAppSelector((state) => state.meshtastic);
const [isChannel, setIsChannel] = React.useState(false);
const [isChannel, setIsChannel] = useState(false);
React.useState(() => {
useState(() => {
setIsChannel(
meshtasticState.radio.channels.findIndex(
(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 => {
if (meshtasticState.ready) {
void connection.sendText(
@@ -33,6 +34,13 @@ export const MessageBar = ({ chatIndex }: MessageBarProps): JSX.Element => {
true,
isChannel ? chatIndex-- : 0,
(id) => {
console.log(`Chat Index, ${chatIndex}`);
console.log(`Chat Index --, ${chatIndex--}`);
console.log(
`Chat Index computed, ${isChannel ? chatIndex-- : chatIndex}`,
);
dispatch(
ackMessage({
chatIndex: isChannel ? chatIndex-- : chatIndex,
+6 -5
View File
@@ -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';
@@ -12,9 +13,9 @@ import { Message } from '@pages/Messages/Message';
import { MessageBar } from '@pages/Messages/MessageBar';
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(
(state) => state.meshtastic.radio.hardware,
@@ -25,7 +26,7 @@ export const Messages = (): JSX.Element => {
(state) => state.meshtastic.radio.channels,
).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);
React.useEffect(() => {
useEffect(() => {
if (chatRef.current) {
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 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">
<IconButton icon={<FiHash className="h-4 w-4" />} />
<IconButton nested icon={<FiHash className="h-4 w-4" />} />
<div className="my-auto">
{channels.findIndex((ch) => ch.index === selectedChatIndex) !==
-1 ? (
+9 -6
View File
@@ -1,7 +1,8 @@
import React from 'react';
import type React from 'react';
import { useEffect, useState } from 'react';
import { m } from 'framer-motion';
import mapbox from 'mapbox-gl';
import { LngLat } from 'mapbox-gl';
import { BiCrown } from 'react-icons/bi';
import {
FiAlignLeft,
@@ -40,11 +41,11 @@ export const NodeCard = ({
setSelected,
}: NodeCardProps): JSX.Element => {
const { map } = useMapbox();
const [infoOpen, setInfoOpen] = React.useState(false);
const [infoOpen, setInfoOpen] = useState(false);
const [PositionConfidence, setPositionConfidence] =
React.useState<PositionConfidence>('none');
useState<PositionConfidence>('none');
React.useEffect(() => {
useEffect(() => {
setPositionConfidence(
node.currentPosition
? new Date(node.currentPosition.posTimestamp * 1000) >
@@ -62,6 +63,7 @@ export const NodeCard = ({
actions={
<>
<IconButton
nested
tooltip={PositionConfidence !== 'none' ? 'Fly to Node' : ''}
disabled={PositionConfidence === 'none'}
onClick={(e): void => {
@@ -69,7 +71,7 @@ export const NodeCard = ({
setSelected();
if (PositionConfidence !== 'none' && node.currentPosition) {
map?.flyTo({
center: new mapbox.LngLat(
center: new LngLat(
node.currentPosition.longitudeI / 1e7,
node.currentPosition.latitudeI / 1e7,
),
@@ -88,6 +90,7 @@ export const NodeCard = ({
}
/>
<IconButton
nested
tooltip="Show Node Info"
onClick={(e): void => {
e.stopPropagation();
+8 -6
View File
@@ -1,4 +1,5 @@
import React from 'react';
import type React from 'react';
import { useEffect, useState } from 'react';
import { m } from 'framer-motion';
import type { Edge, Node } from 'react-flow-renderer';
@@ -15,15 +16,15 @@ import { Hashicon } from '@emeraldpay/hashicon-react';
import { useAppSelector } from '@hooks/useAppSelector';
export const Nodes = (): JSX.Element => {
const [graphNodes, setGraphNodes] = React.useState<Node[]>([]);
const [graphEdges, setGraphEdges] = React.useState<Edge[]>([]);
const [selected, setSelected] = React.useState<number>(0);
const [graphNodes, setGraphNodes] = useState<Node[]>([]);
const [graphEdges, setGraphEdges] = useState<Edge[]>([]);
const [selected, setSelected] = useState<number>(0);
const nodes = useAppSelector((state) => state.meshtastic.nodes);
const myNodeNum = useAppSelector(
(state) => state.meshtastic.radio.hardware.myNodeNum,
);
React.useEffect(() => {
useEffect(() => {
const tmpNodes: Node[] = [];
// User Terminal
tmpNodes.push({
@@ -45,7 +46,7 @@ export const Nodes = (): JSX.Element => {
setGraphNodes(tmpNodes);
}, [nodes, myNodeNum]);
React.useEffect(() => {
useEffect(() => {
const tmpEdges: Edge[] = [];
nodes.map((node, index) => {
@@ -91,6 +92,7 @@ export const Nodes = (): JSX.Element => {
}}
actions={
<IconButton
nested
onClick={(e): void => {
e.stopPropagation();
setSelected(node.number);