mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 18:43:41 -05:00
web/timeline: use blurhash before image loaded and for spoilers
Signed-off-by: Sumner Evans <me@sumnerevans.com>
This commit is contained in:
parent
e9f0d7e2a7
commit
b31c92def3
7 changed files with 101 additions and 10 deletions
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
cd web > /dev/null
|
||||
if [[ -f "./node_modules/.bin/eslint" ]]; then
|
||||
ARGS=("$@")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
cd web > /dev/null
|
||||
if [[ -f "./node_modules/.bin/tsc" ]]; then
|
||||
tsc --build --noEmit
|
||||
|
|
18
web/package-lock.json
generated
18
web/package-lock.json
generated
|
@ -12,8 +12,10 @@
|
|||
"@types/react": "npm:types-react@rc",
|
||||
"@types/react-dom": "npm:types-react-dom@rc",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.29",
|
||||
"blurhash": "^2.0.5",
|
||||
"katex": "^0.16.11",
|
||||
"react": "^19.0.0-rc.1",
|
||||
"react-blurhash": "^0.3.0",
|
||||
"react-dom": "^19.0.0-rc.1",
|
||||
"react-spinners": "^0.14.1",
|
||||
"unhomoglyph": "^1.0.6"
|
||||
|
@ -2271,6 +2273,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/blurhash": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz",
|
||||
"integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
@ -4641,6 +4649,16 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-blurhash": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.3.0.tgz",
|
||||
"integrity": "sha512-XlKr4Ns1iYFRnk6DkAblNbAwN/bTJvxTVoxMvmTcURdc5oLoXZwqAF9N3LZUh/HT+QFlq5n6IS6VsDGsviYAiQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"blurhash": "^2.0.3",
|
||||
"react": ">=15"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc.1.tgz",
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
"@types/react": "npm:types-react@rc",
|
||||
"@types/react-dom": "npm:types-react-dom@rc",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.29",
|
||||
"blurhash": "^2.0.5",
|
||||
"katex": "^0.16.11",
|
||||
"react": "^19.0.0-rc.1",
|
||||
"react-blurhash": "^0.3.0",
|
||||
"react-dom": "^19.0.0-rc.1",
|
||||
"react-spinners": "^0.14.1",
|
||||
"unhomoglyph": "^1.0.6"
|
||||
|
|
|
@ -126,6 +126,11 @@ export interface RelatesTo {
|
|||
}
|
||||
}
|
||||
|
||||
export interface ContentWarning {
|
||||
type: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface BaseMessageEventContent {
|
||||
msgtype: string
|
||||
body: string
|
||||
|
@ -133,6 +138,8 @@ export interface BaseMessageEventContent {
|
|||
format?: "org.matrix.custom.html"
|
||||
"m.mentions"?: Mentions
|
||||
"m.relates_to"?: RelatesTo
|
||||
"m.content_warning"?: ContentWarning
|
||||
"town.robin.msc3725.content_warning"?: ContentWarning
|
||||
}
|
||||
|
||||
export interface TextMessageEventContent extends BaseMessageEventContent {
|
||||
|
@ -178,6 +185,7 @@ export interface MediaInfo {
|
|||
|
||||
"fi.mau.hide_controls"?: boolean
|
||||
"fi.mau.loop"?: boolean
|
||||
"xyz.amorgan.blurhash"?: string
|
||||
}
|
||||
|
||||
export interface LocationMessageEventContent extends BaseMessageEventContent {
|
||||
|
|
|
@ -199,6 +199,21 @@ div.html-body {
|
|||
}
|
||||
|
||||
div.media-container {
|
||||
&.image-container {
|
||||
.placeholder {
|
||||
position: relative;
|
||||
|
||||
.spoiler-indicator {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.video-container {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { CSSProperties, use } from "react"
|
||||
import React, { CSSProperties, use } from "react"
|
||||
import { Blurhash } from "react-blurhash"
|
||||
import { getEncryptedMediaURL, getMediaURL } from "@/api/media.ts"
|
||||
import type { EventType, MediaMessageEventContent } from "@/api/types"
|
||||
import { ImageContainerSize, calculateMediaSize } from "@/util/mediasize.ts"
|
||||
|
@ -23,18 +24,65 @@ import DownloadIcon from "@/icons/download.svg?react"
|
|||
export const useMediaContent = (
|
||||
content: MediaMessageEventContent, evtType: EventType, containerSize?: ImageContainerSize,
|
||||
): [React.ReactElement | null, string, CSSProperties] => {
|
||||
const imgEl = React.useRef<HTMLImageElement>(null)
|
||||
const [loaded, setLoaded] = React.useState(false)
|
||||
const onImageLoaded = () => setLoaded(true)
|
||||
|
||||
const blurhashEl = React.useRef<Blurhash>(null)
|
||||
const [spoilerShowing, setSpoilerShowing] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const imgElCurrent = imgEl.current
|
||||
|
||||
if (imgElCurrent) {
|
||||
imgElCurrent.addEventListener("load", onImageLoaded)
|
||||
return () => imgElCurrent.removeEventListener("load", onImageLoaded)
|
||||
}
|
||||
}, [imgEl])
|
||||
|
||||
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 [<img
|
||||
loading="lazy"
|
||||
style={style.media}
|
||||
|
||||
const blurhash = content.info ? content.info["xyz.amorgan.blurhash"] : undefined
|
||||
const isSpoiler = content["m.content_warning"]?.type === "m.spoiler" ||
|
||||
content["town.robin.msc3725.content_warning"]?.type === "town.robin.msc3725.spoiler"
|
||||
const showPlaceholder = (blurhash && !loaded) || (isSpoiler && !spoilerShowing)
|
||||
|
||||
const mediaStyle = Object.assign(
|
||||
style.media,
|
||||
showPlaceholder ? { display: "none" } : { display: "inline-block" })
|
||||
|
||||
return [
|
||||
<>
|
||||
<div
|
||||
onClick={() => setSpoilerShowing(!spoilerShowing)}
|
||||
style={!showPlaceholder ? { display: "none" } : {}}
|
||||
className="placeholder"
|
||||
>
|
||||
{blurhash && <Blurhash
|
||||
ref={blurhashEl}
|
||||
hash={blurhash}
|
||||
width={style.container.width}
|
||||
height={style.container.height}
|
||||
resolutionX={48}
|
||||
resolutionY={48}
|
||||
/>}
|
||||
{!blurhash && <div style={style.container}>
|
||||
</div>}
|
||||
{isSpoiler && !spoilerShowing && <div className="spoiler-indicator">Spoiler</div>}
|
||||
</div>
|
||||
<img
|
||||
ref={imgEl}
|
||||
style={mediaStyle}
|
||||
src={mediaURL}
|
||||
alt={content.filename ?? content.body}
|
||||
onClick={use(LightboxContext)}
|
||||
/>, "image-container", style.container]
|
||||
/>
|
||||
</>
|
||||
, "image-container", style.container]
|
||||
} else if (content.msgtype === "m.video") {
|
||||
const autoplay = false
|
||||
const controls = !content.info?.["fi.mau.hide_controls"]
|
||||
|
|
Loading…
Add table
Reference in a new issue