Browse Source

WIP

pull/1/head
Sacha Weatherstone 5 years ago
parent
commit
58de82b8d7
  1. 22
      .eslintrc
  2. 29
      package.json
  3. 71
      src/App.tsx
  4. 14
      src/Main.tsx
  5. 19
      src/components/ChatMessage.tsx
  6. 76
      src/components/Header.tsx
  7. 12
      src/components/MessageBox.tsx
  8. 6
      src/components/Sidebar.tsx
  9. 2
      src/components/Sidebar/Channels/Channel.tsx
  10. 2
      src/components/Sidebar/Channels/Index.tsx
  11. 10
      src/components/Sidebar/Device/Index.tsx
  12. 50
      src/components/Sidebar/Device/Settings.tsx
  13. 2
      src/components/Sidebar/Nodes/Index.tsx
  14. 2
      src/components/Sidebar/Nodes/Node.tsx
  15. 2
      src/components/Sidebar/UI/Index.tsx
  16. 5
      src/components/Sidebar/UI/Translations.tsx
  17. 3
      src/components/basic/ToggleSwitch.tsx
  18. 2
      src/components/logo.tsx
  19. 24
      src/index.tsx
  20. 3263
      yarn-error.log
  21. 1010
      yarn.lock

22
.eslintrc

@ -0,0 +1,22 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:react/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/consistent-type-imports": "error"
},
"settings": {
"react": {
"version": "detect"
}
}
}

29
package.json

@ -7,36 +7,45 @@
"start": "NODE_ENV=development snowpack dev", "start": "NODE_ENV=development snowpack dev",
"build": "snowpack build", "build": "snowpack build",
"package": "yarn gzipper c -i html,js,css build build/output", "package": "yarn gzipper c -i html,js,css build build/output",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", "format": "prettier --write 'src/**/*.{ts,tsx}'",
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"" "lint": "eslint 'src/**/*.{ts,tsx}'"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.2.0", "@headlessui/react": "^1.2.0",
"@heroicons/react": "^1.0.1", "@heroicons/react": "^1.0.1",
"@meshtastic/meshtasticjs": "^0.6.12", "@meshtastic/meshtasticjs": "^0.6.12",
"observable-hooks": "^4.0.3", "observable-hooks": "^4.0.3",
"react": "^17.0.0", "react": "^0.0.0-experimental-132b72d7b",
"react-dom": "^17.0.0", "react-dom": "^0.0.0-experimental-132b72d7b",
"react-flags-select": "^2.1.2", "react-flags-select": "^2.1.2",
"react-hook-form": "^7.5.2", "react-hook-form": "^7.6.5",
"react-json-pretty": "^2.2.0", "react-json-pretty": "^2.2.0",
"rxjs": "^7.0.1", "rxjs": "^7.0.1",
"yarn": "^1.22.10" "yarn": "^1.22.10"
}, },
"devDependencies": { "devDependencies": {
"@snowpack/plugin-dotenv": "^2.0.5", "@snowpack/plugin-dotenv": "^2.0.5",
"@snowpack/plugin-postcss": "^1.2.2", "@snowpack/plugin-postcss": "^1.4.0",
"@snowpack/plugin-react-refresh": "^2.5.0", "@snowpack/plugin-react-refresh": "^2.5.0",
"@snowpack/plugin-typescript": "^1.2.0", "@snowpack/plugin-typescript": "^1.2.0",
"@types/react": "^17.0.5", "@types/eslint": "^7.2.11",
"@types/react-dom": "^17.0.4", "@types/react": "^17.0.6",
"@types/react-dom": "^17.0.5",
"@types/snowpack-env": "^2.3.2", "@types/snowpack-env": "^2.3.2",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"autoprefixer": "^10.2.5", "autoprefixer": "^10.2.5",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"gzipper": "^4.5.0", "gzipper": "^4.5.0",
"postcss": "^8.2.15", "postcss": "^8.3.0",
"postcss-cli": "^8.3.1", "postcss-cli": "^8.3.1",
"prettier": "^2.3.0", "prettier": "^2.3.0",
"snowpack": "^3.3.7", "snowpack": "^3.5.1",
"tailwindcss": "^2.1.2", "tailwindcss": "^2.1.2",
"typescript": "^4.2.4" "typescript": "^4.2.4"
} }

71
src/App.tsx

@ -1,8 +1,12 @@
import React from 'react'; import React from 'react';
import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import { import {
Client, Client,
IHTTPConnection,
Protobuf, Protobuf,
SettingsManager, SettingsManager,
Types, Types,
@ -36,33 +40,27 @@ export interface languageTemplate {
no_message_placeholder: string; no_message_placeholder: string;
} }
const App = () => { const App = (): JSX.Element => {
const [ const [deviceStatus, setDeviceStatus] =
deviceStatus, React.useState<Types.DeviceStatusEnum>(
setDeviceStatus, Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
] = React.useState<Types.DeviceStatusEnum>( );
Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
);
const [myNodeInfo, setMyNodeInfo] = React.useState<Protobuf.MyNodeInfo>( const [myNodeInfo, setMyNodeInfo] = React.useState<Protobuf.MyNodeInfo>(
Protobuf.MyNodeInfo.create(), Protobuf.MyNodeInfo.create(),
); );
const [channels, setChannels] = React.useState([] as Protobuf.Channel[]); const [channels, setChannels] = React.useState([] as Protobuf.Channel[]);
const [nodes, setNodes] = React.useState<Types.NodeInfoPacket[]>([]); const [nodes, setNodes] = React.useState<Types.NodeInfoPacket[]>([]);
const [connection, setConnection] = React.useState<IHTTPConnection>( const [connection, setConnection] =
new IHTTPConnection(), React.useState<ISerialConnection | IHTTPConnection | IBLEConnection>();
);
const [isReady, setIsReady] = React.useState<boolean>(false); const [isReady, setIsReady] = React.useState<boolean>(false);
const [ const [lastMeshInterraction, setLastMeshInterraction] =
lastMeshInterraction, React.useState<number>(0);
setLastMeshInterraction,
] = React.useState<number>(0);
const [language, setLanguage] = React.useState<LanguageEnum>( const [language, setLanguage] = React.useState<LanguageEnum>(
LanguageEnum.ENGLISH, LanguageEnum.ENGLISH,
); );
const [translations, setTranslations] = React.useState<languageTemplate>( const [translations, setTranslations] =
Translations_English, React.useState<languageTemplate>(Translations_English);
);
const [darkmode, setDarkmode] = React.useState<boolean>(false); const [darkmode, setDarkmode] = React.useState<boolean>(false);
React.useEffect(() => { React.useEffect(() => {
@ -97,8 +95,12 @@ const App = () => {
}); });
setConnection(connection); setConnection(connection);
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE; SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
}, []);
const deviceStatusEvent = connection.onDeviceStatusEvent.subscribe( React.useEffect(() => {
console.log('UPDATING');
const deviceStatusEvent = connection?.onDeviceStatusEvent.subscribe(
(status) => { (status) => {
setDeviceStatus(status); setDeviceStatus(status);
if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) { if (status === Types.DeviceStatusEnum.DEVICE_CONFIGURED) {
@ -106,11 +108,10 @@ const App = () => {
} }
}, },
); );
const myNodeInfoEvent = connection.onMyNodeInfoEvent.subscribe( const myNodeInfoEvent =
setMyNodeInfo, connection?.onMyNodeInfoEvent.subscribe(setMyNodeInfo);
);
const nodeInfoPacketEvent = connection.onNodeInfoPacketEvent.subscribe( const nodeInfoPacketEvent = connection?.onNodeInfoPacketEvent.subscribe(
(node) => { (node) => {
if ( if (
nodes.findIndex( nodes.findIndex(
@ -128,34 +129,34 @@ const App = () => {
}, },
); );
const adminPacketEvent = connection.onAdminPacketEvent.subscribe( const adminPacketEvent = connection?.onAdminPacketEvent.subscribe(
(adminMessage) => { (adminMessage) => {
switch (adminMessage.data.variant.oneofKind) { switch (adminMessage.data.variant.oneofKind) {
case 'getChannelResponse': case 'getChannelResponse':
if (adminMessage.data.variant.getChannelResponse) { if (adminMessage.data.variant.getChannelResponse) {
let message = adminMessage.data.variant.getChannelResponse; const message = adminMessage.data.variant.getChannelResponse;
setChannels((channels) => [...channels, message]); setChannels((channels) => [...channels, message]);
} }
break;
default: default:
break; break;
} }
}, },
); );
const meshHeartbeat = connection.onMeshHeartbeat.subscribe( const meshHeartbeat = connection?.onMeshHeartbeat.subscribe(
setLastMeshInterraction, setLastMeshInterraction,
); );
return () => { return () => {
deviceStatusEvent.unsubscribe(); deviceStatusEvent?.unsubscribe();
myNodeInfoEvent.unsubscribe(); myNodeInfoEvent?.unsubscribe();
nodeInfoPacketEvent.unsubscribe(); nodeInfoPacketEvent?.unsubscribe();
adminPacketEvent.unsubscribe(); adminPacketEvent?.unsubscribe();
meshHeartbeat.unsubscribe(); meshHeartbeat?.unsubscribe();
connection.disconnect(); connection?.disconnect();
}; };
}, []); }, [connection, nodes]);
return ( return (
<div className="flex flex-col h-screen w-screen"> <div className="flex flex-col h-screen w-screen">
@ -163,6 +164,8 @@ const App = () => {
status={deviceStatus} status={deviceStatus}
IsReady={isReady} IsReady={isReady}
LastMeshInterraction={lastMeshInterraction} LastMeshInterraction={lastMeshInterraction}
connection={connection}
setConnection={setConnection}
/> />
<Main <Main
isReady={isReady} isReady={isReady}

14
src/Main.tsx

@ -1,7 +1,9 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import type { import type {
IBLEConnection,
IHTTPConnection, IHTTPConnection,
ISerialConnection,
Protobuf, Protobuf,
Types, Types,
} from '@meshtastic/meshtasticjs'; } from '@meshtastic/meshtasticjs';
@ -12,7 +14,7 @@ import MessageBox from './components/MessageBox';
import Sidebar from './components/Sidebar'; import Sidebar from './components/Sidebar';
interface MainProps { interface MainProps {
connection: IHTTPConnection; connection?: ISerialConnection | IHTTPConnection | IBLEConnection;
myNodeInfo: Protobuf.MyNodeInfo; myNodeInfo: Protobuf.MyNodeInfo;
nodes: Types.NodeInfoPacket[]; nodes: Types.NodeInfoPacket[];
channels: Protobuf.Channel[]; channels: Protobuf.Channel[];
@ -24,14 +26,14 @@ interface MainProps {
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>; setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
} }
const Main = (props: MainProps) => { const Main = (props: MainProps): JSX.Element => {
const [messages, setMessages] = React.useState< const [messages, setMessages] = React.useState<
{ message: Types.TextPacket; ack: boolean }[] { message: Types.TextPacket; ack: boolean }[]
>([]); >([]);
const [sidebarOpen, setSidebarOpen] = useState<boolean>(false); const [sidebarOpen, setSidebarOpen] = useState<boolean>(false);
React.useEffect(() => { React.useEffect(() => {
const textPacketEvent = props.connection.onTextPacketEvent.subscribe( const textPacketEvent = props.connection?.onTextPacketEvent.subscribe(
(message) => { (message) => {
setMessages((messages) => [ setMessages((messages) => [
...messages, ...messages,
@ -39,11 +41,11 @@ const Main = (props: MainProps) => {
]); ]);
}, },
); );
return () => textPacketEvent.unsubscribe(); return () => textPacketEvent?.unsubscribe();
}, [props.connection]); }, [props.connection]);
React.useEffect(() => { React.useEffect(() => {
const routingPacketEvent = props.connection.onRoutingPacketEvent.subscribe( const routingPacketEvent = props.connection?.onRoutingPacketEvent.subscribe(
(routingPacket) => { (routingPacket) => {
setMessages( setMessages(
messages.map((message) => { messages.map((message) => {
@ -60,7 +62,7 @@ const Main = (props: MainProps) => {
); );
}, },
); );
return () => routingPacketEvent.unsubscribe(); return () => routingPacketEvent?.unsubscribe();
}, [props.connection, messages]); }, [props.connection, messages]);
return ( return (

19
src/components/ChatMessage.tsx

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import { import {
CheckCircleIcon, CheckCircleIcon,
@ -13,7 +13,16 @@ interface ChatMessageProps {
nodes: Types.NodeInfoPacket[]; nodes: Types.NodeInfoPacket[];
} }
const ChatMessage = (props: ChatMessageProps) => { const ChatMessage = (props: ChatMessageProps): JSX.Element => {
const [node, setNode] = useState<Types.NodeInfoPacket>();
React.useEffect(() => {
setNode(
props.nodes.find(
(node) => node.data.num === props.message.message.packet.from,
),
);
}, [props.nodes, props.message]);
return ( return (
<div className="flex items-end"> <div className="flex items-end">
<div <div
@ -35,11 +44,7 @@ const ChatMessage = (props: ChatMessageProps) => {
> >
<div className="flex text-xs text-gray-500 space-x-1"> <div className="flex text-xs text-gray-500 space-x-1">
<div className="font-medium"> <div className="font-medium">
{/* { {node?.data.user?.longName ?? 'UNK'}
props.nodes.find(
(node) => node.data.num === props.message.message.packet.from,
).data.user.longName
} */}
</div> </div>
<p>-</p> <p>-</p>
<div className="underline"> <div className="underline">

76
src/components/Header.tsx

@ -1,11 +1,19 @@
import React from 'react'; import React from 'react';
import { import {
ChipIcon,
DeviceMobileIcon, DeviceMobileIcon,
RssIcon,
StatusOfflineIcon, StatusOfflineIcon,
StatusOnlineIcon, StatusOnlineIcon,
WifiIcon,
} from '@heroicons/react/outline'; } from '@heroicons/react/outline';
import { Types } from '@meshtastic/meshtasticjs'; import {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
Types,
} from '@meshtastic/meshtasticjs';
import Logo from './logo'; import Logo from './logo';
@ -13,17 +21,75 @@ interface HeaderProps {
status: Types.DeviceStatusEnum; status: Types.DeviceStatusEnum;
IsReady: boolean; IsReady: boolean;
LastMeshInterraction: number; LastMeshInterraction: number;
connection?: IHTTPConnection | ISerialConnection | IBLEConnection;
setConnection: React.Dispatch<
React.SetStateAction<
IHTTPConnection | ISerialConnection | IBLEConnection | undefined
>
>;
} }
const Header = (props: HeaderProps) => { const Header = (props: HeaderProps): JSX.Element => {
const [activeConnection, setActiveConnection] =
React.useState<'http' | 'serial' | 'ble'>('http');
return ( return (
<nav className="w-full shadow-md"> <nav className="w-full shadow-md">
<div className="flex w-full container mx-auto justify-between px-6 py-4"> <div className="flex w-full container mx-auto justify-between px-6 py-4">
<Logo /> <Logo />
<div></div> <div></div>
<div className="flex items-center"> <div className="flex space-x-2 items-center">
<div className="flex pl-4"> <button
className={`rounded-md px-3 py-2 ${
activeConnection === 'serial' ? 'bg-green-300' : 'bg-gray-300'
}`}
onClick={() => {
props.connection?.disconnect();
const connection = new ISerialConnection();
connection.connect({});
setActiveConnection('serial');
props.setConnection(connection);
}}
>
<ChipIcon className="m-auto h-5 w-5" />
</button>
<button
className={`rounded-md px-3 py-2 ${
activeConnection === 'http' ? 'bg-green-300' : 'bg-gray-300'
}`}
onClick={() => {
props.connection?.disconnect();
const connection = new IHTTPConnection();
connection.connect({
address:
import.meta.env.NODE_ENV === 'production'
? window.location.hostname
: import.meta.env.SNOWPACK_PUBLIC_DEVICE_IP,
receiveBatchRequests: false,
tls: false,
fetchInterval: 2000,
});
setActiveConnection('http');
props.setConnection(connection);
}}
>
<WifiIcon className="m-auto h-5 w-5" />
</button>
<button
className={`rounded-md px-3 py-2 ${
activeConnection === 'ble' ? 'bg-green-300' : 'bg-gray-300'
}`}
onClick={() => {
props.connection?.disconnect();
const connection = new IBLEConnection();
connection.connect({});
setActiveConnection('ble');
props.setConnection(connection);
}}
>
<RssIcon className="m-auto h-5 w-5" />
</button>
<div className="flex pl-2">
<div <div
className={`w-5 h-5 rounded-full ${ className={`w-5 h-5 rounded-full ${
new Date(props.LastMeshInterraction) < new Date(props.LastMeshInterraction) <
@ -43,7 +109,7 @@ const Header = (props: HeaderProps) => {
)} )}
</div> </div>
<div className="flex pl-4"> <div className="flex pl-2">
<div <div
className={`w-5 h-5 rounded-full ${ className={`w-5 h-5 rounded-full ${
props.status <= Types.DeviceStatusEnum.DEVICE_DISCONNECTED props.status <= Types.DeviceStatusEnum.DEVICE_DISCONNECTED

12
src/components/MessageBox.tsx

@ -1,7 +1,11 @@
import React from 'react'; import React from 'react';
import { MenuIcon, PaperAirplaneIcon } from '@heroicons/react/outline'; import { MenuIcon, PaperAirplaneIcon } from '@heroicons/react/outline';
import type { IHTTPConnection } from '@meshtastic/meshtasticjs'; import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../App'; import type { languageTemplate } from '../App';
@ -9,15 +13,15 @@ export interface MessageBoxProps {
translations: languageTemplate; translations: languageTemplate;
sidebarOpen: boolean; sidebarOpen: boolean;
setSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>; setSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
connection: IHTTPConnection; connection?: ISerialConnection | IHTTPConnection | IBLEConnection;
isReady: boolean; isReady: boolean;
} }
const MessageBox = (props: MessageBoxProps) => { const MessageBox = (props: MessageBoxProps): JSX.Element => {
const [currentMessage, setCurrentMessage] = React.useState(''); const [currentMessage, setCurrentMessage] = React.useState('');
const sendMessage = () => { const sendMessage = () => {
if (props.isReady) { if (props.isReady) {
props.connection.sendText(currentMessage, undefined, true); props.connection?.sendText(currentMessage, undefined, true);
setCurrentMessage(''); setCurrentMessage('');
} }
}; };

6
src/components/Sidebar.tsx

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import type { import type {
IBLEConnection,
IHTTPConnection, IHTTPConnection,
ISerialConnection,
Protobuf, Protobuf,
Types, Types,
} from '@meshtastic/meshtasticjs'; } from '@meshtastic/meshtasticjs';
@ -16,7 +18,7 @@ interface SidebarProps {
isReady: boolean; isReady: boolean;
nodes: Types.NodeInfoPacket[]; nodes: Types.NodeInfoPacket[];
channels: Protobuf.Channel[]; channels: Protobuf.Channel[];
connection: IHTTPConnection; connection?: ISerialConnection | IHTTPConnection | IBLEConnection;
language: LanguageEnum; language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>; setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
translations: languageTemplate; translations: languageTemplate;
@ -26,7 +28,7 @@ interface SidebarProps {
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>; setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
} }
const Sidebar = (props: SidebarProps) => { const Sidebar = (props: SidebarProps): JSX.Element => {
return ( return (
<div <div
className={`${ className={`${

2
src/components/Sidebar/Channels/Channel.tsx

@ -8,7 +8,7 @@ export interface ChannelProps {
channel: Protobuf.Channel; channel: Protobuf.Channel;
} }
const Channel = (props: ChannelProps) => { const Channel = (props: ChannelProps): JSX.Element => {
return ( return (
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

2
src/components/Sidebar/Channels/Index.tsx

@ -17,7 +17,7 @@ export interface ChannelsProps {
translations: languageTemplate; translations: languageTemplate;
} }
const Channels = (props: ChannelsProps) => { const Channels = (props: ChannelsProps): JSX.Element => {
return ( return (
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

10
src/components/Sidebar/Device/Index.tsx

@ -6,18 +6,22 @@ import {
ChevronDownIcon, ChevronDownIcon,
ChevronRightIcon, ChevronRightIcon,
} from '@heroicons/react/outline'; } from '@heroicons/react/outline';
import type { IHTTPConnection } from '@meshtastic/meshtasticjs'; import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../../../App'; import type { languageTemplate } from '../../../App';
import Settings from './Settings'; import Settings from './Settings';
interface DeviceProps { interface DeviceProps {
isReady: boolean; isReady: boolean;
connection: IHTTPConnection; connection?: ISerialConnection | IHTTPConnection | IBLEConnection;
translations: languageTemplate; translations: languageTemplate;
} }
const Device = (props: DeviceProps) => { const Device = (props: DeviceProps): JSX.Element => {
return ( return (
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

50
src/components/Sidebar/Device/Settings.tsx

@ -1,27 +1,27 @@
import React from 'react'; import React from 'react';
import { ObservableResource, useObservableSuspense } from 'observable-hooks';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import JSONPretty from 'react-json-pretty'; import JSONPretty from 'react-json-pretty';
import type { languageTemplate } from 'src/App';
import { SaveIcon } from '@heroicons/react/outline'; import { SaveIcon } from '@heroicons/react/outline';
import { IHTTPConnection, Protobuf } from '@meshtastic/meshtasticjs'; import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import { Protobuf } from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../../../../src/App';
export interface SettingsProps { export interface SettingsProps {
isReady: boolean; isReady: boolean;
connection: IHTTPConnection; connection?: ISerialConnection | IHTTPConnection | IBLEConnection;
translations: languageTemplate; translations: languageTemplate;
} }
const Settings = (props: SettingsProps) => { const Settings = (props: SettingsProps): JSX.Element => {
React.useEffect(() => { React.useEffect(() => {
const a = useObservableSuspense( const adminPacketEvent = props.connection?.onAdminPacketEvent.subscribe(
new ObservableResource(
props.connection.onAdminPacketEvent.asObservable(),
),
);
const adminPacketEvent = props.connection.onAdminPacketEvent.subscribe(
(adminMessage) => { (adminMessage) => {
switch (adminMessage.data.variant.oneofKind) { switch (adminMessage.data.variant.oneofKind) {
case 'getRadioResponse': case 'getRadioResponse':
@ -34,35 +34,23 @@ const Settings = (props: SettingsProps) => {
}, },
); );
return () => adminPacketEvent.unsubscribe(); return () => adminPacketEvent?.unsubscribe();
}, []); }, []);
const [ const [preferences, setPreferences] =
preferences, React.useState<Protobuf.RadioConfig_UserPreferences>();
setPreferences, const { register, handleSubmit } =
] = React.useState<Protobuf.RadioConfig_UserPreferences>(); useForm<Protobuf.RadioConfig_UserPreferences>({
const { defaultValues: preferences,
register, });
setValue,
handleSubmit,
formState: { errors },
} = useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: preferences,
});
const onSubmit = handleSubmit((data) => console.log(data)); const onSubmit = handleSubmit((data) => console.log(data));
return ( return (
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b"> <div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
<div className="my-auto">{props.translations.device_region_title}</div> <div className="my-auto">{props.translations.device_region_title}</div>
<div className="flex shadow-md rounded-md ml-2"> <div className="flex shadow-md rounded-md ml-2">
<select <select value={preferences?.region ?? Protobuf.RegionCode.Unset}>
value={preferences.region ?? Protobuf.RegionCode.Unset}
// onChange={(e) => {
// preferences.region = parseInt(e.target.value);
// }}
>
<option value={Protobuf.RegionCode.ANZ}> <option value={Protobuf.RegionCode.ANZ}>
{Protobuf.RegionCode[Protobuf.RegionCode.ANZ]} {Protobuf.RegionCode[Protobuf.RegionCode.ANZ]}
</option> </option>

2
src/components/Sidebar/Nodes/Index.tsx

@ -18,7 +18,7 @@ interface NodesProps {
myId: number; myId: number;
} }
const Nodes = (props: NodesProps) => { const Nodes = (props: NodesProps): JSX.Element => {
return ( return (
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

2
src/components/Sidebar/Nodes/Node.tsx

@ -17,7 +17,7 @@ export interface NodeProps {
myId: number; myId: number;
} }
const Node = (props: NodeProps) => { const Node = (props: NodeProps): JSX.Element => {
return ( return (
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

2
src/components/Sidebar/UI/Index.tsx

@ -18,7 +18,7 @@ interface UIProps {
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>; setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
} }
const UI = (props: UIProps) => { const UI = (props: UIProps): JSX.Element => {
return ( return (
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

5
src/components/Sidebar/UI/Translations.tsx

@ -5,7 +5,8 @@ import { Br, Jp, Us } from 'react-flags-select';
import { Disclosure } from '@headlessui/react'; import { Disclosure } from '@headlessui/react';
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline'; import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline';
import { LanguageEnum, languageTemplate } from '../../../App'; import type { languageTemplate } from '../../../App';
import { LanguageEnum } from '../../../App';
export interface TranslationsProps { export interface TranslationsProps {
language: LanguageEnum; language: LanguageEnum;
@ -13,7 +14,7 @@ export interface TranslationsProps {
translations: languageTemplate; translations: languageTemplate;
} }
const Translations = (props: TranslationsProps) => { const Translations = (props: TranslationsProps): JSX.Element => {
return ( return (
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

3
src/components/basic/ToggleSwitch.tsx

@ -4,10 +4,9 @@ import { Switch } from '@headlessui/react';
interface ToggleSwitchProps { interface ToggleSwitchProps {
active: boolean; active: boolean;
toggle?: Function;
} }
const ToggleSwitch = (props: ToggleSwitchProps) => { const ToggleSwitch = (props: ToggleSwitchProps): JSX.Element => {
const [active, setActive] = React.useState(false); const [active, setActive] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {

2
src/components/logo.tsx

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
const Logo = () => { const Logo = (): JSX.Element => {
return ( return (
<svg <svg
height="30" height="30"

24
src/index.tsx

@ -1,17 +1,25 @@
/// <reference types="react/experimental" />
/// <reference types="react-dom/experimental" />
import './index.css';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import App from './App'; import App from './App';
import './index.css';
ReactDOM.render( const element = document.getElementById('root');
<React.StrictMode>
<App /> if (element) {
</React.StrictMode>, ReactDOM.createRoot(element).render(
document.getElementById('root'), <React.StrictMode>
); <App />
</React.StrictMode>,
);
}
// Hot Module Replacement (HMR) - Remove this snippet to remove HMR. // Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
// Learn more: https://snowpack.dev/concepts/hot-module-replacement // Learn more: https://www.snowpack.dev/#hot-module-replacement
if (import.meta.hot) { if (import.meta.hot) {
import.meta.hot.accept(); import.meta.hot.accept();
} }

3263
yarn-error.log

File diff suppressed because it is too large

1010
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save