// 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 =