Use Headless UI & get visual message ack's working
This commit is contained in:
Generated
-8582
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -9,10 +9,11 @@
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.1.1",
|
||||
"@heroicons/react": "^1.0.1",
|
||||
"@meshtastic/meshtasticjs": "^0.6.10",
|
||||
"@meshtastic/meshtasticjs": "^0.6.12",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-flags-select": "^2.1.2",
|
||||
"rxjs": "^7.0.0",
|
||||
"yarn": "^1.22.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -20,12 +21,12 @@
|
||||
"@snowpack/plugin-postcss": "^1.2.2",
|
||||
"@snowpack/plugin-react-refresh": "^2.5.0",
|
||||
"@snowpack/plugin-typescript": "^1.2.0",
|
||||
"@types/react": "^17.0.4",
|
||||
"@types/react": "^17.0.5",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/snowpack-env": "^2.3.2",
|
||||
"autoprefixer": "^10.2.5",
|
||||
"gzipper": "^4.5.0",
|
||||
"postcss": "^8.2.13",
|
||||
"postcss": "^8.2.14",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"prettier": "^2.0.5",
|
||||
"snowpack": "^3.3.7",
|
||||
|
||||
+101
-91
@@ -37,23 +37,38 @@ export interface languageTemplate {
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const [deviceStatus, setDeviceStatus] = React.useState(
|
||||
{} as Types.DeviceStatusEnum,
|
||||
const [
|
||||
deviceStatus,
|
||||
setDeviceStatus,
|
||||
] = React.useState<Types.DeviceStatusEnum>(
|
||||
Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
|
||||
);
|
||||
const [myNodeInfo, setMyNodeInfo] = React.useState({} as Protobuf.MyNodeInfo);
|
||||
const [messages, setMessages] = React.useState(
|
||||
[] as { message: Types.TextPacket; ack: false }[],
|
||||
const [myNodeInfo, setMyNodeInfo] = React.useState<Protobuf.MyNodeInfo>(
|
||||
Protobuf.MyNodeInfo.create(),
|
||||
);
|
||||
const [channels, setChannels] = React.useState([] as Protobuf.Channel[]);
|
||||
const [nodes, setNodes] = React.useState([] as Types.NodeInfoPacket[]);
|
||||
const [connection, setConnection] = React.useState({} as IHTTPConnection);
|
||||
const [isReady, setIsReady] = React.useState(false);
|
||||
const [lastMeshInterraction, setLastMeshInterraction] = React.useState(0);
|
||||
const [preferences, setPreferences] = React.useState(
|
||||
{} as Protobuf.RadioConfig_UserPreferences,
|
||||
const [nodes, setNodes] = React.useState<Types.NodeInfoPacket[]>([]);
|
||||
const [connection, setConnection] = React.useState<IHTTPConnection>(
|
||||
new IHTTPConnection(),
|
||||
);
|
||||
const [language, setLanguage] = React.useState(LanguageEnum.ENGLISH);
|
||||
const [translations, setTranslations] = React.useState(Translations_English);
|
||||
const [isReady, setIsReady] = React.useState<boolean>(false);
|
||||
const [
|
||||
lastMeshInterraction,
|
||||
setLastMeshInterraction,
|
||||
] = React.useState<number>(0);
|
||||
const [
|
||||
preferences,
|
||||
setPreferences,
|
||||
] = React.useState<Protobuf.RadioConfig_UserPreferences>(
|
||||
Protobuf.RadioConfig_UserPreferences.create(),
|
||||
);
|
||||
const [language, setLanguage] = React.useState<LanguageEnum>(
|
||||
LanguageEnum.ENGLISH,
|
||||
);
|
||||
const [translations, setTranslations] = React.useState<languageTemplate>(
|
||||
Translations_English,
|
||||
);
|
||||
const [darkmode, setDarkmode] = React.useState<boolean>(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
switch (language) {
|
||||
@@ -75,7 +90,7 @@ const App = () => {
|
||||
React.useEffect(() => {
|
||||
const client = new Client();
|
||||
const connection = client.createHTTPConnection();
|
||||
setConnection(connection);
|
||||
|
||||
connection.connect({
|
||||
address:
|
||||
import.meta.env.NODE_ENV === 'production'
|
||||
@@ -87,80 +102,74 @@ const App = () => {
|
||||
});
|
||||
setConnection(connection);
|
||||
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
|
||||
connection.onDeviceStatusEvent.subscribe((status) => {
|
||||
setDeviceStatus(status);
|
||||
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
|
||||
setIsReady(true);
|
||||
}
|
||||
});
|
||||
connection.onMyNodeInfoEvent.subscribe(setMyNodeInfo);
|
||||
connection.onTextPacketEvent.subscribe((message) => {
|
||||
setMessages((messages) => [
|
||||
...messages,
|
||||
{ message: message, ack: false },
|
||||
]);
|
||||
});
|
||||
connection.onNodeInfoPacketEvent.subscribe((node) => {
|
||||
if (
|
||||
nodes.findIndex(
|
||||
(currentNode) => currentNode.data.num === node.data.num,
|
||||
) >= 0
|
||||
) {
|
||||
setNodes(
|
||||
nodes.map((currentNode) =>
|
||||
currentNode.data.num === node.data.num ? node : currentNode,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
setNodes((nodes) => [...nodes, node]);
|
||||
}
|
||||
});
|
||||
|
||||
connection.onAdminPacketEvent.subscribe((adminMessage) => {
|
||||
switch (adminMessage.data.variant.oneofKind) {
|
||||
case 'getRadioResponse':
|
||||
if (adminMessage.data.variant.getRadioResponse.preferences) {
|
||||
setPreferences(
|
||||
adminMessage.data.variant.getRadioResponse.preferences,
|
||||
);
|
||||
}
|
||||
const deviceStatusEvent = connection.onDeviceStatusEvent.subscribe(
|
||||
(status) => {
|
||||
setDeviceStatus(status);
|
||||
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
|
||||
setIsReady(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
const myNodeInfoEvent = connection.onMyNodeInfoEvent.subscribe(
|
||||
setMyNodeInfo,
|
||||
);
|
||||
|
||||
break;
|
||||
case 'getChannelResponse':
|
||||
if (adminMessage.data.variant.getChannelResponse) {
|
||||
let message = adminMessage.data.variant.getChannelResponse;
|
||||
setChannels((channels) => [...channels, message]);
|
||||
}
|
||||
const nodeInfoPacketEvent = connection.onNodeInfoPacketEvent.subscribe(
|
||||
(node) => {
|
||||
if (
|
||||
nodes.findIndex(
|
||||
(currentNode) => currentNode.data.num === node.data.num,
|
||||
) >= 0
|
||||
) {
|
||||
setNodes(
|
||||
nodes.map((currentNode) =>
|
||||
currentNode.data.num === node.data.num ? node : currentNode,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
setNodes((nodes) => [...nodes, node]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
const adminPacketEvent = connection.onAdminPacketEvent.subscribe(
|
||||
(adminMessage) => {
|
||||
switch (adminMessage.data.variant.oneofKind) {
|
||||
case 'getRadioResponse':
|
||||
if (adminMessage.data.variant.getRadioResponse.preferences) {
|
||||
setPreferences(
|
||||
adminMessage.data.variant.getRadioResponse.preferences,
|
||||
);
|
||||
}
|
||||
|
||||
connection.onMeshHeartbeat.subscribe(setLastMeshInterraction);
|
||||
break;
|
||||
case 'getChannelResponse':
|
||||
if (adminMessage.data.variant.getChannelResponse) {
|
||||
let message = adminMessage.data.variant.getChannelResponse;
|
||||
setChannels((channels) => [...channels, message]);
|
||||
}
|
||||
|
||||
connection.onRoutingPacketEvent.subscribe((routingPacket) => {
|
||||
console.log(routingPacket);
|
||||
// console.log(messages);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// messages.map((message) => {
|
||||
// console.log(
|
||||
// `${
|
||||
// routingPacket.payloadVariant.oneofKind === 'decoded'
|
||||
// ? routingPacket.payloadVariant.decoded.requestId
|
||||
// : null
|
||||
// } === ${message.message.packet.id}: ${
|
||||
// routingPacket.payloadVariant.oneofKind === 'decoded'
|
||||
// ? routingPacket.payloadVariant.decoded.requestId
|
||||
// : null === message.message.packet.id
|
||||
// }`,
|
||||
// );
|
||||
// });
|
||||
// messages.find((message) => {
|
||||
// message.message.packet.id === routingPacket.decoded.requestId;
|
||||
// });
|
||||
});
|
||||
const meshHeartbeat = connection.onMeshHeartbeat.subscribe(
|
||||
setLastMeshInterraction,
|
||||
);
|
||||
|
||||
return () => {
|
||||
deviceStatusEvent.unsubscribe();
|
||||
myNodeInfoEvent.unsubscribe();
|
||||
nodeInfoPacketEvent.unsubscribe();
|
||||
adminPacketEvent.unsubscribe();
|
||||
meshHeartbeat.unsubscribe();
|
||||
connection.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen w-screen">
|
||||
<Header
|
||||
@@ -169,16 +178,17 @@ const App = () => {
|
||||
LastMeshInterraction={lastMeshInterraction}
|
||||
/>
|
||||
<Main
|
||||
IsReady={isReady}
|
||||
Messages={messages}
|
||||
MyNodeInfo={myNodeInfo}
|
||||
Connection={connection}
|
||||
Nodes={nodes}
|
||||
Channels={channels}
|
||||
Preferences={preferences}
|
||||
Language={language}
|
||||
SetLanguage={setLanguage}
|
||||
Translations={translations}
|
||||
isReady={isReady}
|
||||
myNodeInfo={myNodeInfo}
|
||||
connection={connection}
|
||||
nodes={nodes}
|
||||
channels={channels}
|
||||
preferences={preferences}
|
||||
language={language}
|
||||
setLanguage={setLanguage}
|
||||
translations={translations}
|
||||
darkmode={darkmode}
|
||||
setDarkmode={setDarkmode}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+73
-76
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { MenuIcon, PaperAirplaneIcon } from '@heroicons/react/outline';
|
||||
import type {
|
||||
IHTTPConnection,
|
||||
Protobuf,
|
||||
@@ -9,102 +8,100 @@ import type {
|
||||
|
||||
import type { LanguageEnum, languageTemplate } from './App';
|
||||
import ChatMessage from './components/ChatMessage';
|
||||
import MessageBox from './components/MessageBox';
|
||||
import Sidebar from './components/Sidebar';
|
||||
|
||||
interface MainProps {
|
||||
Messages: { message: Types.TextPacket; ack: boolean }[];
|
||||
Connection: IHTTPConnection;
|
||||
MyNodeInfo: Protobuf.MyNodeInfo;
|
||||
Nodes: Types.NodeInfoPacket[];
|
||||
Channels: Protobuf.Channel[];
|
||||
IsReady: boolean;
|
||||
Preferences: Protobuf.RadioConfig_UserPreferences;
|
||||
Language: LanguageEnum;
|
||||
SetLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
|
||||
Translations: languageTemplate;
|
||||
connection: IHTTPConnection;
|
||||
myNodeInfo: Protobuf.MyNodeInfo;
|
||||
nodes: Types.NodeInfoPacket[];
|
||||
channels: Protobuf.Channel[];
|
||||
isReady: boolean;
|
||||
preferences: Protobuf.RadioConfig_UserPreferences;
|
||||
language: LanguageEnum;
|
||||
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
|
||||
translations: languageTemplate;
|
||||
darkmode: boolean;
|
||||
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const Main = (props: MainProps) => {
|
||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||
const [mobileNavOpen, setMobileNavOpen] = React.useState(true);
|
||||
const [messages, setMessages] = React.useState<
|
||||
{ message: Types.TextPacket; ack: boolean }[]
|
||||
>([]);
|
||||
const [settingsModalOpen, setSettingsModalOpen] = useState<boolean>(false);
|
||||
|
||||
const sendMessage = () => {
|
||||
if (props.IsReady) {
|
||||
props.Connection.sendText(currentMessage, undefined, true);
|
||||
setCurrentMessage('');
|
||||
}
|
||||
};
|
||||
React.useEffect(() => {
|
||||
const textPacketEvent = props.connection.onTextPacketEvent.subscribe(
|
||||
(message) => {
|
||||
setMessages((messages) => [
|
||||
...messages,
|
||||
{ message: message, ack: false },
|
||||
]);
|
||||
},
|
||||
);
|
||||
return () => textPacketEvent.unsubscribe();
|
||||
}, [props.connection]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const routingPacketEvent = props.connection.onRoutingPacketEvent.subscribe(
|
||||
(routingPacket) => {
|
||||
setMessages(
|
||||
messages.map((message) => {
|
||||
return routingPacket.packet.payloadVariant.oneofKind ===
|
||||
'decoded' &&
|
||||
message.message.packet.id ===
|
||||
routingPacket.packet.payloadVariant.decoded.requestId
|
||||
? {
|
||||
ack: true,
|
||||
message: message.message,
|
||||
}
|
||||
: message;
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
return () => routingPacketEvent.unsubscribe();
|
||||
}, [props.connection, messages]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row flex-grow space-2">
|
||||
<div className="flex flex-col md:flex-row flex-grow m-3 space-y-2 md:space-y-0 space-x-0 md:space-x-2">
|
||||
<div className="flex flex-col flex-grow container mx-auto">
|
||||
<div className="flex flex-col flex-grow py-6 px-3 space-y-2">
|
||||
{props.Messages.length ? (
|
||||
props.Messages.map((message, Main) => (
|
||||
<div className="flex flex-col flex-grow py-6 space-y-2">
|
||||
{messages.length ? (
|
||||
messages.map((message, Main) => (
|
||||
<ChatMessage
|
||||
nodes={props.Nodes}
|
||||
nodes={props.nodes}
|
||||
key={Main}
|
||||
message={message}
|
||||
myId={props.MyNodeInfo.myNodeNum}
|
||||
myId={props.myNodeInfo.myNodeNum}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="m-auto text-2xl text-gray-500">
|
||||
{props.Translations.no_messages_message}
|
||||
{props.translations.no_messages_message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex space-x-2 w-full p-3">
|
||||
<form
|
||||
className="flex flex-wrap relative w-full"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}}
|
||||
>
|
||||
{props.IsReady}
|
||||
<input
|
||||
type="text"
|
||||
placeholder={`${props.Translations.no_message_placeholder}...`}
|
||||
disabled={!props.IsReady}
|
||||
value={currentMessage}
|
||||
onChange={(e) => {
|
||||
setCurrentMessage(e.target.value);
|
||||
}}
|
||||
className={`p-3 placeholder-gray-400 text-gray-700 relative rounded-md shadow-md focus:outline-none w-full pr-10 ${
|
||||
props.IsReady ? 'cursor-text' : 'cursor-not-allowed'
|
||||
}`}
|
||||
/>
|
||||
<span className="flex z-10 h-full text-gray-400 absolute w-8 right-0">
|
||||
<PaperAirplaneIcon
|
||||
onClick={sendMessage}
|
||||
className={`text-xl hover:text-gray-500 h-6 w-6 my-auto ${
|
||||
props.IsReady ? 'cursor-pointer' : 'cursor-not-allowed'
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
</form>
|
||||
<div
|
||||
className="flex p-3 text-xl hover:text-gray-500 text-gray-400 rounded-md shadow-md focus:outline-none cursor-pointer md:hidden"
|
||||
onClick={() => {
|
||||
setMobileNavOpen(!mobileNavOpen);
|
||||
}}
|
||||
>
|
||||
<MenuIcon className="m-auto h-6 2-6" />
|
||||
</div>
|
||||
</div>
|
||||
<MessageBox
|
||||
connection={props.connection}
|
||||
isReady={props.isReady}
|
||||
setSettingsModalOpen={setSettingsModalOpen}
|
||||
translations={props.translations}
|
||||
/>
|
||||
</div>
|
||||
<Sidebar
|
||||
IsReady={props.IsReady}
|
||||
Nodes={props.Nodes}
|
||||
Channels={props.Channels}
|
||||
Preferences={props.Preferences}
|
||||
Connection={props.Connection}
|
||||
MobileNavOpen={mobileNavOpen}
|
||||
Language={props.Language}
|
||||
SetLanguage={props.SetLanguage}
|
||||
Translations={props.Translations}
|
||||
myId={props.MyNodeInfo.myNodeNum}
|
||||
isReady={props.isReady}
|
||||
nodes={props.nodes}
|
||||
channels={props.channels}
|
||||
preferences={props.preferences}
|
||||
connection={props.connection}
|
||||
language={props.language}
|
||||
setLanguage={props.setLanguage}
|
||||
translations={props.translations}
|
||||
myId={props.myNodeInfo.myNodeNum}
|
||||
darkmode={props.darkmode}
|
||||
setDarkmode={props.setDarkmode}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MenuIcon, PaperAirplaneIcon } from '@heroicons/react/outline';
|
||||
import type { IHTTPConnection } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { languageTemplate } from '../App';
|
||||
|
||||
export interface MessageBoxProps {
|
||||
translations: languageTemplate;
|
||||
setSettingsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
connection: IHTTPConnection;
|
||||
isReady: boolean;
|
||||
}
|
||||
|
||||
const MessageBox = (props: MessageBoxProps) => {
|
||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||
const sendMessage = () => {
|
||||
if (props.isReady) {
|
||||
props.connection.sendText(currentMessage, undefined, true);
|
||||
setCurrentMessage('');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex text-lg font-medium border rounded-md space-x-2 md:space-x-0 w-full">
|
||||
<div
|
||||
className="flex p-3 text-xl hover:text-gray-500 text-gray-400 rounded-md shadow-md focus:outline-none cursor-pointer md:hidden"
|
||||
onClick={() => {
|
||||
props.setSettingsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<MenuIcon className="m-auto h-6 2-6" />
|
||||
</div>
|
||||
<form
|
||||
className="flex flex-wrap relative w-full"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}}
|
||||
>
|
||||
{props.isReady}
|
||||
<input
|
||||
type="text"
|
||||
placeholder={`${props.translations.no_message_placeholder}...`}
|
||||
disabled={!props.isReady}
|
||||
value={currentMessage}
|
||||
onChange={(e) => {
|
||||
setCurrentMessage(e.target.value);
|
||||
}}
|
||||
className={`p-3 placeholder-gray-400 text-gray-700 relative rounded-md shadow-md focus:outline-none w-full pr-10 ${
|
||||
props.isReady ? 'cursor-text' : 'cursor-not-allowed'
|
||||
}`}
|
||||
/>
|
||||
<span className="flex z-10 h-full text-gray-400 absolute w-8 right-0">
|
||||
<PaperAirplaneIcon
|
||||
onClick={sendMessage}
|
||||
className={`text-xl hover:text-gray-500 h-6 w-6 my-auto ${
|
||||
props.isReady ? 'cursor-pointer' : 'cursor-not-allowed'
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageBox;
|
||||
@@ -1,67 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline';
|
||||
|
||||
interface NavItemProps {
|
||||
isDropdown: boolean;
|
||||
open: boolean;
|
||||
isNested: boolean;
|
||||
titleContent: React.ReactNode;
|
||||
dropdownContent?: React.ReactNode;
|
||||
onClick?: Function;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const NavItem = (props: NavItemProps) => {
|
||||
React.useEffect(() => {
|
||||
if (props.open) {
|
||||
setNavItemOpen(props.open);
|
||||
}
|
||||
}, []);
|
||||
const [navItemOpen, setNavItemOpen] = React.useState(false);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`flex w-full text-lg font-medium justify-between ${
|
||||
navItemOpen && props.isNested ? 'bg-gray-100' : null
|
||||
} ${props.isNested ? 'border-b px-3 py-1' : 'p-3'} ${
|
||||
props.isDropdown && navItemOpen ? 'shadow-md' : 'border-b'
|
||||
} ${
|
||||
props.isDropdown || props.isNested
|
||||
? 'hover:bg-gray-200 cursor-pointer'
|
||||
: null
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (props.isDropdown) setNavItemOpen(!navItemOpen);
|
||||
if (props.onClick) {
|
||||
props.onClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.titleContent}
|
||||
{props.isDropdown && !props.isLoading ? (
|
||||
navItemOpen ? (
|
||||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
) : (
|
||||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
)
|
||||
) : null}
|
||||
{props.isLoading ? (
|
||||
// <FaSpinner className="animate-spin my-auto" />
|
||||
<div>loading</div>
|
||||
) : null}
|
||||
</div>
|
||||
{props.isDropdown ? (
|
||||
<div
|
||||
className={`duration-200 ease-in-out transition-all overflow-hidden max-h-0 border-l-8 ${
|
||||
props.isNested ? 'border-gray-500' : 'border-gray-300'
|
||||
} ${navItemOpen ? 'max-h-full' : null}`}
|
||||
>
|
||||
{props.dropdownContent}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavItem;
|
||||
+34
-37
@@ -7,55 +7,52 @@ import type {
|
||||
} from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { LanguageEnum, languageTemplate } from '../App';
|
||||
import SidebarChannels from './Sidebar/SidebarChannels';
|
||||
import SidebarDeviceSettings from './Sidebar/SidebarDeviceSettings';
|
||||
import SidebarNodes from './Sidebar/SidebarNodes';
|
||||
import SidebarUISettings from './Sidebar/SidebarUISettings';
|
||||
import Channels from './Sidebar/Channels/Index';
|
||||
import Device from './Sidebar/Device/Index';
|
||||
import Nodes from './Sidebar/Nodes/Index';
|
||||
import UI from './Sidebar/UI/Index';
|
||||
|
||||
interface SidebarProps {
|
||||
IsReady: boolean;
|
||||
Nodes: Types.NodeInfoPacket[];
|
||||
Channels: Protobuf.Channel[];
|
||||
Preferences: Protobuf.RadioConfig_UserPreferences;
|
||||
Connection: IHTTPConnection;
|
||||
MobileNavOpen: boolean;
|
||||
Language: LanguageEnum;
|
||||
SetLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
|
||||
Translations: languageTemplate;
|
||||
isReady: boolean;
|
||||
nodes: Types.NodeInfoPacket[];
|
||||
channels: Protobuf.Channel[];
|
||||
preferences: Protobuf.RadioConfig_UserPreferences;
|
||||
connection: IHTTPConnection;
|
||||
language: LanguageEnum;
|
||||
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
|
||||
translations: languageTemplate;
|
||||
myId: number;
|
||||
darkmode: boolean;
|
||||
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const Sidebar = (props: SidebarProps) => {
|
||||
const updatePreferences = () => {};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col rounded-md m-3 md:ml-0 shadow-md w-full max-w-sm ${
|
||||
!props.MobileNavOpen ? 'hidden' : 'visible'
|
||||
}`}
|
||||
>
|
||||
<SidebarNodes
|
||||
IsReady={props.IsReady}
|
||||
Nodes={props.Nodes}
|
||||
Translations={props.Translations}
|
||||
<div className="flex flex-col rounded-md md:ml-0 shadow-md border w-full max-w-sm">
|
||||
<Nodes
|
||||
isReady={props.isReady}
|
||||
nodes={props.nodes}
|
||||
translations={props.translations}
|
||||
myId={props.myId}
|
||||
/>
|
||||
<SidebarDeviceSettings
|
||||
IsReady={props.IsReady}
|
||||
Preferences={props.Preferences}
|
||||
Connection={props.Connection}
|
||||
Translations={props.Translations}
|
||||
<Device
|
||||
isReady={props.isReady}
|
||||
preferences={props.preferences}
|
||||
connection={props.connection}
|
||||
translations={props.translations}
|
||||
/>
|
||||
<SidebarChannels
|
||||
IsReady={props.IsReady}
|
||||
Channels={props.Channels}
|
||||
Translations={props.Translations}
|
||||
<Channels
|
||||
isReady={props.isReady}
|
||||
channels={props.channels}
|
||||
translations={props.translations}
|
||||
/>
|
||||
<div className="flex-grow border-b"></div>
|
||||
<SidebarUISettings
|
||||
Language={props.Language}
|
||||
SetLanguage={props.SetLanguage}
|
||||
Translations={props.Translations}
|
||||
<UI
|
||||
language={props.language}
|
||||
setLanguage={props.setLanguage}
|
||||
translations={props.translations}
|
||||
darkmode={props.darkmode}
|
||||
setDarkmode={props.setDarkmode}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface ChannelProps {
|
||||
channel: Protobuf.Channel;
|
||||
}
|
||||
|
||||
const Channel = (props: ChannelProps) => {
|
||||
return (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<div className="flex">
|
||||
{props.channel.index} -{' '}
|
||||
{Protobuf.Channel_Role[props.channel.role]}
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Bandwidth:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.bandwidth}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Channel Number:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.channelNum}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Coding Rate:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.codingRate}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>ID:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.id}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Modem Config:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.modemConfig
|
||||
? Protobuf.ChannelSettings_ModemConfig[
|
||||
props.channel.settings.modemConfig
|
||||
]
|
||||
: null}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Name:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.name}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>PSK:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.psk.toLocaleString()}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Spread Factor:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.spreadFactor}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Tx Power:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.txPower}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Uplink:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.uplinkEnabled ? 'true' : 'false'}
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Downlink:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{props.channel.settings?.downlinkEnabled ? 'true' : 'false'}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export default Channel;
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
HashtagIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { languageTemplate } from '../../../App';
|
||||
import Channel from './Channel';
|
||||
|
||||
export interface ChannelsProps {
|
||||
isReady: boolean;
|
||||
channels: Protobuf.Channel[];
|
||||
translations: languageTemplate;
|
||||
}
|
||||
|
||||
const Channels = (props: ChannelsProps) => {
|
||||
return (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<div className="flex">
|
||||
<HashtagIcon className="my-auto mr-2 2-5 h-5" />
|
||||
{props.translations.device_channels_title}
|
||||
</div>
|
||||
{open ? (
|
||||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
) : (
|
||||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<>
|
||||
{props.channels.map((channel, index) => {
|
||||
if (channel.role !== Protobuf.Channel_Role.DISABLED)
|
||||
return <Channel channel={channel} />;
|
||||
})}
|
||||
</>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export default Channels;
|
||||
@@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import {
|
||||
AdjustmentsIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
SaveIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import { IHTTPConnection, Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { languageTemplate } from '../../../App';
|
||||
|
||||
interface DeviceProps {
|
||||
isReady: boolean;
|
||||
preferences: Protobuf.RadioConfig_UserPreferences;
|
||||
connection: IHTTPConnection;
|
||||
translations: languageTemplate;
|
||||
}
|
||||
|
||||
const Device = (props: DeviceProps) => {
|
||||
return (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<div className="flex">
|
||||
<AdjustmentsIcon className="my-auto mr-2 w-5 h-5" />
|
||||
{props.translations.device_settings_title}
|
||||
</div>
|
||||
{open ? (
|
||||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
) : (
|
||||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<>
|
||||
<div className="flex whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">
|
||||
{props.translations.device_region_title}
|
||||
</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<select
|
||||
value={
|
||||
props.preferences?.region ?? Protobuf.RegionCode.Unset
|
||||
}
|
||||
onChange={(e) => {
|
||||
props.preferences.region = parseInt(e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value={Protobuf.RegionCode.ANZ}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.ANZ]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.CN}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.CN]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.EU433}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.EU433]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.EU865}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.EU865]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.JP}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.JP]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.KR}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.KR]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.TW}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.TW]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.US}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.US]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.Unset}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.Unset]}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">
|
||||
{props.translations.device_wifi_ssid}
|
||||
</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<input
|
||||
onChange={() => {}}
|
||||
type="text"
|
||||
value={props.preferences.wifiSsid}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">
|
||||
{props.translations.device_wifi_psk}
|
||||
</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<input
|
||||
type="password"
|
||||
value={props.preferences.wifiPassword}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex group p-1 bg-gray-100 cursor-pointer hover:bg-gray-200 border-b">
|
||||
<div
|
||||
className="flex m-auto font-medium group-hover:text-gray-700"
|
||||
onClick={() => {
|
||||
props.connection.setPreferences(props.preferences);
|
||||
}}
|
||||
>
|
||||
<SaveIcon className="m-auto mr-2 group-hover:text-gray-700 w-5 h-5" />
|
||||
{props.translations.save_changes_button}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export default Device;
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import type { Types } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { languageTemplate } from '../../../App';
|
||||
import Node from './Node';
|
||||
|
||||
interface NodesProps {
|
||||
isReady: boolean;
|
||||
nodes: Types.NodeInfoPacket[];
|
||||
translations: languageTemplate;
|
||||
myId: number;
|
||||
}
|
||||
|
||||
const Nodes = (props: NodesProps) => {
|
||||
return (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="flex rounded-t-md w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<div className="flex">
|
||||
<UsersIcon className="my-auto mr-2 w-5 h-5" />
|
||||
{props.translations.nodes_title}
|
||||
</div>
|
||||
{open ? (
|
||||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
) : (
|
||||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel className="shadow-inner">
|
||||
{props.nodes.length ? (
|
||||
props.nodes.map((node, index) => (
|
||||
<Node key={index} node={node} myId={props.myId} />
|
||||
))
|
||||
) : (
|
||||
<div className="flex border-b border-gray-300">
|
||||
<div className="m-auto p-3 text-gray-500">
|
||||
{props.translations.no_nodes_message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export default Nodes;
|
||||
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import {
|
||||
ClockIcon,
|
||||
DesktopComputerIcon,
|
||||
FlagIcon,
|
||||
GlobeIcon,
|
||||
LightningBoltIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import type { Types } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface NodeProps {
|
||||
node: Types.NodeInfoPacket;
|
||||
myId: number;
|
||||
}
|
||||
|
||||
const Node = (props: NodeProps) => {
|
||||
return (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<div className="flex">
|
||||
{props.node.data.num === props.myId ? (
|
||||
<FlagIcon className="text-yellow-500 my-auto mr-2 w-5 h-5" />
|
||||
) : (
|
||||
<DesktopComputerIcon className="my-auto mr-2 w-5 h-5" />
|
||||
)}
|
||||
<div className="m-auto">{props.node.data.user?.longName}</div>
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<div>
|
||||
<p>
|
||||
SNR:{' '}
|
||||
{props.node.packet?.rxSnr ? props.node.packet.rxSnr : 'Unknown'}
|
||||
</p>
|
||||
<p>
|
||||
RSSI:{' '}
|
||||
{props.node.packet?.rxRssi
|
||||
? props.node.packet.rxRssi
|
||||
: 'Unknown'}
|
||||
</p>
|
||||
<p>
|
||||
{`Last heard: ${
|
||||
props.node.data?.lastHeard
|
||||
? new Date(props.node.data.lastHeard).toLocaleString()
|
||||
: 'Unknown'
|
||||
}`}{' '}
|
||||
{}
|
||||
</p>
|
||||
<div className="flex">
|
||||
<GlobeIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>
|
||||
{props.node.data.position?.latitudeI},
|
||||
{props.node.data.position?.longitudeI}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<GlobeIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>{props.node.data.position?.altitude}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<ClockIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>{props.node.data.position?.time}</p>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<LightningBoltIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>{props.node.data.position?.batteryLevel}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export default Node;
|
||||
@@ -1,146 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { HashtagIcon } from '@heroicons/react/outline';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { languageTemplate } from '../../App';
|
||||
import NavItem from '../NavItem';
|
||||
|
||||
interface SidebarChannelsProps {
|
||||
IsReady: boolean;
|
||||
Channels: Protobuf.Channel[];
|
||||
Translations: languageTemplate;
|
||||
}
|
||||
|
||||
const SidebarChannels = (props: SidebarChannelsProps) => {
|
||||
return (
|
||||
<NavItem
|
||||
isDropdown={true}
|
||||
open={false}
|
||||
isNested={false}
|
||||
titleContent={
|
||||
<div className="flex">
|
||||
<HashtagIcon className="my-auto mr-2 2-5 h-5" />
|
||||
{props.Translations.device_channels_title}
|
||||
</div>
|
||||
}
|
||||
isLoading={!props.IsReady}
|
||||
dropdownContent={
|
||||
<>
|
||||
{props.Channels.map((channel, index) => {
|
||||
if (channel.role !== Protobuf.Channel_Role.DISABLED)
|
||||
return (
|
||||
<NavItem
|
||||
key={index}
|
||||
isDropdown={true}
|
||||
isNested={true}
|
||||
open={false}
|
||||
titleContent={
|
||||
<div className="flex">
|
||||
{channel.index} - {Protobuf.Channel_Role[channel.role]}
|
||||
</div>
|
||||
}
|
||||
dropdownContent={
|
||||
<NavItem
|
||||
isDropdown={false}
|
||||
isNested={false}
|
||||
open={false}
|
||||
titleContent={
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Bandwidth:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.bandwidth}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Channel Number:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.channelNum}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Coding Rate:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.codingRate}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>ID:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.id}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Modem Config:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.modemConfig
|
||||
? Protobuf.ChannelSettings_ModemConfig[
|
||||
channel.settings.modemConfig
|
||||
]
|
||||
: null}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Name:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.name}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>PSK:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.psk.toLocaleString()}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Spread Factor:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.spreadFactor}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Tx Power:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.txPower}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Uplink:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.uplinkEnabled
|
||||
? 'true'
|
||||
: 'false'}
|
||||
</code>
|
||||
</div>
|
||||
<div className="flex justify-between border-b hover:bg-gray-200">
|
||||
<p>Downlink:</p>
|
||||
<code className="bg-gray-200 rounded-full px-2">
|
||||
{channel.settings?.downlinkEnabled
|
||||
? 'true'
|
||||
: 'false'}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarChannels;
|
||||
@@ -1,105 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { AdjustmentsIcon, SaveIcon } from '@heroicons/react/outline';
|
||||
import { IHTTPConnection, Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { languageTemplate } from '../../App';
|
||||
import NavItem from '../NavItem';
|
||||
|
||||
interface SidebarDeviceSettingsProps {
|
||||
IsReady: boolean;
|
||||
Preferences: Protobuf.RadioConfig_UserPreferences;
|
||||
Connection: IHTTPConnection;
|
||||
Translations: languageTemplate;
|
||||
}
|
||||
|
||||
const SidebarDeviceSettings = (props: SidebarDeviceSettingsProps) => {
|
||||
return (
|
||||
<NavItem
|
||||
isDropdown={true}
|
||||
open={false}
|
||||
isNested={false}
|
||||
titleContent={
|
||||
<div className="flex">
|
||||
<AdjustmentsIcon className="my-auto mr-2 w-5 h-5" />
|
||||
{props.Translations.device_settings_title}
|
||||
</div>
|
||||
}
|
||||
isLoading={!props.IsReady}
|
||||
dropdownContent={
|
||||
<>
|
||||
<div className="flex whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">
|
||||
{props.Translations.device_region_title}
|
||||
</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<select
|
||||
value={props.Preferences?.region ?? Protobuf.RegionCode.Unset}
|
||||
onChange={(e) => {
|
||||
props.Preferences.region = parseInt(e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value={Protobuf.RegionCode.ANZ}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.ANZ]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.CN}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.CN]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.EU433}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.EU433]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.EU865}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.EU865]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.JP}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.JP]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.KR}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.KR]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.TW}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.TW]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.US}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.US]}
|
||||
</option>
|
||||
<option value={Protobuf.RegionCode.Unset}>
|
||||
{Protobuf.RegionCode[Protobuf.RegionCode.Unset]}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">{props.Translations.device_wifi_ssid}</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<input
|
||||
onChange={() => {}}
|
||||
type="text"
|
||||
value={props.Preferences.wifiSsid}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex whitespace-nowrap p-3 justify-between border-b">
|
||||
<div className="my-auto">{props.Translations.device_wifi_psk}</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
<input type="password" value={props.Preferences.wifiPassword} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex group p-1 bg-gray-100 cursor-pointer hover:bg-gray-200 border-b">
|
||||
<div
|
||||
className="flex m-auto font-medium group-hover:text-gray-700"
|
||||
onClick={() => {
|
||||
props.Connection.setPreferences(props.Preferences);
|
||||
}}
|
||||
>
|
||||
<SaveIcon className="m-auto mr-2 group-hover:text-gray-700 w-5 h-5" />
|
||||
{props.Translations.save_changes_button}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarDeviceSettings;
|
||||
@@ -1,119 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
ClockIcon,
|
||||
DesktopComputerIcon,
|
||||
FlagIcon,
|
||||
GlobeIcon,
|
||||
LightningBoltIcon,
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import type { Types } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import type { languageTemplate } from '../../App';
|
||||
import NavItem from '../NavItem';
|
||||
|
||||
interface sidebarNodesProps {
|
||||
IsReady: boolean;
|
||||
Nodes: Types.NodeInfoPacket[];
|
||||
Translations: languageTemplate;
|
||||
myId: number;
|
||||
}
|
||||
|
||||
const SidebarNodes = (props: sidebarNodesProps) => {
|
||||
return (
|
||||
<NavItem
|
||||
isDropdown={true}
|
||||
open={false}
|
||||
isNested={false}
|
||||
titleContent={
|
||||
<div className="flex">
|
||||
<UsersIcon className="my-auto mr-2 w-5 h-5" />
|
||||
{props.Translations.nodes_title}
|
||||
<div className="flex m-auto rounded-full bg-gray-300 w-6 h-6 text-sm ml-2">
|
||||
<div className="m-auto">{props.Nodes.length ?? 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
isLoading={!props.IsReady}
|
||||
dropdownContent={
|
||||
props.Nodes.length ? (
|
||||
props.Nodes.map((node, index) => (
|
||||
<NavItem
|
||||
key={index}
|
||||
isDropdown={true}
|
||||
isNested={true}
|
||||
open={false}
|
||||
titleContent={
|
||||
<div key={index} className="flex">
|
||||
{node.data.num === props.myId ? (
|
||||
<FlagIcon className="text-yellow-500 my-auto mr-2 w-5 h-5" />
|
||||
) : (
|
||||
<DesktopComputerIcon className="my-auto mr-2 w-5 h-5" />
|
||||
)}
|
||||
<div className="m-auto">{node.data.user?.longName}</div>
|
||||
</div>
|
||||
}
|
||||
dropdownContent={
|
||||
<NavItem
|
||||
isDropdown={false}
|
||||
isNested={true}
|
||||
open={false}
|
||||
titleContent={
|
||||
<div>
|
||||
<p>
|
||||
SNR:{' '}
|
||||
{node.packet?.rxSnr ? node.packet.rxSnr : 'Unknown'}
|
||||
</p>
|
||||
<p>
|
||||
RSSI:{' '}
|
||||
{node.packet?.rxRssi ? node.packet.rxRssi : 'Unknown'}
|
||||
</p>
|
||||
<p>
|
||||
{`Last heard: ${
|
||||
node.data?.lastHeard
|
||||
? new Date(node.data.lastHeard).toLocaleString()
|
||||
: 'Unknown'
|
||||
}`}{' '}
|
||||
{}
|
||||
</p>
|
||||
<div className="flex">
|
||||
<GlobeIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>
|
||||
{node.data.position?.latitudeI},
|
||||
{node.data.position?.longitudeI}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<GlobeIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>{node.data.position?.altitude}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<ClockIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>{node.data.position?.time}</p>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<LightningBoltIcon className="my-auto mr-2 w-5 h-5" />
|
||||
<p>{node.data.position?.batteryLevel}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex border-b border-gray-300">
|
||||
<div className="m-auto p-3 text-gray-500">
|
||||
{props.Translations.no_nodes_message}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarNodes;
|
||||
@@ -1,132 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Br, Jp, Us } from 'react-flags-select';
|
||||
|
||||
import { CogIcon } from '@heroicons/react/outline';
|
||||
|
||||
import type { languageTemplate } from '../../App';
|
||||
import { LanguageEnum } from '../../App';
|
||||
import ToggleSwitch from '../basic/ToggleSwitch';
|
||||
import NavItem from '../NavItem';
|
||||
|
||||
interface SidebarUISettingsProps {
|
||||
Language: LanguageEnum;
|
||||
SetLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
|
||||
Translations: languageTemplate;
|
||||
}
|
||||
|
||||
const SidebarUISettings = (props: SidebarUISettingsProps) => {
|
||||
return (
|
||||
<NavItem
|
||||
isDropdown={true}
|
||||
isNested={false}
|
||||
open={false}
|
||||
titleContent={
|
||||
<div className="flex">
|
||||
<CogIcon className="my-auto mr-2 w-5 h-5" />
|
||||
{props.Translations.ui_settings_title}
|
||||
</div>
|
||||
}
|
||||
dropdownContent={
|
||||
<>
|
||||
<NavItem
|
||||
isDropdown={false}
|
||||
isNested={true}
|
||||
open={false}
|
||||
titleContent={
|
||||
<>
|
||||
<div className="my-auto">
|
||||
{props.Translations.color_scheme_title}
|
||||
</div>
|
||||
<div className="flex shadow-md rounded-md ml-2">
|
||||
{/* <div className="bg-gray-200 flex group p-2 rounded-l-md border border-gray-300 hover:bg-gray-200 cursor-pointer">
|
||||
<FaSun className="m-auto group-hover:text-gray-700" />
|
||||
</div>
|
||||
<div className="flex group p-2 border border-gray-300 hover:bg-gray-200 cursor-pointer">
|
||||
<FaMoon className="m-auto group-hover:text-gray-700" />
|
||||
</div>
|
||||
<div className="flex group p-2 rounded-r-md border border-gray-300 hover:bg-gray-200 cursor-pointer">
|
||||
<FaLaptop className="m-auto group-hover:text-gray-700" />
|
||||
</div> */}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
isDropdown={true}
|
||||
isNested={true}
|
||||
open={false}
|
||||
titleContent={
|
||||
<div className="flex my-auto">
|
||||
{props.Translations.language_title}
|
||||
<div className="my-auto">
|
||||
{props.Language === LanguageEnum.ENGLISH ? (
|
||||
<Us className="ml-2 w-8 shadow-md" />
|
||||
) : props.Language === LanguageEnum.JAPANESE ? (
|
||||
<Jp className="ml-2 w-8 shadow-md" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
dropdownContent={
|
||||
<>
|
||||
<NavItem
|
||||
onClick={() => {
|
||||
props.SetLanguage(LanguageEnum.ENGLISH);
|
||||
}}
|
||||
open={false}
|
||||
isDropdown={false}
|
||||
isNested={true}
|
||||
titleContent={
|
||||
<>
|
||||
English <Us className="w-8 shadow-md" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
onClick={() => {
|
||||
props.SetLanguage(LanguageEnum.PORTUGUESE);
|
||||
}}
|
||||
open={false}
|
||||
isDropdown={false}
|
||||
isNested={true}
|
||||
titleContent={
|
||||
<>
|
||||
Português <Br className="w-8 shadow-md" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
onClick={() => {
|
||||
props.SetLanguage(LanguageEnum.JAPANESE);
|
||||
}}
|
||||
open={false}
|
||||
isDropdown={false}
|
||||
isNested={true}
|
||||
titleContent={
|
||||
<>
|
||||
日本語 <Jp className="w-8 shadow-md" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
isDropdown={false}
|
||||
isNested={true}
|
||||
open={false}
|
||||
titleContent={
|
||||
<>
|
||||
<div className="">Test</div>
|
||||
<ToggleSwitch active={true} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarUISettings;
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
CogIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
|
||||
import type { LanguageEnum, languageTemplate } from '../../../App';
|
||||
import Translations from './Translations';
|
||||
|
||||
interface UIProps {
|
||||
language: LanguageEnum;
|
||||
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
|
||||
translations: languageTemplate;
|
||||
darkmode: boolean;
|
||||
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const UI = (props: UIProps) => {
|
||||
return (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<div className="flex">
|
||||
<CogIcon className="my-auto mr-2 w-5 h-5" />
|
||||
{props.translations.ui_settings_title}
|
||||
</div>
|
||||
{open ? (
|
||||
<ChevronDownIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
) : (
|
||||
<ChevronRightIcon className="my-auto group-hover:text-gray-700 w-5 h-5" />
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<Translations
|
||||
language={props.language}
|
||||
setLanguage={props.setLanguage}
|
||||
translations={props.translations}
|
||||
/>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export default UI;
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Br, Jp, Us } from 'react-flags-select';
|
||||
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
|
||||
import { LanguageEnum, languageTemplate } from '../../../App';
|
||||
|
||||
export interface TranslationsProps {
|
||||
language: LanguageEnum;
|
||||
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
|
||||
translations: languageTemplate;
|
||||
}
|
||||
|
||||
const Translations = (props: TranslationsProps) => {
|
||||
return (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="flex w-full text-lg font-medium justify-between p-3 border-b hover:bg-gray-200 cursor-pointer">
|
||||
<div className="flex my-auto">
|
||||
{props.translations.language_title}
|
||||
<div className="my-auto">
|
||||
{props.language === LanguageEnum.ENGLISH ? (
|
||||
<Us className="ml-2 w-8 shadow-md" />
|
||||
) : props.language === LanguageEnum.JAPANESE ? (
|
||||
<Jp className="ml-2 w-8 shadow-md" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<div>
|
||||
English <Us className="w-8 shadow-md" />
|
||||
</div>
|
||||
<div>
|
||||
Português <Br className="w-8 shadow-md" />
|
||||
</div>
|
||||
<div>
|
||||
日本語 <Jp className="w-8 shadow-md" />
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export default Translations;
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Switch } from '@headlessui/react';
|
||||
|
||||
interface ToggleSwitchProps {
|
||||
active: boolean;
|
||||
toggle?: Function;
|
||||
@@ -13,20 +15,19 @@ const ToggleSwitch = (props: ToggleSwitchProps) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
setActive(!active);
|
||||
}}
|
||||
<Switch
|
||||
checked={active}
|
||||
onChange={setActive}
|
||||
className={`w-12 h-6 flex items-center bg-gray-300 rounded-full p-1 duration-300 ease-in-out my-auto ${
|
||||
active ? 'bg-green-400' : null
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
<span
|
||||
className={`bg-white w-4 h-4 rounded-full shadow-md transform duration-300 ease-in-out ${
|
||||
active ? 'translate-x-6' : null
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
></span>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
Arguments:
|
||||
/usr/bin/node /usr/share/yarn/bin/yarn.js add -D tailwindcss@latest postcss@latest autoprefixer@latest
|
||||
|
||||
PATH:
|
||||
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Program Files/AdoptOpenJDK/jdk-11.0.10.9-hotspot/bin:/mnt/c/Program Files (x86)/Common Files/Oracle/Java/javapath:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/Program Files (x86)/NVIDIA Corporation/PhysX/Common:/mnt/c/Program Files/Git/cmd:/mnt/c/Program Files/nodejs/:/mnt/c/Users/sacha/AppData/Local/Programs/Python/Python39/Scripts/:/mnt/c/Users/sacha/AppData/Local/Programs/Python/Python39/:/mnt/c/Users/sacha/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/sacha/AppData/Local/Programs/Microsoft VS Code/bin:/mnt/c/Users/sacha/AppData/Roaming/npm:/home/sachaw/.yarn/bin
|
||||
|
||||
Yarn version:
|
||||
1.22.5
|
||||
|
||||
Node version:
|
||||
15.12.0
|
||||
|
||||
Platform:
|
||||
linux x64
|
||||
|
||||
Trace:
|
||||
Error: getaddrinfo EAI_AGAIN registry.yarnpkg.com
|
||||
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:69:26)
|
||||
|
||||
npm manifest:
|
||||
{
|
||||
"scripts": {
|
||||
"start": "snowpack dev",
|
||||
"build": "snowpack build",
|
||||
"test": "web-test-runner \"src/**/*.test.tsx\"",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
|
||||
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@snowpack/plugin-dotenv": "^2.0.5",
|
||||
"@snowpack/plugin-react-refresh": "^2.4.0",
|
||||
"@snowpack/plugin-typescript": "^1.2.0",
|
||||
"@snowpack/web-test-runner-plugin": "^0.2.0",
|
||||
"@testing-library/react": "^11.0.0",
|
||||
"@types/chai": "^4.2.13",
|
||||
"@types/mocha": "^8.2.0",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/snowpack-env": "^2.3.2",
|
||||
"@web/test-runner": "^0.12.0",
|
||||
"chai": "^4.2.0",
|
||||
"prettier": "^2.0.5",
|
||||
"snowpack": "^3.0.1",
|
||||
"typescript": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
yarn manifest:
|
||||
No manifest
|
||||
|
||||
Lockfile:
|
||||
No lockfile
|
||||
@@ -223,10 +223,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.1.tgz#66d25f6441920bd5c2146ea27fd33995885452dd"
|
||||
integrity sha512-uikw2gKCmqnvjVxitecWfFLMOKyL9BTFcU4VM3hHj9OMwpkCr5Ke+MRMyY2/aQVmsYs4VTq7NCFX05MYwAHi3g==
|
||||
|
||||
"@meshtastic/meshtasticjs@^0.6.10":
|
||||
version "0.6.10"
|
||||
resolved "https://registry.yarnpkg.com/@meshtastic/meshtasticjs/-/meshtasticjs-0.6.10.tgz#ef8de850fb37e808302f5e6535e9f5963448c3b5"
|
||||
integrity sha512-WFGHmTr4L4PFPQzzpqamialLMX79zbX90jalHSCzVG/5wkZfd+6dqe5v+b3cvCTyhDiVmf6DKmRhmAS3iCLLpA==
|
||||
"@meshtastic/meshtasticjs@^0.6.12":
|
||||
version "0.6.12"
|
||||
resolved "https://registry.yarnpkg.com/@meshtastic/meshtasticjs/-/meshtasticjs-0.6.12.tgz#10368d67d983fdedae46fc176bc9da07bb67e7e7"
|
||||
integrity sha512-Ys8odPrmWaLHcyzRE8+iQT2TCMIWPQXldIkEc7ydmAZO3Hc5Ol1pOHHsd/EMh9AvM/pR5ZhbSX2j8q5hBhG6UQ==
|
||||
dependencies:
|
||||
"@protobuf-ts/runtime" "^2.0.0-alpha.21"
|
||||
rxjs "^6.6.7"
|
||||
@@ -374,10 +374,10 @@
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@^17.0.4":
|
||||
version "17.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.4.tgz#a67c6f7a460d2660e950d9ccc1c2f18525c28220"
|
||||
integrity sha512-onz2BqScSFMoTRdJUZUDD/7xrusM8hBA2Fktk2qgaTYPCgPvWnDEgkrOs8hhPUf2jfcIXkJ5yK6VfYormJS3Jw==
|
||||
"@types/react@^17.0.5":
|
||||
version "17.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.5.tgz#3d887570c4489011f75a3fc8f965bf87d09a1bea"
|
||||
integrity sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
@@ -2388,10 +2388,10 @@ postcss@^8.1.6, postcss@^8.2.1:
|
||||
nanoid "^3.1.22"
|
||||
source-map "^0.6.1"
|
||||
|
||||
postcss@^8.2.13:
|
||||
version "8.2.13"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.13.tgz#dbe043e26e3c068e45113b1ed6375d2d37e2129f"
|
||||
integrity sha512-FCE5xLH+hjbzRdpbRb1IMCvPv9yZx2QnDarBEYSN0N0HYk+TcXsEhwdFcFb+SRWOKzKGErhIEbBK2ogyLdTtfQ==
|
||||
postcss@^8.2.14:
|
||||
version "8.2.14"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.14.tgz#dcf313eb8247b3ce8078d048c0e8262ca565ad2b"
|
||||
integrity sha512-+jD0ZijcvyCqPQo/m/CW0UcARpdFylq04of+Q7RKX6f/Tu+dvpUI/9Sp81+i6/vJThnOBX09Quw0ZLOVwpzX3w==
|
||||
dependencies:
|
||||
colorette "^1.2.2"
|
||||
nanoid "^3.1.22"
|
||||
@@ -2627,6 +2627,13 @@ rxjs@^6.6.7:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.0.0.tgz#c55d67c52aee8804d32ab60965e335bd41e2dc2d"
|
||||
integrity sha512-I1V/ArAtGJg4kmCfms8fULm0SwYgEsAf2d5WPCBGzTYm2qTjO3Tx4EDFaGjbOox8CeEsC69jQK22mnmfyA26sw==
|
||||
dependencies:
|
||||
tslib "~2.1.0"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
@@ -2935,6 +2942,11 @@ tslib@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
|
||||
Reference in New Issue
Block a user