DM's and layout fixes
This commit is contained in:
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { m } from 'framer-motion';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector.js';
|
||||
import { useAppSelector } from '@app/hooks/useAppSelector';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
|
||||
export const Info = (): JSX.Element => {
|
||||
|
||||
@@ -41,7 +41,7 @@ export const MapContainer = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full">
|
||||
<div className="absolute right-0 z-20 m-4 space-y-2 rounded-md border border-gray-300 bg-white p-2 shadow-md dark:border-gray-600 dark:bg-primaryDark">
|
||||
<div className="absolute right-0 z-10 m-4 space-y-2 rounded-md border border-gray-300 bg-white p-2 shadow-md dark:border-gray-600 dark:bg-primaryDark">
|
||||
<IconButton
|
||||
active={mapState.style === 'Satellite'}
|
||||
onClick={(): void => {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
|
||||
import { m } from 'framer-motion';
|
||||
import { FiSettings } from 'react-icons/fi';
|
||||
import { MdPublic } from 'react-icons/md';
|
||||
import TimeAgo from 'timeago-react';
|
||||
|
||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { IconButton, Tooltip } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
export interface ChannelChatProps {
|
||||
channel: Protobuf.Channel;
|
||||
selectedIndex: number;
|
||||
setSelectedIndex: (index: number) => void;
|
||||
}
|
||||
|
||||
export const 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.number !== 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 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-200 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.number === nodeId)?.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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FiSettings } from 'react-icons/fi';
|
||||
|
||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||
import type { Node } from '@core/slices/meshtasticSlice';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
|
||||
export interface DmChatProps {
|
||||
node: Node;
|
||||
selectedIndex: number;
|
||||
setSelectedIndex: (index: number) => void;
|
||||
}
|
||||
|
||||
export const DmChat = ({
|
||||
node,
|
||||
selectedIndex,
|
||||
setSelectedIndex,
|
||||
}: DmChatProps): JSX.Element => {
|
||||
return (
|
||||
<SidebarItem
|
||||
key={node.number}
|
||||
selected={node.number === selectedIndex}
|
||||
setSelected={(): void => {
|
||||
setSelectedIndex(node.number);
|
||||
}}
|
||||
actions={<IconButton icon={<FiSettings />} />}
|
||||
>
|
||||
<div className="flex dark:text-white">
|
||||
<div className="m-auto">
|
||||
<Hashicon value={node.number.toString()} size={32} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-auto mr-auto font-semibold dark:text-white">
|
||||
{node.user?.longName ?? 'Unknown'}
|
||||
</div>
|
||||
</SidebarItem>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import type React from 'react';
|
||||
|
||||
import { FiClock } from 'react-icons/fi';
|
||||
|
||||
import type { Node } from '@app/core/slices/meshtasticSlice';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { Tooltip } from '@meshtastic/components';
|
||||
@@ -23,9 +25,11 @@ export const Message = ({
|
||||
<div className="group mb-3 hover:bg-gray-200 dark:hover:bg-primaryDark">
|
||||
{lastMsgSameUser ? (
|
||||
<div
|
||||
className={`mx-6 -mt-3 flex gap-2 ${lastMsgSameUser ? '' : 'py-1'}`}
|
||||
className={`mx-6 -mt-3 flex justify-between ${
|
||||
lastMsgSameUser ? '' : 'py-1'
|
||||
}`}
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex gap-2">
|
||||
<Tooltip content={rxTime.toString()}>
|
||||
<div className="my-auto ml-auto w-8 pt-1 text-xs text-transparent dark:group-hover:text-gray-400">
|
||||
{rxTime
|
||||
@@ -37,14 +41,20 @@ export const Message = ({
|
||||
.replace('PM', '')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div
|
||||
className={`my-auto dark:text-gray-300 ${
|
||||
ack ? '' : 'animate-pulse dark:text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`my-auto dark:text-gray-300 ${
|
||||
ack ? '' : 'animate-pulse dark:text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
<Tooltip content="Response time">
|
||||
<div className="flex gap-1 pt-1 text-xs text-transparent dark:group-hover:text-gray-400">
|
||||
<FiClock className="mt-0.5" />
|
||||
<div>25s</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mx-6 flex gap-2">
|
||||
|
||||
@@ -7,23 +7,38 @@ import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { Input } from '@meshtastic/components';
|
||||
|
||||
export interface MessageBarProps {
|
||||
channelIndex: number;
|
||||
chatIndex: number;
|
||||
}
|
||||
|
||||
export const MessageBar = ({ channelIndex }: MessageBarProps): JSX.Element => {
|
||||
export const MessageBar = ({ chatIndex }: MessageBarProps): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
||||
const meshtasticState = useAppSelector((state) => state.meshtastic);
|
||||
|
||||
const [isChannel, setIsChannel] = React.useState(false);
|
||||
|
||||
React.useState(() => {
|
||||
setIsChannel(
|
||||
meshtasticState.radio.channels.findIndex(
|
||||
(channel) => channel.index === chatIndex,
|
||||
) !== -1,
|
||||
);
|
||||
});
|
||||
|
||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||
const sendMessage = (): void => {
|
||||
if (ready) {
|
||||
if (meshtasticState.ready) {
|
||||
void connection.sendText(
|
||||
currentMessage,
|
||||
undefined,
|
||||
isChannel ? undefined : chatIndex,
|
||||
true,
|
||||
channelIndex--,
|
||||
isChannel ? chatIndex-- : 0,
|
||||
(id) => {
|
||||
dispatch(ackMessage({ channel: channelIndex--, messageId: id }));
|
||||
dispatch(
|
||||
ackMessage({
|
||||
chatIndex: isChannel ? chatIndex-- : chatIndex,
|
||||
messageId: id,
|
||||
}),
|
||||
);
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
@@ -45,7 +60,7 @@ export const MessageBar = ({ channelIndex }: MessageBarProps): JSX.Element => {
|
||||
type="text"
|
||||
minLength={2}
|
||||
placeholder="Enter Message"
|
||||
disabled={!ready}
|
||||
disabled={!meshtasticState.ready}
|
||||
value={currentMessage}
|
||||
onChange={(e): void => {
|
||||
setCurrentMessage(e.target.value);
|
||||
|
||||
+51
-110
@@ -1,27 +1,30 @@
|
||||
import React from 'react';
|
||||
|
||||
import { m } from 'framer-motion';
|
||||
import { FiHash, FiMessageCircle, FiSettings } from 'react-icons/fi';
|
||||
import { MdPublic } from 'react-icons/md';
|
||||
import TimeAgo from 'timeago-react';
|
||||
import { FiHash, FiMessageCircle } from 'react-icons/fi';
|
||||
|
||||
import { Layout } from '@app/components/layout';
|
||||
import { SidebarItem } from '@components/layout/Sidebar/SidebarItem';
|
||||
import { Hashicon } from '@emeraldpay/hashicon-react';
|
||||
import { useAppSelector } from '@hooks/useAppSelector';
|
||||
import { IconButton, Tooltip } from '@meshtastic/components';
|
||||
import { IconButton } from '@meshtastic/components';
|
||||
import { Protobuf } from '@meshtastic/meshtasticjs';
|
||||
|
||||
import { ChannelChat } from './ChannelChat';
|
||||
import { DmChat } from './DmChat';
|
||||
import { Message } from './Message';
|
||||
import { MessageBar } from './MessageBar';
|
||||
|
||||
export const Messages = (): JSX.Element => {
|
||||
const [selectedChatIndex, setSelectedChatIndex] = React.useState<number>(0);
|
||||
|
||||
const chatRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const myNodeNum = useAppSelector(
|
||||
(state) => state.meshtastic.radio.hardware,
|
||||
).myNodeNum;
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const chats = useAppSelector((state) => state.meshtastic.chats);
|
||||
const channels = useAppSelector(
|
||||
(state) => state.meshtastic.radio.channels,
|
||||
).filter((ch) => ch.channel.role !== Protobuf.Channel_Role.DISABLED);
|
||||
const [channelIndex, setChannelIndex] = React.useState(0);
|
||||
const chatRef = React.useRef<HTMLDivElement>(null);
|
||||
).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (chatRef.current) {
|
||||
@@ -35,92 +38,29 @@ export const Messages = (): JSX.Element => {
|
||||
icon={<FiMessageCircle />}
|
||||
sidebarContents={
|
||||
<div className="flex flex-col gap-2">
|
||||
{nodes.map((node) => (
|
||||
<SidebarItem
|
||||
key={node.number}
|
||||
selected={false}
|
||||
setSelected={(): void => {
|
||||
void Promise.resolve();
|
||||
}}
|
||||
actions={<IconButton icon={<FiSettings />} />}
|
||||
>
|
||||
<div className="flex dark:text-white">
|
||||
<div className="m-auto">
|
||||
<Hashicon value={node.number.toString()} size={32} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-auto mr-auto font-semibold dark:text-white">
|
||||
{node.user?.longName ?? 'Unknown'}
|
||||
</div>
|
||||
</SidebarItem>
|
||||
))}
|
||||
<div className="mx-2 rounded-md border-2 border-gray-300 dark:border-gray-600" />
|
||||
{channels.map((channel) => (
|
||||
<SidebarItem
|
||||
key={channel.channel.index}
|
||||
selected={channelIndex === channel.channel.index}
|
||||
setSelected={(): void => {
|
||||
setChannelIndex(channel.channel.index);
|
||||
}}
|
||||
actions={<IconButton icon={<FiSettings />} />}
|
||||
>
|
||||
<div className="flex h-8 w-8 rounded-full bg-gray-200 dark:bg-primaryDark dark:text-white">
|
||||
<div className="m-auto">
|
||||
{channel.channel.role === Protobuf.Channel_Role.PRIMARY ? (
|
||||
<MdPublic />
|
||||
) : (
|
||||
<p>
|
||||
{channel.channel.settings?.name.length
|
||||
? channel.channel.settings.name
|
||||
.substring(0, 3)
|
||||
.toUpperCase()
|
||||
: `CH: ${channel.channel.index}`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{channel.messages.length ? (
|
||||
<>
|
||||
<div className="mx-2 flex h-8">
|
||||
{[
|
||||
...new Set(
|
||||
channel.messages.flatMap(({ message }) => [
|
||||
message.packet.from,
|
||||
]),
|
||||
),
|
||||
]
|
||||
.sort()
|
||||
.map((nodeId) => {
|
||||
return (
|
||||
<Tooltip
|
||||
key={nodeId}
|
||||
content={
|
||||
nodes.find((node) => node.number === nodeId)?.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>
|
||||
<TimeAgo
|
||||
className="my-auto ml-auto text-xs font-semibold dark:text-gray-400"
|
||||
datetime={channel.lastChatInterraction}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="my-auto dark:text-white">No messages</div>
|
||||
)}
|
||||
</SidebarItem>
|
||||
))}
|
||||
{nodes
|
||||
.filter((node) => node.number !== myNodeNum)
|
||||
.map((node) => (
|
||||
<DmChat
|
||||
key={node.number}
|
||||
node={node}
|
||||
selectedIndex={selectedChatIndex}
|
||||
setSelectedIndex={setSelectedChatIndex}
|
||||
/>
|
||||
))}
|
||||
{nodes.length !== 0 && channels.length !== 0 && (
|
||||
<div className="mx-2 rounded-md border-2 border-gray-300 dark:border-gray-600" />
|
||||
)}
|
||||
{channels
|
||||
.filter((channel) => channel.settings?.name !== 'admin')
|
||||
.map((channel) => (
|
||||
<ChannelChat
|
||||
key={channel.index}
|
||||
channel={channel}
|
||||
selectedIndex={selectedChatIndex}
|
||||
setSelectedIndex={setSelectedChatIndex}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -128,14 +68,13 @@ export const Messages = (): JSX.Element => {
|
||||
<div className="flex w-full justify-between border-b border-gray-300 px-2 dark:border-gray-600 dark:text-gray-300">
|
||||
<div className="my-auto flex gap-2 py-2 text-sm">
|
||||
<IconButton icon={<FiHash className="h-4 w-4" />} />
|
||||
<div className="my-auto">
|
||||
{channels[channelIndex]?.channel.settings?.name.length
|
||||
? channels[channelIndex]?.channel.settings?.name
|
||||
: channels[channelIndex]?.channel.role ===
|
||||
Protobuf.Channel_Role.PRIMARY
|
||||
{/* <div className="my-auto">
|
||||
{channels[channelIndex]?.settings?.name.length
|
||||
? channels[channelIndex]?.settings?.name
|
||||
: channels[channelIndex]?.role === Protobuf.Channel_Role.PRIMARY
|
||||
? 'Primary'
|
||||
: `Channel: ${channels[channelIndex]?.channel.index}`}
|
||||
</div>
|
||||
: `Channel: ${channels[channelIndex]?.index}`}
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -143,7 +82,7 @@ export const Messages = (): JSX.Element => {
|
||||
className="flex flex-grow flex-col space-y-2 overflow-y-auto border-b border-gray-300 bg-white pb-6 dark:border-gray-600 dark:bg-secondaryDark"
|
||||
>
|
||||
<div className="mt-auto">
|
||||
{channels[channelIndex]?.messages.map((message, index) => (
|
||||
{chats[selectedChatIndex]?.messages.map((message, index) => (
|
||||
<Message
|
||||
key={index}
|
||||
message={message.message.data}
|
||||
@@ -152,17 +91,19 @@ export const Messages = (): JSX.Element => {
|
||||
lastMsgSameUser={
|
||||
index === 0
|
||||
? false
|
||||
: channels[channelIndex]?.messages[index - 1].message.packet
|
||||
.from === message.message.packet.from
|
||||
: chats[selectedChatIndex].messages[index - 1].message
|
||||
.packet.from === message.message.packet.from
|
||||
}
|
||||
sender={nodes.find(
|
||||
(node) => node.number === message.message.packet.from,
|
||||
)}
|
||||
sender={nodes.find((node) => {
|
||||
console.log(message);
|
||||
|
||||
return node.number === message.message.packet.from;
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<MessageBar channelIndex={channelIndex} />
|
||||
<MessageBar chatIndex={selectedChatIndex} />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -52,59 +52,61 @@ export const NodeCard = ({
|
||||
);
|
||||
}, [node.currentPosition]);
|
||||
return (
|
||||
<SidebarItem
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
actions={
|
||||
<>
|
||||
<IconButton
|
||||
disabled={PositionConfidence === 'none'}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
setSelected();
|
||||
if (PositionConfidence !== 'none' && node.currentPosition) {
|
||||
map?.flyTo({
|
||||
center: new mapbox.LngLat(
|
||||
node.currentPosition.longitudeI / 1e7,
|
||||
node.currentPosition.latitudeI / 1e7,
|
||||
),
|
||||
zoom: 16,
|
||||
});
|
||||
<>
|
||||
<SidebarItem
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
actions={
|
||||
<>
|
||||
<IconButton
|
||||
disabled={PositionConfidence === 'none'}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
setSelected();
|
||||
if (PositionConfidence !== 'none' && node.currentPosition) {
|
||||
map?.flyTo({
|
||||
center: new mapbox.LngLat(
|
||||
node.currentPosition.longitudeI / 1e7,
|
||||
node.currentPosition.latitudeI / 1e7,
|
||||
),
|
||||
zoom: 16,
|
||||
});
|
||||
}
|
||||
}}
|
||||
icon={
|
||||
PositionConfidence === 'high' ? (
|
||||
<MdGpsFixed />
|
||||
) : PositionConfidence === 'low' ? (
|
||||
<MdGpsNotFixed />
|
||||
) : (
|
||||
<MdGpsOff />
|
||||
)
|
||||
}
|
||||
}}
|
||||
icon={
|
||||
PositionConfidence === 'high' ? (
|
||||
<MdGpsFixed />
|
||||
) : PositionConfidence === 'low' ? (
|
||||
<MdGpsNotFixed />
|
||||
) : (
|
||||
<MdGpsOff />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
setInfoOpen(true);
|
||||
}}
|
||||
icon={<FiAlignLeft />}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="flex dark:text-white">
|
||||
<div className="m-auto">
|
||||
<Hashicon value={node.number.toString()} size={32} />
|
||||
/>
|
||||
<IconButton
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
setInfoOpen(true);
|
||||
}}
|
||||
icon={<FiAlignLeft />}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="flex dark:text-white">
|
||||
<div className="m-auto">
|
||||
<Hashicon value={node.number.toString()} size={32} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
|
||||
{node.lastHeard.getTime()
|
||||
? node.lastHeard.toLocaleTimeString(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: 'Never'}
|
||||
</div>
|
||||
<div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
|
||||
{node.lastHeard.getTime()
|
||||
? node.lastHeard.toLocaleTimeString(undefined, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: 'Never'}
|
||||
</div>
|
||||
</SidebarItem>
|
||||
<SidebarOverlay
|
||||
title={`Node ${node.user?.longName ?? 'UNK'} `}
|
||||
open={infoOpen}
|
||||
@@ -134,6 +136,6 @@ export const NodeCard = ({
|
||||
</>
|
||||
</CollapsibleSection>
|
||||
</SidebarOverlay>
|
||||
</SidebarItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user