import AddReactionIcon from "@mui/icons-material/AddReaction"; import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import DownloadIcon from "@mui/icons-material/Download"; import { Box, Button, ButtonGroup, Chip, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, Tooltip, Typography, useTheme, } from "@mui/material"; import EmojiPicker, { EmojiStyle, Theme } from "emoji-picker-react"; import React from "react"; import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent"; import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; import type { Room } from "../../api/matrix/MatrixApiRoom"; import { useAlert } from "../../hooks/contexts_provider/AlertDialogProvider"; import { useConfirm } from "../../hooks/contexts_provider/ConfirmDialogProvider"; import { useLoadingMessage } from "../../hooks/contexts_provider/LoadingMessageProvider"; import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider"; import type { Message, MessageReaction, RoomEventsManager, } from "../../utils/RoomEventsManager"; import { useUserInfo } from "../dashboard/BaseAuthenticatedPage"; import { EmojiIcon } from "../EmojiIcon"; import { AccountIcon } from "./AccountIcon"; export function RoomMessagesList(p: { room: Room; users: UsersMap; mgr: RoomEventsManager; }): React.ReactElement { const messagesEndRef = React.createRef(); // Automatically scroll to bottom when number of messages change React.useEffect(() => { if (messagesEndRef) messagesEndRef.current?.scrollIntoView({ behavior: "instant" }); }, [p.mgr.messages.length]); return (
{p.mgr.messages.map((m, idx) => ( 0 && p.mgr.messages[idx - 1].account === m.account && m.time_sent - p.mgr.messages[idx - 1].time_sent < 60 * 3 * 1000 } firstMessageOfDay={ idx === 0 || m.time_sent_dayjs.startOf("day").unix() != p.mgr.messages[idx - 1].time_sent_dayjs.startOf("day").unix() } /> ))}
); } function RoomMessage(p: { room: Room; users: UsersMap; message: Message; previousFromSamePerson: boolean; firstMessageOfDay: boolean; }): React.ReactElement { const theme = useTheme(); const user = useUserInfo(); const alert = useAlert(); const confirm = useConfirm(); const snackbar = useSnackbar(); const loadingMessage = useLoadingMessage(); const [showImageFullScreen, setShowImageFullScreen] = React.useState(false); const [editMessage, setEditMessage] = React.useState(); const [pickReaction, setPickReaction] = React.useState(false); const closeImageFullScreen = () => setShowImageFullScreen(false); const sender = p.users.get(p.message.account); const handleDeleteMessage = async () => { if (!(await confirm(`Do you really want to delete this message?`))) return; try { await MatrixApiEvent.DeleteEvent(p.room, p.message.event_id); } catch (e) { console.error(`Failed to delete message!`, e), alert(`Failed to delete message!${e}`); } }; const handleEditMessage = () => setEditMessage(p.message.content); const handleCancelEditMessage = () => setEditMessage(undefined); const handleSubmitEditMessage = async (event: React.FormEvent) => { event.preventDefault(); try { loadingMessage.show(`Updating message content...`); await MatrixApiEvent.SetTextMessageContent( p.room, p.message.event_id, editMessage! ); setEditMessage(undefined); } catch (e) { console.error(`Failed to edit message!`, e); alert(`Failed to edit message content! ${e}`); } finally { loadingMessage.hide(); } }; const handleAddReaction = () => setPickReaction(true); const handleCancelAddReaction = () => setPickReaction(false); const handleSelectEmoji = async (key: string) => { loadingMessage.show("Setting reaction..."); try { await MatrixApiEvent.ReactToEvent(p.room, p.message.event_id, key); setPickReaction(false); } catch (e) { console.error("Failed to select emoji!", e); alert(`Failed to select emoji! ${e}`); } finally { loadingMessage.hide(); } }; const handleToggleReaction = async ( key: string, reaction: MessageReaction | undefined ) => { try { if (!reaction) await MatrixApiEvent.ReactToEvent(p.room, p.message.event_id, key); else await MatrixApiEvent.DeleteEvent(p.room, reaction.event_id); } catch (e) { console.error(`Failed to toggle reaction!`, e); snackbar(`Failed to toggle reaction! ${e}`); } }; return ( <> {/* Print date if required */} {p.firstMessageOfDay && ( {p.message.time_sent_dayjs.format("DD/MM/YYYY")} )} {/* Give person name if required */} {(!p.previousFromSamePerson || p.firstMessageOfDay) && sender && (
    {sender.display_name}
)} {/* Message content */}   {p.message.time_sent_dayjs.format("HH:mm")} {/** Message itself */}
{/* Image */} {p.message.type === "m.image" && ( setShowImageFullScreen(true)} src={MatrixApiEvent.GetEventFileURL( p.room, p.message.event_id, true )} style={{ maxWidth: "200px", }} /> )} {/* Audio */} {p.message.type === "m.audio" && ( )} {/* Video */} {p.message.type === "m.video" && ( )} {/* File */} {p.message.type === "m.file" && ( )} {/* Text message */} {p.message.type === "m.text" && p.message.content}
{/* Common reactions */} {/* 👍 */} {/* ♥️ */} {/* 😂 */} {/* Add reaction */} {/* Edit text message */} {p.message.account === user.info.matrix_user_id && !p.message.file && ( )} {/* Delete message */} {p.message.account === user.info.matrix_user_id && ( )}
{/* Reaction */} {[...p.message.reactions.keys()].map((r) => { const reactions = p.message.reactions.get(r)!; const userReaction = reactions.find( (r) => r.account === user.info.matrix_user_id ); return ( {reactions .map((r) => p.users.get(r.account)?.display_name) .join("\n")} } > handleToggleReaction(r, userReaction), }, label: { style: { height: "2em" } }, }} color={userReaction !== undefined ? "success" : undefined} variant="filled" label={
{reactions.length}
} />
); })}
{/* Full screen image dialog */} {/* Pick reaction dialog */} handleSelectEmoji(emoji.emoji)} /> {/* Edit message dialog */} Edit message content Enter new message content:
setEditMessage(e.target.value)} />
); } function ReactionButton(p: { room: Room; message: Message; emojiKey: string; }): React.ReactElement { const alert = useAlert(); const user = useUserInfo(); const sendEmoji = async () => { try { await MatrixApiEvent.ReactToEvent(p.room, p.message.event_id, p.emojiKey); } catch (e) { console.error("Failed to send reaction!", e); alert(`Failed to send reaction! ${e}`); } }; // Do not offer to react to existing reactions if ( p.message.reactions .get(p.emojiKey) ?.find( (r) => r.key === p.emojiKey && r.account === user.info.matrix_user_id ) !== undefined ) return <>; return ( ); }