71 changed files with 1697 additions and 1282 deletions
File diff suppressed because it is too large
@ -0,0 +1,21 @@ |
|||||
|
import type React from 'react'; |
||||
|
|
||||
|
import { m } from 'framer-motion'; |
||||
|
|
||||
|
export interface CardProps { |
||||
|
className?: string; |
||||
|
children: React.ReactNode; |
||||
|
} |
||||
|
|
||||
|
export const Card = ({ className, children }: CardProps): JSX.Element => { |
||||
|
return ( |
||||
|
<m.div |
||||
|
className={`flex select-none rounded-md bg-white p-4 shadow-md dark:bg-primaryDark ${className}`} |
||||
|
initial={{ opacity: 0 }} |
||||
|
animate={{ opacity: 1 }} |
||||
|
exit={{ opacity: 0 }} |
||||
|
> |
||||
|
{children} |
||||
|
</m.div> |
||||
|
); |
||||
|
}; |
||||
@ -1,10 +0,0 @@ |
|||||
import type 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> : <></>; |
|
||||
}; |
|
||||
@ -1,46 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import { IconButton } from '@meshtastic/components'; |
|
||||
|
|
||||
export interface ListItemProps { |
|
||||
selected: boolean; |
|
||||
selectedIcon: JSX.Element; |
|
||||
actions?: JSX.Element; |
|
||||
status: JSX.Element; |
|
||||
onClick?: () => void; |
|
||||
children: React.ReactNode; |
|
||||
} |
|
||||
|
|
||||
export const ListItem = ({ |
|
||||
selected, |
|
||||
selectedIcon, |
|
||||
actions, |
|
||||
status, |
|
||||
onClick, |
|
||||
children, |
|
||||
}: ListItemProps): JSX.Element => { |
|
||||
return ( |
|
||||
<div |
|
||||
onClick={(): void => { |
|
||||
onClick && onClick(); |
|
||||
}} |
|
||||
className={`flex select-none rounded-md border bg-gray-100 shadow-md dark:bg-primaryDark ${ |
|
||||
selected |
|
||||
? 'border-primary dark:border-primary' |
|
||||
: 'border-gray-100 dark:border-primaryDark' |
|
||||
}`}
|
|
||||
> |
|
||||
<div className="w-3 rounded-l-md bg-green-500" /> |
|
||||
<div className="flex justify-between p-2"> |
|
||||
<div className="my-auto flex space-x-2"> |
|
||||
{status} |
|
||||
<div className="flex gap-2">{children}</div> |
|
||||
</div> |
|
||||
<div className="flex gap-2"> |
|
||||
{actions} |
|
||||
<IconButton active={selected} icon={selectedIcon} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
@ -0,0 +1,9 @@ |
|||||
|
import type React from 'react'; |
||||
|
|
||||
|
export const Loading = (): JSX.Element => { |
||||
|
return ( |
||||
|
<div className="absolute top-0 bottom-0 left-0 right-0 z-10 flex rounded-md backdrop-blur-sm backdrop-filter"> |
||||
|
<div className="m-auto text-lg font-medium text-gray-400">Loading</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,19 @@ |
|||||
|
import 'tippy.js/dist/tippy.css'; |
||||
|
|
||||
|
import type React from 'react'; |
||||
|
|
||||
|
import cuid from 'cuid'; |
||||
|
|
||||
|
import Tippy, { TippyProps } from '@tippyjs/react'; |
||||
|
|
||||
|
export const Tooltip = ({ |
||||
|
children, |
||||
|
content, |
||||
|
...props |
||||
|
}: TippyProps): JSX.Element => { |
||||
|
return ( |
||||
|
<Tippy content={content} {...props}> |
||||
|
<div key={cuid()}>{children}</div> |
||||
|
</Tippy> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,73 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { FiCheck } from 'react-icons/fi'; |
||||
|
|
||||
|
type DefaultButtonProps = JSX.IntrinsicElements['button']; |
||||
|
|
||||
|
export enum ButtonSize { |
||||
|
Small = 'small', |
||||
|
Medium = 'medium', |
||||
|
Large = 'large', |
||||
|
} |
||||
|
|
||||
|
export interface ButtonProps extends DefaultButtonProps { |
||||
|
icon?: JSX.Element; |
||||
|
active?: boolean; |
||||
|
border?: boolean; |
||||
|
size?: ButtonSize; |
||||
|
confirmAction?: () => void; |
||||
|
} |
||||
|
|
||||
|
export const Button = ({ |
||||
|
icon, |
||||
|
className, |
||||
|
active, |
||||
|
border, |
||||
|
size = ButtonSize.Medium, |
||||
|
confirmAction, |
||||
|
disabled, |
||||
|
children, |
||||
|
...props |
||||
|
}: ButtonProps): JSX.Element => { |
||||
|
const [hasConfirmed, setHasConfirmed] = React.useState(false); |
||||
|
|
||||
|
const handleConfirm = (): void => { |
||||
|
if (typeof confirmAction == 'function') { |
||||
|
if (hasConfirmed) { |
||||
|
void confirmAction(); |
||||
|
} |
||||
|
setHasConfirmed(true); |
||||
|
setTimeout(() => { |
||||
|
setHasConfirmed(false); |
||||
|
}, 3000); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<button |
||||
|
onClick={handleConfirm} |
||||
|
className={`flex select-none items-center space-x-3 rounded-md border border-transparent text-sm transition duration-200 ease-in-out focus-within:border-primary focus-within:shadow-border active:scale-95 dark:text-white dark:focus-within:border-primary
|
||||
|
${ |
||||
|
size === ButtonSize.Small |
||||
|
? 'p-0' |
||||
|
: size === ButtonSize.Medium |
||||
|
? 'p-2' |
||||
|
: 'p-4' |
||||
|
} |
||||
|
${ |
||||
|
disabled |
||||
|
? 'cursor-not-allowed bg-white dark:bg-primaryDark' |
||||
|
: 'cursor-pointer hover:bg-gray-100 hover:shadow-md dark:hover:bg-secondaryDark' |
||||
|
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${className}`}
|
||||
|
{...props} |
||||
|
> |
||||
|
{icon && ( |
||||
|
<div className="text-gray-500 dark:text-gray-400"> |
||||
|
{hasConfirmed ? <FiCheck /> : icon} |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
<span>{children}</span> |
||||
|
</button> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,35 @@ |
|||||
|
import type React from 'react'; |
||||
|
|
||||
|
type DefaulButtonProps = JSX.IntrinsicElements['button']; |
||||
|
|
||||
|
export interface IconButtonProps extends DefaulButtonProps { |
||||
|
icon: React.ReactNode; |
||||
|
active?: boolean; |
||||
|
} |
||||
|
|
||||
|
export const IconButton = ({ |
||||
|
icon, |
||||
|
active, |
||||
|
disabled, |
||||
|
...props |
||||
|
}: IconButtonProps): JSX.Element => { |
||||
|
return ( |
||||
|
<div className="my-auto text-gray-500 dark:text-gray-400"> |
||||
|
<button |
||||
|
type="button" |
||||
|
disabled={disabled} |
||||
|
className={`rounded-md p-2 transition duration-200 ease-in-out active:scale-95 ${ |
||||
|
active |
||||
|
? 'bg-gray-200 dark:bg-gray-600' |
||||
|
: 'hover:bg-gray-200 dark:hover:bg-gray-600' |
||||
|
} ${ |
||||
|
disabled ? 'cursor-not-allowed text-gray-400 dark:text-gray-700' : '' |
||||
|
}`}
|
||||
|
{...props} |
||||
|
> |
||||
|
{icon} |
||||
|
<span className="sr-only">Refresh</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,48 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { Label } from '@components/generic/form/Label'; |
||||
|
|
||||
|
type DefaultInputProps = JSX.IntrinsicElements['input']; |
||||
|
|
||||
|
export interface CheckboxProps extends DefaultInputProps { |
||||
|
action?: (enabled: boolean) => void; |
||||
|
label: string; |
||||
|
valid?: boolean; |
||||
|
validationMessage?: string; |
||||
|
error?: boolean; |
||||
|
} |
||||
|
|
||||
|
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>( |
||||
|
function Input( |
||||
|
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps, |
||||
|
ref, |
||||
|
) { |
||||
|
return ( |
||||
|
<div className="flex w-full flex-col"> |
||||
|
<Label label={label} /> |
||||
|
<div className="ml-auto"> |
||||
|
<input |
||||
|
ref={ref} |
||||
|
type="checkbox" |
||||
|
id={id} |
||||
|
className={`h-8 w-8 appearance-none rounded-md border border-gray-400 transition duration-200 ease-in-out checked:border-transparent checked:bg-primary focus-within:shadow-border focus:outline-none dark:border-gray-200 ${ |
||||
|
props.disabled |
||||
|
? 'border-gray-400 bg-gray-300 text-gray-500 dark:border-gray-700 dark:bg-secondaryDark dark:text-gray-400' |
||||
|
: '' |
||||
|
} ${ |
||||
|
error |
||||
|
? 'border-red-500' |
||||
|
: props.disabled |
||||
|
? 'border-gray-200' |
||||
|
: 'focus-within:border-primary hover:border-primary dark:focus-within:border-primary dark:hover:border-primary' |
||||
|
}`}
|
||||
|
{...props} |
||||
|
/> |
||||
|
</div> |
||||
|
{!valid && ( |
||||
|
<div className="text-sm text-gray-600">{validationMessage}</div> |
||||
|
)} |
||||
|
</div> |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
@ -0,0 +1,37 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { InputWrapper } from '@components/generic/form/InputWrapper'; |
||||
|
import { Label } from '@components/generic/form/Label'; |
||||
|
|
||||
|
type DefaultInputProps = JSX.IntrinsicElements['input']; |
||||
|
|
||||
|
export interface InputProps extends DefaultInputProps { |
||||
|
label?: string; |
||||
|
error?: string; |
||||
|
action?: JSX.Element; |
||||
|
prefix?: string; |
||||
|
suffix?: string; |
||||
|
} |
||||
|
|
||||
|
export const Input = React.forwardRef<HTMLInputElement, InputProps>( |
||||
|
function Input({ label, error, action, suffix, ...props }: InputProps, ref) { |
||||
|
return ( |
||||
|
<div className="w-full"> |
||||
|
{label && <Label label={label} error={error} />} |
||||
|
<InputWrapper error={error} disabled={props.disabled}> |
||||
|
<input |
||||
|
ref={ref} |
||||
|
className="h-10 w-full bg-transparent px-3 py-2 focus:outline-none disabled:cursor-not-allowed dark:text-white" |
||||
|
{...props} |
||||
|
/> |
||||
|
{suffix && ( |
||||
|
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400"> |
||||
|
{suffix} |
||||
|
</span> |
||||
|
)} |
||||
|
{action && <div className="mr-1 flex">{action}</div>} |
||||
|
</InputWrapper> |
||||
|
</div> |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
@ -0,0 +1,29 @@ |
|||||
|
import type React from 'react'; |
||||
|
|
||||
|
export interface LabelProps { |
||||
|
error?: string; |
||||
|
disabled?: boolean; |
||||
|
children: React.ReactNode; |
||||
|
} |
||||
|
|
||||
|
export const InputWrapper = ({ |
||||
|
error, |
||||
|
disabled, |
||||
|
children, |
||||
|
}: LabelProps): JSX.Element => ( |
||||
|
<div |
||||
|
className={`flex w-full rounded-md border border-gray-400 transition duration-200 ease-in-out dark:border-gray-200 ${ |
||||
|
disabled |
||||
|
? 'border-gray-400 bg-gray-300 text-gray-500 dark:border-gray-700 dark:bg-secondaryDark dark:text-gray-400' |
||||
|
: '' |
||||
|
} ${ |
||||
|
error |
||||
|
? 'border-red-500 dark:border-red-500' |
||||
|
: disabled |
||||
|
? '' |
||||
|
: ' focus-within:border-primary focus-within:shadow-border hover:border-primary dark:focus-within:border-primary dark:hover:border-primary' |
||||
|
}`}
|
||||
|
> |
||||
|
{children} |
||||
|
</div> |
||||
|
); |
||||
@ -0,0 +1,68 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import { InputWrapper } from '@components/generic/form/InputWrapper'; |
||||
|
import { Label } from '@components/generic/form/Label'; |
||||
|
|
||||
|
type DefaultSelectProps = JSX.IntrinsicElements['select']; |
||||
|
|
||||
|
export interface SelectProps extends DefaultSelectProps { |
||||
|
options?: { |
||||
|
name: string | number; |
||||
|
value: string | number; |
||||
|
}[]; |
||||
|
optionsEnum?: { [s: string]: string | number }; |
||||
|
label?: string; |
||||
|
error?: string; |
||||
|
small?: boolean; |
||||
|
} |
||||
|
|
||||
|
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>( |
||||
|
({ options, optionsEnum, label, error, small, ...props }, ref) => { |
||||
|
const optionsEnumValues = optionsEnum |
||||
|
? Object.entries(optionsEnum).filter( |
||||
|
(value) => typeof value[1] === 'number', |
||||
|
) |
||||
|
: []; |
||||
|
return ( |
||||
|
<div> |
||||
|
{label && <Label label={label} error={error} />} |
||||
|
<InputWrapper error={error} disabled={props.disabled}> |
||||
|
<select |
||||
|
ref={ref} |
||||
|
className={`w-full rounded-md bg-transparent focus:border-primary focus:outline-none disabled:cursor-not-allowed dark:text-white ${ |
||||
|
small ? 'm-1' : 'mx-2 h-10' |
||||
|
}`}
|
||||
|
disabled={ |
||||
|
props.disabled |
||||
|
? true |
||||
|
: !(optionsEnumValues.length || options?.length) |
||||
|
} |
||||
|
{...props} |
||||
|
> |
||||
|
{!(optionsEnumValues.length || options?.length) && ( |
||||
|
<option key="loading" className="dark:bg-gray-700"> |
||||
|
Loading |
||||
|
</option> |
||||
|
)} |
||||
|
{optionsEnumValues.length && |
||||
|
optionsEnumValues.map(([name, value], index) => ( |
||||
|
<option key={index} className="dark:bg-gray-700" value={value}> |
||||
|
{name} |
||||
|
</option> |
||||
|
))} |
||||
|
{options && |
||||
|
options.map((option, index) => ( |
||||
|
<option |
||||
|
key={index} |
||||
|
className="dark:bg-gray-700" |
||||
|
value={option.value} |
||||
|
> |
||||
|
{option.name} |
||||
|
</option> |
||||
|
))} |
||||
|
</select> |
||||
|
</InputWrapper> |
||||
|
</div> |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
@ -1,31 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import JSONPretty from 'react-json-pretty'; |
|
||||
|
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector'; |
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton'; |
|
||||
|
|
||||
export const ExternalNotificationsDebugPanel = (): JSX.Element => { |
|
||||
const preferences = useAppSelector( |
|
||||
(state) => state.meshtastic.radio.preferences, |
|
||||
); |
|
||||
|
|
||||
const debugData = { |
|
||||
extNotificationPluginActive: preferences.extNotificationPluginActive, |
|
||||
extNotificationPluginAlertBell: preferences.extNotificationPluginAlertBell, |
|
||||
extNotificationPluginAlertMessage: |
|
||||
preferences.extNotificationPluginAlertMessage, |
|
||||
extNotificationPluginEnabled: preferences.extNotificationPluginEnabled, |
|
||||
extNotificationPluginOutput: preferences.extNotificationPluginOutput, |
|
||||
extNotificationPluginOutputMs: preferences.extNotificationPluginOutputMs, |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<div className="fixed right-0 m-2"> |
|
||||
<CopyButton data={JSON.stringify(debugData)} /> |
|
||||
</div> |
|
||||
<JSONPretty className="max-w-sm" data={debugData} /> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,27 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import JSONPretty from 'react-json-pretty'; |
|
||||
|
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector'; |
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton'; |
|
||||
|
|
||||
export const RangeTestDebugPanel = (): JSX.Element => { |
|
||||
const preferences = useAppSelector( |
|
||||
(state) => state.meshtastic.radio.preferences, |
|
||||
); |
|
||||
|
|
||||
const debugData = { |
|
||||
rangeTestPluginEnabled: preferences.rangeTestPluginEnabled, |
|
||||
rangeTestPluginSave: preferences.rangeTestPluginSave, |
|
||||
rangeTestPluginSender: preferences.rangeTestPluginSender, |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<div className="fixed right-0 m-2"> |
|
||||
<CopyButton data={JSON.stringify(debugData)} /> |
|
||||
</div> |
|
||||
<JSONPretty className="max-w-sm" data={debugData} /> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,30 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import JSONPretty from 'react-json-pretty'; |
|
||||
|
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector'; |
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton'; |
|
||||
|
|
||||
export const SerialDebugPanel = (): JSX.Element => { |
|
||||
const preferences = useAppSelector( |
|
||||
(state) => state.meshtastic.radio.preferences, |
|
||||
); |
|
||||
|
|
||||
const debugData = { |
|
||||
serialpluginEnabled: preferences.serialpluginEnabled, |
|
||||
serialpluginEcho: preferences.serialpluginEcho, |
|
||||
serialpluginMode: preferences.serialpluginMode, |
|
||||
serialpluginRxd: preferences.serialpluginRxd, |
|
||||
serialpluginTxd: preferences.serialpluginTxd, |
|
||||
serialpluginTimeout: preferences.serialpluginTimeout, |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<div className="fixed right-0 m-2"> |
|
||||
<CopyButton data={JSON.stringify(debugData)} /> |
|
||||
</div> |
|
||||
<JSONPretty className="max-w-sm" data={debugData} /> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,31 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import JSONPretty from 'react-json-pretty'; |
|
||||
|
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector'; |
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton'; |
|
||||
|
|
||||
export const StoreForwardDebugPanel = (): JSX.Element => { |
|
||||
const preferences = useAppSelector( |
|
||||
(state) => state.meshtastic.radio.preferences, |
|
||||
); |
|
||||
|
|
||||
const debugData = { |
|
||||
storeForwardPluginEnabled: preferences.storeForwardPluginEnabled, |
|
||||
storeForwardPluginHeartbeat: preferences.storeForwardPluginHeartbeat, |
|
||||
storeForwardPluginRecords: preferences.storeForwardPluginRecords, |
|
||||
storeForwardPluginHistoryReturnMax: |
|
||||
preferences.storeForwardPluginHistoryReturnMax, |
|
||||
storeForwardPluginHistoryReturnWindow: |
|
||||
preferences.storeForwardPluginHistoryReturnWindow, |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<div className="fixed right-0 m-2"> |
|
||||
<CopyButton data={JSON.stringify(debugData)} /> |
|
||||
</div> |
|
||||
<JSONPretty className="max-w-sm" data={debugData} /> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,21 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import JSONPretty from 'react-json-pretty'; |
|
||||
|
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton'; |
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|
||||
|
|
||||
export interface DebugPanelProps { |
|
||||
channel: Protobuf.Channel; |
|
||||
} |
|
||||
|
|
||||
export const DebugPanel = ({ channel }: DebugPanelProps): JSX.Element => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="fixed right-0 m-2"> |
|
||||
<CopyButton data={JSON.stringify(channel)} /> |
|
||||
</div> |
|
||||
<JSONPretty className="max-w-sm" data={channel} /> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,20 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import QRCode from 'react-qr-code'; |
|
||||
|
|
||||
import type { Protobuf } from '@meshtastic/meshtasticjs'; |
|
||||
|
|
||||
export interface QRCodePanelProps { |
|
||||
channel: Protobuf.Channel; |
|
||||
} |
|
||||
|
|
||||
export const QRCodePanel = ({ channel }: QRCodePanelProps): JSX.Element => { |
|
||||
return ( |
|
||||
<div className="m-auto"> |
|
||||
<QRCode |
|
||||
className="rounded-md" |
|
||||
value={`https://www.meshtastic.org/d/#${channel.index}`} |
|
||||
/> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,56 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import { FiBluetooth, FiCpu, FiWifi } from 'react-icons/fi'; |
|
||||
|
|
||||
import { connType, openConnectionModal } from '@core/slices/appSlice'; |
|
||||
import { useAppDispatch } from '@hooks/useAppDispatch'; |
|
||||
import { useAppSelector } from '@hooks/useAppSelector'; |
|
||||
import { Button } from '@meshtastic/components'; |
|
||||
import { Types } from '@meshtastic/meshtasticjs'; |
|
||||
|
|
||||
export const DeviceStatus = (): JSX.Element => { |
|
||||
const dispatch = useAppDispatch(); |
|
||||
const appState = useAppSelector((state) => state.app); |
|
||||
const state = useAppSelector((state) => state.meshtastic); |
|
||||
|
|
||||
return ( |
|
||||
<Button |
|
||||
active |
|
||||
onClick={(): void => { |
|
||||
dispatch(dispatch(openConnectionModal())); |
|
||||
}} |
|
||||
> |
|
||||
<div className="flex gap-2 px-2"> |
|
||||
<div |
|
||||
className={` |
|
||||
my-auto h-2 w-2 min-w-[2] rounded-full ${ |
|
||||
[ |
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTED, |
|
||||
Types.DeviceStatusEnum.DEVICE_CONFIGURED, |
|
||||
].includes(state.deviceStatus) |
|
||||
? 'bg-green-400' |
|
||||
: [ |
|
||||
Types.DeviceStatusEnum.DEVICE_CONNECTING, |
|
||||
Types.DeviceStatusEnum.DEVICE_RECONNECTING, |
|
||||
Types.DeviceStatusEnum.DEVICE_CONFIGURING, |
|
||||
].includes(state.deviceStatus) |
|
||||
? 'bg-yellow-400' |
|
||||
: 'bg-gray-400' |
|
||||
}`}
|
|
||||
></div> |
|
||||
<div className="my-auto"> |
|
||||
{state.nodes.find( |
|
||||
(node) => node.number === state.radio.hardware.myNodeNum, |
|
||||
)?.user?.longName ?? 'Disconnected'} |
|
||||
</div> |
|
||||
{appState.connType === connType.BLE ? ( |
|
||||
<FiBluetooth className="h-5 w-5" /> |
|
||||
) : appState.connType === connType.SERIAL ? ( |
|
||||
<FiCpu className="h-5 w-5" /> |
|
||||
) : ( |
|
||||
<FiWifi className="h-5 w-5" /> |
|
||||
)} |
|
||||
</div> |
|
||||
</Button> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,21 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import JSONPretty from 'react-json-pretty'; |
|
||||
|
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton'; |
|
||||
import type { Node } from '@core/slices/meshtasticSlice'; |
|
||||
|
|
||||
export interface DebugPanelProps { |
|
||||
node: Node; |
|
||||
} |
|
||||
|
|
||||
export const DebugPanel = ({ node }: DebugPanelProps): JSX.Element => { |
|
||||
return ( |
|
||||
<div className="relative"> |
|
||||
<div className="fixed right-0 m-2"> |
|
||||
<CopyButton data={JSON.stringify(node)} /> |
|
||||
</div> |
|
||||
<JSONPretty className="max-w-sm" data={node} /> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,32 +0,0 @@ |
|||||
import type React from 'react'; |
|
||||
|
|
||||
import { CopyButton } from '@components/menu/buttons/CopyButton'; |
|
||||
import type { Node } from '@core/slices/meshtasticSlice'; |
|
||||
|
|
||||
export interface PositionPanelProps { |
|
||||
node: Node; |
|
||||
} |
|
||||
|
|
||||
export const PositionPanel = ({ node }: PositionPanelProps): JSX.Element => { |
|
||||
return ( |
|
||||
<div className="p-2"> |
|
||||
{node.currentPosition && ( |
|
||||
<div className="flex h-10 select-none justify-between rounded-md border border-gray-300 bg-transparent bg-gray-200 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 "> |
|
||||
<div className="my-auto px-1"> |
|
||||
{(node.currentPosition.latitudeI / 1e7).toPrecision(6)}, |
|
||||
{(node.currentPosition?.longitudeI / 1e7).toPrecision(6)} |
|
||||
</div> |
|
||||
<CopyButton |
|
||||
data={ |
|
||||
node.currentPosition |
|
||||
? `${node.currentPosition.latitudeI / 1e7},${ |
|
||||
node.currentPosition.longitudeI / 1e7 |
|
||||
}` |
|
||||
: '' |
|
||||
} |
|
||||
/> |
|
||||
</div> |
|
||||
)} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
Loading…
Reference in new issue