diff --git a/package.json b/package.json index 5916f1fb..4276f191 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "react-flags-select": "^2.1.2", "react-hook-form": "^7.9.0", "react-i18next": "^11.11.4", - "react-redux": "^7.2.4" + "react-redux": "^7.2.4", + "type-route": "^0.6.0" }, "devDependencies": { "@snowpack/plugin-dotenv": "^2.0.5", diff --git a/public/fonts/inter/Inter-italic.var.woff2 b/public/fonts/inter/Inter-italic.var.woff2 deleted file mode 100644 index 03875311..00000000 Binary files a/public/fonts/inter/Inter-italic.var.woff2 and /dev/null differ diff --git a/public/fonts/inter/Inter-roman.var.woff2 b/public/fonts/inter/Inter-roman.var.woff2 deleted file mode 100644 index a6efdc48..00000000 Binary files a/public/fonts/inter/Inter-roman.var.woff2 and /dev/null differ diff --git a/public/fonts/inter/inter.css b/public/fonts/inter/inter.css deleted file mode 100644 index 3294b018..00000000 --- a/public/fonts/inter/inter.css +++ /dev/null @@ -1,17 +0,0 @@ -@font-face { - font-family: 'Inter var'; - font-weight: 100 900; - font-display: swap; - font-style: normal; - font-named-instance: 'Regular'; - src: url("Inter-roman.var.woff2?v=3.18") format("woff2"); -} - -@font-face { - font-family: 'Inter var'; - font-weight: 100 900; - font-display: swap; - font-style: italic; - font-named-instance: 'Italic'; - src: url("Inter-italic.var.woff2?v=3.18") format("woff2"); -} diff --git a/public/index.html b/public/index.html index aa028384..1a54fe71 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ - + - Snowpack App + Meshtastic Web
diff --git a/src/App.tsx b/src/App.tsx index 6d851948..a4976520 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,25 @@ import React from 'react'; -import { HomeIcon, MenuIcon, MoonIcon } from '@heroicons/react/outline'; import { Protobuf, SettingsManager, Types } from '@meshtastic/meshtasticjs'; -import { NavItem } from './components/nav/NavItem'; +import { DeviceStatusDropdown } from './components/menu/buttons/DeviceStatusDropdown'; +import { LanguageDropdown } from './components/menu/buttons/LanguageDropdown'; +import { MobileNavToggle } from './components/menu/buttons/MobileNavToggle'; +import { ThemeToggle } from './components/menu/buttons/ThemeToggle'; +import { Logo } from './components/menu/Logo'; +import { MobileNav } from './components/menu/MobileNav'; +import { Navigation } from './components/menu/Navigation'; import { connection } from './connection'; -import { useAppDispatch } from './hooks/redux'; +import { useAppDispatch, useAppSelector } from './hooks/redux'; +import { About } from './pages/About'; +import { Messages } from './pages/Messages'; +import { Nodes } from './pages/Nodes'; +import { Settings } from './pages/Settings'; +import { useRoute } from './router'; import { + ackMessage, addChannel, + addMessage, addNode, setDeviceStatus, setLastMeshInterraction, @@ -18,6 +30,10 @@ import { const App = (): JSX.Element => { const dispatch = useAppDispatch(); + const route = useRoute(); + + const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo); + const darkMode = useAppSelector((state) => state.app.darkMode); React.useEffect(() => { SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE; @@ -70,77 +86,67 @@ const App = (): JSX.Element => { connection.onMeshHeartbeat.subscribe((date) => dispatch(setLastMeshInterraction(date.getTime())), ); - }, [dispatch]); + + connection.onTextPacket.subscribe((message) => { + console.log(message.packet.from, '===', myNodeInfo.myNodeNum); + + dispatch( + addMessage({ + message: message, + ack: message.packet.from !== myNodeInfo.myNodeNum, + isSender: message.packet.from === myNodeInfo.myNodeNum, + received: new Date(message.packet.rxTime), + }), + ); + }); + + connection.onRoutingPacket.subscribe((routingPacket) => { + if (routingPacket.packet.payloadVariant.oneofKind === 'decoded') { + dispatch( + ackMessage(routingPacket.packet.payloadVariant.decoded.requestId), + ); + } + }); + + return () => { + connection.onDeviceStatus.cancelAll(); + connection.onMyNodeInfo.cancelAll(); + connection.onNodeInfoPacket.cancelAll(); + connection.onAdminPacket.cancelAll(); + connection.onMeshHeartbeat.cancelAll(); + connection.onTextPacket.cancelAll(); + connection.onRoutingPacket.cancelAll(); + }; + }, [dispatch, myNodeInfo.myNodeNum]); return ( - //
- //
- //
- //
-
-
-
-
- {/* NORMAL NAV ICON */} -
- Logo image - Logo image -
- {/* END NORMAL NAV ICON */} - {/* MOBILE NAV BUTTON */} - - {/* END MOBILE NAV BUTTON */} -
- {/* HEADER BUTTON */} - - {/* END HEADER BUTTON */} - {/* THEME BUTTON */} - - {/* END THEME BUTTON */} -
-
-
-
- {/* 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 ( -
- -
- -
Loading
-
- } - > -
-
-
{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 ( - - ); -}; 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()); - }} - > - - -
{ - e.preventDefault(); - sendMessage(); - }} - > - {ready} - { - setCurrentMessage(e.target.value); - }} - className={`p-3 placeholder-gray-400 text-gray-700 relative rounded-3xl border shadow-md focus:outline-none w-full pr-10 ${ - ready ? 'cursor-text' : 'cursor-not-allowed' - }`} - /> - - - -
-
- ); -}; 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 */} -
- Logo image - Logo image -
- {/* 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 ( +
+
+ +
+
+
{message}
+
+
+ {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 ( +
+
+ + + +
+
{ + e.preventDefault(); + sendMessage(); + }} + > + { + setCurrentMessage(e.target.value); + }} + className="focus:outline-none h-10 w-full resize-none rounded-full border border-gray-300 dark:bg-gray-900 px-4" + /> + +
+
+ ); +}; 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 ( +
+ +
+ {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 ( + + ); +}; 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 ( +
{ + if (clickAction) { + clickAction(); + } + }} + > + + {React.cloneElement(icon, { + className: 'h-6 w-6 mr-3 text-gray-500 dark:text-gray-400', + })} + {text} + +
+ ); +}; 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" + > + + +
+ +
+ } + 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 }) => ( + + )} + + ))} + {/* ... */} + +
+
+ ); +}; 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 ( + + ); +}; 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 ( + + ); +}; 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 ( +
+
+
+
+
+ {tagline} +
+
+
+

+ {title} +

+
+
+
+
+
+
{children}
+
+
+
+ ); +}; 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 ( + +
+
WiFi
+
+ + +
+
+
+ ); +}; 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"