36 changed files with 1097 additions and 624 deletions
@ -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<Protobuf.RadioConfig_UserPreferences>({ |
|
||||
defaultValues: preferences, |
|
||||
}); |
|
||||
|
|
||||
const onSubmit = handleSubmit((data) => connection.setPreferences(data)); |
|
||||
return ( |
|
||||
<form onSubmit={onSubmit}> |
|
||||
<div className="flex bg-gray-50 whitespace-nowrap p-3 justify-between border-b"> |
|
||||
<div className="my-auto">{t('strings.device_region')}</div> |
|
||||
<div className="flex shadow-md rounded-3xl ml-2"> |
|
||||
<select |
|
||||
{...register('region', { |
|
||||
valueAsNumber: true, |
|
||||
})} |
|
||||
> |
|
||||
<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">{t('strings.wifi_ssid')}</div> |
|
||||
<div className="flex shadow-md rounded-3xl 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">{t('strings.wifi_psk')}</div> |
|
||||
<div className="flex shadow-md rounded-3xl 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" /> |
|
||||
{t('strings.save_changes')} |
|
||||
</button> |
|
||||
</div> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
@ -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 ( |
||||
|
<div className="flex w-full max-w-screen-md justify-start items-start"> |
||||
|
<form |
||||
|
className="w-1/2" |
||||
|
onSubmit={handleSubmit((data) => console.info(data))} |
||||
|
> |
||||
|
<div className="mt-48 mb-16"> |
||||
|
<Typography className="mb-24 font-medium text-14"> |
||||
|
Native Input: |
||||
|
</Typography> |
||||
|
<input |
||||
|
className="border-1 outline-none rounded-8 p-8" |
||||
|
{...register('Native')} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-48 mb-16"> |
||||
|
<Typography className="mb-24 font-medium text-14"> |
||||
|
MUI Checkbox |
||||
|
</Typography> |
||||
|
<Controller |
||||
|
name="Checkbox" |
||||
|
control={control} |
||||
|
defaultValue={false} |
||||
|
render={({ field: { onChange, value } }) => ( |
||||
|
<Checkbox |
||||
|
checked={value} |
||||
|
onChange={(ev) => onChange(ev.target.checked)} |
||||
|
/> |
||||
|
)} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-48 mb-16"> |
||||
|
<Typography className="mb-24 font-medium text-14"> |
||||
|
Radio Group |
||||
|
</Typography> |
||||
|
<Controller |
||||
|
render={({ field }) => ( |
||||
|
<RadioGroup {...field} aria-label="gender" name="gender1"> |
||||
|
<FormControlLabel |
||||
|
value="female" |
||||
|
control={<Radio />} |
||||
|
label="Female" |
||||
|
/> |
||||
|
<FormControlLabel |
||||
|
value="male" |
||||
|
control={<Radio />} |
||||
|
label="Male" |
||||
|
/> |
||||
|
</RadioGroup> |
||||
|
)} |
||||
|
name="RadioGroup" |
||||
|
control={control} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-48 mb-16"> |
||||
|
<Typography className="mb-24 font-medium text-14"> |
||||
|
MUI TextField |
||||
|
</Typography> |
||||
|
<Controller |
||||
|
render={({ field }) => <TextField {...field} variant="outlined" />} |
||||
|
name="TextField" |
||||
|
control={control} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
{/* <div className="mt-48 mb-16"> |
||||
|
<Typography className="mb-24 font-medium text-14"> |
||||
|
MUI Select |
||||
|
</Typography> |
||||
|
<Controller |
||||
|
render={({ field }) => ( |
||||
|
<Select {...field} variant="outlined"> |
||||
|
<MenuItem value={10}>Ten</MenuItem> |
||||
|
<MenuItem value={20}>Twenty</MenuItem> |
||||
|
<MenuItem value={30}>Thirty</MenuItem> |
||||
|
</Select> |
||||
|
)} |
||||
|
name="Select" |
||||
|
control={control} |
||||
|
/> |
||||
|
</div> */} |
||||
|
|
||||
|
<div className="mt-48 mb-16"> |
||||
|
<Typography className="mb-24 font-medium text-14"> |
||||
|
MUI Switch |
||||
|
</Typography> |
||||
|
<Controller |
||||
|
name="switch" |
||||
|
control={control} |
||||
|
defaultValue={false} |
||||
|
render={({ field: { onChange, value } }) => ( |
||||
|
<Switch |
||||
|
checked={value} |
||||
|
onChange={(ev) => onChange(ev.target.checked)} |
||||
|
/> |
||||
|
)} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-48 mb-16"> |
||||
|
<Typography className="mb-24 font-medium text-14"> |
||||
|
React Select |
||||
|
</Typography> |
||||
|
{/* <Controller |
||||
|
render={({ field }) => <ReactSelect {...field} />} |
||||
|
options={options} |
||||
|
name="ReactSelect" |
||||
|
isClearable |
||||
|
control={control} |
||||
|
onChange={([selected]) => { |
||||
|
return { value: selected }; |
||||
|
}} |
||||
|
/> */} |
||||
|
</div> |
||||
|
|
||||
|
<div className="flex my-48 items-center"> |
||||
|
<Button |
||||
|
className="mx-8" |
||||
|
variant="contained" |
||||
|
color="secondary" |
||||
|
type="submit" |
||||
|
> |
||||
|
Submit |
||||
|
</Button> |
||||
|
|
||||
|
<Button |
||||
|
className="mx-8" |
||||
|
type="button" |
||||
|
onClick={() => { |
||||
|
reset(defaultValues); |
||||
|
}} |
||||
|
> |
||||
|
Reset Form |
||||
|
</Button> |
||||
|
</div> |
||||
|
</form> |
||||
|
|
||||
|
<div className="w-1/2 my-48 p-24"> |
||||
|
<pre className="language-js p-24 w-400"> |
||||
|
{JSON.stringify(data, null, 2)} |
||||
|
</pre> |
||||
|
|
||||
|
<Typography |
||||
|
className="mt-16 font-medium text-12 italic" |
||||
|
color="textSecondary" |
||||
|
> |
||||
|
Render Count: {renderCount} |
||||
|
</Typography> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -1,38 +1,38 @@ |
|||||
import React from 'react'; |
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<T> extends UseControllerProps<T> { |
||||
|
label: string; |
||||
options: { |
options: { |
||||
value: string; |
value: string; |
||||
label: string; |
label: string; |
||||
}[]; |
}[]; |
||||
label: string; |
|
||||
} |
} |
||||
|
|
||||
export const Select = React.forwardRef< |
export const Select = <T extends FieldValues>({ |
||||
HTMLSelectElement, |
name, |
||||
SelectProps & DefaultSelectProps |
control, |
||||
>(function Select( |
label, |
||||
{ options, label, id, ...props }: SelectProps & DefaultSelectProps, |
options, |
||||
ref, |
}: SelectProps<T>): JSX.Element => { |
||||
) { |
|
||||
return ( |
return ( |
||||
<div className="space-y-1"> |
<Controller |
||||
<label htmlFor={id} className="block text-sm font-medium dark:text-white"> |
name={name} |
||||
{label} |
control={control} |
||||
</label> |
rules={{ |
||||
<select |
required: 'This is required', |
||||
ref={ref} |
}} |
||||
{...props} |
render={({ field: { onChange, value, name } }) => ( |
||||
className="block w-full p-2 border dark:border-gray-600 rounded-md shadow-sm dark:bg-secondaryDark" |
<div className="space-y-1"> |
||||
> |
<span className="block text-sm font-medium dark:text-white"> |
||||
{options.map((option) => ( |
{label} |
||||
<option key={option.value} value={option.value}> |
</span> |
||||
{option.label} |
<ReactSelect options={options} /> |
||||
</option> |
</div> |
||||
))} |
)} |
||||
</select> |
/> |
||||
</div> |
|
||||
); |
); |
||||
}); |
}; |
||||
|
|||||
@ -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<T> extends UseControllerProps<T> { |
||||
|
label: string; |
||||
|
} |
||||
|
|
||||
|
export const Switch = <T extends FieldValues>({ |
||||
|
name, |
||||
|
control, |
||||
|
label, |
||||
|
}: SwitchProps<T>): JSX.Element => { |
||||
|
return ( |
||||
|
<Controller |
||||
|
name={name} |
||||
|
control={control} |
||||
|
rules={{ |
||||
|
required: 'This is required', |
||||
|
}} |
||||
|
render={({ field: { onChange, value, name } }) => ( |
||||
|
<div className="flex flex-col"> |
||||
|
<span className="block text-sm font-medium dark:text-white"> |
||||
|
{label} |
||||
|
</span> |
||||
|
<div className="relative w-14 mr-2 ml-auto select-none"> |
||||
|
<MaterialSwitch |
||||
|
id={name} |
||||
|
checked={value} |
||||
|
onChange={(ev) => onChange(ev.target.checked)} |
||||
|
/> |
||||
|
</div> |
||||
|
</div> |
||||
|
)} |
||||
|
/> |
||||
|
); |
||||
|
}; |
||||
@ -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 ( |
|
||||
<div className="flex flex-col"> |
|
||||
<span className="block text-sm font-medium dark:text-white">{label}</span> |
|
||||
<div className="relative w-14 mr-2 ml-auto select-none"> |
|
||||
<input |
|
||||
ref={ref} |
|
||||
{...props} |
|
||||
type="checkbox" |
|
||||
className="peer checked:right-0 absolute w-7 h-7 rounded-full bg-white appearance-none cursor-pointer" |
|
||||
/> |
|
||||
<label |
|
||||
htmlFor={id} |
|
||||
className="block overflow-hidden h-7 rounded-full peer-checked:bg-primary shadow-sm bg-gray-300 dark:bg-gray-700 cursor-pointer" |
|
||||
></label> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}); |
|
||||
@ -1,31 +1,28 @@ |
|||||
import React from 'react'; |
import React from 'react'; |
||||
|
|
||||
export interface ButtonProps { |
import MaterialButton from '@material-ui/core/Button'; |
||||
children: React.ReactNode; |
import type { ButtonProps as MaterialButtonProps } from '@material-ui/core/Button/Button'; |
||||
className?: string; |
|
||||
clickAction?: () => void; |
interface LocalButtonProps { |
||||
type?: 'button' | 'submit' | 'reset' | undefined; |
text: string; |
||||
|
icon?: JSX.Element; |
||||
} |
} |
||||
|
|
||||
export const Button = ({ |
export type ButtonProps = MaterialButtonProps & LocalButtonProps; |
||||
children, |
|
||||
className, |
export const Button = ({ text, icon, ...props }: ButtonProps): JSX.Element => { |
||||
clickAction, |
|
||||
type, |
|
||||
}: ButtonProps): JSX.Element => { |
|
||||
return ( |
return ( |
||||
<button |
<MaterialButton |
||||
className={`w-10 h-10 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 active:bg-gray-300 dark:active:bg-gray-800 hover:shadow-inner text-gray-500 dark:text-gray-400 ${ |
{...props} |
||||
className ?? '' |
className="dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" |
||||
}`}
|
|
||||
onClick={() => { |
|
||||
if (clickAction) { |
|
||||
clickAction(); |
|
||||
} |
|
||||
}} |
|
||||
type={type} |
|
||||
> |
> |
||||
<span className="flex justify-center">{children}</span> |
<div className="flex p-3"> |
||||
</button> |
{icon && |
||||
|
React.cloneElement(icon, { |
||||
|
className: 'h-6 w-6 mr-3 text-gray-500 dark:text-gray-400', |
||||
|
})} |
||||
|
<span>{text}</span> |
||||
|
</div> |
||||
|
</MaterialButton> |
||||
); |
); |
||||
}; |
}; |
||||
|
|||||
@ -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 && ( |
|
||||
<div |
|
||||
className="z-10 fixed inset-0 transition-opacity" |
|
||||
onClick={onClose} |
|
||||
> |
|
||||
<div |
|
||||
className="absolute inset-0 backdrop-filter backdrop-blur" |
|
||||
tabIndex={0} |
|
||||
></div> |
|
||||
</div> |
|
||||
)} |
|
||||
|
|
||||
<aside |
|
||||
className={`transform top-0 left-0 w-64 bg-white dark:bg-secondaryDark shadow-md border-r dark:border-gray-600 fixed h-full overflow-auto ease-in-out transition-all duration-300 z-30 ${ |
|
||||
open ? 'translate-x-0' : '-translate-x-full' |
|
||||
}`}
|
|
||||
> |
|
||||
{children} |
|
||||
</aside> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
@ -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 ( |
||||
|
<MaterialIconButton {...props} className="text-gray-500 dark:text-gray-400"> |
||||
|
{children} |
||||
|
</MaterialIconButton> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,44 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { useForm } from 'react-hook-form'; |
||||
|
|
||||
|
import { XCircleIcon } from '@heroicons/react/outline'; |
||||
|
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
|
import { connection } from '../../core/connection'; |
||||
|
|
||||
|
export interface NodeProps { |
||||
|
node: Protobuf.NodeInfo; |
||||
|
onClose: () => void; |
||||
|
} |
||||
|
|
||||
|
export const NodeDetails = ({ node }: NodeProps): JSX.Element => { |
||||
|
const { register, handleSubmit } = useForm<Protobuf.User>({ |
||||
|
defaultValues: node.user, |
||||
|
}); |
||||
|
|
||||
|
const onSubmit = handleSubmit((data) => { |
||||
|
console.log(data); |
||||
|
connection.setOwner(data); |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<div> |
||||
|
<div className="flex dark:bg-primaryDark p-2 rounded-t-md justify-between border-b dark:border-gray-600 dark:text-white"> |
||||
|
<div>{node.user?.longName ?? node.num}</div> |
||||
|
<XCircleIcon className="h-5 w-5 dark:text-white my-auto" /> |
||||
|
</div> |
||||
|
<div> |
||||
|
<form onSubmit={onSubmit}> |
||||
|
{/* <Input label="Node Name" {...register('longName', {})} /> */} |
||||
|
<button |
||||
|
type="submit" |
||||
|
className="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" |
||||
|
> |
||||
|
Save |
||||
|
</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -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', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
); |
||||
|
}; |
||||
@ -1,7 +1,7 @@ |
|||||
import type { TypedUseSelectorHook } from 'react-redux'; |
import type { TypedUseSelectorHook } from 'react-redux'; |
||||
import { useDispatch, useSelector } 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<AppDispatch>(); |
export const useAppDispatch = () => useDispatch<AppDispatch>(); |
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; |
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; |
||||
|
|||||
@ -1,11 +1,13 @@ |
|||||
import React from 'react'; |
import React from 'react'; |
||||
|
|
||||
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate'; |
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate'; |
||||
|
import { TestForm } from '../components/TestForm'; |
||||
|
|
||||
export const About = (): JSX.Element => { |
export const About = (): JSX.Element => { |
||||
return ( |
return ( |
||||
<PrimaryTemplate title="meshtastic-web" tagline="About"> |
<PrimaryTemplate title="meshtastic-web" tagline="About"> |
||||
<p>Content</p> |
<p>Content</p> |
||||
|
<TestForm /> |
||||
</PrimaryTemplate> |
</PrimaryTemplate> |
||||
); |
); |
||||
}; |
}; |
||||
|
|||||
@ -1,17 +1,45 @@ |
|||||
import React from 'react'; |
import React from 'react'; |
||||
|
|
||||
|
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
||||
|
|
||||
import { Node } from '../components/nodes/Node'; |
import { Node } from '../components/nodes/Node'; |
||||
|
import { NodeDetails } from '../components/nodes/NodeDetails'; |
||||
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate'; |
import { PrimaryTemplate } from '../components/templates/PrimaryTemplate'; |
||||
import { useAppSelector } from '../hooks/redux'; |
import { useAppSelector } from '../hooks/redux'; |
||||
|
|
||||
export const Nodes = (): JSX.Element => { |
export const Nodes = (): JSX.Element => { |
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes); |
const nodes = useAppSelector((state) => state.meshtastic.nodes); |
||||
|
const [currentNode, setCurrentNode] = React.useState< |
||||
|
Protobuf.NodeInfo | undefined |
||||
|
>(); |
||||
|
|
||||
return ( |
return ( |
||||
<PrimaryTemplate title="Administration" tagline="Node"> |
<PrimaryTemplate title="Administration" tagline="Node"> |
||||
{nodes.map((node) => ( |
<div className="flex w-full space-x-5"> |
||||
<Node key={node.num} node={node} /> |
<div className="w-1/3"> |
||||
))} |
{nodes.map((node) => ( |
||||
|
<Node |
||||
|
key={node.num} |
||||
|
node={node} |
||||
|
onClick={() => { |
||||
|
setCurrentNode(node); |
||||
|
}} |
||||
|
/> |
||||
|
))} |
||||
|
</div> |
||||
|
<div className="w-2/3"> |
||||
|
{currentNode ? ( |
||||
|
<NodeDetails |
||||
|
onClose={() => { |
||||
|
setCurrentNode(undefined); |
||||
|
}} |
||||
|
node={currentNode} |
||||
|
/> |
||||
|
) : ( |
||||
|
<div>Node not selected</div> |
||||
|
)} |
||||
|
</div> |
||||
|
</div> |
||||
</PrimaryTemplate> |
</PrimaryTemplate> |
||||
); |
); |
||||
}; |
}; |
||||
|
|||||
File diff suppressed because it is too large
Loading…
Reference in new issue