for-legacy-web/src/components/common/messaging/bars/ReplyBar.tsx

261 lines
9.2 KiB
TypeScript

import { At, Reply as ReplyIcon } from "@styled-icons/boxicons-regular";
import { File, XCircle } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
import { StateUpdater, useEffect } from "preact/hooks";
import { internalSubscribe } from "../../../../lib/eventEmitter";
import { useApplicationState } from "../../../../mobx/State";
import { SECTION_MENTION } from "../../../../mobx/stores/Layout";
import { Reply } from "../../../../mobx/stores/MessageQueue";
import Tooltip from "../../../common/Tooltip";
import IconButton from "../../../ui/IconButton";
import Markdown from "../../../markdown/Markdown";
import UserShort from "../../user/UserShort";
import { SystemMessage } from "../SystemMessage";
import { ReplyBase } from "../attachments/MessageReply";
interface Props {
channel: Channel;
replies: Reply[];
setReplies: StateUpdater<Reply[]>;
}
const Base = styled.div`
@keyframes bounce-from-bottom {
0% {
transform: translateY(33px);
}
100% {
transform: translateY(0px);
}
}
display: flex;
height: 30px;
padding: 0 20px;
user-select: none;
align-items: center;
/*background: var(--message-box);*/
background: var(--secondary-background);
/*animation: fadeIn 5s;*/
animation-name: bounce-from-bottom;
animation-duration: 340ms;
animation-delay: 0ms;
animation-timing-function: cubic-bezier(0.2, 0.9, 0.5, 1.16);
animation-fill-mode: forwards;
> div {
flex-grow: 1;
margin-bottom: 0;
&::before {
display: none;
}
}
.toggle {
gap: 2px;
display: flex;
font-size: 12px;
align-items: center;
font-weight: 800;
text-transform: uppercase;
min-width: 6ch;
}
.replyto {
align-self: center;
font-weight: 500;
flex-shrink: 0;
}
.content {
display: flex;
pointer-events: none;
.username {
display: flex;
align-items: center;
gap: 6px;
font-weight: 600;
flex-shrink: 0;
}
.message {
display: flex;
max-height: 26px;
gap: 4px;
}
}
.actions {
gap: 12px;
display: flex;
}
/*@media (pointer: coarse) { //FIXME: Make action buttons bigger on pointer coarse
.actions > svg {
height: 25px;
}
}*/
`;
// ! FIXME: Move to global config
const MAX_REPLIES = 5;
export default observer(({ channel, replies, setReplies }: Props) => {
const client = channel.client;
const layout = useApplicationState().layout;
// Event listener for adding new messages to reply bar.
useEffect(() => {
return internalSubscribe("ReplyBar", "add", (_message) => {
const message = _message as Message;
if (
replies.length >= MAX_REPLIES ||
replies.find((x) => x.id === message._id)
)
return;
setReplies([
...replies,
{
id: message._id,
mention:
message.author_id === client.user!._id
? false
: layout.getSectionState(SECTION_MENTION, false),
},
]);
});
}, [replies, setReplies, client.user]);
// Map all the replies to messages we are aware of.
const messages = replies.map((x) => client.messages.get(x.id));
// Remove any replies which don't resolve to valid messages.
useEffect(() => {
if (messages.includes(undefined)) {
setReplies(
replies.filter((_, i) => typeof messages[i] !== "undefined"),
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [messages, replies, setReplies]);
return (
<div>
{replies.map((reply, index) => {
const message = messages[index];
if (!message) return null;
return (
<Base key={reply.id}>
<ReplyBase preview>
<div class="replyto">
<Text id="app.main.channel.replying" />
</div>
<div class="content">
<div class="username">
<UserShort
size={16}
showServerIdentity
user={message.author}
masquerade={message.masquerade!}
/>
</div>
<div class="message">
{message.attachments && (
<>
<File size={16} />
<em>
{message.attachments.length >
1 ? (
<Text id="app.main.channel.misc.sent_multiple_files" />
) : (
<Text id="app.main.channel.misc.sent_file" />
)}
</em>
</>
)}
{message.author_id ===
"00000000000000000000000000" ? (
<SystemMessage
message={message}
hideInfo
/>
) : (
<Markdown
disallowBigEmoji
content={(
message.content as string
).replace(/\n/g, " ")}
/>
)}
</div>
</div>
</ReplyBase>
<span class="actions">
{message.author_id !== client.user!._id && (
<IconButton
onClick={() => {
let state = false;
setReplies(
replies.map((_, i) => {
if (i === index) {
state = !_.mention;
return {
..._,
mention: !_.mention,
};
}
return _;
}),
);
layout.setSectionState(
SECTION_MENTION,
state,
false,
);
}}>
<Tooltip content="Toggle author ping">
<span class="toggle">
<At size={15} />
<Text
id={
reply.mention
? "general.on"
: "general.off"
}
/>
</span>
</Tooltip>
</IconButton>
)}
<IconButton
onClick={() =>
setReplies(
replies.filter((_, i) => i !== index),
)
}>
<XCircle size={16} />
</IconButton>
</span>
</Base>
);
})}
</div>
);
});