diff --git a/package.json b/package.json
index 4276f191..62231119 100644
--- a/package.json
+++ b/package.json
@@ -11,12 +11,15 @@
"lint": "eslint 'src/**/*.{ts,tsx}'"
},
"dependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
"@headlessui/react": "^1.3.0",
"@heroicons/react": "^1.0.1",
+ "@material-ui/core": "^5.0.0-beta.3",
"@meshtastic/meshtasticjs": "^0.6.16",
"@reduxjs/toolkit": "^1.6.0",
+ "add": "^2.0.6",
"boring-avatars": "^1.5.8",
- "framer-motion": "^4.1.17",
"i18next": "^20.3.5",
"i18next-browser-languagedetector": "^6.1.2",
"react": "^17.0.2",
@@ -25,7 +28,9 @@
"react-hook-form": "^7.9.0",
"react-i18next": "^11.11.4",
"react-redux": "^7.2.4",
- "type-route": "^0.6.0"
+ "react-select": "^5.0.0-beta.0",
+ "type-route": "^0.6.0",
+ "yarn": "^1.22.11"
},
"devDependencies": {
"@snowpack/plugin-dotenv": "^2.0.5",
diff --git a/public/index.html b/public/index.html
index 916482b3..e3678126 100644
--- a/public/index.html
+++ b/public/index.html
@@ -15,6 +15,10 @@
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap"
rel="stylesheet"
/>
+
{
const dispatch = useAppDispatch();
@@ -126,39 +128,41 @@ const App = (): JSX.Element => {
}, [dispatch, myNodeInfo.myNodeNum]);
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
-
-
- {route.name === 'messages' &&
}
- {route.name === 'nodes' &&
}
- {route.name === 'settings' &&
}
- {route.name === 'about' &&
}
- {route.name === false && 'Not Found'}
+
+
+ {route.name === 'messages' &&
}
+ {route.name === 'nodes' &&
}
+ {route.name === 'settings' &&
}
+ {route.name === 'about' &&
}
+ {route.name === false && 'Not Found'}
+
-
+
);
};
diff --git a/src/components/Sidebar/Device/Settings.tsx b/src/components/Sidebar/Device/Settings.tsx
deleted file mode 100644
index 422ba6c0..00000000
--- a/src/components/Sidebar/Device/Settings.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React from 'react';
-
-import { useForm } from 'react-hook-form';
-import { useTranslation } from 'react-i18next';
-
-import { SaveIcon } from '@heroicons/react/outline';
-import { Protobuf } from '@meshtastic/meshtasticjs';
-
-import { connection } from '../../../connection';
-import { useAppSelector } from '../../../hooks/redux';
-
-export const Settings = (): JSX.Element => {
- const { t } = useTranslation();
- const preferences = useAppSelector((state) => state.meshtastic.preferences);
-
- const { register, handleSubmit } =
- useForm
({
- defaultValues: preferences,
- });
-
- const onSubmit = handleSubmit((data) => connection.setPreferences(data));
- return (
-
- );
-};
diff --git a/src/components/TestForm.tsx b/src/components/TestForm.tsx
new file mode 100644
index 00000000..aca81872
--- /dev/null
+++ b/src/components/TestForm.tsx
@@ -0,0 +1,204 @@
+import React from 'react';
+
+import { Controller, useForm } from 'react-hook-form';
+
+import Button from '@material-ui/core/Button';
+import Checkbox from '@material-ui/core/Checkbox';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Radio from '@material-ui/core/Radio';
+import RadioGroup from '@material-ui/core/RadioGroup';
+import Switch from '@material-ui/core/Switch';
+import TextField from '@material-ui/core/TextField';
+import Typography from '@material-ui/core/Typography';
+
+let renderCount = 0;
+
+const options = [
+ {
+ value: 'chocolate',
+ label: 'Chocolate',
+ },
+ {
+ value: 'strawberry',
+ label: 'Strawberry',
+ },
+ {
+ value: 'vanilla',
+ label: 'Vanilla',
+ },
+];
+
+const defaultValues = {
+ Native: '',
+ TextField: '',
+ Select: '',
+ ReactSelect: '',
+ Checkbox: false,
+ switch: false,
+ RadioGroup: '',
+};
+
+export const TestForm = () => {
+ const { handleSubmit, register, reset, control, watch } = useForm({
+ defaultValues,
+ mode: 'onChange',
+ });
+ renderCount++;
+
+ const data = watch();
+
+ return (
+
+
+
+
+
+ {JSON.stringify(data, null, 2)}
+
+
+
+ Render Count: {renderCount}
+
+
+
+ );
+};
diff --git a/src/components/chat/MessageBar.tsx b/src/components/chat/MessageBar.tsx
index aace3545..1eb8dff2 100644
--- a/src/components/chat/MessageBar.tsx
+++ b/src/components/chat/MessageBar.tsx
@@ -8,9 +8,9 @@ import {
PaperClipIcon,
} from '@heroicons/react/outline';
-import { connection } from '../../connection';
+import { connection } from '../../core/connection';
import { useAppSelector } from '../../hooks/redux';
-import { Button } from '../generic/Button';
+import { IconButton } from '../generic/IconButton';
export const MessageBar = (): JSX.Element => {
const ready = useAppSelector((state) => state.meshtastic.ready);
@@ -25,13 +25,13 @@ export const MessageBar = (): JSX.Element => {
return (
);
diff --git a/src/components/form/Select.tsx b/src/components/form/Select.tsx
index b95a3cdb..ba89734e 100644
--- a/src/components/form/Select.tsx
+++ b/src/components/form/Select.tsx
@@ -1,38 +1,38 @@
import React from 'react';
-type DefaultSelectProps = JSX.IntrinsicElements['select'];
+import type { FieldValues, UseControllerProps } from 'react-hook-form';
+import { Controller } from 'react-hook-form';
+import ReactSelect from 'react-select';
-export interface SelectProps {
+interface SelectProps extends UseControllerProps {
+ label: string;
options: {
value: string;
label: string;
}[];
- label: string;
}
-export const Select = React.forwardRef<
- HTMLSelectElement,
- SelectProps & DefaultSelectProps
->(function Select(
- { options, label, id, ...props }: SelectProps & DefaultSelectProps,
- ref,
-) {
+export const Select = ({
+ name,
+ control,
+ label,
+ options,
+}: SelectProps): JSX.Element => {
return (
-
-
-
-
+ (
+
+
+ {label}
+
+
+
+ )}
+ />
);
-});
+};
diff --git a/src/components/form/Switch.tsx b/src/components/form/Switch.tsx
new file mode 100644
index 00000000..86ee6e98
--- /dev/null
+++ b/src/components/form/Switch.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+
+import type { FieldValues, UseControllerProps } from 'react-hook-form';
+import { Controller } from 'react-hook-form';
+
+import MaterialSwitch from '@material-ui/core/Switch';
+
+interface SwitchProps extends UseControllerProps {
+ label: string;
+}
+
+export const Switch = ({
+ name,
+ control,
+ label,
+}: SwitchProps): JSX.Element => {
+ return (
+ (
+
+
+ {label}
+
+
+ onChange(ev.target.checked)}
+ />
+
+
+ )}
+ />
+ );
+};
diff --git a/src/components/form/Toggle.tsx b/src/components/form/Toggle.tsx
deleted file mode 100644
index 94b50775..00000000
--- a/src/components/form/Toggle.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-
-type DefaultInputProps = JSX.IntrinsicElements['input'];
-
-export interface ToggleProps {
- label: string;
-}
-
-export const Toggle = React.forwardRef<
- HTMLInputElement,
- ToggleProps & DefaultInputProps
->(function Input(
- { label, id, checked, ...props }: ToggleProps & DefaultInputProps,
- ref,
-) {
- return (
-
- );
-});
diff --git a/src/components/generic/Button.tsx b/src/components/generic/Button.tsx
index db847c6e..74aab5f5 100644
--- a/src/components/generic/Button.tsx
+++ b/src/components/generic/Button.tsx
@@ -1,31 +1,28 @@
import React from 'react';
-export interface ButtonProps {
- children: React.ReactNode;
- className?: string;
- clickAction?: () => void;
- type?: 'button' | 'submit' | 'reset' | undefined;
+import MaterialButton from '@material-ui/core/Button';
+import type { ButtonProps as MaterialButtonProps } from '@material-ui/core/Button/Button';
+
+interface LocalButtonProps {
+ text: string;
+ icon?: JSX.Element;
}
-export const Button = ({
- children,
- className,
- clickAction,
- type,
-}: ButtonProps): JSX.Element => {
+export type ButtonProps = MaterialButtonProps & LocalButtonProps;
+
+export const Button = ({ text, icon, ...props }: ButtonProps): JSX.Element => {
return (
-
+
+ {icon &&
+ React.cloneElement(icon, {
+ className: 'h-6 w-6 mr-3 text-gray-500 dark:text-gray-400',
+ })}
+ {text}
+
+
);
};
diff --git a/src/components/generic/Drawer.tsx b/src/components/generic/Drawer.tsx
deleted file mode 100644
index 43061fe6..00000000
--- a/src/components/generic/Drawer.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-
-export interface DrawerProps {
- open: boolean;
- onClose: () => void;
- children: React.ReactNode;
-}
-
-export const Drawer = ({
- open,
- onClose,
- children,
-}: DrawerProps): JSX.Element => {
- return (
- <>
- {open && (
-
- )}
-
-
- >
- );
-};
diff --git a/src/components/generic/IconButton.tsx b/src/components/generic/IconButton.tsx
new file mode 100644
index 00000000..2fbb61ce
--- /dev/null
+++ b/src/components/generic/IconButton.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import MaterialIconButton from '@material-ui/core/IconButton';
+import type { IconButtonProps } from '@material-ui/core/IconButton/IconButton';
+
+export const IconButton = ({
+ children,
+ ...props
+}: IconButtonProps): JSX.Element => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/components/menu/MobileNav.tsx b/src/components/menu/MobileNav.tsx
index 99d97dc5..cb84c596 100644
--- a/src/components/menu/MobileNav.tsx
+++ b/src/components/menu/MobileNav.tsx
@@ -6,13 +6,13 @@ import {
InformationCircleIcon,
ViewGridIcon,
} from '@heroicons/react/outline';
+import SwipeableDrawer from '@material-ui/core/SwipeableDrawer/SwipeableDrawer';
+import { routes } from '../../core/router';
+import { closeMobileNav, openMobileNav } from '../../core/slices/appSlice';
import { useAppDispatch, useAppSelector } from '../../hooks/redux';
-import { routes } from '../../router';
-import { closeMobileNav } from '../../slices/appSlice';
-import { Drawer } from '../generic/Drawer';
+import { Button } from '../generic/Button';
import { Logo } from './Logo';
-import { MenuButton } from './MenuButton';
export const MobileNav = (): JSX.Element => {
const dispatch = useAppDispatch();
@@ -20,49 +20,41 @@ export const MobileNav = (): JSX.Element => {
const mobileNavOpen = useAppSelector((state) => state.app.mobileNavOpen);
return (
- {
dispatch(closeMobileNav());
}}
+ onOpen={() => {
+ dispatch(openMobileNav());
+ }}
>
-
+
-
}
text={'Messages'}
- link={routes.messages().link}
- clickAction={() => {
- dispatch(closeMobileNav());
- }}
+ {...routes.messages().link}
/>
-
}
text={'Nodes'}
- link={routes.nodes().link}
- clickAction={() => {
- dispatch(closeMobileNav());
- }}
+ {...routes.nodes().link}
/>
-
}
text={'Settings'}
- link={routes.settings().link}
- clickAction={() => {
- dispatch(closeMobileNav());
- }}
+ {...routes.settings().link}
/>
-
}
text={'About'}
- link={routes.about().link}
- clickAction={() => {
- dispatch(closeMobileNav());
- }}
+ {...routes.about().link}
/>
-
+
);
};
diff --git a/src/components/menu/Navigation.tsx b/src/components/menu/Navigation.tsx
index 325f869b..a3700328 100644
--- a/src/components/menu/Navigation.tsx
+++ b/src/components/menu/Navigation.tsx
@@ -7,32 +7,32 @@ import {
ViewGridIcon,
} from '@heroicons/react/outline';
-import { routes } from '../../router';
-import { MenuButton } from './MenuButton';
+import { routes } from '../../core/router';
+import { Button } from '../generic/Button';
export const Navigation = (): JSX.Element => {
return (
- }
text={'Messages'}
- link={routes.messages().link}
+ {...routes.messages().link}
/>
- }
text={'Nodes'}
- link={routes.nodes().link}
+ {...routes.nodes().link}
/>
- }
text={'Settings'}
- link={routes.settings().link}
+ {...routes.settings().link}
/>
- }
text={'About'}
- link={routes.about().link}
+ {...routes.about().link}
/>
diff --git a/src/components/menu/buttons/DeviceStatusDropdown.tsx b/src/components/menu/buttons/DeviceStatusDropdown.tsx
index b8e6eed6..eed595fa 100644
--- a/src/components/menu/buttons/DeviceStatusDropdown.tsx
+++ b/src/components/menu/buttons/DeviceStatusDropdown.tsx
@@ -3,14 +3,14 @@ import React from 'react';
import { SwitchVerticalIcon } from '@heroicons/react/outline';
import { useAppSelector } from '../../../hooks/redux';
-import { Button } from '../../generic/Button';
+import { IconButton } from '../../generic/IconButton';
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
index 11b27d20..48ce2ad8 100644
--- a/src/components/menu/buttons/LanguageDropdown.tsx
+++ b/src/components/menu/buttons/LanguageDropdown.tsx
@@ -4,9 +4,9 @@ import { Jp, Pt, Us } from 'react-flags-select';
import { Menu } from '@headlessui/react';
+import i18n from '../../../core/translation';
import { useAppDispatch } from '../../../hooks/redux';
-import i18n from '../../../translation';
-import { Button } from '../../generic/Button';
+import { IconButton } from '../../generic/IconButton';
export const LanguageDropdown = (): JSX.Element => {
const dispatch = useAppDispatch();
@@ -32,11 +32,11 @@ export const LanguageDropdown = (): JSX.Element => {
return (
);
};
diff --git a/src/connection.ts b/src/core/connection.ts
similarity index 100%
rename from src/connection.ts
rename to src/core/connection.ts
diff --git a/src/router.ts b/src/core/router.ts
similarity index 100%
rename from src/router.ts
rename to src/core/router.ts
diff --git a/src/slices/appSlice.ts b/src/core/slices/appSlice.ts
similarity index 100%
rename from src/slices/appSlice.ts
rename to src/core/slices/appSlice.ts
diff --git a/src/slices/meshtasticSlice.ts b/src/core/slices/meshtasticSlice.ts
similarity index 100%
rename from src/slices/meshtasticSlice.ts
rename to src/core/slices/meshtasticSlice.ts
diff --git a/src/store.ts b/src/core/store.ts
similarity index 100%
rename from src/store.ts
rename to src/core/store.ts
diff --git a/src/core/theme.ts b/src/core/theme.ts
new file mode 100644
index 00000000..faa3797f
--- /dev/null
+++ b/src/core/theme.ts
@@ -0,0 +1,28 @@
+import type { Theme } from '@material-ui/core';
+import { createTheme } from '@material-ui/core/styles';
+
+export const theme = (darkMode: boolean): Theme => {
+ return createTheme(
+ darkMode
+ ? {
+ palette: {
+ mode: 'dark',
+ primary: {
+ main: '#67ea94',
+ },
+ background: {
+ default: '#0F172A',
+ paper: '#0F172A',
+ },
+ },
+ }
+ : {
+ palette: {
+ mode: 'light',
+ primary: {
+ main: '#67ea94',
+ },
+ },
+ },
+ );
+};
diff --git a/src/translation.ts b/src/core/translation.ts
similarity index 74%
rename from src/translation.ts
rename to src/core/translation.ts
index e32cf7e8..cd99a95c 100644
--- a/src/translation.ts
+++ b/src/core/translation.ts
@@ -2,9 +2,9 @@ import i18n from 'i18next';
import detector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
-import { en } from './translations/en';
-import { jp } from './translations/jp';
-import { pt } from './translations/pt';
+import { en } from '../translations/en';
+import { jp } from '../translations/jp';
+import { pt } from '../translations/pt';
i18n
.use(detector)
diff --git a/src/hooks/redux.ts b/src/hooks/redux.ts
index 6f02a25a..1579525b 100644
--- a/src/hooks/redux.ts
+++ b/src/hooks/redux.ts
@@ -1,7 +1,7 @@
import type { TypedUseSelectorHook } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
-import type { AppDispatch, RootState } from '../store';
+import type { AppDispatch, RootState } from '../core/store';
export const useAppDispatch = () => useDispatch();
export const useAppSelector: TypedUseSelectorHook = useSelector;
diff --git a/src/index.tsx b/src/index.tsx
index 99b2a251..50fd75c0 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,5 +1,5 @@
import './index.css';
-import './translation';
+import './core/translation';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -7,8 +7,8 @@ import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
-import { RouteProvider } from './router';
-import { store } from './store';
+import { RouteProvider } from './core/router';
+import { store } from './core/store';
ReactDOM.render(
diff --git a/src/pages/About.tsx b/src/pages/About.tsx
index 0af0d231..8b48c746 100644
--- a/src/pages/About.tsx
+++ b/src/pages/About.tsx
@@ -1,11 +1,13 @@
import React from 'react';
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
+import { TestForm } from '../components/TestForm';
export const About = (): JSX.Element => {
return (
Content
+
);
};
diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx
index 6239a32f..faffa10a 100644
--- a/src/pages/Messages.tsx
+++ b/src/pages/Messages.tsx
@@ -5,7 +5,7 @@ import { Protobuf } from '@meshtastic/meshtasticjs';
import { Message } from '../components/chat/Message';
import { MessageBar } from '../components/chat/MessageBar';
-import { Button } from '../components/generic/Button';
+import { IconButton } from '../components/generic/IconButton';
import { useAppSelector } from '../hooks/redux';
export const Messages = (): JSX.Element => {
@@ -29,12 +29,12 @@ export const Messages = (): JSX.Element => {
{channelName()}
-
-
+
@@ -56,6 +56,6 @@ export const Messages = (): JSX.Element => {
);
};
-
;
+;
diff --git a/src/pages/Nodes.tsx b/src/pages/Nodes.tsx
index 0c88f381..f9f12a0d 100644
--- a/src/pages/Nodes.tsx
+++ b/src/pages/Nodes.tsx
@@ -1,17 +1,45 @@
import React from 'react';
+import type { Protobuf } from '@meshtastic/meshtasticjs';
+
import { Node } from '../components/nodes/Node';
+import { NodeDetails } from '../components/nodes/NodeDetails';
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
import { useAppSelector } from '../hooks/redux';
export const Nodes = (): JSX.Element => {
const nodes = useAppSelector((state) => state.meshtastic.nodes);
+ const [currentNode, setCurrentNode] = React.useState<
+ Protobuf.NodeInfo | undefined
+ >();
return (
- {nodes.map((node) => (
-
- ))}
+
+
+ {nodes.map((node) => (
+ {
+ setCurrentNode(node);
+ }}
+ />
+ ))}
+
+
+ {currentNode ? (
+
{
+ setCurrentNode(undefined);
+ }}
+ node={currentNode}
+ />
+ ) : (
+ Node not selected
+ )}
+
+
);
};
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
index 3eacc992..f70a0e21 100644
--- a/src/pages/Settings.tsx
+++ b/src/pages/Settings.tsx
@@ -7,14 +7,14 @@ import { Protobuf } from '@meshtastic/meshtasticjs';
import { Input } from '../components/form/Input';
import { Select } from '../components/form/Select';
-import { Toggle } from '../components/form/Toggle';
+import { Switch } from '../components/form/Switch';
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate';
-import { connection } from '../connection';
-import { useAppDispatch, useAppSelector } from '../hooks/redux';
+import { connection } from '../core/connection';
import {
setHostOverride,
setHostOverrideEnabled,
-} from '../slices/meshtasticSlice';
+} from '../core/slices/meshtasticSlice';
+import { useAppDispatch, useAppSelector } from '../hooks/redux';
export const Settings = (): JSX.Element => {
const { t } = useTranslation();
@@ -26,7 +26,7 @@ export const Settings = (): JSX.Element => {
(state) => state.meshtastic.hostOverrideEnabled,
);
- const { register, handleSubmit } =
+ const { register, handleSubmit, control } =
useForm
({
defaultValues: radioConfig,
});
@@ -65,36 +65,46 @@ export const Settings = (): JSX.Element => {
Node
-
-
-
-
-
-
-
+