Browse Source

WIP

pull/1/head
Sacha Weatherstone 5 years ago
parent
commit
3984beb1ff
  1. 4
      README.md
  2. 14
      package.json
  3. 5254
      pnpm-lock.yaml
  4. 10
      public/index.html
  5. 4
      src/App.tsx
  6. 4
      src/components/generic/Blur.tsx
  7. 4
      src/components/generic/Button.tsx
  8. 158
      src/components/generic/Chart.tsx
  9. 3
      src/components/generic/Drawer.tsx
  10. 21
      src/components/generic/Input.tsx
  11. 15
      src/components/generic/Select.tsx
  12. 6
      src/components/generic/SidebarItem.tsx
  13. 92
      src/components/generic/Tabs.tsx
  14. 51
      src/components/generic/Toggle.tsx
  15. 9
      src/components/nodes/Node.tsx
  16. 2
      src/components/templates/PrimaryTemplate.tsx
  17. 2
      src/hooks/breakpoint.ts
  18. 119
      src/pages/Nodes/Node.tsx
  19. 72
      src/pages/settings/Connection.tsx
  20. 7
      src/pages/settings/Device.tsx
  21. 19
      src/pages/settings/Index.tsx
  22. 6
      src/pages/settings/Interface.tsx
  23. 10
      src/pages/settings/Radio.tsx
  24. 5133
      yarn.lock

4
README.md

@ -11,11 +11,11 @@ Official [Meshtastic](https://meshtastic.org) web interface, that can be run ind
Build the project:
```bash
yarn build
pnpm build
```
GZip the output:
```bash
yarn package
pnpm package
```

14
package.json

@ -6,27 +6,29 @@
"scripts": {
"start": "NODE_ENV=development snowpack dev",
"build": "snowpack build",
"package": "yarn gzipper c -i html,js,css build build/output",
"package": "pnpm gzipper c -i html,js,css build build/output",
"format": "prettier --write 'src/**/*.{ts,tsx}'",
"lint": "eslint 'src/**/*.{ts,tsx}'"
},
"dependencies": {
"@headlessui/react": "^1.4.0",
"@heroicons/react": "^1.0.1",
"@meshtastic/meshtasticjs": "^0.6.16",
"@meshtastic/meshtasticjs": "^0.6.17",
"@reduxjs/toolkit": "^1.6.0",
"apexcharts": "^3.27.3",
"boring-avatars": "^1.5.8",
"i18next": "^20.3.5",
"i18next-browser-languagedetector": "^6.1.2",
"moment": "^2.29.1",
"react": "^17.0.2",
"react-apexcharts": "^1.3.9",
"react-dom": "^17.0.2",
"react-flags-select": "^2.1.2",
"react-hook-form": "^7.9.0",
"react-hook-form": "^7.13.0-next.5",
"react-i18next": "^11.11.4",
"react-redux": "^7.2.4",
"type-route": "^0.6.0",
"use-breakpoint": "^2.0.1",
"yarn": "^1.22.11"
"use-breakpoint": "^2.0.1"
},
"devDependencies": {
"@snowpack/plugin-dotenv": "^2.0.5",
@ -46,7 +48,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-babel-module": "^5.3.1",
"eslint-import-resolver-typescript": "^2.4.0",
"eslint-plugin-import": "^2.24.0",
"eslint-plugin-import": "^2.24.1",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"gzipper": "^5.0.0",

5254
pnpm-lock.yaml

File diff suppressed because it is too large

10
public/index.html

@ -34,15 +34,5 @@
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/static/index.js"></script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

4
src/App.tsx

@ -137,7 +137,7 @@ const App = (): JSX.Element => {
>
<div className="flex flex-col h-full bg-gray-200 dark:bg-primaryDark">
<div className="flex flex-shrink-0 overflow-hidden bg-primary dark:bg-primary">
<div className="w-full overflow-hidden bg-white border-b md:mt-12 md:mx-8 md:pt-4 md:pb-3 md:rounded-t-xl dark:border-gray-600 md:shadow-md dark:bg-primaryDark">
<div className="w-full overflow-hidden bg-white border-b md:mt-12 md:mx-8 md:pt-4 md:pb-3 md:rounded-t-3xl dark:border-gray-600 md:shadow-md dark:bg-primaryDark">
<div className="flex items-center justify-between h-16 px-4 md:px-6">
<div className="hidden md:flex">
<Logo />
@ -156,7 +156,7 @@ const App = (): JSX.Element => {
<MobileNav />
<div className="flex flex-grow w-full min-h-0 md:px-8 md:mb-8">
<div className="flex w-full bg-gray-100 md:shadow-xl md:overflow-hidden dark:bg-secondaryDark md:rounded-b-xl">
<div className="flex w-full bg-gray-100 md:shadow-xl md:overflow-hidden dark:bg-secondaryDark md:rounded-b-3xl">
{route.name === 'messages' && <Messages />}
{route.name === 'nodes' && <Nodes />}
{route.name === 'settings' && <Settings />}

4
src/components/generic/Blur.tsx

@ -2,12 +2,10 @@ import React from 'react';
type DefaultDivProps = JSX.IntrinsicElements['div'];
interface LocalBlurProps {
interface BlurProps extends DefaultDivProps {
disableOnMd?: boolean;
}
export type BlurProps = LocalBlurProps & DefaultDivProps;
export const Blur = ({
disableOnMd,
className,

4
src/components/generic/Button.tsx

@ -2,15 +2,13 @@ import React from 'react';
type DefaultButtonProps = JSX.IntrinsicElements['button'];
interface LocalButtonProps {
interface ButtonProps extends DefaultButtonProps {
icon?: JSX.Element;
circle?: boolean;
active?: boolean;
border?: boolean;
}
export type ButtonProps = LocalButtonProps & DefaultButtonProps;
export const Button = ({
icon,
circle,

158
src/components/generic/Chart.tsx

@ -0,0 +1,158 @@
import React from 'react';
import ApexChart from 'react-apexcharts';
import { Button } from './Button';
type DefaultDivProps = JSX.IntrinsicElements['div'];
interface ISeries {
name: string;
data: {
x: string | Date;
y: number;
}[];
}
interface ChartProps extends DefaultDivProps {
title: string;
description: string;
hasMultipleSeries: boolean;
series: ISeries[];
}
export const Chart = ({
title,
description,
hasMultipleSeries,
series,
...props
}: ChartProps): JSX.Element => {
const [activeSeries, setActiveSeries] = React.useState<ISeries>(series[0]);
return (
<div
className="flex flex-col flex-auto text-white shadow-md dark bg-primaryDark rounded-3xl"
{...props}
>
<div className="flex items-center justify-between mx-10 mt-10">
<div className="flex flex-col">
<div className="mr-4 text-2xl font-semibold leading-7 tracking-tight md:text-3xl">
{title}
</div>
<div className="font-medium text-gray-400">{description}</div>
</div>
{hasMultipleSeries && (
<div className="flex space-x-2">
{series.map((data, index) => (
<Button
active={data.name === activeSeries.name}
key={index}
className="font-medium"
onClick={(): void => {
setActiveSeries(series[index]);
}}
>
{data.name}
</Button>
))}
</div>
)}
</div>
<div className="h-80">
<ApexChart
height="96%"
type="area"
options={{
chart: {
animations: {
speed: 400,
animateGradually: {
enabled: false,
},
},
toolbar: {
show: false,
},
zoom: {
enabled: false,
},
},
colors: ['#818CF8'],
dataLabels: {
enabled: false,
},
fill: {
colors: ['#312E81'],
},
grid: {
padding: {
top: 10,
left: 0,
right: 0,
},
xaxis: {
lines: {
show: false,
},
},
yaxis: {
lines: {
show: false,
},
},
},
stroke: {
width: 2,
},
tooltip: {
followCursor: true,
theme: 'dark',
x: {
format: 'MMM dd, yyyy',
},
y: {
formatter: (value: number): string => `${value}`,
},
},
xaxis: {
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
crosshairs: {
stroke: {
color: '#475569',
dashArray: 0,
width: 2,
},
},
labels: {
style: {
colors: '#CBD5E1',
},
},
tooltip: {
enabled: false,
},
type: 'datetime',
},
yaxis: {
axisTicks: {
show: false,
},
axisBorder: {
show: false,
},
show: false,
},
}}
series={[activeSeries]}
/>
</div>
</div>
);
};

3
src/components/generic/Drawer.tsx

@ -4,12 +4,11 @@ import { Blur } from '@components/generic/Blur';
type DefaultAsideProps = JSX.IntrinsicElements['aside'];
interface LocalDrawerProps {
interface DrawerProps extends DefaultAsideProps {
open: boolean;
permenant?: boolean;
onClose: () => void;
}
export type DrawerProps = LocalDrawerProps & DefaultAsideProps;
export const Drawer = ({
open,

21
src/components/generic/Input.tsx

@ -2,18 +2,24 @@ import React from 'react';
type DefaultInputProps = JSX.IntrinsicElements['input'];
interface LocalInputProps {
interface InputProps extends DefaultInputProps {
icon?: JSX.Element;
label?: string;
valid?: boolean;
validationMessage?: string;
}
export type InputProps = LocalInputProps & DefaultInputProps;
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
function Input(
{ icon, label, valid, validationMessage, id, ...props }: InputProps,
{
icon,
label,
valid,
validationMessage,
id,
disabled,
...props
}: InputProps,
ref,
) {
return (
@ -35,9 +41,14 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
id={id}
ref={ref}
disabled={disabled}
{...props}
className={`block w-full h-11 rounded-md border shadow-sm focus:outline-none focus:border-primary dark:focus:border-primary bg-white dark:bg-secondaryDark dark:border-gray-600 dark:text-white ${
className={`block w-full h-11 rounded-md border shadow-sm focus:outline-none focus:border-primary dark:focus:border-primary dark:border-gray-600 dark:text-white ${
icon ? 'pl-9' : 'pl-2'
} ${
disabled
? 'bg-gray-200 dark:bg-primaryDark cursor-not-allowed'
: 'bg-white dark:bg-secondaryDark'
}`}
/>
</div>

15
src/components/generic/Select.tsx

@ -11,7 +11,11 @@ export interface SelectProps {
icon: JSX.Element;
}[];
id: string;
value: string;
active: {
name: string;
value: string;
icon: JSX.Element;
};
onChange: (value: string) => void;
}
@ -19,7 +23,7 @@ export const Select = ({
label,
options,
id,
value,
active,
onChange,
}: SelectProps): JSX.Element => {
return (
@ -28,10 +32,11 @@ export const Select = ({
{label}
</label>
<Listbox value={value} onChange={onChange}>
<Listbox value={active.value} onChange={onChange}>
<div className="relative mt-1">
<Listbox.Button className="relative w-full text-left bg-white border rounded-md shadow-sm h-11 focus:outline-none focus:border-primary dark:focus:border-primary dark:bg-secondaryDark dark:border-gray-600 dark:text-white">
<span className="block truncate">{value}</span>
<Listbox.Button className="flex w-full text-left bg-white border rounded-md shadow-sm h-11 focus:outline-none focus:border-primary dark:focus:border-primary dark:bg-secondaryDark dark:border-gray-600 dark:text-white">
<div className="">{active.icon}</div>
<span className="block truncate">{active.name}</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
className="w-5 h-5 text-gray-400"

6
src/components/generic/SidebarItem.tsx

@ -2,26 +2,26 @@ import React from 'react';
type DefaultDivProps = JSX.IntrinsicElements['div'];
interface LocalSidebarItemProps {
interface SidebarItemProps extends DefaultDivProps {
title: string;
description: string;
selected: boolean;
icon: JSX.Element;
}
export type SidebarItemProps = LocalSidebarItemProps & DefaultDivProps;
export const SidebarItem = ({
title,
description,
selected,
icon,
...props
}: SidebarItemProps): JSX.Element => {
return (
<div
className={`flex p-5 cursor-pointer select-none dark:hover:bg-primaryDark ${
selected ? 'bg-gray-200 dark:bg-primaryDark' : 'dark:bg-secondaryDark'
}`}
{...props}
>
<div className="text-gray-500 dark:text-gray-400">{icon}</div>
<div className="ml-3 text-left">

92
src/components/generic/Tabs.tsx

@ -0,0 +1,92 @@
import React from 'react';
import { Tab } from '@headlessui/react';
type DefaultDivProps = JSX.IntrinsicElements['div'];
interface TabProps extends DefaultDivProps {
tabs: {
name: string;
body: JSX.Element;
}[];
}
export const Tabs = ({ tabs }: TabProps) => {
// let [categories] = useState({
// Recent: [
// {
// id: 1,
// title: 'Does drinking coffee make you smarter?',
// date: '5h ago',
// commentCount: 5,
// shareCount: 2,
// },
// {
// id: 2,
// title: "So you've bought coffee... now what?",
// date: '2h ago',
// commentCount: 3,
// shareCount: 2,
// },
// ],
// Popular: [
// {
// id: 1,
// title: 'Is tech making coffee better or worse?',
// date: 'Jan 7',
// commentCount: 29,
// shareCount: 16,
// },
// {
// id: 2,
// title: 'The most innovative things happening in coffee',
// date: 'Mar 19',
// commentCount: 24,
// shareCount: 12,
// },
// ],
// Trending: [
// {
// id: 1,
// title: 'Ask Me Anything: 10 answers to your questions about coffee',
// date: '2d ago',
// commentCount: 9,
// shareCount: 5,
// },
// {
// id: 2,
// title: "The worst advice we've ever heard about coffee",
// date: '4d ago',
// commentCount: 1,
// shareCount: 2,
// },
// ],
// })
return (
<Tab.Group as="div">
<Tab.List className="flex p-2 space-x-2 border shadow-md rounded-t-3xl dark:border-gray-600">
{tabs.map((tab) => (
<Tab
key={tab.name}
className={({ selected }) => `w-full text-lg font-medium`}
>
{tab.name}
</Tab>
))}
</Tab.List>
<Tab.Panels>
{tabs.map((tab, index) => (
<Tab.Panel
key={index}
className={
'border dark:border-gray-600 rounded-b-3xl p-2 h-80 shadow-md'
}
>
{tab.body}
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
);
};

51
src/components/generic/Toggle.tsx

@ -0,0 +1,51 @@
import React from 'react';
import { Switch } from '@headlessui/react';
type DefaultButtonProps = JSX.IntrinsicElements['button'];
interface ToggleProps extends DefaultButtonProps {
label?: string;
valid?: boolean;
validationMessage?: string;
}
export const Toggle = ({
label,
valid,
validationMessage,
id,
...props
}: ToggleProps): JSX.Element => {
const [enabled, setEnabled] = React.useState(false);
return (
<div className="w-full">
<label htmlFor={id} className="block text-sm font-medium dark:text-white">
{label}
</label>
<div className="float-right">
<Switch
id={id}
{...props}
checked={enabled}
onChange={setEnabled}
className={`${
enabled ? 'bg-primary' : 'bg-gray-200 dark:bg-primaryDark'
}
relative inline-flex flex-shrink-0 h-[38px] w-[74px] border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`}
>
<span className="sr-only">Use setting</span>
<span
aria-hidden="true"
className={`${enabled ? 'translate-x-9' : 'translate-x-0'}
pointer-events-none inline-block h-[34px] w-[34px] rounded-full bg-white shadow-lg transform ring-0 transition ease-in-out duration-200`}
/>
</Switch>
</div>
{!valid && (
<div className="text-sm text-gray-600">{validationMessage}</div>
)}
</div>
);
};

9
src/components/nodes/Node.tsx

@ -6,18 +6,15 @@ import type { Protobuf } from '@meshtastic/meshtasticjs';
type DefaultDivProps = JSX.IntrinsicElements['div'];
export interface NodeProps {
export interface NodeProps extends DefaultDivProps {
node: Protobuf.NodeInfo;
}
export const Node = ({
node,
...props
}: NodeProps & DefaultDivProps): JSX.Element => {
export const Node = ({ node, ...props }: NodeProps): JSX.Element => {
return (
<div
{...props}
className="flex space-x-4 items-center w-full rounded-md dark:bg-primaryDark shadow-md border dark:border-gray-600 p-2 mt-6 dark:text-white hover:bg-gray-200 dark:hover:bg-gray-900"
className="flex items-center w-full p-2 mt-6 space-x-4 border rounded-md shadow-md dark:bg-primaryDark dark:border-gray-600 dark:text-white hover:bg-gray-200 dark:hover:bg-gray-900"
>
<Avatar
size={30}

2
src/components/templates/PrimaryTemplate.tsx

@ -17,7 +17,7 @@ export const PrimaryTemplate = ({
}: PrimaryTemplateProps): JSX.Element => {
return (
<div className="flex flex-col flex-auto min-w-0">
<div className="flex p-6 bg-white border-b md:flex-row flex-0 md:items-center md:justify-between md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
<div className="flex px-6 py-2 bg-white border-b md:p-6 md:flex-row flex-0 md:items-center md:justify-between md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
{button && <div className="pr-2 m-auto md:hidden">{button}</div>}
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center font-medium">

2
src/hooks/breakpoint.ts

@ -3,7 +3,7 @@
import useBreakpointHook from 'use-breakpoint';
const BREAKPOINTS = {
sm: 640,
sm: 0,
// => @media (min-width: 640px) { ... }
md: 768,

119
src/pages/Nodes/Node.tsx

@ -1,5 +1,8 @@
import React from 'react';
import moment from 'moment';
import { Chart } from '@app/components/generic/Chart';
import { Button } from '@components/generic/Button';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { MenuIcon } from '@heroicons/react/outline';
@ -26,7 +29,121 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
/>
}
>
<div className="w-full max-w-3xl space-y-2 md:max-w-xl">Content</div>
<div className="w-full space-y-2">
<Chart
title="Visitors Overview"
description="Number of unique visitors"
hasMultipleSeries={true}
series={[
{
name: 'Series 1',
data: [
{
x: moment().subtract(12, 'months').day(1).toDate(),
y: 4884,
},
{
x: moment().subtract(12, 'months').day(4).toDate(),
y: 5351,
},
{
x: moment().subtract(12, 'months').day(7).toDate(),
y: 5293,
},
{
x: moment().subtract(12, 'months').day(10).toDate(),
y: 4908,
},
{
x: moment().subtract(12, 'months').day(13).toDate(),
y: 5027,
},
{
x: moment().subtract(12, 'months').day(16).toDate(),
y: 4837,
},
{
x: moment().subtract(12, 'months').day(19).toDate(),
y: 4484,
},
{
x: moment().subtract(12, 'months').day(22).toDate(),
y: 4071,
},
{
x: moment().subtract(12, 'months').day(25).toDate(),
y: 4124,
},
{
x: moment().subtract(12, 'months').day(28).toDate(),
y: 4563,
},
{
x: moment().subtract(11, 'months').day(1).toDate(),
y: 3820,
},
{
x: moment().subtract(11, 'months').day(4).toDate(),
y: 3968,
},
],
},
{
name: 'Series 2',
data: [
{
x: moment().subtract(12, 'months').day(1).toDate(),
y: 4332,
},
{
x: moment().subtract(12, 'months').day(4).toDate(),
y: 6642,
},
{
x: moment().subtract(12, 'months').day(7).toDate(),
y: 5531,
},
{
x: moment().subtract(12, 'months').day(10).toDate(),
y: 2231,
},
{
x: moment().subtract(12, 'months').day(13).toDate(),
y: 5532,
},
{
x: moment().subtract(12, 'months').day(16).toDate(),
y: 3352,
},
{
x: moment().subtract(12, 'months').day(19).toDate(),
y: 6633,
},
{
x: moment().subtract(12, 'months').day(22).toDate(),
y: 1442,
},
{
x: moment().subtract(12, 'months').day(25).toDate(),
y: 4332,
},
{
x: moment().subtract(12, 'months').day(28).toDate(),
y: 6332,
},
{
x: moment().subtract(11, 'months').day(1).toDate(),
y: 5334,
},
{
x: moment().subtract(11, 'months').day(4).toDate(),
y: 5253,
},
],
},
]}
/>
</div>
</PrimaryTemplate>
);
};

72
src/pages/settings/Connection.tsx

@ -0,0 +1,72 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { Tabs } from '@app/components/generic/Tabs';
import { connection } from '@app/core/connection';
import { useAppSelector } from '@app/hooks/redux';
import { Button } from '@components/generic/Button';
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
import { MenuIcon, SaveIcon } from '@heroicons/react/outline';
import type { Protobuf } from '@meshtastic/meshtasticjs';
export interface ConnectionProps {
navOpen: boolean;
setNavOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
export const Connection = ({
navOpen,
setNavOpen,
}: ConnectionProps): JSX.Element => {
const { t } = useTranslation();
const user = useAppSelector((state) => state.meshtastic.user);
const { register, handleSubmit, formState } = useForm<Protobuf.User>({
defaultValues: user,
});
const onSubmit = handleSubmit((data) => {
void connection.setOwner(data);
});
return (
<PrimaryTemplate
title="Connection"
tagline="Settings"
button={
<Button
icon={<MenuIcon className="w-5 h-5" />}
onClick={(): void => {
setNavOpen(!navOpen);
}}
circle
/>
}
footer={
<Button
className="px-10 ml-auto"
icon={<SaveIcon className="w-5 h-5" />}
disabled={!formState.isDirty}
active
border
>
{t('strings.save_changes')}
</Button>
}
>
<div className="w-full max-w-3xl md:max-w-xl">
<form className="space-y-2" onSubmit={onSubmit}>
<Tabs
tabs={[
{ name: 'HTTP', body: <div>HTTP</div> },
{ name: 'Bluetooth', body: <div>BLE</div> },
{ name: 'Serial', body: <div>SERIAL</div> },
]}
/>
</form>
</div>
</PrimaryTemplate>
);
};

7
src/pages/settings/Device.tsx

@ -3,6 +3,7 @@ import React from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { Toggle } from '@app/components/generic/Toggle';
import { connection } from '@app/core/connection';
import { useAppSelector } from '@app/hooks/redux';
import { Button } from '@components/generic/Button';
@ -56,6 +57,12 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
<div className="w-full max-w-3xl md:max-w-xl">
<form className="space-y-2" onSubmit={onSubmit}>
<Input label={'Device Name'} {...register('longName')} />
<Input
label={'Short Name'}
maxLength={3}
{...register('shortName')}
/>
<Toggle label="Licenced Operator?" {...register('isLicensed')} />
</form>
</div>
</PrimaryTemplate>

19
src/pages/settings/Index.tsx

@ -8,10 +8,12 @@ import { Tab } from '@headlessui/react';
import {
CollectionIcon,
DeviceMobileIcon,
LinkIcon,
WifiIcon,
XCircleIcon,
} from '@heroicons/react/outline';
import { Connection } from './Connection';
import { Device } from './Device';
import { Interface } from './Interface';
import { Radio } from './Radio';
@ -46,6 +48,20 @@ export const Settings = (): JSX.Element => {
/>
</div>
</div>
<Tab
onClick={(): void => {
setNavOpen(false);
}}
>
{({ selected }): JSX.Element => (
<SidebarItem
title="Connection"
description="Method and peramaters for connecting to the device"
selected={selected}
icon={<LinkIcon className="flex-shrink-0 w-6 h-6" />}
/>
)}
</Tab>
<Tab
onClick={(): void => {
setNavOpen(false);
@ -84,6 +100,9 @@ export const Settings = (): JSX.Element => {
</Drawer>
<div className="flex w-full">
<Tab.Panels className="flex w-full">
<Tab.Panel className="flex w-full">
<Connection navOpen={navOpen} setNavOpen={setNavOpen} />
</Tab.Panel>
<Tab.Panel className="flex w-full">
<Device navOpen={navOpen} setNavOpen={setNavOpen} />
</Tab.Panel>

6
src/pages/settings/Interface.tsx

@ -47,7 +47,11 @@ export const Interface = ({
<div className="w-full max-w-3xl space-y-2 md:max-w-xl">
<Select
label="Language"
value={i18n.language}
active={{
name: '',
value: '',
icon: <Us />,
}}
onChange={(value): void => {
void i18n.changeLanguage(value);
}}

10
src/pages/settings/Radio.tsx

@ -61,6 +61,16 @@ export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => {
label={t('strings.wifi_psk')}
{...register('wifiPassword')}
/>
<Input
label={'Charge current'}
disabled
{...register('chargeCurrent')}
/>
<Input
label={'Last GPS Attempt'}
disabled
{...register('gpsAttemptTime')}
/>
</form>
</div>
</PrimaryTemplate>

5133
yarn.lock

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