Browse Source

WIP

pull/1/head
Sacha Weatherstone 5 years ago
parent
commit
339143ef7f
  1. 5
      package.json
  2. 5
      snowpack.config.mjs
  3. 7
      src/App.tsx
  4. 70
      src/Main.tsx
  5. 4
      src/components/MessageBox.tsx
  6. 20
      src/components/Sidebar.tsx
  7. 4
      src/components/Sidebar/Channels/Index.tsx
  8. 21
      src/components/Sidebar/Device/Index.tsx
  9. 16
      src/components/Sidebar/Device/Settings.tsx
  10. 4
      src/components/Sidebar/Nodes/Index.tsx
  11. 2
      src/components/Sidebar/Nodes/Node.tsx
  12. 12
      src/components/Sidebar/UI/Index.tsx
  13. 26
      src/components/Sidebar/UI/Translations.tsx
  14. 42
      src/hooks/useTranslationsContextValue.ts
  15. 5
      src/index.tsx
  16. 73
      src/translations/TranslationContext.tsx
  17. 41
      src/translations/TranslationsContext.tsx
  18. 2
      src/translations/en.ts
  19. 2
      src/translations/jp.ts
  20. 2
      src/translations/pt.ts
  21. 2
      tsconfig.json
  22. 13
      yarn.lock

5
package.json

@ -19,10 +19,9 @@
"react": "^18.0.0-alpha-dbe3363cc",
"react-dom": "^18.0.0-alpha-dbe3363cc",
"react-flags-select": "^2.1.2",
"react-hook-form": "^7.8.4",
"react-hook-form": "^7.8.6",
"react-json-pretty": "^2.2.0",
"rxjs": "^7.1.0",
"yarn": "^1.22.10"
"rxjs": "^7.1.0"
},
"devDependencies": {
"@snowpack/plugin-dotenv": "^2.0.5",

5
snowpack.config.js → snowpack.config.mjs

@ -1,13 +1,13 @@
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
export default {
mount: {
public: { url: '/', static: true },
src: { url: '/static' },
},
plugins: [
'@snowpack/plugin-postcss',
'@snowpack/plugin-react-refresh',
'@snowpack/plugin-dotenv',
'@snowpack/plugin-postcss',
[
'@snowpack/plugin-typescript',
{
@ -21,6 +21,7 @@ module.exports = {
// {"match": "routes", "src": ".*", "dest": "/index.html"},
],
optimize: {
/* Example: Bundle your final build: */
bundle: true,
sourcemap: false,
splitting: true,

7
src/App.tsx

@ -15,7 +15,6 @@ import {
import Header from './components/Header';
import Main from './Main';
import { channelSubject$, nodeSubject$, preferencesSubject$ } from './streams';
import { LanguageEnum } from './translations/TranslationContext';
const App = (): JSX.Element => {
const [deviceStatus, setDeviceStatus] =
@ -31,10 +30,6 @@ const App = (): JSX.Element => {
const [isReady, setIsReady] = React.useState<boolean>(false);
const [lastMeshInterraction, setLastMeshInterraction] =
React.useState<number>(0);
const [language, setLanguage] = React.useState<LanguageEnum>(
LanguageEnum.ENGLISH,
);
const [darkmode, setDarkmode] = React.useState<boolean>(false);
React.useEffect(() => {
@ -115,8 +110,6 @@ const App = (): JSX.Element => {
isReady={isReady}
myNodeInfo={myNodeInfo}
connection={connection}
language={language}
setLanguage={setLanguage}
darkmode={darkmode}
setDarkmode={setDarkmode}
/>

70
src/Main.tsx

@ -11,21 +11,21 @@ import type {
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';
import { useTranslationsContextValue } from './hooks/useTranslationsContextValue';
import { TranslationsContext } from './translations/TranslationsContext';
interface MainProps {
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
myNodeInfo: Protobuf.MyNodeInfo;
isReady: boolean;
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
darkmode: boolean;
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
}
const Main = (props: MainProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const translationsContextValue = useTranslationsContextValue();
const { translations } = React.useContext(TranslationsContext);
const [messages, setMessages] = React.useState<
{ message: Types.TextPacket; ack: boolean }[]
>([]);
@ -65,41 +65,39 @@ const Main = (props: MainProps): JSX.Element => {
}, [props.connection, messages]);
return (
<div className="flex flex-col md:flex-row flex-grow m-3 space-y-2 md:space-y-0 space-x-0 md:space-x-2">
<div className="flex flex-col flex-grow container mx-auto">
<div className="flex flex-col flex-grow py-6 space-y-2">
{messages.length ? (
messages.map((message, Main) => (
<ChatMessage
key={Main}
message={message}
myId={props.myNodeInfo.myNodeNum}
/>
))
) : (
<div className="m-auto text-2xl text-gray-500">
{translations.no_messages_message}
</div>
)}
<TranslationsContext.Provider value={translationsContextValue}>
<div className="flex flex-col md:flex-row flex-grow m-3 space-y-2 md:space-y-0 space-x-0 md:space-x-2">
<div className="flex flex-col flex-grow container mx-auto">
<div className="flex flex-col flex-grow py-6 space-y-2">
{messages.length ? (
messages.map((message, Main) => (
<ChatMessage
key={Main}
message={message}
myId={props.myNodeInfo.myNodeNum}
/>
))
) : (
<div className="m-auto text-2xl text-gray-500">
{translations.no_messages_message}
</div>
)}
</div>
<MessageBox
connection={props.connection}
isReady={props.isReady}
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
/>
</div>
<MessageBox
connection={props.connection}
isReady={props.isReady}
<Sidebar
myId={props.myNodeInfo.myNodeNum}
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
darkmode={props.darkmode}
setDarkmode={props.setDarkmode}
/>
</div>
<Sidebar
isReady={props.isReady}
connection={props.connection}
language={props.language}
setLanguage={props.setLanguage}
myId={props.myNodeInfo.myNodeNum}
sidebarOpen={sidebarOpen}
darkmode={props.darkmode}
setDarkmode={props.setDarkmode}
/>
</div>
</TranslationsContext.Provider>
);
};

4
src/components/MessageBox.tsx

@ -7,7 +7,7 @@ import type {
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import { TranslationContext } from '../translations/TranslationContext';
import { TranslationsContext } from '../translations/TranslationsContext';
export interface MessageBoxProps {
sidebarOpen: boolean;
@ -17,7 +17,7 @@ export interface MessageBoxProps {
}
const MessageBox = (props: MessageBoxProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const { translations } = React.useContext(TranslationsContext);
const [currentMessage, setCurrentMessage] = React.useState('');
const sendMessage = () => {
if (props.isReady) {

20
src/components/Sidebar.tsx

@ -1,22 +1,11 @@
import React from 'react';
import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import type { LanguageEnum } from '../translations/TranslationContext';
import Channels from './Sidebar/Channels/Index';
import Device from './Sidebar/Device/Index';
import Nodes from './Sidebar/Nodes/Index';
import UI from './Sidebar/UI/Index';
interface SidebarProps {
isReady: boolean;
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
myId: number;
sidebarOpen: boolean;
darkmode: boolean;
@ -31,15 +20,10 @@ const Sidebar = (props: SidebarProps): JSX.Element => {
} flex-col rounded-md md:ml-0 shadow-md border w-full max-w-sm`}
>
<Nodes myId={props.myId} />
<Device isReady={props.isReady} connection={props.connection} />
<Device />
<Channels />
<div className="flex-grow border-b"></div>
<UI
language={props.language}
setLanguage={props.setLanguage}
darkmode={props.darkmode}
setDarkmode={props.setDarkmode}
/>
<UI darkmode={props.darkmode} setDarkmode={props.setDarkmode} />
</div>
);
};

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

@ -7,11 +7,11 @@ import {
HashtagIcon,
} from '@heroicons/react/outline';
import { TranslationContext } from '../../../translations/TranslationContext';
import { TranslationsContext } from '../../../translations/TranslationsContext';
import ChannelList from './ChannelList';
const Channels = (): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const { translations } = React.useContext(TranslationsContext);
return (
<Disclosure>
{({ open }) => (

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

@ -6,22 +6,12 @@ import {
ChevronDownIcon,
ChevronRightIcon,
} from '@heroicons/react/outline';
import type {
IBLEConnection,
IHTTPConnection,
ISerialConnection,
} from '@meshtastic/meshtasticjs';
import { TranslationContext } from '../../../translations/TranslationContext';
import { TranslationsContext } from '../../../translations/TranslationsContext';
import Settings from './Settings';
interface DeviceProps {
isReady: boolean;
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
}
const Device = (props: DeviceProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const Device = (): JSX.Element => {
const { translations } = React.useContext(TranslationsContext);
return (
<Disclosure>
{({ open }) => (
@ -46,10 +36,7 @@ const Device = (props: DeviceProps): JSX.Element => {
</div>
}
>
<Settings
connection={props.connection}
isReady={props.isReady}
/>
<Settings />
</React.Suspense>
</>
</Disclosure.Panel>

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

@ -5,23 +5,13 @@ 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 { preferencesResource } from '../../../streams';
import { TranslationContext } from '../../../translations/TranslationContext';
import { TranslationsContext } from '../../../translations/TranslationsContext';
export interface SettingsProps {
isReady: boolean;
connection: ISerialConnection | IHTTPConnection | IBLEConnection;
}
const Settings = (props: SettingsProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const Settings = (): JSX.Element => {
const { translations } = React.useContext(TranslationsContext);
const preferences = useObservableSuspense(preferencesResource);
const { register, handleSubmit } =

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

@ -7,7 +7,7 @@ import {
UsersIcon,
} from '@heroicons/react/outline';
import { TranslationContext } from '../../../translations/TranslationContext';
import { TranslationsContext } from '../../../translations/TranslationsContext';
import NodeList from './NodeList';
interface NodesProps {
@ -15,7 +15,7 @@ interface NodesProps {
}
const Nodes = (props: NodesProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const { translations } = React.useContext(TranslationsContext);
return (
<Disclosure>
{({ open }) => (

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

@ -36,7 +36,7 @@ const Node = (props: NodeProps): JSX.Element => {
) : null}
<Avatar
size={30}
name={props.node.data.user?.longName}
name={props.node.data.user?.longName ?? 'Unknown'}
variant="beam"
colors={[
'#213435',

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

@ -7,19 +7,16 @@ import {
CogIcon,
} from '@heroicons/react/outline';
import type { LanguageEnum } from '../../../translations/TranslationContext';
import { TranslationContext } from '../../../translations/TranslationContext';
import { TranslationsContext } from '../../../translations/TranslationsContext';
import Translations from './Translations';
interface UIProps {
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
darkmode: boolean;
setDarkmode: React.Dispatch<React.SetStateAction<boolean>>;
}
const UI = (props: UIProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const { translations } = React.useContext(TranslationsContext);
return (
<Disclosure>
{({ open }) => (
@ -36,10 +33,7 @@ const UI = (props: UIProps): JSX.Element => {
</div>
</Disclosure.Button>
<Disclosure.Panel>
<Translations
language={props.language}
setLanguage={props.setLanguage}
/>
<Translations />
</Disclosure.Panel>
</>
)}

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

@ -7,16 +7,12 @@ import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/outline';
import {
LanguageEnum,
TranslationContext,
} from '../../../translations/TranslationContext';
TranslationsContext,
} from '../../../translations/TranslationsContext';
export interface TranslationsProps {
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
}
const Translations = (props: TranslationsProps): JSX.Element => {
const { translations } = React.useContext(TranslationContext);
const Translations = (): JSX.Element => {
const { translations, language, setLanguage } =
React.useContext(TranslationsContext);
return (
<Disclosure>
{({ open }) => (
@ -30,11 +26,11 @@ const Translations = (props: TranslationsProps): JSX.Element => {
)}
{translations.language_title}
<div className="my-auto">
{props.language === LanguageEnum.ENGLISH ? (
{language === LanguageEnum.ENGLISH ? (
<Us className="ml-2 w-8" />
) : props.language === LanguageEnum.JAPANESE ? (
) : language === LanguageEnum.JAPANESE ? (
<Jp className="ml-2 w-8" />
) : props.language === LanguageEnum.PORTUGUESE ? (
) : language === LanguageEnum.PORTUGUESE ? (
<Br className="ml-2 w-8" />
) : null}
</div>
@ -44,7 +40,7 @@ const Translations = (props: TranslationsProps): JSX.Element => {
<div
className="flex bg-gray-100 hover:bg-gray-200 cursor-pointer justify-between p-2"
onClick={() => {
props.setLanguage(LanguageEnum.ENGLISH);
setLanguage(LanguageEnum.ENGLISH);
}}
>
English <Us className="w-8 my-auto" />
@ -52,7 +48,7 @@ const Translations = (props: TranslationsProps): JSX.Element => {
<div
className="flex bg-gray-100 hover:bg-gray-200 cursor-pointer justify-between p-2"
onClick={() => {
props.setLanguage(LanguageEnum.PORTUGUESE);
setLanguage(LanguageEnum.PORTUGUESE);
}}
>
Português <Br className="w-8 my-auto" />
@ -60,7 +56,7 @@ const Translations = (props: TranslationsProps): JSX.Element => {
<div
className="flex bg-gray-100 hover:bg-gray-200 cursor-pointer justify-between p-2"
onClick={() => {
props.setLanguage(LanguageEnum.JAPANESE);
setLanguage(LanguageEnum.JAPANESE);
}}
>
<Jp className="w-8 my-auto" />

42
src/hooks/useTranslationsContextValue.ts

@ -0,0 +1,42 @@
import React from 'react';
import Translations_EN from '../translations/en';
import Translations_JP from '../translations/jp';
import Translations_PT from '../translations/pt';
import type {
languageTemplate,
TranslationsContextData,
} from '../translations/TranslationsContext';
import { LanguageEnum } from '../translations/TranslationsContext';
export const useTranslationsContextValue = (): TranslationsContextData => {
const [currentLanguage, setcurrentLanguage] = React.useState<LanguageEnum>(
LanguageEnum.ENGLISH,
);
const [translation, setTranslation] =
React.useState<languageTemplate>(Translations_EN);
const setLanguage = React.useCallback(
(language: LanguageEnum) => {
setcurrentLanguage(language);
switch (language) {
case LanguageEnum.ENGLISH:
setTranslation(Translations_EN);
break;
case LanguageEnum.JAPANESE:
setTranslation(Translations_JP);
break;
case LanguageEnum.PORTUGUESE:
setTranslation(Translations_PT);
break;
}
},
[setcurrentLanguage, setTranslation],
);
return {
language: currentLanguage,
setLanguage: setLanguage,
translations: translation,
};
};

5
src/index.tsx

@ -4,7 +4,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { TranslationContext } from './translations/TranslationContext';
const rootElement = document.getElementById('root');
if (!rootElement) throw new Error('Failed to find the root element');
@ -12,9 +11,7 @@ const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<TranslationContext>
<App />
</TranslationContext>
<App />
</React.StrictMode>,
);

73
src/translations/TranslationContext.tsx

@ -1,73 +0,0 @@
import React from 'react';
import Translations_EN from './en';
import Translations_JP from './jp';
import Translations_PT from './pt';
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,
}
const Context = React.createContext<{
language: LanguageEnum;
setLanguage: React.Dispatch<React.SetStateAction<LanguageEnum>>;
translations: languageTemplate;
}>({
language: LanguageEnum.ENGLISH,
setLanguage: () => {},
translations: Translations_EN,
});
export const TranslationContext = ({
children,
}: {
children: React.ReactNode;
}): JSX.Element => {
const [language, setLanguage] = React.useState<LanguageEnum>(
LanguageEnum.ENGLISH,
);
const [translation, setTranslation] =
React.useState<languageTemplate>(Translations_EN);
React.useEffect(() => {
switch (language) {
case LanguageEnum.ENGLISH:
setTranslation(Translations_EN);
break;
case LanguageEnum.JAPANESE:
setTranslation(Translations_JP);
break;
case LanguageEnum.PORTUGUESE:
setTranslation(Translations_PT);
break;
}
}, [language]);
return (
<Context.Provider
value={{
language: language,
setLanguage: setLanguage,
translations: translation,
}}
>
{children}
</Context.Provider>
);
};

41
src/translations/TranslationsContext.tsx

@ -0,0 +1,41 @@
import React from 'react';
import Translations_EN 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 interface TranslationsContextData {
language: LanguageEnum;
setLanguage: (postId: number) => void;
translations: languageTemplate;
}
export const translationsContextDefaultValue: TranslationsContextData = {
language: LanguageEnum.ENGLISH,
setLanguage: () => null,
translations: Translations_EN,
};
export const TranslationsContext = React.createContext<TranslationsContextData>(
translationsContextDefaultValue,
);

2
src/translations/en.ts

@ -1,4 +1,4 @@
import type { languageTemplate } from './TranslationContext';
import type { languageTemplate } from './TranslationsContext';
export default {
no_messages_message: 'No messages yet',

2
src/translations/jp.ts

@ -1,4 +1,4 @@
import type { languageTemplate } from './TranslationContext';
import type { languageTemplate } from './TranslationsContext';
export default {
no_messages_message: 'まだメッセージはありません',

2
src/translations/pt.ts

@ -1,4 +1,4 @@
import type { languageTemplate } from './TranslationContext';
import type { languageTemplate } from './TranslationsContext';
export default {
no_messages_message: 'Não a mensagens ainda',

2
tsconfig.json

@ -6,7 +6,6 @@
"moduleResolution": "node",
"jsx": "preserve",
"baseUrl": "./",
"types": ["react/next", "react-dom/next"],
/* paths - import rewriting/resolving */
"paths": {
// If you configured any Snowpack aliases, add them here.
@ -20,6 +19,7 @@
"strict": true,
"strictNullChecks": true,
"skipLibCheck": true,
"types": ["react/next", "react-dom/next", "snowpack-env"],
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,

13
yarn.lock

@ -3302,10 +3302,10 @@ react-flags-select@^2.1.2:
dependencies:
classnames "^2.2.6"
react-hook-form@^7.8.4:
version "7.8.4"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.8.4.tgz#4405b242db7b946f387f2a6c01f92390a2564893"
integrity sha512-hVh7uAJZS4/c20kNjW4rW+pD7+4RhuMDF3qY82f8wFuuPkoO+Pg5JHSJUqGdPdlI82snOlZ4DK/avIBn5SxItg==
react-hook-form@^7.8.6:
version "7.8.6"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.8.6.tgz#93335fbada5eae3ddee8f6dffe5bac868295ec4d"
integrity sha512-kKdaySvv21tyH8WM5jYpKRxTFdknmHi7ssdBrU7cW0qRwXcr9lxVyO14MjUFlfEGe9S5VzBmTz7NKNSarV3AFw==
react-is@^16.8.1:
version "16.13.1"
@ -4156,8 +4156,3 @@ yargs@^16.0.0:
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yarn@^1.22.10:
version "1.22.10"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.10.tgz#c99daa06257c80f8fa2c3f1490724e394c26b18c"
integrity sha512-IanQGI9RRPAN87VGTF7zs2uxkSyQSrSPsju0COgbsKQOOXr5LtcVPeyXWgwVa0ywG3d8dg6kSYKGBuYK021qeA==

Loading…
Cancel
Save