-
- {/* NAV ITEM */}
-
}
- text={'Dashboard'}
- />
- {/* END NAV ITEM */}
+
+
-
-
-
content
+
+
+
+
+ {route.name === 'messages' &&
}
+ {route.name === 'nodes' &&
}
+ {route.name === 'settings' &&
}
+ {route.name === 'about' &&
}
+ {route.name === false && 'Not Found'}
+
diff --git a/src/Main.tsx b/src/Main.tsx
deleted file mode 100644
index 8b9acb89..00000000
--- a/src/Main.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-
-import { useTranslation } from 'react-i18next';
-
-import type { Types } from '@meshtastic/meshtasticjs';
-
-import { ChatMessage } from './components/ChatMessage';
-import { MessageBox } from './components/MessageBox';
-import { Sidebar } from './components/Sidebar';
-import { connection } from './connection';
-
-export const Main = (): JSX.Element => {
- const [messages, setMessages] = React.useState<
- { message: Types.TextPacket; ack: boolean }[]
- >([]);
- const { t } = useTranslation();
-
- React.useEffect(() => {
- connection.onTextPacket.subscribe((message) => {
- setMessages((messages) => [
- ...messages,
- { message: message, ack: false },
- ]);
- });
- }, []);
-
- React.useEffect(() => {
- connection.onRoutingPacket.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;
- }),
- );
- });
- }, [messages]);
-
- return (
-
-
-
- {messages.length ? (
- messages.map((message, Main) => (
-
- ))
- ) : (
-
- {t('placeholder.no_messages')}
-
- )}
-
-
-
-
-
- );
-};
diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx
deleted file mode 100644
index 83e172c8..00000000
--- a/src/components/ChatMessage.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-
-import Avatar from 'boring-avatars';
-
-import {
- CheckCircleIcon,
- DotsCircleHorizontalIcon,
-} from '@heroicons/react/outline';
-import type { Types } from '@meshtastic/meshtasticjs';
-
-import { useAppSelector } from '../hooks/redux';
-
-interface ChatMessageProps {
- message: { message: Types.TextPacket; ack: boolean };
-}
-
-export const ChatMessage = (props: ChatMessageProps): JSX.Element => {
- const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
- const nodes = useAppSelector((state) => state.meshtastic.nodes);
-
- const node = nodes.find((node) => {
- return node.num === props.message.message.packet.from;
- });
-
- return (
-
-
-
- }
- >
-
-
-
{node?.user?.longName ?? 'UNK'}
-
-
-
- {new Date(
- props.message.message.packet.rxTime > 0
- ? props.message.message.packet.rxTime
- : Date.now(),
- ).toLocaleString()}
-
-
-
- {props.message.message.data}
- {node?.num === myNodeInfo.myNodeNum &&
- (props.message.ack ? (
-
- ) : (
-
- ))}
-
-
-
-
-
- );
-};
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
deleted file mode 100644
index 22498c2f..00000000
--- a/src/components/Header.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-
-import {
- DeviceMobileIcon,
- StatusOfflineIcon,
- StatusOnlineIcon,
-} from '@heroicons/react/outline';
-import { Types } from '@meshtastic/meshtasticjs';
-
-import { useAppSelector } from '../hooks/redux';
-import { Logo } from './Logo';
-
-export const Header = (): JSX.Element => {
- const deviceStatus = useAppSelector((state) => state.meshtastic.deviceStatus);
- const ready = useAppSelector((state) => state.meshtastic.ready);
- const lastMeshInterraction = useAppSelector(
- (state) => state.meshtastic.lastMeshInterraction,
- );
-
- return (
-
-
-
-
-
-
-
-
- {new Date(lastMeshInterraction) > new Date(Date.now() - 40000) ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
- );
-};
diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx
deleted file mode 100644
index eb31f6a9..00000000
--- a/src/components/Logo.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-
-export const Logo = (): JSX.Element => {
- return (
-
-
-
-
-
-
-
-
-
-
-
- ESHT
-
-
-
-
-
-
-
-
-
-
- ST
-
-
-
-
- C
-
-
-
-
-
- );
-};
diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx
deleted file mode 100644
index 0905a32e..00000000
--- a/src/components/MessageBox.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import React from 'react';
-
-import { motion } from 'framer-motion';
-import { useTranslation } from 'react-i18next';
-
-import { MenuIcon, PaperAirplaneIcon } from '@heroicons/react/outline';
-
-import { connection } from '../connection';
-import { useAppDispatch, useAppSelector } from '../hooks/redux';
-import { toggleSidebar } from '../slices/appSlice';
-
-export const MessageBox = (): JSX.Element => {
- const ready = useAppSelector((state) => state.meshtastic.ready);
- const [currentMessage, setCurrentMessage] = React.useState('');
- const sendMessage = () => {
- if (ready) {
- connection.sendText(currentMessage, undefined, true);
- setCurrentMessage('');
- }
- };
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
-
- return (
-
-
{
- dispatch(toggleSidebar());
- }}
- >
-
-
-
-
- );
-};
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
deleted file mode 100644
index a2af360f..00000000
--- a/src/components/Sidebar.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-
-import { AnimatePresence, motion } from 'framer-motion';
-
-import { useAppSelector } from '../hooks/redux';
-import { Channels } from './Sidebar/Channels/Index';
-import { Device } from './Sidebar/Device/Index';
-import { Nodes } from './Sidebar/Nodes/Index';
-import { UI } from './Sidebar/UI/Index';
-
-export const Sidebar = (): JSX.Element => {
- const sidebarOpen = useAppSelector((state) => state.app.sidebarOpen);
-
- return (
-
- {sidebarOpen && (
-
-
-
-
-
-
-
- )}
-
- );
-};
diff --git a/src/components/Sidebar/Channels/Channel.tsx b/src/components/Sidebar/Channels/Channel.tsx
deleted file mode 100644
index 3ee9366c..00000000
--- a/src/components/Sidebar/Channels/Channel.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import React from 'react';
-
-import { Disclosure } from '@headlessui/react';
-import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline';
-import { Protobuf } from '@meshtastic/meshtasticjs';
-
-export interface ChannelProps {
- channel: Protobuf.Channel;
-}
-
-export const Channel = (props: ChannelProps): JSX.Element => {
- return (
-
- {({ open }) => (
- <>
-
-
- {open ? (
-
- ) : (
-
- )}
- {props.channel.index} -{' '}
- {Protobuf.Channel_Role[props.channel.role]}
-
-
-
-
-
-
Bandwidth:
-
- {props.channel.settings?.bandwidth}
-
-
-
-
-
Channel Number:
-
- {props.channel.settings?.channelNum}
-
-
-
-
-
Coding Rate:
-
- {props.channel.settings?.codingRate}
-
-
-
-
-
ID:
-
- {props.channel.settings?.id}
-
-
-
-
-
Modem Config:
-
- {props.channel.settings?.modemConfig
- ? Protobuf.ChannelSettings_ModemConfig[
- props.channel.settings.modemConfig
- ]
- : null}
-
-
-
-
-
Name:
-
- {props.channel.settings?.name}
-
-
-
-
-
PSK:
-
- {props.channel.settings?.psk.toLocaleString()}
-
-
-
-
-
Spread Factor:
-
- {props.channel.settings?.spreadFactor}
-
-
-
-
-
Tx Power:
-
- {props.channel.settings?.txPower}
-
-
-
-
-
Uplink:
-
- {props.channel.settings?.uplinkEnabled ? 'true' : 'false'}
-
-
-
-
Downlink:
-
- {props.channel.settings?.downlinkEnabled ? 'true' : 'false'}
-
-
-
-
- >
- )}
-
- );
-};
diff --git a/src/components/Sidebar/Channels/ChannelList.tsx b/src/components/Sidebar/Channels/ChannelList.tsx
deleted file mode 100644
index 5782336d..00000000
--- a/src/components/Sidebar/Channels/ChannelList.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-
-import { Protobuf } from '@meshtastic/meshtasticjs';
-
-import { useAppSelector } from '../../../hooks/redux';
-import { Channel } from './Channel';
-
-export const ChannelList = (): JSX.Element => {
- const channels = useAppSelector((state) => state.meshtastic.channels);
-
- return (
- <>
- {channels.map((channel, index) => {
- if (channel.role !== Protobuf.Channel_Role.DISABLED)
- return
;
- })}
- >
- );
-};
diff --git a/src/components/Sidebar/Channels/Index.tsx b/src/components/Sidebar/Channels/Index.tsx
deleted file mode 100644
index 05521d9a..00000000
--- a/src/components/Sidebar/Channels/Index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import { useTranslation } from 'react-i18next';
-
-import { HashtagIcon } from '@heroicons/react/outline';
-
-import { Dropdown } from '../../basic/Dropdown';
-import { ChannelList } from './ChannelList';
-
-export const Channels = (): JSX.Element => {
- const { t } = useTranslation();
- return (
-
}
- title={t('settings.channel')}
- content={
}
- fallbackMessage={'Loading...'}
- />
- );
-};
diff --git a/src/components/Sidebar/Device/Index.tsx b/src/components/Sidebar/Device/Index.tsx
deleted file mode 100644
index 75927fee..00000000
--- a/src/components/Sidebar/Device/Index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import { useTranslation } from 'react-i18next';
-
-import { AdjustmentsIcon } from '@heroicons/react/outline';
-
-import { Dropdown } from '../../basic/Dropdown';
-import { Settings } from './Settings';
-
-export const Device = (): JSX.Element => {
- const { t } = useTranslation();
- return (
-
}
- title={t('settings.device')}
- content={
}
- fallbackMessage={'Loading...'}
- />
- );
-};
diff --git a/src/components/Sidebar/Nodes/Index.tsx b/src/components/Sidebar/Nodes/Index.tsx
deleted file mode 100644
index 82dd606a..00000000
--- a/src/components/Sidebar/Nodes/Index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import { useTranslation } from 'react-i18next';
-
-import { UsersIcon } from '@heroicons/react/outline';
-
-import { Dropdown } from '../../basic/Dropdown';
-import { NodeList } from './NodeList';
-
-export const Nodes = (): JSX.Element => {
- const { t } = useTranslation();
- return (
-
}
- title={t('strings.nodes')}
- content={
}
- fallbackMessage={t('placeholder.no_messages')}
- />
- );
-};
diff --git a/src/components/Sidebar/Nodes/Node.tsx b/src/components/Sidebar/Nodes/Node.tsx
deleted file mode 100644
index d740ecb2..00000000
--- a/src/components/Sidebar/Nodes/Node.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from 'react';
-
-import Avatar from 'boring-avatars';
-
-import { Disclosure } from '@headlessui/react';
-import {
- ChevronDownIcon,
- ChevronRightIcon,
- ClockIcon,
- FlagIcon,
- GlobeIcon,
- LightningBoltIcon,
-} from '@heroicons/react/outline';
-import type { Protobuf } from '@meshtastic/meshtasticjs';
-
-import { useAppSelector } from '../../../hooks/redux';
-
-export interface NodeProps {
- node: Protobuf.NodeInfo;
-}
-
-export const Node = (props: NodeProps): JSX.Element => {
- const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
-
- return (
-
- {({ open }) => (
- <>
-
-
- {open ? (
-
- ) : (
-
- )}
-
- {props.node.num === myNodeInfo.myNodeNum ? (
-
- ) : null}
-
-
- {props.node.user?.longName}
-
-
-
-
-
{props.node.snr}
-
- {`Last heard: ${
- props.node?.lastHeard
- ? new Date(props.node.lastHeard).toLocaleString()
- : 'Unknown'
- }`}{' '}
- {}
-
-
-
-
- {props.node.position?.latitudeI &&
- props.node.position?.longitudeI
- ? `${props.node.position.latitudeI / 1e7},
- ${props.node.position.longitudeI / 1e7}`
- : 'Unknown'}
- , El:
- {props.node.position?.altitude}
-
-
-
-
-
-
{props.node.position?.time}
-
-
-
-
{props.node.position?.batteryLevel}
-
-
-
- >
- )}
-
- );
-};
diff --git a/src/components/Sidebar/Nodes/NodeList.tsx b/src/components/Sidebar/Nodes/NodeList.tsx
deleted file mode 100644
index 30f72002..00000000
--- a/src/components/Sidebar/Nodes/NodeList.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-
-import { useAppSelector } from '../../../hooks/redux';
-import { Node } from './Node';
-
-export const NodeList = (): JSX.Element => {
- const nodes = useAppSelector((state) => state.meshtastic.nodes);
-
- return (
- <>
- {nodes.map((node, index) => (
-
- ))}
- >
- );
-};
diff --git a/src/components/Sidebar/UI/Index.tsx b/src/components/Sidebar/UI/Index.tsx
deleted file mode 100644
index a9042121..00000000
--- a/src/components/Sidebar/UI/Index.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-
-import { useTranslation } from 'react-i18next';
-
-import { CogIcon } from '@heroicons/react/outline';
-
-import { Dropdown } from '../../basic/Dropdown';
-import { Translations } from './Translations';
-
-export const UI = (): JSX.Element => {
- const { t } = useTranslation();
-
- return (
-
}
- title={t('settings.ui')}
- content={
}
- fallbackMessage={'Loading...'}
- />
- );
-};
diff --git a/src/components/Sidebar/UI/Translations.tsx b/src/components/Sidebar/UI/Translations.tsx
deleted file mode 100644
index 441e0791..00000000
--- a/src/components/Sidebar/UI/Translations.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-
-import { Br, Jp, Us } from 'react-flags-select';
-import { useTranslation } from 'react-i18next';
-
-import { Disclosure } from '@headlessui/react';
-import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline';
-
-export const Translations = (): JSX.Element => {
- const { t } = useTranslation();
-
- return (
-
- {({ open }) => (
- <>
-
-
- {open ? (
-
- ) : (
-
- )}
- {t('strings.language')}
- {/*
- {language === LanguageEnum.ENGLISH ? (
-
- ) : language === LanguageEnum.JAPANESE ? (
-
- ) : language === LanguageEnum.PORTUGUESE ? (
-
- ) : null}
-
*/}
-
-
-
- {
- // setLanguage(LanguageEnum.ENGLISH);
- }}
- >
- English
-
- {
- // setLanguage(LanguageEnum.PORTUGUESE);
- }}
- >
- Português
-
- {
- // setLanguage(LanguageEnum.JAPANESE);
- }}
- >
- 日本語
-
-
- >
- )}
-
- );
-};
diff --git a/src/components/Tmp.tsx b/src/components/Tmp.tsx
deleted file mode 100644
index c3a0aa32..00000000
--- a/src/components/Tmp.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import React from 'react';
-
-import { HomeIcon, MenuIcon } from '@heroicons/react/outline';
-
-export const Tmp = () => {
- return (
-
-
-
-
- {/* NORMAL NAV ICON */}
-
-
-
-
- {/* END NORMAL NAV ICON */}
- {/* MOBILE NAV BUTTON */}
-
-
-
-
-
- {/* END MOBILE NAV BUTTON */}
-
- {/* HEADER BUTTON */}
-
-
-
-
-
-
-
- {/* END HEADER BUTTON */}
-
-
-
-
- {/* NAV ITEM */}
-
-
- Dashboard
-
- {/* END NAV ITEM */}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {' '}
- Confirmation Dialog{' '}
-
-
-
-
-
-
-
-
- {' '}
- One of the repetitive and tedious jobs in Angular is to
- create confirmation dialogs. Since dialogs require their own
- component you have to either create a separate component
- every time you need a confirmation dialog or you have to
- create your own confirmation dialog system that can be
- configured.{' '}
-
-
- {' '}
- In order for you to save time while developing with Fuse, we
- have created a simple yet powerful{' '}
- FuseConfirmationService to create customized
- confirmation dialogs on-the-fly.{' '}
-
-
- {' '}
- Below you can configure and preview the confirmation dialog.
- You can use the generated configuration object within your
- code to have the exact same dialog.{' '}
-
-
- {' '}
- For more detailed information and API documentation, check
- the{' '}
-
- documentation
- {' '}
- page.{' '}
-
-
-
-
-
-
-
-
- );
-};
diff --git a/src/components/basic/Dropdown.tsx b/src/components/basic/Dropdown.tsx
deleted file mode 100644
index 9c294841..00000000
--- a/src/components/basic/Dropdown.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-
-import { AnimatePresence, motion } from 'framer-motion';
-
-import { Disclosure } from '@headlessui/react';
-import { ChevronDownIcon } from '@heroicons/react/outline';
-
-interface DropdownProps {
- icon: JSX.Element;
- title: string;
- content: JSX.Element;
- fallbackMessage: string;
-}
-
-export const Dropdown = (props: DropdownProps): JSX.Element => {
- return (
-
- {({ open }) => (
- <>
-
-
-
-
-
- {props.icon}
- {props.title}
-
-
-
-
- {open && (
-
-
-
- {props.fallbackMessage}
-
-
- }
- >
- {props.content}
-
-
- )}
-
- >
- )}
-
- );
-};
diff --git a/src/components/basic/ToggleSwitch.tsx b/src/components/basic/ToggleSwitch.tsx
deleted file mode 100644
index c649842c..00000000
--- a/src/components/basic/ToggleSwitch.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-
-import { Switch } from '@headlessui/react';
-
-interface ToggleSwitchProps {
- active: boolean;
-}
-
-export const ToggleSwitch = (props: ToggleSwitchProps): JSX.Element => {
- const [active, setActive] = React.useState(false);
-
- React.useEffect(() => {
- setActive(props.active);
- }, []);
-
- return (
-
-
-
- );
-};
diff --git a/src/components/chat/Message.tsx b/src/components/chat/Message.tsx
new file mode 100644
index 00000000..caa23315
--- /dev/null
+++ b/src/components/chat/Message.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+
+import Avatar from 'boring-avatars';
+
+export interface MessageProps {
+ message: string;
+ ack: boolean;
+ isSender: boolean;
+ rxTime: Date;
+}
+
+export const Message = ({
+ message,
+ ack,
+ isSender,
+ rxTime,
+}: MessageProps): JSX.Element => {
+ return (
+
+
+
+
+ {rxTime.getHours()}:{rxTime.getMinutes()}
+
+
+ );
+};
diff --git a/src/components/chat/MessageBar.tsx b/src/components/chat/MessageBar.tsx
new file mode 100644
index 00000000..aace3545
--- /dev/null
+++ b/src/components/chat/MessageBar.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+import {
+ EmojiHappyIcon,
+ PaperAirplaneIcon,
+ PaperClipIcon,
+} from '@heroicons/react/outline';
+
+import { connection } from '../../connection';
+import { useAppSelector } from '../../hooks/redux';
+import { Button } from '../generic/Button';
+
+export const MessageBar = (): JSX.Element => {
+ const ready = useAppSelector((state) => state.meshtastic.ready);
+ const [currentMessage, setCurrentMessage] = React.useState('');
+ const sendMessage = () => {
+ if (ready) {
+ connection.sendText(currentMessage, undefined, true);
+ setCurrentMessage('');
+ }
+ };
+ const { t } = useTranslation();
+ return (
+
+ );
+};
diff --git a/src/components/form/Input.tsx b/src/components/form/Input.tsx
new file mode 100644
index 00000000..fa8cff40
--- /dev/null
+++ b/src/components/form/Input.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+
+export interface InputProps {
+ valid?: boolean;
+ placeholder?: string;
+ validationMessage?: string;
+ icon?: JSX.Element;
+ type: string;
+ name: string;
+ value?: string;
+}
+
+export const Input = ({
+ valid,
+ placeholder,
+ validationMessage,
+ icon,
+ type,
+ name,
+ value,
+}: InputProps): JSX.Element => {
+ return (
+
+
+ {name}
+
+
+ {icon && (
+
+ {React.cloneElement(icon, {
+ className: 'w-5 h-5 text-gray-500 dark:text-gray-600',
+ })}
+
+ )}
+
+
+ {!valid && (
+
{validationMessage}
+ )}
+
+ );
+};
diff --git a/src/components/generic/Button.tsx b/src/components/generic/Button.tsx
new file mode 100644
index 00000000..db847c6e
--- /dev/null
+++ b/src/components/generic/Button.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+
+export interface ButtonProps {
+ children: React.ReactNode;
+ className?: string;
+ clickAction?: () => void;
+ type?: 'button' | 'submit' | 'reset' | undefined;
+}
+
+export const Button = ({
+ children,
+ className,
+ clickAction,
+ type,
+}: ButtonProps): JSX.Element => {
+ return (
+
{
+ if (clickAction) {
+ clickAction();
+ }
+ }}
+ type={type}
+ >
+ {children}
+
+ );
+};
diff --git a/src/components/menu/Logo.tsx b/src/components/menu/Logo.tsx
new file mode 100644
index 00000000..450d3f49
--- /dev/null
+++ b/src/components/menu/Logo.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+
+export const Logo = (): JSX.Element => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/menu/MenuButton.tsx b/src/components/menu/MenuButton.tsx
new file mode 100644
index 00000000..9e8ab580
--- /dev/null
+++ b/src/components/menu/MenuButton.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import type { Link } from 'type-route';
+
+interface MenuButtonProps {
+ icon: JSX.Element;
+ text: string;
+ link: Link;
+ clickAction?: () => void;
+}
+
+export const MenuButton = ({
+ icon,
+ text,
+ link,
+ clickAction,
+}: MenuButtonProps): JSX.Element => {
+ return (
+
+ );
+};
diff --git a/src/components/menu/MobileNav.tsx b/src/components/menu/MobileNav.tsx
new file mode 100644
index 00000000..2869d63f
--- /dev/null
+++ b/src/components/menu/MobileNav.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+
+import { Dialog } from '@headlessui/react';
+import {
+ AnnotationIcon,
+ CogIcon,
+ InformationCircleIcon,
+ ViewGridIcon,
+ XCircleIcon,
+} from '@heroicons/react/outline';
+
+import { useAppDispatch, useAppSelector } from '../../hooks/redux';
+import { routes } from '../../router';
+import { closeMobileNav } from '../../slices/appSlice';
+import { Button } from '../generic/Button';
+import { MenuButton } from './MenuButton';
+
+export const MobileNav = (): JSX.Element => {
+ const dispatch = useAppDispatch();
+
+ const mobileNavOpen = useAppSelector((state) => state.app.mobileNavOpen);
+
+ return (
+
dispatch(closeMobileNav())}
+ className="flex fixed inset-0 z-10 overflow-y-auto"
+ >
+
+
+
+
{
+ dispatch(closeMobileNav());
+ }}
+ >
+
+
+
+ }
+ text={'Messages'}
+ link={routes.messages().link}
+ clickAction={() => {
+ dispatch(closeMobileNav());
+ }}
+ />
+ }
+ text={'Nodes'}
+ link={routes.nodes().link}
+ clickAction={() => {
+ dispatch(closeMobileNav());
+ }}
+ />
+ }
+ text={'Settings'}
+ link={routes.settings().link}
+ clickAction={() => {
+ dispatch(closeMobileNav());
+ }}
+ />
+ }
+ text={'About'}
+ link={routes.about().link}
+ clickAction={() => {
+ dispatch(closeMobileNav());
+ }}
+ />
+
+
+
+ );
+};
diff --git a/src/components/menu/Navigation.tsx b/src/components/menu/Navigation.tsx
new file mode 100644
index 00000000..325f869b
--- /dev/null
+++ b/src/components/menu/Navigation.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+
+import {
+ AnnotationIcon,
+ CogIcon,
+ InformationCircleIcon,
+ ViewGridIcon,
+} from '@heroicons/react/outline';
+
+import { routes } from '../../router';
+import { MenuButton } from './MenuButton';
+
+export const Navigation = (): JSX.Element => {
+ return (
+
+
+ }
+ text={'Messages'}
+ link={routes.messages().link}
+ />
+ }
+ text={'Nodes'}
+ link={routes.nodes().link}
+ />
+ }
+ text={'Settings'}
+ link={routes.settings().link}
+ />
+ }
+ text={'About'}
+ link={routes.about().link}
+ />
+
+
+ );
+};
diff --git a/src/components/menu/buttons/DeviceStatusDropdown.tsx b/src/components/menu/buttons/DeviceStatusDropdown.tsx
new file mode 100644
index 00000000..7546cb60
--- /dev/null
+++ b/src/components/menu/buttons/DeviceStatusDropdown.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+import { Types } from '@meshtastic/meshtasticjs';
+
+import { useAppSelector } from '../../../hooks/redux';
+import { Button } from '../../generic/Button';
+
+export const DeviceStatusDropdown = (): JSX.Element => {
+ const ready = useAppSelector((state) => state.meshtastic.ready);
+ const deviceStatus = useAppSelector((state) => state.meshtastic.deviceStatus);
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/menu/buttons/LanguageDropdown.tsx b/src/components/menu/buttons/LanguageDropdown.tsx
new file mode 100644
index 00000000..11b27d20
--- /dev/null
+++ b/src/components/menu/buttons/LanguageDropdown.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+
+import { Jp, Pt, Us } from 'react-flags-select';
+
+import { Menu } from '@headlessui/react';
+
+import { useAppDispatch } from '../../../hooks/redux';
+import i18n from '../../../translation';
+import { Button } from '../../generic/Button';
+
+export const LanguageDropdown = (): JSX.Element => {
+ const dispatch = useAppDispatch();
+
+ const languages = [
+ {
+ name: 'English',
+ value: 'en',
+ flag:
,
+ },
+ {
+ name: 'Português',
+ value: 'pt',
+ flag:
,
+ },
+ {
+ name: 'Japanese',
+ value: 'jp',
+ flag:
,
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+ {languages.map((language, index) => (
+ {
+ i18n.changeLanguage(language.value);
+ }}
+ >
+ {({ active }) => (
+
+ {language.flag}
+ {language.name}
+
+ )}
+
+ ))}
+ {/* ... */}
+
+
+
+ );
+};
diff --git a/src/components/menu/buttons/MobileNavToggle.tsx b/src/components/menu/buttons/MobileNavToggle.tsx
new file mode 100644
index 00000000..e6faef5a
--- /dev/null
+++ b/src/components/menu/buttons/MobileNavToggle.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+import { MenuIcon } from '@heroicons/react/outline';
+
+import { useAppDispatch } from '../../../hooks/redux';
+import { openMobileNav } from '../../../slices/appSlice';
+import { Button } from '../../generic/Button';
+
+export const MobileNavToggle = (): JSX.Element => {
+ const dispatch = useAppDispatch();
+
+ return (
+
{
+ dispatch(openMobileNav());
+ }}
+ className="md:hidden"
+ >
+
+
+ );
+};
diff --git a/src/components/menu/buttons/ThemeToggle.tsx b/src/components/menu/buttons/ThemeToggle.tsx
new file mode 100644
index 00000000..f8796b9a
--- /dev/null
+++ b/src/components/menu/buttons/ThemeToggle.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import { MoonIcon, SunIcon } from '@heroicons/react/outline';
+
+import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
+import { setDarkModeEnabled } from '../../../slices/appSlice';
+import { Button } from '../../generic/Button';
+
+export const ThemeToggle = (): JSX.Element => {
+ const dispatch = useAppDispatch();
+ const darkMode = useAppSelector((state) => state.app.darkMode);
+
+ return (
+
{
+ dispatch(setDarkModeEnabled(!darkMode));
+ }}
+ >
+ {darkMode ? (
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/src/components/nav/NavItem.tsx b/src/components/nav/NavItem.tsx
deleted file mode 100644
index 0acc848a..00000000
--- a/src/components/nav/NavItem.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-
-interface NavItemProps {
- icon: JSX.Element;
- text: string;
-}
-
-export const NavItem = ({ icon, text }: NavItemProps) => {
- return (
-
- {icon}
- {text}
-
- );
-};
diff --git a/src/components/templates/PrimaryTemplate.tsx b/src/components/templates/PrimaryTemplate.tsx
new file mode 100644
index 00000000..28bc1d19
--- /dev/null
+++ b/src/components/templates/PrimaryTemplate.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+export interface PrimaryTemplateProps {
+ children: React.ReactNode;
+ title: string;
+ tagline: string;
+}
+
+export const PrimaryTemplate = ({
+ children,
+ title,
+ tagline,
+}: PrimaryTemplateProps): JSX.Element => {
+ return (
+
+ );
+};
diff --git a/src/index.tsx b/src/index.tsx
index 9c306f44..99b2a251 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -7,15 +7,16 @@ import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
-// import { Tmp } from './components/Tmp';
+import { RouteProvider } from './router';
import { store } from './store';
ReactDOM.render(
-
-
- {/* */}
-
+
+
+
+
+
,
document.getElementById('root'),
);
diff --git a/src/pages/About.tsx b/src/pages/About.tsx
new file mode 100644
index 00000000..0af0d231
--- /dev/null
+++ b/src/pages/About.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
+
+export const About = (): JSX.Element => {
+ return (
+
+ Content
+
+ );
+};
diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx
new file mode 100644
index 00000000..d88d823a
--- /dev/null
+++ b/src/pages/Messages.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import { Message } from '../components/chat/Message';
+import { MessageBar } from '../components/chat/MessageBar';
+import { useAppSelector } from '../hooks/redux';
+
+export const Messages = (): JSX.Element => {
+ const messages = useAppSelector((state) => state.meshtastic.messages);
+
+ return (
+
+
+ {messages.map((message, index) => (
+
+ ))}
+
+
+
+ );
+};
diff --git a/src/pages/Nodes.tsx b/src/pages/Nodes.tsx
new file mode 100644
index 00000000..16f9d9cb
--- /dev/null
+++ b/src/pages/Nodes.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
+
+export const Nodes = (): JSX.Element => {
+ return (
+
+ Nodes
+
+ );
+};
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
new file mode 100644
index 00000000..039e5269
--- /dev/null
+++ b/src/pages/Settings.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+import { Input } from '../components/form/Input';
+import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
+import { useAppSelector } from '../hooks/redux';
+
+export const Settings = (): JSX.Element => {
+ const { t } = useTranslation();
+ const radioConfig = useAppSelector((state) => state.meshtastic.preferences);
+
+ console.log(radioConfig);
+
+ return (
+
+
+
+ );
+};
diff --git a/src/router.ts b/src/router.ts
new file mode 100644
index 00000000..6ac63051
--- /dev/null
+++ b/src/router.ts
@@ -0,0 +1,8 @@
+import { createRouter, defineRoute } from 'type-route';
+
+export const { RouteProvider, useRoute, routes } = createRouter({
+ messages: defineRoute('/'),
+ nodes: defineRoute('/nodes'),
+ settings: defineRoute('/settings'),
+ about: defineRoute('/about'),
+});
diff --git a/src/slices/appSlice.ts b/src/slices/appSlice.ts
index 45153d0f..4fec2bb1 100644
--- a/src/slices/appSlice.ts
+++ b/src/slices/appSlice.ts
@@ -1,31 +1,44 @@
+import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
+export type currentPageName = 'messages' | 'settings';
+
interface AppState {
- sidebarOpen: boolean;
+ mobileNavOpen: boolean;
darkMode: boolean;
+ currentPage: currentPageName;
}
const initialState: AppState = {
- sidebarOpen: true,
+ mobileNavOpen: false,
darkMode: false,
+ currentPage: 'messages',
};
export const appSlice = createSlice({
name: 'app',
initialState,
reducers: {
- openSidebar(state) {
- state.sidebarOpen = true;
+ openMobileNav(state) {
+ state.mobileNavOpen = true;
},
- closeSidebar(state) {
- state.sidebarOpen = false;
+ closeMobileNav(state) {
+ state.mobileNavOpen = false;
},
- toggleSidebar(state) {
- state.sidebarOpen = !state.sidebarOpen;
+ setDarkModeEnabled(state, action: PayloadAction
) {
+ state.darkMode = action.payload;
+ },
+ setCurrentPage(state, action: PayloadAction) {
+ state.currentPage = action.payload;
},
},
});
-export const { openSidebar, closeSidebar, toggleSidebar } = appSlice.actions;
+export const {
+ openMobileNav,
+ closeMobileNav,
+ setDarkModeEnabled,
+ setCurrentPage,
+} = appSlice.actions;
export default appSlice.reducer;
diff --git a/src/slices/meshtasticSlice.ts b/src/slices/meshtasticSlice.ts
index 3eba4d0c..d0ba31e5 100644
--- a/src/slices/meshtasticSlice.ts
+++ b/src/slices/meshtasticSlice.ts
@@ -2,40 +2,35 @@ import { Protobuf, Types } from '@meshtastic/meshtasticjs';
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
+export interface MessageWithAck {
+ message: Types.TextPacket;
+ ack: boolean;
+ isSender: boolean;
+ received: Date;
+}
+
interface AppState {
deviceStatus: Types.DeviceStatusEnum;
lastMeshInterraction: number;
ready: boolean;
- fromRaioPackets: Protobuf.FromRadio[];
- meshPackets: Protobuf.MeshPacket[];
myNodeInfo: Protobuf.MyNodeInfo;
- radioConfig: Protobuf.RadioConfig[];
- routingPackets: Types.RoutingPacket[];
positionPackets: Types.PositionPacket[];
- textPackets: Types.TextPacket[];
- logRecords: Protobuf.LogRecord[];
- //
nodes: Protobuf.NodeInfo[];
channels: Protobuf.Channel[];
preferences: Protobuf.RadioConfig_UserPreferences;
+ messages: MessageWithAck[];
}
const initialState: AppState = {
deviceStatus: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
lastMeshInterraction: 0,
ready: false,
- fromRaioPackets: [],
- meshPackets: [],
myNodeInfo: Protobuf.MyNodeInfo.create(),
- radioConfig: [],
- routingPackets: [],
positionPackets: [],
- textPackets: [],
- logRecords: [],
- //
nodes: [],
channels: [],
preferences: Protobuf.RadioConfig_UserPreferences.create(),
+ messages: [],
};
export const meshtasticSlice = createSlice({
@@ -51,31 +46,12 @@ export const meshtasticSlice = createSlice({
setReady: (state, action: PayloadAction) => {
state.ready = action.payload;
},
- addFromRadioPacket: (state, action: PayloadAction) => {
- state.fromRaioPackets.push(action.payload);
- },
- addMeshPacket: (state, action: PayloadAction) => {
- state.meshPackets.push(action.payload);
- },
setMyNodeInfo: (state, action: PayloadAction) => {
state.myNodeInfo = action.payload;
},
- addRadioConfig: (state, action: PayloadAction) => {
- state.radioConfig.push(action.payload);
- },
- addRoutingPacket: (state, action: PayloadAction) => {
- state.routingPackets.push(action.payload);
- },
addPositionPacket: (state, action: PayloadAction) => {
state.positionPackets.push(action.payload);
},
- addTextPacket: (state, action: PayloadAction) => {
- state.textPackets.push(action.payload);
- },
- addLogRecord: (state, action: PayloadAction) => {
- state.logRecords.push(action.payload);
- },
- //
addNode: (state, action: PayloadAction) => {
if (
state.nodes.findIndex((node) => node.num === action.payload.num) !== -1
@@ -109,6 +85,16 @@ export const meshtasticSlice = createSlice({
) => {
state.preferences = action.payload;
},
+ addMessage: (state, action: PayloadAction) => {
+ state.messages.push(action.payload);
+ },
+ ackMessage: (state, messageId: PayloadAction) => {
+ state.messages.map((message) => {
+ if (message.message.packet.id === messageId.payload) {
+ message.ack = true;
+ }
+ });
+ },
},
});
@@ -116,17 +102,13 @@ export const {
setDeviceStatus,
setLastMeshInterraction,
setReady,
- addFromRadioPacket,
- addMeshPacket,
setMyNodeInfo,
- addRadioConfig,
- addRoutingPacket,
addPositionPacket,
- addTextPacket,
- addLogRecord,
addNode,
addChannel,
setPreferences,
+ addMessage,
+ ackMessage,
} = meshtasticSlice.actions;
export default meshtasticSlice.reducer;
diff --git a/src/translation.ts b/src/translation.ts
index 6a44d2db..e32cf7e8 100644
--- a/src/translation.ts
+++ b/src/translation.ts
@@ -2,7 +2,9 @@ import i18n from 'i18next';
import detector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
-import en from './translations/en.json';
+import { en } from './translations/en';
+import { jp } from './translations/jp';
+import { pt } from './translations/pt';
i18n
.use(detector)
@@ -10,9 +12,9 @@ i18n
.init({
fallbackLng: 'en',
resources: {
- en: {
- translation: en,
- },
+ en: { translation: en },
+ jp: { translation: jp },
+ pt: { translation: pt },
},
});
diff --git a/src/translations/en.json b/src/translations/en.json
deleted file mode 100644
index 7e7df9f1..00000000
--- a/src/translations/en.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "errors": {},
- "placeholder": {
- "message": "Enter Message",
- "no_messages": "No messages yet",
- "no_nodes": "No nodes found"
- },
- "strings": {
- "nodes": "Nodes",
- "color_scheme": "Color scheme",
- "language": "Language",
- "device_region": "Device region",
- "wifi_ssid": "WiFi SSID",
- "wifi_psk": "WiFi PSK",
- "save_changes": "Save changes"
- },
- "settings": {
- "ui": "UI Settings",
- "device": "Device Settings",
- "channel": "Channels"
- }
-}
diff --git a/src/translations/en.ts b/src/translations/en.ts
new file mode 100644
index 00000000..5ccacf56
--- /dev/null
+++ b/src/translations/en.ts
@@ -0,0 +1,22 @@
+export const en = {
+ errors: {},
+ placeholder: {
+ message: 'Enter Message',
+ no_messages: 'No messages yet',
+ no_nodes: 'No nodes found',
+ },
+ strings: {
+ nodes: 'Nodes',
+ color_scheme: 'Color scheme',
+ language: 'Language',
+ device_region: 'Device region',
+ wifi_ssid: 'WiFi SSID',
+ wifi_psk: 'WiFi PSK',
+ save_changes: 'Save changes',
+ },
+ settings: {
+ ui: 'UI Settings',
+ device: 'Device Settings',
+ channel: 'Channels',
+ },
+};
diff --git a/src/translations/jp.json b/src/translations/jp.json
deleted file mode 100644
index c5e5133b..00000000
--- a/src/translations/jp.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "errors": {},
- "placeholder": {
- "message": "メッセージを入力してください",
- "no_messages": "まだメッセージはありません",
- "no_nodes": "ノードが見つかりません"
- },
- "strings": {
- "nodes": "ノード",
- "color_scheme": "カラースキーム",
- "language": "言語",
- "device_region": "デバイスリージョン",
- "wifi_ssid": "WiFi名",
- "wifi_psk": "WiFiパスワード",
- "save_changes": "変更内容を保存"
- },
- "settings": {
- "ui": "UI設定",
- "device": "デバイスの設定",
- "channel": "#################"
- }
-}
diff --git a/src/translations/jp.ts b/src/translations/jp.ts
new file mode 100644
index 00000000..fd431f06
--- /dev/null
+++ b/src/translations/jp.ts
@@ -0,0 +1,22 @@
+export const jp = {
+ errors: {},
+ placeholder: {
+ message: 'メッセージを入力してください',
+ no_messages: 'まだメッセージはありません',
+ no_nodes: 'ノードが見つかりません',
+ },
+ strings: {
+ nodes: 'ノード',
+ color_scheme: 'カラースキーム',
+ language: '言語',
+ device_region: 'デバイスリージョン',
+ wifi_ssid: 'WiFi名',
+ wifi_psk: 'WiFiパスワード',
+ save_changes: '変更内容を保存',
+ },
+ settings: {
+ ui: 'UI設定',
+ device: 'デバイスの設定',
+ channel: '#################',
+ },
+};
diff --git a/src/translations/pt.json b/src/translations/pt.json
deleted file mode 100644
index 6161c29f..00000000
--- a/src/translations/pt.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "errors": {},
- "placeholder": {
- "message": "Entre mensagem",
- "no_messages": "Não a mensagens ainda",
- "no_nodes": "Nenhum nó foi encontrado"
- },
- "strings": {
- "nodes": "Nós",
- "color_scheme": "Esquema de cores",
- "language": "Idioma",
- "device_region": "Região do dispositivo",
- "wifi_ssid": "Nome do WiFi",
- "wifi_psk": "Senha do WiFi",
- "save_changes": "Salvar alterações"
- },
- "settings": {
- "ui": "Configurações da Interface",
- "device": "Configurações do dispositivo",
- "channel": "Canais"
- }
-}
diff --git a/src/translations/pt.ts b/src/translations/pt.ts
new file mode 100644
index 00000000..b1af305b
--- /dev/null
+++ b/src/translations/pt.ts
@@ -0,0 +1,22 @@
+export const pt = {
+ errors: {},
+ placeholder: {
+ message: 'Entre mensagem',
+ no_messages: 'Não a mensagens ainda',
+ no_nodes: 'Nenhum nó foi encontrado',
+ },
+ strings: {
+ nodes: 'Nós',
+ color_scheme: 'Esquema de cores',
+ language: 'Idioma',
+ device_region: 'Região do dispositivo',
+ wifi_ssid: 'Nome do WiFi',
+ wifi_psk: 'Senha do WiFi',
+ save_changes: 'Salvar alterações',
+ },
+ settings: {
+ ui: 'Configurações da Interface',
+ device: 'Configurações do dispositivo',
+ channel: 'Canais',
+ },
+};
diff --git a/tailwind.config.js b/tailwind.config.js
index 7f666bc3..c490f0c3 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,7 +1,7 @@
module.exports = {
mode: 'jit',
purge: ['./public/**/*.html', './src/**/*.tsx'],
- darkMode: false, // or 'media' or 'class'
+ darkMode: 'class', // or 'media' or 'class'
theme: {
fontFamily: {
sans: 'Inter var',
@@ -10,6 +10,8 @@ module.exports = {
extend: {
colors: {
primary: '#67ea94',
+ primaryDark: '#1E293B',
+ secondaryDark: '#0F172A',
},
},
},
diff --git a/yarn.lock b/yarn.lock
index ceded10c..b241c896 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -188,7 +188,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.12.13"
-"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.7.6", "@babel/runtime@^7.9.2":
version "7.14.8"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446"
integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==
@@ -2448,6 +2448,13 @@ hey-listen@^1.0.8:
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
+history@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08"
+ integrity sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==
+ dependencies:
+ "@babel/runtime" "^7.7.6"
+
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
@@ -4965,6 +4972,13 @@ type-fest@^0.20.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+type-route@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/type-route/-/type-route-0.6.0.tgz#0762fde72c592f7fc9eea3451f7c538811b7439e"
+ integrity sha512-uh5bxHxHOKNDNTetGwBgtSP5ba3SUtnKcdj3d5AjbIALVbYBwaix4wwfpyxqrE9ia31LknXUc+359FChcC01jw==
+ dependencies:
+ history "^5.0.0"
+
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"