framer-motion#m TypeScript Examples
The following examples show how to use
framer-motion#m.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Logs.tsx From meshtastic-web with GNU General Public License v3.0 | 6 votes |
Wrapper = ({ className, children, }: { className?: string; children: React.ReactNode; }): JSX.Element => ( <td className={className}> <m.div className="-my-0.5 flex max-w-min cursor-pointer truncate rounded-sm px-0.5 hover:bg-gray-400 dark:hover:bg-gray-700" whileHover={{ scale: 1.01 }} whileTap={{ scale: 0.99 }} > {children} </m.div> </td> )
Example #2
Source File: Card.tsx From meshtastic-web with GNU General Public License v3.0 | 6 votes |
Card = ({
className,
title,
actions,
border,
children,
}: CardProps): JSX.Element => {
return (
<div
className={`flex h-full w-full flex-col rounded-md drop-shadow-md ${
border ? 'border border-gray-400 dark:border-gray-600' : ''
} ${className ?? ''}`}
>
{(title || actions) && (
<div className="w-full select-none justify-between rounded-t-md border-b border-gray-400 bg-gray-200 p-2 px-2 text-lg font-medium dark:border-gray-600 dark:bg-tertiaryDark dark:text-white">
<div className="handle flex h-8 justify-between">
<div className="my-auto ml-2 truncate">{title}</div>
{actions}
</div>
</div>
)}
<m.div
className={`flex flex-grow select-none flex-col gap-4 bg-white p-4 dark:bg-primaryDark ${
title || actions ? 'rounded-b-md' : 'rounded-md'
}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.1 }}
>
{children}
</m.div>
</div>
);
}
Example #3
Source File: ContextItem.tsx From meshtastic-web with GNU General Public License v3.0 | 6 votes |
ContextItem = ({ title, icon }: ContextItem): JSX.Element => {
return (
<div className="cursor-pointer first:rounded-t-md last:rounded-b-md hover:dark:bg-secondaryDark">
<m.div
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="flex gap-2 p-2"
>
<div className="my-auto">{icon}</div>
<div className="truncate">{title}</div>
</m.div>
</div>
);
}
Example #4
Source File: BottomNavItem.tsx From meshtastic-web with GNU General Public License v3.0 | 6 votes |
BottomNavItem = ({
tooltip,
onClick,
className,
children,
}: BottomNavItemProps) => {
return (
<Tooltip disabled={!tooltip} content={tooltip}>
<div
onClick={onClick}
className={`group flex h-full cursor-pointer select-none p-1 hover:bg-gray-300 dark:text-white dark:hover:bg-primaryDark ${className}`}
>
<m.div className="flex w-full gap-1" whileTap={{ scale: 0.99 }}>
{children}
</m.div>
</div>
</Tooltip>
);
}
Example #5
Source File: SidebarItem.tsx From meshtastic-web with GNU General Public License v3.0 | 6 votes |
SidebarItem = ({
selected,
setSelected,
actions,
children,
}: SidebarItemProps): JSX.Element => {
return (
<div
onClick={(): void => {
setSelected();
}}
className={`mx-2 flex cursor-pointer select-none rounded-md border bg-gray-200 p-2 shadow-md first:mt-2 last:mb-2 hover:border-primary dark:bg-tertiaryDark dark:hover:border-primary ${
selected ? 'border-primary' : 'border-gray-400 dark:border-gray-600'
}`}
>
<m.div
className="flex w-full justify-between"
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
>
<div className="flex gap-2">{children}</div>
<div className="flex gap-1">{actions}</div>
</m.div>
</div>
);
}
Example #6
Source File: IconButton.tsx From meshtastic-web with GNU General Public License v3.0 | 6 votes |
IconButton = ({
icon,
tooltip,
nested,
active,
disabled,
className,
...props
}: IconButtonProps): JSX.Element => {
return (
<Tooltip disabled={!tooltip} content={tooltip}>
<button
type="button"
disabled={disabled}
className={`rounded-md p-2 hover:bg-gray-300 ${
active ? 'bg-gray-300 dark:bg-secondaryDark' : ''
} ${
nested ? 'dark:hover:bg-primaryDark' : 'dark:hover:bg-secondaryDark'
} ${
disabled ? 'cursor-not-allowed text-gray-400 dark:text-gray-700' : ''
} ${className ?? ''}`}
{...props}
>
<m.div
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.95 }}
className="my-auto text-gray-600 dark:text-gray-400"
>
{icon}
</m.div>
<span className="sr-only">Refresh</span>
</button>
</Tooltip>
);
}
Example #7
Source File: Tab.tsx From meshtastic-web with GNU General Public License v3.0 | 5 votes |
Tab = ({
link,
icon,
title,
active,
activeRight,
activeLeft,
}: TabProps): JSX.Element => {
return (
<div
className={`max-w-[10rem] md:flex-grow ${
active
? 'bg-white dark:bg-primaryDark'
: 'bg-gray-300 dark:bg-secondaryDark'
}`}
>
<div
className={`group flex flex-grow cursor-pointer select-none py-2 hover:underline dark:text-white ${
active
? 'z-10 rounded-t-lg bg-gray-300 shadow-inner dark:bg-secondaryDark'
: 'bg-white drop-shadow-md dark:bg-primaryDark'
} ${activeRight ? 'rounded-br-lg' : ''} ${
activeLeft ? 'rounded-bl-lg' : ''
}`}
{...(link && link)}
>
<div
className={`my-auto w-full px-3 ${
active || activeLeft
? ''
: 'border-l border-gray-400 dark:border-gray-600'
}`}
>
<m.div
className="flex gap-2"
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
>
<div className="my-auto">{icon}</div>
<div className="hidden md:flex">{title}</div>
</m.div>
</div>
</div>
</div>
);
}
Example #8
Source File: FileBrowser.tsx From meshtastic-web with GNU General Public License v3.0 | 5 votes |
FileBrowser = (): JSX.Element => {
const connectionParams = useAppSelector(
(state) => state.app.connectionParams,
);
const appState = useAppSelector((state) => state.app);
const meshtasticState = useAppSelector((state) => state.meshtastic);
const { data } = useSWR<Files>(
`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
connectionParams.HTTP.address
}${
meshtasticState.radio.hardware.firmwareVersion.includes('1.2')
? '/json/spiffs/browse/static'
: '/json/fs/browse/static'
}`,
fetcher,
);
return (
<div className="flex h-full p-4">
<Card
title="File Browser"
actions={<Button icon={<FiFilePlus />}>Upload File</Button>}
className="flex-grow flex-col"
>
<div className="h-full px-4">
<AnimatePresence>
{(!data || data?.data.files.length === 0) && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={appState.darkMode ? '/Files_Dark.svg' : '/Files.svg'}
/>
</div>
)}
</AnimatePresence>
{data?.data.files.map((file) => (
<div
key={file.name}
className="flex h-10 w-full border-b border-gray-400 px-4 font-medium dark:border-gray-600 dark:text-white"
>
<div className="my-auto w-1/3">
<a
target="_blank"
rel="noopener noreferrer"
href={`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
connectionParams.HTTP.address
}/${file.name.replace('static/', '')}`}
>
{file.name.replace('static/', '').replace('.gz', '')}
</a>
</div>
<div className="my-auto w-1/3"></div>
</div>
))}
</div>
</Card>
</div>
);
}
Example #9
Source File: Button.tsx From meshtastic-web with GNU General Public License v3.0 | 5 votes |
Button = ({
icon,
className,
border,
size = ButtonSize.Medium,
confirmAction,
onClick,
disabled,
children,
}: ButtonProps): JSX.Element => {
const [hasConfirmed, setHasConfirmed] = useState(false);
const handleConfirm = (): void => {
if (typeof confirmAction == 'function') {
if (hasConfirmed) {
void confirmAction();
}
setHasConfirmed(true);
setTimeout(() => {
setHasConfirmed(false);
}, 3000);
}
};
return (
<m.button
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.97 }}
onClick={handleConfirm}
className={`flex select-none items-center space-x-3 rounded-md border border-transparent text-sm focus-within:border-primary focus-within:shadow-border 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-white hover:drop-shadow-md dark:hover:bg-secondaryDark'
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${
className ?? ''
}`}
onClickCapture={onClick}
>
{icon && (
<div className="text-gray-500 dark:text-gray-400">
{hasConfirmed ? <FiCheck /> : icon}
</div>
)}
<span>{children}</span>
</m.button>
);
}
Example #10
Source File: SidebarOverlay.tsx From meshtastic-web with GNU General Public License v3.0 | 5 votes |
SidebarOverlay = ({
title,
open,
close,
direction,
children,
}: SidebarOverlayProps): JSX.Element => {
return (
<AnimatePresence>
{open && (
<m.div
className="absolute z-30 flex h-full w-full flex-col bg-white dark:bg-primaryDark"
animate={direction === 'x' ? { translateX: 0 } : { translateY: 0 }}
initial={
direction === 'x' ? { translateX: '-100%' } : { translateY: '100%' }
}
exit={
direction === 'x' ? { translateX: '-100%' } : { translateY: '100%' }
}
transition={{ type: 'just' }}
>
{/* @ts-expect-error */}
<AnimateSharedLayout>
{/* <div className="flex gap-2 border-b border-gray-400 p-2 dark:border-gray-600"> */}
<div className="bg-white px-1 pt-1 drop-shadow-md dark:bg-primaryDark">
<div className="flex h-10 gap-1">
<div className="my-auto">
<IconButton
onClick={(): void => {
close();
}}
icon={<FiArrowLeft />}
/>
</div>
<div className="my-auto text-lg font-medium dark:text-white">
{title}
</div>
</div>
</div>
<div className="flex-grow overflow-y-auto">{children}</div>
</AnimateSharedLayout>
</m.div>
)}
</AnimatePresence>
);
}
Example #11
Source File: ExternalSection.tsx From meshtastic-web with GNU General Public License v3.0 | 5 votes |
ExternalSection = ({
title,
icon,
active,
onClick,
}: ExternalSectionProps): JSX.Element => {
const [open, setOpen] = useState(false);
const toggleOpen = (): void => setOpen(!open);
return (
<m.div
onClick={(): void => {
onClick();
}}
>
<m.div
layout
className={`w-full cursor-pointer select-none overflow-hidden border-l-4 bg-gray-200 dark:bg-tertiaryDark dark:text-gray-400 ${
active
? 'border-l-primary dark:border-l-primary'
: 'border-gray-400 dark:border-secondaryDark'
}`}
>
<m.div
layout
onClick={toggleOpen}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="flex justify-between gap-2 border-b border-gray-400 p-2 text-sm font-medium dark:border-primaryDark"
>
<m.div className="flex gap-2 ">
<m.div className="my-auto">{icon}</m.div>
{title}
</m.div>
<m.div className="my-auto">
<FiChevronRight />
</m.div>
</m.div>
</m.div>
</m.div>
);
}
Example #12
Source File: CollapsibleSection.tsx From meshtastic-web with GNU General Public License v3.0 | 5 votes |
CollapsibleSection = ({
title,
icon,
status,
children,
}: CollapsibleSectionProps): JSX.Element => {
const [open, setOpen] = useState(false);
const toggleOpen = (): void => setOpen(!open);
return (
<m.div>
<m.div
layout
onClick={toggleOpen}
className={`w-full cursor-pointer select-none overflow-hidden border-l-4 border-b bg-gray-200 p-2 text-sm font-medium dark:border-primaryDark dark:bg-tertiaryDark dark:text-gray-400 ${
open
? 'border-l-primary dark:border-l-primary'
: 'border-gray-400 dark:border-secondaryDark'
}`}
>
<m.div
layout
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="my-auto flex justify-between gap-2"
>
<m.div className="flex gap-2">
<m.div className="my-auto flex gap-2">
{status !== undefined ? (
<>
<div
className={`my-auto h-2 w-2 rounded-full ${
status ? 'bg-green-500' : 'bg-red-500'
}`}
/>
{icon}
</>
) : (
<>{icon}</>
)}
</m.div>
{title}
</m.div>
<m.div
animate={open ? 'open' : 'closed'}
initial={{ rotate: 180 }}
variants={{
open: { rotate: 0 },
closed: { rotate: 180 },
}}
transition={{ type: 'just' }}
className="my-auto"
>
<FiArrowUp />
</m.div>
</m.div>
</m.div>
<AnimatePresence>
{open && (
<m.div
className="p-2"
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{children}
</m.div>
)}
</AnimatePresence>
</m.div>
);
}
Example #13
Source File: Modal.tsx From meshtastic-web with GNU General Public License v3.0 | 5 votes |
Modal = ({
open,
bgDismiss,
onClose,
actions,
...props
}: ModalProps): JSX.Element => {
const darkMode = useAppSelector((state) => state.app.darkMode);
return (
<AnimatePresence>
{open && (
<m.div
className={`fixed inset-0 ${darkMode ? 'dark' : ''} ${
open ? 'z-30' : 'z-0'
}`}
>
<m.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.1 }}
className="fixed h-full w-full backdrop-blur-md backdrop-brightness-75 backdrop-filter"
onClick={(): void => {
bgDismiss && onClose();
}}
/>
<m.div className="text-center ">
<span
className="inline-block h-screen align-middle "
aria-hidden="true"
>
​
</span>
<div className="inline-block w-full max-w-3xl align-middle">
<Card
border
actions={
<div className="flex gap-2">
{actions}
<IconButton
tooltip="Close"
icon={<FiX />}
onClick={onClose}
/>
</div>
}
className="relative flex-col"
{...props}
/>
</div>
</m.div>
</m.div>
)}
</AnimatePresence>
);
}
Example #14
Source File: Logs.tsx From meshtastic-web with GNU General Public License v3.0 | 4 votes |
Logs = (): JSX.Element => {
const dispatch = useAppDispatch();
const meshtasticState = useAppSelector((state) => state.meshtastic);
const appState = useAppSelector((state) => state.app);
type lookupType = { [key: number]: string };
const emitterLookup: lookupType = {
[Types.Emitter.sendText]: 'text-rose-500',
[Types.Emitter.sendPacket]: 'text-pink-500',
[Types.Emitter.sendRaw]: 'text-fuchsia-500',
[Types.Emitter.setConfig]: 'text-purple-500',
[Types.Emitter.confirmSetConfig]: 'text-violet-500',
[Types.Emitter.setOwner]: 'text-indigo-500',
[Types.Emitter.setChannel]: 'text-blue-500',
[Types.Emitter.confirmSetChannel]: 'text-sky-500',
[Types.Emitter.deleteChannel]: 'text-cyan-500',
[Types.Emitter.getChannel]: 'text-teal-500',
[Types.Emitter.getAllChannels]: 'text-emerald-500',
[Types.Emitter.getConfig]: 'text-green-500',
[Types.Emitter.getOwner]: 'text-lime-500',
[Types.Emitter.configure]: 'text-yellow-500',
[Types.Emitter.handleFromRadio]: 'text-amber-500',
[Types.Emitter.handleMeshPacket]: 'text-orange-500',
[Types.Emitter.connect]: 'text-red-500',
[Types.Emitter.ping]: 'text-stone-500',
[Types.Emitter.readFromRadio]: 'text-zinc-500',
[Types.Emitter.writeToRadio]: 'text-gray-500',
[Types.Emitter.setDebugMode]: 'text-slate-500',
};
const levelLookup: lookupType = {
[Protobuf.LogRecord_Level.UNSET]: 'text-green-500',
[Protobuf.LogRecord_Level.CRITICAL]: 'text-purple-500',
[Protobuf.LogRecord_Level.ERROR]: 'text-red-500',
[Protobuf.LogRecord_Level.WARNING]: 'text-orange-500',
[Protobuf.LogRecord_Level.INFO]: 'text-blue-500',
[Protobuf.LogRecord_Level.DEBUG]: 'text-neutral-500',
[Protobuf.LogRecord_Level.TRACE]: 'text-slate-500',
};
return (
<div className="flex h-full flex-col gap-4 p-4">
<Card
title="Device Logs"
actions={
<IconButton
icon={<FiTrash />}
onClick={(): void => {
dispatch(clearLogs());
}}
/>
}
className="flex-grow overflow-y-auto"
>
<table className="table-cell flex-grow">
<tbody
className="
block h-full flex-col overflow-y-auto font-mono text-xs dark:text-gray-400"
>
<AnimatePresence>
{meshtasticState.logs.length === 0 && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={
appState.darkMode
? '/View_Code_Dark.svg'
: '/View_Code.svg'
}
/>
</div>
)}
{meshtasticState.logs.map((log, index) => (
<m.tr
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="group hover:bg-gray-300 dark:hover:bg-secondaryDark"
>
<m.td
className="w-6 cursor-pointer"
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
>
<div className="m-auto pl-2 text-white group-hover:text-black dark:text-primaryDark dark:group-hover:text-gray-400">
<FiArrowRight />
</div>
</m.td>
<Wrapper>
{log.date
.toLocaleString(undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
.replaceAll('/', '-')
.replace(',', '')}
</Wrapper>
<Wrapper>
<div className={emitterLookup[log.emitter]}>
[{Types.EmitterScope[log.scope]}.
{Types.Emitter[log.emitter]}]
</div>
</Wrapper>
<Wrapper className={levelLookup[log.level]}>
[{Protobuf.LogRecord_Level[log.level]}]{/* </div> */}
</Wrapper>
<td
className={`m-auto ${
log.packet ? '' : 'dark:text-secondaryDark'
}`}
>
<FiPaperclip />
</td>
<td className="w-full truncate pl-1">{log.message}</td>
</m.tr>
// </ContextMenu>
))}
</AnimatePresence>
</tbody>
</table>
</Card>
</div>
);
}
Example #15
Source File: Connection.tsx From meshtastic-web with GNU General Public License v3.0 | 4 votes |
Connection = (): JSX.Element => {
const dispatch = useAppDispatch();
const meshtasticState = useAppSelector((state) => state.meshtastic);
const appState = useAppSelector((state) => state.app);
const chromiunm = !!window.chrome;
useEffect(() => {
if (!import.meta.env.VITE_PUBLIC_HOSTED) {
dispatch(
setConnectionParams({
type: connType.HTTP,
params: {
address: connectionUrl,
tls: false,
receiveBatchRequests: false,
fetchInterval: 2000,
},
}),
);
void setConnection(connType.HTTP);
}
}, [dispatch]);
useEffect(() => {
if (meshtasticState.ready) {
dispatch(closeConnectionModal());
}
}, [meshtasticState.ready, dispatch]);
return (
<Modal
title="Connect to a device"
open={appState.connectionModalOpen}
onClose={(): void => {
dispatch(closeConnectionModal());
}}
>
<div className="flex max-w-3xl flex-col gap-4 md:flex-row">
<div className="flex flex-col md:w-1/2">
<div className="flex flex-grow flex-col space-y-2">
<Select
label="Connection Method"
optionsEnum={connType}
value={appState.connType}
onChange={(e): void => {
dispatch(setConnType(parseInt(e.target.value)));
}}
disabled={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
{appState.connType === connType.HTTP && (
<HTTP
connecting={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
)}
{appState.connType === connType.BLE &&
(chromiunm ? (
<BLE
connecting={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
) : (
<div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
<p>Unsupported.</p>
<p>Please use a Chromium based browser.</p>
</div>
))}
{appState.connType === connType.SERIAL &&
(chromiunm ? (
<Serial
connecting={
meshtasticState.deviceStatus ===
Types.DeviceStatusEnum.DEVICE_CONNECTED
}
/>
) : (
<div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
<p>Unsupported.</p>
<p>Please use a Chromium based browser.</p>
</div>
))}
</div>
</div>
<div className="md:w-1/2">
<div className="h-96 overflow-y-auto rounded-md border border-gray-400 bg-gray-200 p-2 drop-shadow-md dark:border-gray-600 dark:bg-tertiaryDark dark:text-gray-400">
{meshtasticState.logs.length === 0 && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="m-auto h-40 w-40 text-green-500"
src={
appState.darkMode ? '/View_Code_Dark.svg' : '/View_Code.svg'
}
/>
</div>
)}
{meshtasticState.logs
.filter((log) => {
return ![
Types.Emitter.handleFromRadio,
Types.Emitter.handleMeshPacket,
Types.Emitter.sendPacket,
].includes(log.emitter);
})
.map((log, index) => (
<div key={index} className="flex">
<div className="truncate font-mono text-sm">
{log.message}
</div>
</div>
))}
</div>
</div>
</div>
</Modal>
);
}
Example #16
Source File: ChannelChat.tsx From meshtastic-web with GNU General Public License v3.0 | 4 votes |
ChannelChat = ({
channel,
selectedIndex,
setSelectedIndex,
}: ChannelChatProps): JSX.Element => {
const myNodeNum = useAppSelector(
(state) => state.meshtastic.radio.hardware,
).myNodeNum;
const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
(node) => node.data.num !== myNodeNum,
);
const chats = useAppSelector((state) => state.meshtastic.chats);
const channels = useAppSelector(
(state) => state.meshtastic.radio.channels,
).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);
return (
<SidebarItem
key={channel.index}
selected={channel.index === selectedIndex}
setSelected={(): void => {
setSelectedIndex(channel.index);
}}
actions={<IconButton nested icon={<FiSettings />} />}
>
<Tooltip
content={
channel.settings?.name.length
? channel.settings.name
: `CH: ${channel.index}`
}
>
<div className="flex h-8 w-8 rounded-full bg-gray-300 dark:bg-primaryDark dark:text-white">
<div className="m-auto">
{channel.role === Protobuf.Channel_Role.PRIMARY ? (
<MdPublic />
) : (
<p>
{channel.settings?.name.length
? channel.settings.name.substring(0, 3).toUpperCase()
: `CH: ${channel.index}`}
</p>
)}
</div>
</div>
</Tooltip>
{chats[channel.index]?.messages.length ? (
<>
<div className="mx-2 flex h-8">
{[
...new Set(
chats[channel.index]?.messages.flatMap(({ message }) => [
message.packet.from,
]),
),
]
.sort()
.map((nodeId) => {
return (
<Tooltip
key={nodeId}
content={
nodes.find((node) => node.data.num === nodeId)?.data.user
?.longName ?? 'UNK'
}
>
<div className="flex h-full">
<m.div
whileHover={{ scale: 1.1 }}
className="my-auto -ml-2"
>
<Hashicon value={nodeId.toString()} size={20} />
</m.div>
</div>
</Tooltip>
);
})}
</div>
<div className="my-auto ml-auto text-xs font-semibold dark:text-gray-400">
{chats[channel.index].messages.length ? (
<TimeAgo datetime={chats[channel.index].lastInterraction} />
) : (
<div>No messages</div>
)}
</div>
</>
) : (
<div className="my-auto dark:text-white">No messages</div>
)}
</SidebarItem>
);
}
Example #17
Source File: NodeCard.tsx From meshtastic-web with GNU General Public License v3.0 | 4 votes |
NodeCard = ({
node,
isMyNode,
selected,
setSelected,
}: NodeCardProps): JSX.Element => {
const { map } = useMapbox();
const [infoOpen, setInfoOpen] = useState(false);
const [PositionConfidence, setPositionConfidence] =
useState<PositionConfidence>('none');
useEffect(() => {
setPositionConfidence(
node.position
? new Date(node.position.posTimestamp * 1000) >
new Date(new Date().getTime() - 1000 * 60 * 30)
? 'high'
: 'low'
: 'none',
);
}, [node.position]);
return (
<>
<SidebarItem
selected={selected}
setSelected={setSelected}
actions={
<>
<IconButton
nested
tooltip={PositionConfidence !== 'none' ? 'Fly to Node' : ''}
disabled={PositionConfidence === 'none'}
onClick={(e): void => {
e.stopPropagation();
setSelected();
if (PositionConfidence !== 'none' && node.position) {
map?.flyTo({
center: new LngLat(
node.position.longitudeI / 1e7,
node.position.latitudeI / 1e7,
),
zoom: 16,
});
}
}}
icon={
PositionConfidence === 'high' ? (
<MdGpsFixed />
) : PositionConfidence === 'low' ? (
<MdGpsNotFixed />
) : (
<MdGpsOff />
)
}
/>
<IconButton
nested
tooltip="Show Node Info"
onClick={(e): void => {
e.stopPropagation();
setInfoOpen(true);
}}
icon={<FiAlignLeft />}
/>
</>
}
>
<div className="flex dark:text-white">
<div className="relative m-auto">
{isMyNode && (
<Tooltip content="Your Node">
<m.div
whileHover={{ scale: 1.05 }}
className="absolute -right-1 -top-1 rounded-full bg-yellow-500 p-0.5"
>
<BiCrown className="h-3 w-3" />
</m.div>
</Tooltip>
)}
<Hashicon value={node.num.toString()} size={32} />
</div>
</div>
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
{node.lastHeard
? new Date(node.lastHeard).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
})
: 'Never'}
</div>
</SidebarItem>
<SidebarOverlay
title={`Node ${node.user?.longName ?? 'UNK'} `}
open={infoOpen}
close={(): void => {
setInfoOpen(false);
}}
direction="x"
>
<CollapsibleSection title="User" icon={<FiUser />}>
<div className="flex p-2">
<div className="m-auto flex flex-col gap-2">
<Hashicon value={node.num.toString()} size={180} />
<div className="text-center text-lg font-medium dark:text-white">
{node?.user?.longName || 'Unknown'}
</div>
</div>
</div>
</CollapsibleSection>
<CollapsibleSection title="Location" icon={<FiMapPin />}>
<>
<div className="flex h-10 select-none justify-between rounded-md border border-gray-400 bg-transparent bg-gray-300 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 ">
{node.position ? (
<>
<div className="my-auto px-1">
{(node.position.latitudeI / 1e7).toPrecision(6)}
,
{(node.position?.longitudeI / 1e7).toPrecision(6)}
</div>
<CopyButton
data={
node.position
? `${node.position.latitudeI / 1e7},${
node.position.longitudeI / 1e7
}`
: ''
}
/>
</>
) : (
<div className="my-auto px-1">No location data received</div>
)}
</div>
</>
</CollapsibleSection>
<CollapsibleSection title="Line of Sight" icon={<IoTelescope />}>
<div>Info</div>
</CollapsibleSection>
<CollapsibleSection title="Administration" icon={<FiSliders />}>
<div>Info</div>
</CollapsibleSection>
<CollapsibleSection title="Debug" icon={<FiCode />}>
<>
<div className="fixed right-0 mr-6">
<CopyButton data={JSON.stringify(node)} />
</div>
<JSONPretty className="max-w-sm" data={node} />
</>
</CollapsibleSection>
</SidebarOverlay>
</>
);
}