Fixes, new debug output & more.
This commit is contained in:
+8
-10
@@ -13,12 +13,12 @@ import {
|
||||
addChannel,
|
||||
addMessage,
|
||||
addNode,
|
||||
addUser,
|
||||
setDeviceStatus,
|
||||
setLastMeshInterraction,
|
||||
setMyNodeInfo,
|
||||
setPreferences,
|
||||
setReady,
|
||||
setUser,
|
||||
} from '@core/slices/meshtasticSlice';
|
||||
import {
|
||||
IHTTPConnection,
|
||||
@@ -82,17 +82,15 @@ const App = (): JSX.Element => {
|
||||
});
|
||||
|
||||
connection.onUserPacket.subscribe((user) => {
|
||||
dispatch(
|
||||
setUser({
|
||||
nodeNum: user.packet.from,
|
||||
user: user.data,
|
||||
}),
|
||||
);
|
||||
console.log('got user packet');
|
||||
|
||||
dispatch(addUser(user.data));
|
||||
});
|
||||
|
||||
connection.onNodeInfoPacket.subscribe(
|
||||
(nodeInfoPacket): void | { payload: Protobuf.NodeInfo; type: string } =>
|
||||
dispatch(addNode(nodeInfoPacket.data)),
|
||||
(nodeInfoPacket): void | { payload: Protobuf.NodeInfo; type: string } => {
|
||||
dispatch(addNode(nodeInfoPacket.data));
|
||||
},
|
||||
);
|
||||
|
||||
connection.onAdminPacket.subscribe((adminPacket) => {
|
||||
@@ -145,7 +143,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-6 md:mx-6 md:pt-4 md:pb-3 md:rounded-t-3xl dark:border-gray-600 md:shadow-md dark:bg-primaryDark">
|
||||
<div className="w-full overflow-hidden bg-white border-b border-gray-300 md:mt-6 md:mx-6 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 />
|
||||
|
||||
@@ -39,12 +39,19 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
|
||||
role: Protobuf.Channel_Role;
|
||||
settings: {
|
||||
name: string;
|
||||
modemConfig: Protobuf.ChannelSettings_ModemConfig;
|
||||
bandwidth?: number;
|
||||
codingRate?: number;
|
||||
spreadFactor?: number;
|
||||
};
|
||||
}>({
|
||||
defaultValues: {
|
||||
role: channel.role,
|
||||
settings: {
|
||||
name: channel.settings?.name,
|
||||
bandwidth: channel.settings?.bandwidth,
|
||||
codingRate: channel.settings?.codingRate,
|
||||
spreadFactor: channel.settings?.spreadFactor,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -77,6 +84,7 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
|
||||
<div className="my-auto space-x-2">
|
||||
<form>
|
||||
<div className="flex space-x-2">
|
||||
{/* @todo: change to disable & make primary buttons */}
|
||||
<Select
|
||||
label="Channel Type"
|
||||
optionsEnum={Protobuf.Channel_Role}
|
||||
@@ -88,6 +96,23 @@ export const Channel = ({ channel }: ChannelProps): JSX.Element => {
|
||||
/>
|
||||
</div>
|
||||
<Input label="Name" {...register('settings.name')} />
|
||||
<Input
|
||||
label="Bandwidth"
|
||||
type="number"
|
||||
{...register('settings.bandwidth')}
|
||||
/>
|
||||
<Input
|
||||
label="Spread Factor"
|
||||
type="number"
|
||||
min={7}
|
||||
max={12}
|
||||
{...register('settings.spreadFactor')}
|
||||
/>
|
||||
<Input
|
||||
label="Coding Rate"
|
||||
type="number"
|
||||
{...register('settings.codingRate')}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<IconButton
|
||||
|
||||
@@ -40,7 +40,7 @@ export const Button = ({
|
||||
return (
|
||||
<button
|
||||
onClick={handleConfirm}
|
||||
className={`items-center select-none flex dark:text-white active:scale-95 ${
|
||||
className={`items-center select-none flex dark:text-white active:scale-95 transition duration-200 ease-in-out ${
|
||||
active && !disabled ? 'bg-gray-100 dark:bg-gray-700' : ''
|
||||
} ${
|
||||
circle ? 'rounded-full h-10 w-10' : 'rounded-md p-3 space-x-3 text-sm'
|
||||
@@ -48,7 +48,9 @@ export const Button = ({
|
||||
disabled
|
||||
? 'cursor-not-allowed dark:bg-primaryDark bg-white'
|
||||
: 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-md'
|
||||
} ${border ? 'border dark:border-gray-600' : ''} ${className}`}
|
||||
} ${
|
||||
border ? 'border border-gray-400 dark:border-gray-200' : ''
|
||||
} ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{icon && (
|
||||
|
||||
@@ -20,7 +20,7 @@ export const Card = ({
|
||||
}: CardProps): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col flex-auto dark:text-white border shadow-md select-none dark:bg-primaryDark dark:border-transparent rounded-3xl ${className}`}
|
||||
className={`flex flex-col flex-auto dark:text-white border-y md:border shadow-md select-none dark:bg-primaryDark border-gray-300 dark:border-transparent md:rounded-3xl ${className}`}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center justify-between mx-10 mt-10">
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
export interface CoverProps {
|
||||
content: JSX.Element;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export const Cover = ({ content, enabled }: CoverProps): JSX.Element => {
|
||||
return enabled ? <div className="m-4 ">{content}</div> : <></>;
|
||||
};
|
||||
@@ -26,7 +26,7 @@ export const Drawer = ({
|
||||
)}
|
||||
|
||||
<aside
|
||||
className={`transform top-0 left-0 bg-white dark:bg-secondaryDark shadow-md max-w-xs w-full border-r dark:border-gray-600 h-full overflow-auto ease-in-out transition-all duration-300 z-30 ${
|
||||
className={`transform top-0 left-0 bg-white dark:bg-secondaryDark shadow-md max-w-xs w-full border-r dark:border-gray-600 border-gray-300 h-full overflow-auto ease-in-out transition-all duration-300 z-30 ${
|
||||
permenant ? '' : 'absolute'
|
||||
} ${open ? 'translate-x-0' : '-translate-x-full'} ${className}`}
|
||||
{...props}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const IconButton = ({
|
||||
<div className="my-auto text-gray-500 dark:text-gray-400">
|
||||
<button
|
||||
type="button"
|
||||
className="p-2 rounded-md active:scale-95 hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
className="p-2 transition duration-200 ease-in-out rounded-md active:scale-95 hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
export interface StatCardProps {
|
||||
title: string;
|
||||
value: string | JSX.Element;
|
||||
}
|
||||
|
||||
export const StatCard = ({ title, value }: StatCardProps): JSX.Element => {
|
||||
return (
|
||||
<div className="w-full border-gray-300 shadow-md border-y md:border h-28 md:rounded-3xl dark:bg-primaryDark dark:border-transparent ">
|
||||
<div className="m-4">
|
||||
<div className="text-lg font-light">{title}</div>
|
||||
<div className="text-3xl font-bold">{value}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
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, className, ...props }: TabProps): JSX.Element => {
|
||||
return (
|
||||
<Tab.Group as="div" className={className}>
|
||||
<Tab.List className="flex border-l border-r border-t shadow-md rounded-t-3xl dark:border-gray-600">
|
||||
{tabs.map((tab) => (
|
||||
<Tab
|
||||
key={tab.name}
|
||||
className={({ selected }): string =>
|
||||
`w-full text-lg font-medium p-2 border-b-2 ${
|
||||
selected
|
||||
? 'dark:border-gray-200 border-gray-600'
|
||||
: 'border-transparent dark:border-transparent'
|
||||
}`
|
||||
}
|
||||
>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels className="h-full">
|
||||
{tabs.map((tab, index) => (
|
||||
<Tab.Panel
|
||||
key={index}
|
||||
className={
|
||||
'border dark:border-gray-600 rounded-b-3xl p-4 h-full shadow-md'
|
||||
}
|
||||
>
|
||||
{tab.body}
|
||||
</Tab.Panel>
|
||||
))}
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
);
|
||||
};
|
||||
@@ -12,38 +12,37 @@ interface CheckboxProps extends DefaultInputProps {
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
export const Checkbox = ({
|
||||
label,
|
||||
valid,
|
||||
validationMessage,
|
||||
id,
|
||||
error,
|
||||
...props
|
||||
}: CheckboxProps): JSX.Element => {
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<Label label={label} />
|
||||
<div className="ml-auto">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
className={`appearance-none w-8 h-8 border rounded-md focus:outline-none checked:bg-primary checked:border-transparent ${
|
||||
props.disabled
|
||||
? 'bg-gray-200 text-gray-500 dark:bg-gray-800 dark:text-gray-400 border-gray-400'
|
||||
: ''
|
||||
} ${
|
||||
error
|
||||
? 'border-red-500'
|
||||
: props.disabled
|
||||
? 'border-gray-200'
|
||||
: ' focus-within:border-primary hover:border-primary'
|
||||
}`}
|
||||
{...props}
|
||||
/>
|
||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
function Input(
|
||||
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps,
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<Label label={label} />
|
||||
<div className="ml-auto">
|
||||
<input
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
id={id}
|
||||
className={`appearance-none w-8 h-8 border rounded-md focus:outline-none checked:bg-primary checked:border-transparent transition duration-200 ease-in-out border-gray-400 dark:border-gray-200 ${
|
||||
props.disabled
|
||||
? 'bg-gray-200 text-gray-500 dark:bg-gray-800 dark:text-gray-400 border-gray-400'
|
||||
: ''
|
||||
} ${
|
||||
error
|
||||
? 'border-red-500'
|
||||
: props.disabled
|
||||
? 'border-gray-200'
|
||||
: ' focus-within:border-primary hover:border-primary'
|
||||
}`}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
{!valid && (
|
||||
<div className="text-sm text-gray-600">{validationMessage}</div>
|
||||
)}
|
||||
</div>
|
||||
{!valid && (
|
||||
<div className="text-sm text-gray-600">{validationMessage}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ export const InputWrapper = ({
|
||||
children,
|
||||
}: LabelProps): JSX.Element => (
|
||||
<div
|
||||
className={`flex w-full border-y border rounded-md ${
|
||||
className={`flex w-full border-gray-400 dark:border-gray-200 border-y border rounded-md transition duration-200 ease-in-out ${
|
||||
disabled
|
||||
? 'bg-gray-200 text-gray-500 dark:bg-gray-800 dark:text-gray-400 border-gray-400'
|
||||
: ''
|
||||
|
||||
@@ -6,9 +6,9 @@ export interface LabelProps {
|
||||
}
|
||||
|
||||
export const Label = ({ label, error }: LabelProps): JSX.Element => (
|
||||
<label className="flex py-1 text-xs font-semibold text-gray-500">
|
||||
<label className="flex py-1 text-xs font-semibold text-gray-500 dark:text-gray-400">
|
||||
{label}
|
||||
{error && <span className="ml-2 text-red-500">{error}</span>}
|
||||
<div className="flex-grow h-0.5 my-auto ml-2 dark:bg-gray-600 bg-gray-500" />
|
||||
<div className="flex-grow h-0.5 my-auto ml-2 dark:bg-gray-700 bg-gray-300 rounded-full" />
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -35,7 +35,7 @@ export const PageLayout = ({
|
||||
setNavOpen(!navOpen);
|
||||
}}
|
||||
>
|
||||
<Tab.List className="flex flex-col border-b divide-y divide-gray-300 dark:divide-gray-600 dark:border-gray-600">
|
||||
<Tab.List className="flex flex-col border-b border-gray-300 divide-y divide-gray-300 dark:divide-gray-600 dark:border-gray-600">
|
||||
<div className="flex items-center justify-between m-8 mr-6 md:my-10">
|
||||
<div className="text-4xl font-extrabold leading-none tracking-tight">
|
||||
{title}
|
||||
|
||||
@@ -17,10 +17,10 @@ export const PrimaryTemplate = ({
|
||||
}: PrimaryTemplateProps): JSX.Element => {
|
||||
return (
|
||||
<div className="flex flex-col flex-auto h-full min-w-0">
|
||||
<div className="flex p-4 bg-white border-b md:flex-row flex-0 md:items-center md:justify-between md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
|
||||
<div className="flex p-4 bg-white border-b border-gray-300 md:flex-row flex-0 md:items-center md:justify-between 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">
|
||||
<a className="whitespace-nowrap font-medium text-primary">
|
||||
<a className="font-medium whitespace-nowrap text-primary">
|
||||
{tagline}
|
||||
</a>
|
||||
<h2 className="text-3xl font-extrabold leading-7 tracking-tight truncate md:text-4xl md:leading-10 dark:text-white">
|
||||
@@ -28,11 +28,11 @@ export const PrimaryTemplate = ({
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-auto flex-grow p-6 overflow-y-auto bg-white md:p-10 dark:bg-secondaryDark">
|
||||
<div className="flex-auto flex-grow py-6 overflow-y-auto bg-white md:p-10 dark:bg-secondaryDark">
|
||||
{children}
|
||||
</div>
|
||||
{footer && (
|
||||
<div className="flex p-4 bg-white border-t md:flex-row flex-0 md:items-center md:justify-between md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
|
||||
<div className="flex p-4 bg-white border-t border-gray-300 md:flex-row flex-0 md:items-center md:justify-between 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">{footer}</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,9 @@ interface MeshtasticState {
|
||||
lastMeshInterraction: number;
|
||||
ready: boolean;
|
||||
myNodeInfo: Protobuf.MyNodeInfo;
|
||||
user: Protobuf.User;
|
||||
myNode: Protobuf.NodeInfo;
|
||||
users: Protobuf.User[];
|
||||
myUser: Protobuf.User;
|
||||
positionPackets: Types.PositionPacket[];
|
||||
nodes: Protobuf.NodeInfo[];
|
||||
channels: Protobuf.Channel[];
|
||||
@@ -37,7 +39,9 @@ const initialState: MeshtasticState = {
|
||||
lastMeshInterraction: 0,
|
||||
ready: false,
|
||||
myNodeInfo: Protobuf.MyNodeInfo.create(),
|
||||
user: Protobuf.User.create(),
|
||||
myNode: Protobuf.NodeInfo.create(),
|
||||
users: [],
|
||||
myUser: Protobuf.User.create(),
|
||||
positionPackets: [],
|
||||
nodes: [],
|
||||
channels: [],
|
||||
@@ -65,26 +69,27 @@ export const meshtasticSlice = createSlice({
|
||||
setMyNodeInfo: (state, action: PayloadAction<Protobuf.MyNodeInfo>) => {
|
||||
state.myNodeInfo = action.payload;
|
||||
},
|
||||
setUser: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
nodeNum: number;
|
||||
user: Protobuf.User;
|
||||
}>,
|
||||
) => {
|
||||
if (action.payload.nodeNum === state.myNodeInfo.myNodeNum) {
|
||||
state.user = action.payload.user;
|
||||
addUser: (state, action: PayloadAction<Protobuf.User>) => {
|
||||
if (action.payload.id === state.myNode.user?.id) {
|
||||
state.myUser = action.payload;
|
||||
}
|
||||
if (
|
||||
state.users.findIndex((user) => user.id === action.payload.id) !== -1
|
||||
) {
|
||||
state.users = state.users.map((user) => {
|
||||
return user.id === action.payload.id ? action.payload : user;
|
||||
});
|
||||
} else {
|
||||
const num = state.nodes.findIndex(
|
||||
(node) => node.num === action.payload.nodeNum,
|
||||
);
|
||||
state.nodes[num].user = action.payload.user;
|
||||
state.users.push(action.payload);
|
||||
}
|
||||
},
|
||||
addPositionPacket: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||
state.positionPackets.push(action.payload);
|
||||
},
|
||||
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
||||
if (action.payload.num === state.myNodeInfo.myNodeNum) {
|
||||
state.myNode = action.payload;
|
||||
}
|
||||
if (
|
||||
state.nodes.findIndex((node) => node.num === action.payload.num) !== -1
|
||||
) {
|
||||
@@ -155,7 +160,7 @@ export const {
|
||||
setLastMeshInterraction,
|
||||
setReady,
|
||||
setMyNodeInfo,
|
||||
setUser,
|
||||
addUser,
|
||||
addPositionPacket,
|
||||
addNode,
|
||||
addChannel,
|
||||
|
||||
@@ -40,7 +40,7 @@ export const Messages = (): JSX.Element => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow p-6 space-y-2 overflow-y-auto bg-white border-b md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
|
||||
<div className="flex flex-col flex-grow p-6 space-y-2 overflow-y-auto bg-white border-b border-gray-300 md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
|
||||
{messages.map((message, index) => (
|
||||
<Message
|
||||
key={index}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import 'react-json-pretty/themes/acai.css';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import moment from 'moment';
|
||||
import { FiMenu, FiTerminal } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
import { Card } from '@app/components/generic/Card';
|
||||
import { Chart } from '@app/components/generic/Chart';
|
||||
import { Checkbox } from '@app/components/generic/form/Checkbox';
|
||||
import { Input } from '@app/components/generic/form/Input';
|
||||
import { IconButton } from '@app/components/generic/IconButton.jsx';
|
||||
import { StatCard } from '@app/components/generic/StatCard';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
@@ -32,6 +37,13 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
}
|
||||
>
|
||||
<div className="w-full space-y-4">
|
||||
<div className="justify-between space-y-2 md:space-y-0 md:space-x-2 md:flex">
|
||||
<StatCard
|
||||
title="Last heard"
|
||||
value={<TimeAgo date={new Date(node.lastHeard * 1000)} />}
|
||||
/>
|
||||
<StatCard title="SNR" value={node.snr.toString()} />
|
||||
</div>
|
||||
<Chart
|
||||
title={`${node.user?.longName ?? 'UNK'}`}
|
||||
description="Airtime"
|
||||
@@ -145,12 +157,12 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Card title="Position" description={node.num.toString()}>
|
||||
<Card
|
||||
title="Position"
|
||||
description={new Date(node.lastHeard * 1000).toLocaleString()}
|
||||
>
|
||||
<div className="p-10">
|
||||
<div>
|
||||
<div></div>
|
||||
<div>{node.position?.satsInView}</div>
|
||||
</div>
|
||||
<JSONPretty data={node.position} />
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiMenu, FiSave } from 'react-icons/fi';
|
||||
import { FiCode, FiMenu, FiSave } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { Channel } from '@app/components/Channel.jsx';
|
||||
import { Card } from '@app/components/generic/Card';
|
||||
import { Cover } from '@app/components/generic/Cover.jsx';
|
||||
import { IconButton } from '@app/components/generic/IconButton.jsx';
|
||||
import { connection } from '@app/core/connection.js';
|
||||
import { useAppSelector } from '@app/hooks/redux.js';
|
||||
@@ -22,6 +24,7 @@ export const Channels = ({
|
||||
}: ChannelsProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const channels = useAppSelector((state) => state.meshtastic.channels);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
@@ -49,20 +52,22 @@ export const Channels = ({
|
||||
<div className="space-y-4">
|
||||
<Card
|
||||
title="Manage Channels"
|
||||
description={
|
||||
<div className="flex space-x-2 truncate">
|
||||
<div className="w-3 h-3 my-auto bg-green-500 rounded-full" />
|
||||
- Primary
|
||||
<div className="w-3 h-3 my-auto rounded-full bg-cyan-500" />
|
||||
- Secondary
|
||||
<div className="w-3 h-3 my-auto bg-gray-400 rounded-full" />
|
||||
- Disabled
|
||||
<div className="w-3 h-3 my-auto rounded-full bg-amber-400" />
|
||||
- Admin
|
||||
</div>
|
||||
description="Edit channel throughput and other settings"
|
||||
buttons={
|
||||
<Button
|
||||
border
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
icon={<FiCode />}
|
||||
>
|
||||
Debug
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="w-full max-w-3xl p-4 space-y-2 md:p-10 md:max-w-xl">
|
||||
<Cover enabled={debug} content={<JSONPretty data={channels} />} />
|
||||
<div className="w-full p-4 space-y-2 md:p-10">
|
||||
{channels.map((channel) => (
|
||||
<Channel key={channel.index} channel={channel} />
|
||||
))}
|
||||
|
||||
@@ -2,13 +2,17 @@ import React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiMenu, FiSave } from 'react-icons/fi';
|
||||
import { FiCode, FiMenu, FiSave } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { Card } from '@app/components/generic/Card';
|
||||
import { Cover } from '@app/components/generic/Cover.jsx';
|
||||
import { Checkbox } from '@app/components/generic/form/Checkbox';
|
||||
import { Select } from '@app/components/generic/form/Select.jsx';
|
||||
import { IconButton } from '@app/components/generic/IconButton.jsx';
|
||||
import { connection } from '@app/core/connection';
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { addUser } from '@app/core/slices/meshtasticSlice.js';
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { Button } from '@components/generic/Button';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
@@ -21,21 +25,27 @@ export interface DeviceProps {
|
||||
|
||||
export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const user = useAppSelector((state) => state.meshtastic.user);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const user = useAppSelector((state) => state.meshtastic.myUser);
|
||||
const { register, handleSubmit, formState } = useForm<{
|
||||
longName: string;
|
||||
shortName: string;
|
||||
isLicensed: boolean;
|
||||
team: Protobuf.Team;
|
||||
}>({
|
||||
defaultValues: {
|
||||
longName: user.longName,
|
||||
shortName: user.shortName,
|
||||
isLicensed: user.isLicensed,
|
||||
team: user.team,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setOwner({ ...user, ...data });
|
||||
// TODO: can be remove once getUser is implemented
|
||||
dispatch(addUser({ ...user, ...data }));
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -66,7 +76,20 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
<Card
|
||||
title="Basic settings"
|
||||
description="Device name and user parameters"
|
||||
buttons={
|
||||
<Button
|
||||
border
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
icon={<FiCode />}
|
||||
>
|
||||
Debug
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Cover enabled={debug} content={<JSONPretty data={user} />} />
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<form className="space-y-2" onSubmit={onSubmit}>
|
||||
<Input label={'Device ID'} value={user.id} disabled />
|
||||
@@ -82,6 +105,11 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
{...register('shortName')}
|
||||
/>
|
||||
<Checkbox label="Licenced Operator?" {...register('isLicensed')} />
|
||||
<Select
|
||||
label="Team"
|
||||
optionsEnum={Protobuf.Team}
|
||||
{...register('team')}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -2,9 +2,11 @@ import React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiMenu, FiSave, FiXCircle } from 'react-icons/fi';
|
||||
import { FiCode, FiMenu, FiSave, FiXCircle } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { Card } from '@app/components/generic/Card';
|
||||
import { Cover } from '@app/components/generic/Cover.jsx';
|
||||
import { Checkbox } from '@app/components/generic/form/Checkbox';
|
||||
import { Select } from '@app/components/generic/form/Select.jsx';
|
||||
import { IconButton } from '@app/components/generic/IconButton.jsx';
|
||||
@@ -23,6 +25,7 @@ export interface RadioProps {
|
||||
export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const radioConfig = useAppSelector((state) => state.meshtastic.preferences);
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
|
||||
const { register, handleSubmit, formState, reset } =
|
||||
useForm<Protobuf.RadioConfig_UserPreferences>({
|
||||
@@ -69,7 +72,20 @@ export const Radio = ({ navOpen, setNavOpen }: RadioProps): JSX.Element => {
|
||||
<Card
|
||||
title="Basic settings"
|
||||
description="Device name and user parameters"
|
||||
buttons={
|
||||
<Button
|
||||
border
|
||||
active={debug}
|
||||
onClick={(): void => {
|
||||
setDebug(!debug);
|
||||
}}
|
||||
icon={<FiCode />}
|
||||
>
|
||||
Debug
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Cover enabled={debug} content={<JSONPretty data={radioConfig} />} />
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<form className="space-y-2" onSubmit={onSubmit}>
|
||||
<div>WiFi</div>
|
||||
|
||||
Reference in New Issue
Block a user