// gomuks - A Matrix client written in Go. // Copyright (C) 2024 Tulir Asokan // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import React, { CSSProperties, JSX, use, useReducer, useState } from "react" import { Blurhash } from "react-blurhash" import { GridLoader } from "react-spinners" import { usePreference } from "@/api/statestore" import { ContentWarningType, MediaMessageEventContent } from "@/api/types" import { ensureString } from "@/util/validation.ts" import ClientContext from "../../ClientContext.ts" import TextMessageBody from "./TextMessageBody.tsx" import EventContentProps from "./props.ts" import { useMediaContent } from "./useMediaContent.tsx" const loaderSize = (style: CSSProperties): number | undefined => { if (!style.width) { return } const width = +(style.width as string).replace("px", "") const height = +(style.height as string).replace("px", "") // GridLoader takes size of individual bubbles for some reason (so need to divide by 3), // and we want the size to be slightly smaller than the container, so just divide by 5 return Math.min(Math.round(Math.min(width, height) / 5), 30) } const switchToTrue = () => true const MediaMessageBody = ({ event, room, sender }: EventContentProps) => { const content = event.content as MediaMessageEventContent let caption = null if (content.body && content.filename && content.body !== content.filename) { caption = } const client = use(ClientContext)! const supportsLoadingPlaceholder = event.type === "m.sticker" || content.msgtype === "m.image" const supportsClickToShow = supportsLoadingPlaceholder || content.msgtype === "m.video" const showPreviewsByDefault = usePreference(client.store, room, "show_media_previews") const [loaded, onLoad] = useReducer(switchToTrue, !supportsLoadingPlaceholder) const [clickedShow, setClickedShow] = useState(false) let contentWarning = content["town.robin.msc3725.content_warning"] if (content["page.codeberg.everypizza.msc4193.spoiler"]) { contentWarning = { type: ContentWarningType.Spoiler, description: content["page.codeberg.everypizza.msc4193.spoiler.reason"], } } const renderMediaElem = !supportsClickToShow || showPreviewsByDefault || clickedShow const renderPlaceholderElem = supportsClickToShow && (!renderMediaElem || (contentWarning && !clickedShow) || !loaded) const isLoadingOnlyCover = !loaded && !contentWarning && renderMediaElem const [mediaContent, containerClass, containerStyle] = useMediaContent(content, event.type, undefined, onLoad) let placeholderElem: JSX.Element | null = null if (renderPlaceholderElem) { const blurhash = ensureString( content.info?.["xyz.amorgan.blurhash"] ?? content.info?.thumbnail_info?.["xyz.amorgan.blurhash"], ) const onClick = !clickedShow ? (evt: React.MouseEvent) => { setClickedShow(true) evt.stopPropagation() } : undefined placeholderElem =
{(blurhash && containerStyle.width) ? :
} {isLoadingOnlyCover ?
:
{ensureString(contentWarning?.description) || "Show media"}
}
} return <>
{placeholderElem} {renderMediaElem ? mediaContent : null}
{caption} } export default MediaMessageBody