commit 0916be7580577e28c466c9f5e01a98adb276e059
parent 02b40bb7f6f09b51c654d903f0e42427eb4f4f3e
Author: MTRNord <mtrnord1@gmail.com>
Date: Fri, 14 Jan 2022 01:46:29 +0100
Add blurhash support
Diffstat:
5 files changed, 78 insertions(+), 6 deletions(-)
diff --git a/components/FrontPageImage.tsx b/components/FrontPageImage.tsx
@@ -1,5 +1,6 @@
import Link from "next/link";
import { PureComponent } from "react";
+import { Blurhash } from "react-blurhash";
import { ImageEvent, ImageGalleryEvent, MatrixEventBase, MatrixImageEvents } from "../helpers/event_types";
import { constMatrixArtServer } from "../helpers/matrix_client";
import { ClientContext } from "./ClientContext";
@@ -83,7 +84,7 @@ export default class FrontPageImage extends PureComponent<Props, State> {
caption_text = (caption[0] as { body: string; mimetype: string; }).body;
}
return event.content['m.image_gallery'].map(image => {
- return this.render_image_box(image['m.thumbnail'][0].url, event.event_id + image['m.file'].url, event.event_id, caption_text);
+ return this.render_image_box(image['m.thumbnail'][0].url, event.event_id + image['m.file'].url, event.event_id, caption_text, image["xyz.amorgan.blurhash"]);
});
}
@@ -97,18 +98,30 @@ export default class FrontPageImage extends PureComponent<Props, State> {
if (caption.length > 0) {
caption_text = (caption[0] as { body: string; mimetype: string; }).body;
}
- return this.render_image_box(event.content['m.thumbnail'][0].url, event.event_id, event.event_id, caption_text);
+ return this.render_image_box(event.content['m.thumbnail'][0].url, event.event_id, event.event_id, caption_text, event.content["xyz.amorgan.blurhash"]);
}
- render_image_box(thumbnail_url: string, id: string, post_id: string, caption: string) {
+ render_image_box(thumbnail_url: string, id: string, post_id: string, caption: string, blurhash?: string) {
// TODO show creators display name instead of mxid and show avatar image
// TODO proper alt text
const direct_link = `/post/${encodeURIComponent(post_id)}`;
+ const image = blurhash ? (
+ <div className="flex">
+ <Blurhash
+ hash={blurhash}
+ height={this.state.imageHeight}
+ width="100%"
+ />
+ <img loading="lazy" alt={caption} title={caption} style={{ height: this.state.imageHeight }} className="relative -ml-[100%] max-w-full object-cover align-bottom" src={this.context.client?.thumbnailLink(thumbnail_url, "scale", Number.parseInt(this.state.imageHeight?.replace("px", "")!), Number.parseInt(this.state.imageHeight?.replace("px", "")!))}></img>
+ </div>
+ ) : (
+ <img alt={caption} title={caption} style={{ height: this.state.imageHeight }} className={`relative max-w-full object-cover align-bottom z-0`} src={this.context.client?.thumbnailLink(thumbnail_url, "scale", Number.parseInt(this.state.imageHeight?.replace("px", "")!), Number.parseInt(this.state.imageHeight?.replace("px", "")!))}></img>
+ );
return (
<li style={{ height: this.state.imageHeight }} key={id}>
<Link href={direct_link} passHref>
<div style={{ height: this.state.imageHeight }} className={`relative cursor-pointer`}>
- <img alt={caption} title={caption} style={{ height: this.state.imageHeight }} className={`relative max-w-full object-cover align-bottom z-0`} src={this.context.client?.thumbnailLink(thumbnail_url, "scale", Number.parseInt(this.state.imageHeight?.replace("px", "")!), Number.parseInt(this.state.imageHeight?.replace("px", "")!))}></img>
+ {image}
<div style={{ height: this.state.imageHeight }} className={`flex-col max-w-full opacity-0 hover:opacity-100 duration-300 absolute bg-gradient-to-b from-transparent to-black/[.25] inset-0 z-10 flex justify-end items-start text-white p-4`}>
<h2 className='truncate max-w-full text-base font-semibold'>{caption}</h2>
<p className='truncate max-w-full text-sm'>{this.state.displayname}</p>
diff --git a/helpers/event_types.ts b/helpers/event_types.ts
@@ -31,6 +31,7 @@ export type ImageEventContent = {
"m.image": ImageFields;
"m.thumbnail": ThumbnailFileEvent[];
"matrixart.tags": string[];
+ "xyz.amorgan.blurhash": string;
};
export type MessageAlike = { "m.text": string; } | { body: string; mimetype: string; };
diff --git a/package-lock.json b/package-lock.json
@@ -13,6 +13,7 @@
"node-localstorage": "^2.2.1",
"pouchdb": "^7.2.2",
"react": "17.0.2",
+ "react-blurhash": "^0.1.3",
"react-dom": "17.0.2"
},
"devDependencies": {
@@ -2680,6 +2681,12 @@
"node": ">=8"
}
},
+ "node_modules/blurhash": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-1.1.4.tgz",
+ "integrity": "sha512-MXIPz6zwYUKayju+Uidf83KhH0vodZfeRl6Ich8Gu+KGl0JgKiFq9LsfqV7cVU5fKD/AotmduZqvOfrGKOfTaA==",
+ "peer": true
+ },
"node_modules/bn.js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
@@ -7211,6 +7218,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-blurhash": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.1.3.tgz",
+ "integrity": "sha512-Q9lqbXg92NU6/2DoIl/cBM8YWL+Z4X66OiG4aT9ozOgjBwx104LHFCH5stf6aF+s0Q9Wf310Ul+dG+VXJltmPg==",
+ "peerDependencies": {
+ "blurhash": "^1.1.1",
+ "react": ">=15"
+ }
+ },
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -10534,6 +10550,12 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
},
+ "blurhash": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-1.1.4.tgz",
+ "integrity": "sha512-MXIPz6zwYUKayju+Uidf83KhH0vodZfeRl6Ich8Gu+KGl0JgKiFq9LsfqV7cVU5fKD/AotmduZqvOfrGKOfTaA==",
+ "peer": true
+ },
"bn.js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
@@ -13964,6 +13986,12 @@
"object-assign": "^4.1.1"
}
},
+ "react-blurhash": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.1.3.tgz",
+ "integrity": "sha512-Q9lqbXg92NU6/2DoIl/cBM8YWL+Z4X66OiG4aT9ozOgjBwx104LHFCH5stf6aF+s0Q9Wf310Ul+dG+VXJltmPg==",
+ "requires": {}
+ },
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
diff --git a/package.json b/package.json
@@ -18,6 +18,7 @@
"node-localstorage": "^2.2.1",
"pouchdb": "^7.2.2",
"react": "17.0.2",
+ "react-blurhash": "^0.1.3",
"react-dom": "17.0.2"
},
"devDependencies": {
diff --git a/pages/post/[id].tsx b/pages/post/[id].tsx
@@ -16,6 +16,7 @@ import { get_data } from "../api/directory";
import { isImageEvent, isImageGalleryEvent } from '../../components/FrontPageImage';
import Link from 'next/link';
import Footer from '../../components/Footer';
+import { Blurhash } from 'react-blurhash';
type Props = InferGetServerSidePropsType<typeof getServerSideProps> & {
router: NextRouter;
@@ -244,6 +245,20 @@ class Post extends PureComponent<Props, State> {
// TODO get this from the event itself
license: "https://creativecommons.org/licenses/by-nc-nd/4.0/"
};
+ const blurhash = imageEvent.content['xyz.amorgan.blurhash'];
+ const image_html = blurhash ? (
+ <div className="flex">
+ <Blurhash
+ className="max-w-full lg:max-w-3xl max-h-[54.25rem]"
+ hash={blurhash}
+ width="100%"
+ height=""
+ />
+ <img loading="lazy" alt={caption} title={caption} className="relative -ml-[100%] object-cover align-bottom" src={thumbnail_url} />
+ </div>
+ ) : (
+ <img alt={caption} title={caption} src={thumbnail_url} />
+ );
return (
<>
<Head>
@@ -266,7 +281,7 @@ class Post extends PureComponent<Props, State> {
thumbnail={true}
>
<a href={url} title={caption} data-src={url}>
- <img alt={caption} title={caption} src={thumbnail_url} />
+ {image_html}
</a>
</LightGallery>
</div>
@@ -289,9 +304,23 @@ class Post extends PureComponent<Props, State> {
// TODO get this from the event itself
license: "https://creativecommons.org/licenses/by-nc-nd/4.0/"
});
+ const blurhash = image["xyz.amorgan.blurhash"];
+ const image_html = blurhash ? (
+ <div className="flex">
+ <Blurhash
+ className="max-w-full lg:max-w-3xl max-h-[54.25rem]"
+ hash={blurhash}
+ width="100%"
+ height=""
+ />
+ <img loading="lazy" alt={caption} title={caption} className="relative -ml-[100%] object-cover align-bottom" src={thumbnail_url} />
+ </div>
+ ) : (
+ <img alt={caption} title={caption} src={thumbnail_url} />
+ );
return (
<a key={url} href={url} title={caption} data-src={url}>
- <img alt={caption} title={caption} src={thumbnail_url} />
+ {image_html}
</a>
);
});