Get suspense working and cleanup TL system

This commit is contained in:
Sacha Weatherstone
2021-06-01 22:32:44 +10:00
parent 1145df197c
commit 59888f32e7
23 changed files with 427 additions and 372 deletions
+14 -66
View File
@@ -1,8 +1,5 @@
import React from 'react';
import { ObservableResource } from 'observable-hooks';
import { Subject } from 'rxjs';
import type {
IBLEConnection,
ISerialConnection,
@@ -17,43 +14,12 @@ import {
import Header from './components/Header';
import Main from './Main';
import { channelSubject$, nodeSubject$, preferencesSubject$ } from './streams';
import Translations_English from './translations/en';
import Translations_Japanese from './translations/jp';
import Translations_Portuguese from './translations/pt';
export enum LanguageEnum {
ENGLISH,
JAPANESE,
PORTUGUESE,
}
export interface languageTemplate {
no_messages_message: string;
ui_settings_title: string;
nodes_title: string;
color_scheme_title: string;
language_title: string;
device_settings_title: string;
device_channels_title: string;
device_region_title: string;
device_wifi_ssid: string;
device_wifi_psk: string;
save_changes_button: string;
no_nodes_message: string;
no_message_placeholder: string;
}
// const adminPacketResource = useSuspense(props.connection.onAdminPacketEvent);
// const tmp$ = new Subject<Types.AdminPacket>().pipe(
// filter(
// (adminPacket) =>
// adminPacket.data.variant.oneofKind === 'getRadioResponse',
// ),
// );
// const tmp$ = props.connection.onAdminPacketEvent;
const tmpSubject = new Subject<Protobuf.RadioConfig_UserPreferences>();
export const adminPacketResource = new ObservableResource(tmpSubject);
import type { languageTemplate } from './translations/TranslationContext';
import { LanguageEnum } from './translations/TranslationContext';
const App = (): JSX.Element => {
const [deviceStatus, setDeviceStatus] =
@@ -63,7 +29,7 @@ const App = (): JSX.Element => {
const [myNodeInfo, setMyNodeInfo] = React.useState<Protobuf.MyNodeInfo>(
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 [connection, setConnection] = React.useState<
ISerialConnection | IHTTPConnection | IBLEConnection
@@ -98,9 +64,9 @@ const App = (): JSX.Element => {
React.useEffect(() => {
const client = new Client();
const connection = client.createHTTPConnection();
const tmpConnection = client.createHTTPConnection();
connection.connect({
tmpConnection.connect({
address:
import.meta.env.NODE_ENV === 'production'
? window.location.hostname
@@ -109,13 +75,11 @@ const App = (): JSX.Element => {
tls: false,
fetchInterval: 2000,
});
setConnection(connection);
setConnection(tmpConnection);
SettingsManager.debugMode = Protobuf.LogRecord_Level.TRACE;
}, []);
React.useEffect(() => {
console.log('UPDATING');
const deviceStatusEvent = connection.onDeviceStatusEvent.subscribe(
(status) => {
setDeviceStatus(status);
@@ -128,34 +92,21 @@ const App = (): JSX.Element => {
connection.onMyNodeInfoEvent.subscribe(setMyNodeInfo);
const nodeInfoPacketEvent = connection.onNodeInfoPacketEvent.subscribe(
(node) => {
if (
nodes.findIndex(
(currentNode) => currentNode.data.num === node.data.num,
) >= 0
) {
setNodes(
nodes.map((currentNode) =>
currentNode.data.num === node.data.num ? node : currentNode,
),
);
} else {
setNodes((nodes) => [...nodes, node]);
}
},
(node) => nodeSubject$.next(node),
);
const adminPacketEvent = connection.onAdminPacketEvent.subscribe(
(adminMessage) => {
switch (adminMessage.data.variant.oneofKind) {
case 'getChannelResponse':
setChannels((channels) => [
...channels,
adminMessage.data.variant.getChannelResponse,
]);
channelSubject$.next(adminMessage.data.variant.getChannelResponse);
break;
case 'getRadioResponse':
tmpSubject.next(adminMessage.data.variant.getRadioResponse);
if (adminMessage.data.variant.getRadioResponse.preferences) {
preferencesSubject$.next(
adminMessage.data.variant.getRadioResponse.preferences,
);
}
break;
default:
break;
@@ -190,11 +141,8 @@ const App = (): JSX.Element => {
isReady={isReady}
myNodeInfo={myNodeInfo}
connection={connection}
nodes={nodes}
channels={channels}
language={language}
setLanguage={setLanguage}
translations={translations}
darkmode={darkmode}
setDarkmode={setDarkmode}
/>
+4 -10
View File
@@ -8,25 +8,24 @@ import type {
Types,
} from '@meshtastic/meshtasticjs';
import type { LanguageEnum, languageTemplate } from './App';
import ChatMessage from './components/ChatMessage';
import MessageBox from './components/MessageBox';
import Sidebar from './components/Sidebar';
import type { LanguageEnum } from './translations/TranslationContext';
import { TranslationContext } from './translations/TranslationContext';
interface MainProps {
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
myNodeInfo: Protobuf.MyNodeInfo;
nodes: Types.NodeInfoPacket[];
channels: Protobuf.Channel[];
isReady: boolean;
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
translations: languageTemplate;
darkmode: boolean;
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
}
const Main = (props: MainProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const [messages, setMessages] = React.useState<
{ message: Types.TextPacket; ack: boolean }[]
>([]);
@@ -72,7 +71,6 @@ const Main = (props: MainProps): JSX.Element => {
{messages.length ? (
messages.map((message, Main) => (
<ChatMessage
nodes={props.nodes}
key={Main}
message={message}
myId={props.myNodeInfo.myNodeNum}
@@ -80,7 +78,7 @@ const Main = (props: MainProps): JSX.Element => {
))
) : (
<div className="m-auto text-2xl text-gray-500">
{props.translations.no_messages_message}
{translations.no_messages_message}
</div>
)}
</div>
@@ -89,17 +87,13 @@ const Main = (props: MainProps): JSX.Element => {
isReady={props.isReady}
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
translations={props.translations}
/>
</div>
<Sidebar
isReady={props.isReady}
nodes={props.nodes}
channels={props.channels}
connection={props.connection}
language={props.language}
setLanguage={props.setLanguage}
translations={props.translations}
myId={props.myNodeInfo.myNodeNum}
sidebarOpen={sidebarOpen}
darkmode={props.darkmode}
+26 -5
View File
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import Avatar from 'boring-avatars';
import { useObservableSuspense } from 'observable-hooks';
import {
CheckCircleIcon,
@@ -8,22 +9,42 @@ import {
} from '@heroicons/react/outline';
import type { Types } from '@meshtastic/meshtasticjs';
import { nodeResource } from '../streams';
interface ChatMessageProps {
message: { message: Types.TextPacket; ack: boolean };
myId: number;
nodes: Types.NodeInfoPacket[];
}
const ChatMessage = (props: ChatMessageProps): JSX.Element => {
const nodeSource = useObservableSuspense(nodeResource);
const [nodes, setNodes] = React.useState<Types.NodeInfoPacket[]>([]);
React.useEffect(() => {
if (
nodes.findIndex(
(currentNode) => currentNode.data.num === nodeSource.data.num,
) >= 0
) {
setNodes(
nodes.map((currentNode) =>
currentNode.data.num === nodeSource.data.num
? nodeSource
: currentNode,
),
);
} else {
setNodes((nodes) => [...nodes, nodeSource]);
}
}, [nodeSource, nodes]);
const [node, setNode] = useState<Types.NodeInfoPacket>();
React.useEffect(() => {
setNode(
props.nodes.find(
(node) => node.data.num === props.message.message.packet.from,
),
nodes.find((node) => node.data.num === props.message.message.packet.from),
);
}, [props.nodes, props.message]);
}, [nodes, props.message]);
return (
<div className="flex items-end">
<Avatar
+3 -3
View File
@@ -7,10 +7,9 @@ import type {
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../App';
import { TranslationContext } from '../translations/TranslationContext';
export interface MessageBoxProps {
translations: languageTemplate;
sidebarOpen: boolean;
setSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
@@ -18,6 +17,7 @@ export interface MessageBoxProps {
}
const MessageBox = (props: MessageBoxProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const [currentMessage, setCurrentMessage] = React.useState('');
const sendMessage = () => {
if (props.isReady) {
@@ -45,7 +45,7 @@ const MessageBox = (props: MessageBoxProps): JSX.Element => {
{props.isReady}
<input
type="text"
placeholder={`${props.translations.no_message_placeholder}...`}
placeholder={`${translations.no_message_placeholder}...`}
disabled={!props.isReady}
value={currentMessage}
onChange={(e) => {
+4 -23
View File
@@ -4,11 +4,9 @@ import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
Protobuf,
Types,
} from '@meshtastic/meshtasticjs';
import type { LanguageEnum, languageTemplate } from '../App';
import type { LanguageEnum } from '../translations/TranslationContext';
import Channels from './Sidebar/Channels/Index';
import Device from './Sidebar/Device/Index';
import Nodes from './Sidebar/Nodes/Index';
@@ -16,12 +14,9 @@ import UI from './Sidebar/UI/Index';
interface SidebarProps {
isReady: boolean;
nodes: Types.NodeInfoPacket[];
channels: Protobuf.Channel[];
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
translations: languageTemplate;
myId: number;
sidebarOpen: boolean;
darkmode: boolean;
@@ -35,27 +30,13 @@ const Sidebar = (props: SidebarProps): JSX.Element => {
props.sidebarOpen ? 'flex' : 'hidden md:flex'
} flex-col rounded-md md:ml-0 shadow-md border w-full max-w-sm`}
>
<Nodes
isReady={props.isReady}
nodes={props.nodes}
translations={props.translations}
myId={props.myId}
/>
<Device
isReady={props.isReady}
connection={props.connection}
translations={props.translations}
/>
<Channels
isReady={props.isReady}
channels={props.channels}
translations={props.translations}
/>
<Nodes myId={props.myId} />
<Device isReady={props.isReady} connection={props.connection} />
<Channels />
<div className="flex-grow border-b"></div>
<UI
language={props.language}
setLanguage={props.setLanguage}
translations={props.translations}
darkmode={props.darkmode}
setDarkmode={props.setDarkmode}
/>
@@ -0,0 +1,43 @@
import React from 'react';
import { useObservableSuspense } from 'observable-hooks';
import { Protobuf } from '@meshtastic/meshtasticjs';
import { channelResource } from '../../../streams';
import Channel from './Channel';
const ChannelList = (): JSX.Element => {
const channelSource = useObservableSuspense(channelResource);
const [channels, setChannels] = React.useState<Protobuf.Channel[]>([]);
React.useEffect(() => {
if (
channels.findIndex(
(currentChannel) => currentChannel.index === channelSource.index,
) >= 0
) {
setChannels(
channels.map((currentChannel) =>
currentChannel.index === channelSource.index
? channelSource
: currentChannel,
),
);
} else {
setChannels((channels) => [...channels, channelSource]);
}
}, [channelSource, channels]);
return (
<>
{channels.map((channel, index) => {
if (channel.role !== Protobuf.Channel_Role.DISABLED)
return <Channel key={index} channel={channel} />;
})}
</>
);
};
export default ChannelList;
+14 -17
View File
@@ -6,18 +6,12 @@ import {
ChevronRightIcon,
HashtagIcon,
} from '@heroicons/react/outline';
import { Protobuf } from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../../../App';
import Channel from './Channel';
import { TranslationContext } from '../../../translations/TranslationContext';
import ChannelList from './ChannelList';
export interface ChannelsProps {
isReady: boolean;
channels: Protobuf.Channel[];
translations: languageTemplate;
}
const Channels = (props: ChannelsProps): JSX.Element => {
const Channels = (): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
return (
<Disclosure>
{({ open }) => (
@@ -30,16 +24,19 @@ const Channels = (props: ChannelsProps): JSX.Element => {
<ChevronRightIcon className="my-auto w-5 h-5 mr-2" />
)}
<HashtagIcon className="my-auto text-gray-600 mr-2 2-5 h-5" />
{props.translations.device_channels_title}
{translations.device_channels_title}
</div>
</Disclosure.Button>
<Disclosure.Panel>
<>
{props.channels.map((channel, index) => {
if (channel.role !== Protobuf.Channel_Role.DISABLED)
return <Channel key={index} channel={channel} />;
})}
</>
<React.Suspense
fallback={
<div className="flex border-b border-gray-300">
<div className="m-auto p-3 text-gray-500">Loading...</div>
</div>
}
>
<ChannelList />
</React.Suspense>
</Disclosure.Panel>
</>
)}
+12 -7
View File
@@ -1,4 +1,4 @@
import React, { Suspense } from 'react';
import React from 'react';
import { Disclosure } from '@headlessui/react';
import {
@@ -12,16 +12,16 @@ import type {
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../../../App';
import { TranslationContext } from '../../../translations/TranslationContext';
import Settings from './Settings';
interface DeviceProps {
isReady: boolean;
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
translations: languageTemplate;
}
const Device = (props: DeviceProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
return (
<Disclosure>
{({ open }) => (
@@ -34,18 +34,23 @@ const Device = (props: DeviceProps): JSX.Element => {
<ChevronRightIcon className="my-auto w-5 h-5 mr-2" />
)}
<AdjustmentsIcon className="text-gray-600 my-auto mr-2 w-5 h-5" />
{props.translations.device_settings_title}
{translations.device_settings_title}
</div>
</Disclosure.Button>
<Disclosure.Panel>
<>
<Suspense fallback={<div>loading</div>}>
<React.Suspense
fallback={
<div className="flex border-b border-gray-300">
<div className="m-auto p-3 text-gray-500">Loading...</div>
</div>
}
>
<Settings
connection={props.connection}
isReady={props.isReady}
translations={props.translations}
/>
</Suspense>
</React.Suspense>
</>
</Disclosure.Panel>
</>
+75 -10
View File
@@ -1,29 +1,94 @@
import React from 'react';
import { useObservableSuspense } from 'observable-hooks';
import { useForm } from 'react-hook-form';
import JSONPretty from 'react-json-pretty';
import { SaveIcon } from '@heroicons/react/outline';
import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import { Protobuf } from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../../../../src/App';
import SettingsForm from './SettingsForm';
import { preferencesResource } from '../../../streams';
import { TranslationContext } from '../../../translations/TranslationContext';
export interface SettingsProps {
isReady: boolean;
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
translations: languageTemplate;
}
const Settings = (props: SettingsProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const preferences = useObservableSuspense(preferencesResource);
const { register, handleSubmit } =
useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: preferences,
});
const onSubmit = handleSubmit((data) => console.log(data));
return (
<React.Suspense fallback={<div>Loading....</div>}>
<SettingsForm
connection={props.connection}
isReady={props.isReady}
translations={props.translations}
/>
</React.Suspense>
<form onSubmit={onSubmit}>
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
<div className="my-auto">{translations.device_region_title}</div>
<div className="flex shadow-md rounded-md ml-2">
<select value={preferences?.region ?? Protobuf.RegionCode.Unset}>
<option value={Protobuf.RegionCode.ANZ}>
{Protobuf.RegionCode[Protobuf.RegionCode.ANZ]}
</option>
<option value={Protobuf.RegionCode.CN}>
{Protobuf.RegionCode[Protobuf.RegionCode.CN]}
</option>
<option value={Protobuf.RegionCode.EU433}>
{Protobuf.RegionCode[Protobuf.RegionCode.EU433]}
</option>
<option value={Protobuf.RegionCode.EU865}>
{Protobuf.RegionCode[Protobuf.RegionCode.EU865]}
</option>
<option value={Protobuf.RegionCode.JP}>
{Protobuf.RegionCode[Protobuf.RegionCode.JP]}
</option>
<option value={Protobuf.RegionCode.KR}>
{Protobuf.RegionCode[Protobuf.RegionCode.KR]}
</option>
<option value={Protobuf.RegionCode.TW}>
{Protobuf.RegionCode[Protobuf.RegionCode.TW]}
</option>
<option value={Protobuf.RegionCode.US}>
{Protobuf.RegionCode[Protobuf.RegionCode.US]}
</option>
<option value={Protobuf.RegionCode.Unset}>
{Protobuf.RegionCode[Protobuf.RegionCode.Unset]}
</option>
</select>
</div>
</div>
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
<div className="my-auto">{translations.device_wifi_ssid}</div>
<div className="flex shadow-md rounded-md ml-2">
<input {...register('wifiSsid', {})} type="text" />
</div>
</div>
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
<div className="my-auto">{translations.device_wifi_psk}</div>
<div className="flex shadow-md rounded-md ml-2">
<input {...register('wifiPassword', {})} type="password" />
</div>
</div>
<div className="flex bg-gray-100 group p-1 cursor-pointer hover:bg-gray-200 border-b">
<button
type="submit"
className="flex m-auto font-medium group-hover:text-gray-700"
>
<SaveIcon className="m-auto mr-2 group-hover:text-gray-700 w-5 h-5" />
{translations.save_changes_button}
</button>
</div>
<JSONPretty data={preferences} />
</form>
);
};
@@ -1,95 +0,0 @@
import React from 'react';
import { useObservableSuspense } from 'observable-hooks';
import { useForm } from 'react-hook-form';
import JSONPretty from 'react-json-pretty';
import { SaveIcon } from '@heroicons/react/outline';
import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import { Protobuf } from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../../../../src/App';
import { adminPacketResource } from '../../../../src/App';
export interface SettingsFormProps {
isReady: boolean;
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
translations: languageTemplate;
}
const SettingsForm = (props: SettingsFormProps): JSX.Element => {
const preferences = useObservableSuspense(adminPacketResource);
const { register, handleSubmit } =
useForm<Protobuf.RadioConfig_UserPreferences>({
defaultValues: preferences,
});
const onSubmit = handleSubmit((data) => console.log(data));
return (
<form onSubmit={onSubmit}>
<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="flex shadow-md rounded-md ml-2">
<select value={preferences?.region ?? Protobuf.RegionCode.Unset}>
<option value={Protobuf.RegionCode.ANZ}>
{Protobuf.RegionCode[Protobuf.RegionCode.ANZ]}
</option>
<option value={Protobuf.RegionCode.CN}>
{Protobuf.RegionCode[Protobuf.RegionCode.CN]}
</option>
<option value={Protobuf.RegionCode.EU433}>
{Protobuf.RegionCode[Protobuf.RegionCode.EU433]}
</option>
<option value={Protobuf.RegionCode.EU865}>
{Protobuf.RegionCode[Protobuf.RegionCode.EU865]}
</option>
<option value={Protobuf.RegionCode.JP}>
{Protobuf.RegionCode[Protobuf.RegionCode.JP]}
</option>
<option value={Protobuf.RegionCode.KR}>
{Protobuf.RegionCode[Protobuf.RegionCode.KR]}
</option>
<option value={Protobuf.RegionCode.TW}>
{Protobuf.RegionCode[Protobuf.RegionCode.TW]}
</option>
<option value={Protobuf.RegionCode.US}>
{Protobuf.RegionCode[Protobuf.RegionCode.US]}
</option>
<option value={Protobuf.RegionCode.Unset}>
{Protobuf.RegionCode[Protobuf.RegionCode.Unset]}
</option>
</select>
</div>
</div>
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
<div className="my-auto">{props.translations.device_wifi_ssid}</div>
<div className="flex shadow-md rounded-md ml-2">
<input {...register('wifiSsid', {})} type="text" />
</div>
</div>
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b">
<div className="my-auto">{props.translations.device_wifi_psk}</div>
<div className="flex shadow-md rounded-md ml-2">
<input {...register('wifiPassword', {})} type="password" />
</div>
</div>
<div className="flex bg-gray-100 group p-1 cursor-pointer hover:bg-gray-200 border-b">
<button
type="submit"
className="flex m-auto font-medium group-hover:text-gray-700"
>
<SaveIcon className="m-auto mr-2 group-hover:text-gray-700 w-5 h-5" />
{props.translations.save_changes_button}
</button>
</div>
<JSONPretty data={preferences} />
</form>
);
};
export default SettingsForm;
+14 -17
View File
@@ -6,19 +6,16 @@ import {
ChevronRightIcon,
UsersIcon,
} from '@heroicons/react/outline';
import type { Types } from '@meshtastic/meshtasticjs';
import type { languageTemplate } from '../../../App';
import Node from './Node';
import { TranslationContext } from '../../../translations/TranslationContext';
import NodeList from './NodeList';
interface NodesProps {
isReady: boolean;
nodes: Types.NodeInfoPacket[];
translations: languageTemplate;
myId: number;
}
const Nodes = (props: NodesProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
return (
<Disclosure>
{({ open }) => (
@@ -31,21 +28,21 @@ const Nodes = (props: NodesProps): JSX.Element => {
<ChevronRightIcon className="my-auto w-5 h-5 mr-2" />
)}
<UsersIcon className="my-auto text-gray-600 mr-2 w-5 h-5" />
{props.translations.nodes_title}
{translations.nodes_title}
</div>
</Disclosure.Button>
<Disclosure.Panel className="shadow-inner">
{props.nodes.length ? (
props.nodes.map((node, index) => (
<Node key={index} node={node} myId={props.myId} />
))
) : (
<div className="flex border-b border-gray-300">
<div className="m-auto p-3 text-gray-500">
{props.translations.no_nodes_message}
<React.Suspense
fallback={
<div className="flex border-b border-gray-300">
<div className="m-auto p-3 text-gray-500">
{translations.no_nodes_message}
</div>
</div>
</div>
)}
}
>
<NodeList myId={props.myId} />
</React.Suspense>
</Disclosure.Panel>
</>
)}
+46
View File
@@ -0,0 +1,46 @@
import React from 'react';
import { useObservableSuspense } from 'observable-hooks';
import type { Types } from '@meshtastic/meshtasticjs';
import { nodeResource } from '../../../streams';
import Node from './Node';
export interface NodeListProps {
myId: number;
}
const NodeList = (props: NodeListProps): JSX.Element => {
const nodeSource = useObservableSuspense(nodeResource);
const [nodes, setNodes] = React.useState<Types.NodeInfoPacket[]>([]);
React.useEffect(() => {
if (
nodes.findIndex(
(currentNode) => currentNode.data.num === nodeSource.data.num,
) >= 0
) {
setNodes(
nodes.map((currentNode) =>
currentNode.data.num === nodeSource.data.num
? nodeSource
: currentNode,
),
);
} else {
setNodes((nodes) => [...nodes, nodeSource]);
}
}, [nodeSource, nodes]);
return (
<>
{nodes.map((node, index) => (
<Node key={index} node={node} myId={props.myId} />
))}
</>
);
};
export default NodeList;
+4 -4
View File
@@ -7,18 +7,19 @@ import {
CogIcon,
} from '@heroicons/react/outline';
import type { LanguageEnum, languageTemplate } from '../../../App';
import type { LanguageEnum } from '../../../translations/TranslationContext';
import { TranslationContext } from '../../../translations/TranslationContext';
import Translations from './Translations';
interface UIProps {
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
translations: languageTemplate;
darkmode: boolean;
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
}
const UI = (props: UIProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
return (
<Disclosure>
{({ open }) => (
@@ -31,14 +32,13 @@ const UI = (props: UIProps): JSX.Element => {
<ChevronRightIcon className="my-auto w-5 h-5 mr-2" />
)}
<CogIcon className="my-auto text-gray-600 mr-2 w-5 h-5" />
{props.translations.ui_settings_title}
{translations.ui_settings_title}
</div>
</Disclosure.Button>
<Disclosure.Panel>
<Translations
language={props.language}
setLanguage={props.setLanguage}
translations={props.translations}
/>
</Disclosure.Panel>
</>
+6 -4
View File
@@ -5,16 +5,18 @@ import { Br, Jp, Us } from 'react-flags-select';
import { Disclosure } from '@headlessui/react';
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline';
import type { languageTemplate } from '../../../App';
import { LanguageEnum } from '../../../App';
import {
LanguageEnum,
TranslationContext,
} from '../../../translations/TranslationContext';
export interface TranslationsProps {
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
translations: languageTemplate;
}
const Translations = (props: TranslationsProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
return (
<Disclosure>
{({ open }) => (
@@ -26,7 +28,7 @@ const Translations = (props: TranslationsProps): JSX.Element => {
) : (
<ChevronRightIcon className="my-auto w-5 h-5 mr-2" />
)}
{props.translations.language_title}
{translations.language_title}
<div className="my-auto">
{props.language === LanguageEnum.ENGLISH ? (
<Us className="ml-2 w-8" />
+15 -1
View File
@@ -7,13 +7,27 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Translations_English from './translations/en';
import {
LanguageEnum,
TranslationContext,
} from './translations/TranslationContext';
const element = document.getElementById('root');
if (element) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
ReactDOM.createRoot(element).render(
<React.StrictMode>
<App />
<TranslationContext.Provider
value={{
language: LanguageEnum.ENGLISH,
translations: Translations_English,
}}
>
<App />
</TranslationContext.Provider>
</React.StrictMode>,
);
}
+17
View File
@@ -0,0 +1,17 @@
import { ObservableResource } from 'observable-hooks';
import { Subject } from 'rxjs';
import type { Protobuf, Types } from '@meshtastic/meshtasticjs';
export const preferencesSubject$ =
new Subject<Protobuf.RadioConfig_UserPreferences>();
export const preferencesResource = new ObservableResource(preferencesSubject$);
export const nodeSubject$ = new Subject<Types.NodeInfoPacket>();
export const nodeResource = new ObservableResource(nodeSubject$);
export const channelSubject$ = new Subject<Protobuf.Channel>();
export const channelResource = new ObservableResource(channelSubject$);
+39
View File
@@ -0,0 +1,39 @@
import React from 'react';
import Translations_English from './en';
export interface languageTemplate {
no_messages_message: string;
ui_settings_title: string;
nodes_title: string;
color_scheme_title: string;
language_title: string;
device_settings_title: string;
device_channels_title: string;
device_region_title: string;
device_wifi_ssid: string;
device_wifi_psk: string;
save_changes_button: string;
no_nodes_message: string;
no_message_placeholder: string;
}
export enum LanguageEnum {
ENGLISH,
JAPANESE,
PORTUGUESE,
}
export const TranslationContext = React.createContext({
language: LanguageEnum.ENGLISH,
translations: Translations_English,
});
// const TranslationProvider: React.FC = ({ children }) => {
// const [language, setLanguage] = React.useState<LanguageEnum>(
// LanguageEnum.ENGLISH,
// );
// return { children };
// };
// export default TranslationProvider;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { languageTemplate } from '../App';
import type { languageTemplate } from './TranslationContext';
export default {
no_messages_message: 'No messages yet',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { languageTemplate } from '../App';
import type { languageTemplate } from './TranslationContext';
export default {
no_messages_message: 'まだメッセージはありません',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { languageTemplate } from '../App';
import type { languageTemplate } from './TranslationContext';
export default {
no_messages_message: 'Não a mensagens ainda',