20 changed files with 952 additions and 805 deletions
File diff suppressed because it is too large
@ -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> |
||||
|
); |
||||
|
}; |
||||
Loading…
Reference in new issue