+ {(blurhash && containerStyle.width) ?
:
}
+ {isLoadingOnlyCover
+ ?
+
+
+ :
+ {ensureString(contentWarning?.description) || "Show media"}
+
}
+
+ }
+
return <>
- {mediaContent}
+ {placeholderElem}
+ {renderMediaElem ? mediaContent : null}
{caption}
>
diff --git a/web/src/ui/timeline/content/index.css b/web/src/ui/timeline/content/index.css
index 75df96e..1c3c6b6 100644
--- a/web/src/ui/timeline/content/index.css
+++ b/web/src/ui/timeline/content/index.css
@@ -199,6 +199,37 @@ div.html-body {
}
div.media-container {
+ &.image-container, &.video-container {
+ contain: strict;
+ }
+
+ > div.placeholder {
+ position: relative;
+ width: 100%;
+ height: 100%;
+
+ > div.empty-placeholder {
+ background-color: var(--media-placeholder-default-background);
+ width: 100%;
+ height: 100%;
+ }
+
+ > div.placeholder-reason, > div.placeholder-spinner {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ > div.placeholder-reason {
+ background-color: var(--media-placeholder-button-background);
+ padding: 0.5rem;
+ cursor: var(--clickable-cursor);
+ user-select: none;
+ border-radius: .25rem;
+ }
+ }
+
&.video-container {
width: 100%;
height: 240px;
diff --git a/web/src/ui/timeline/content/useMediaContent.tsx b/web/src/ui/timeline/content/useMediaContent.tsx
index 90cc573..8516e57 100644
--- a/web/src/ui/timeline/content/useMediaContent.tsx
+++ b/web/src/ui/timeline/content/useMediaContent.tsx
@@ -13,7 +13,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see
.
-import { CSSProperties, use } from "react"
+import React, { CSSProperties, JSX, use } from "react"
import { getEncryptedMediaURL, getMediaURL } from "@/api/media.ts"
import type { EventType, MediaMessageEventContent } from "@/api/types"
import { ImageContainerSize, calculateMediaSize } from "@/util/mediasize.ts"
@@ -21,18 +21,24 @@ import { LightboxContext } from "../../modal/Lightbox.tsx"
import DownloadIcon from "@/icons/download.svg?react"
export const useMediaContent = (
- content: MediaMessageEventContent, evtType: EventType, containerSize?: ImageContainerSize,
-): [React.ReactElement | null, string, CSSProperties] => {
+ content: MediaMessageEventContent,
+ evtType: EventType,
+ containerSize?: ImageContainerSize,
+ onLoad?: () => void,
+ lazyLoad = true,
+): [JSX.Element | null, string, CSSProperties] => {
const mediaURL = content.file?.url ? getEncryptedMediaURL(content.file.url) : getMediaURL(content.url)
const thumbnailURL = content.info?.thumbnail_file?.url
? getEncryptedMediaURL(content.info.thumbnail_file.url) : getMediaURL(content.info?.thumbnail_url)
if (content.msgtype === "m.image" || evtType === "m.sticker") {
const style = calculateMediaSize(content.info?.w, content.info?.h, containerSize)
return [

, "image-container", style.container]
} else if (content.msgtype === "m.video") {