diff --git a/package.json b/package.json
index df428f0..ad70bbe 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"antd": "^5.27.1",
"antd-style": "^3.7.1",
"axios": "^1.11.0",
+ "blurhash": "^2.0.5",
"country-list": "^2.4.1",
"cross-env": "^10.0.0",
"dayjs": "^1.11.18",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e141c01..14d4303 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -107,6 +107,9 @@ importers:
axios:
specifier: ^1.11.0
version: 1.13.4
+ blurhash:
+ specifier: ^2.0.5
+ version: 2.0.5
country-list:
specifier: ^2.4.1
version: 2.4.1
@@ -2358,6 +2361,9 @@ packages:
bluebird@3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
+ blurhash@2.0.5:
+ resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==}
+
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
@@ -8644,6 +8650,8 @@ snapshots:
bluebird@3.7.2: {}
+ blurhash@2.0.5: {}
+
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
diff --git a/src/components/Dashboard/common/ActivityIndicator.jsx b/src/components/Dashboard/common/ActivityIndicator.jsx
index 7f8067b..fcd1453 100644
--- a/src/components/Dashboard/common/ActivityIndicator.jsx
+++ b/src/components/Dashboard/common/ActivityIndicator.jsx
@@ -1,6 +1,7 @@
import { useState, useEffect, useContext, useRef, memo } from 'react'
import { Flex, Card, Popover, Divider } from 'antd'
import { UserOutlined } from '@ant-design/icons'
+import { decode } from 'blurhash'
import PropTypes from 'prop-types'
import EditIcon from '../../Icons/EditIcon'
import { ApiServerContext } from '../context/ApiServerContext'
@@ -19,6 +20,14 @@ const getProfileImageId = (user) => {
)
}
+const getProfileImageBlurHash = (user) => {
+ const profileImage = user?.profileImage
+ if (typeof profileImage === 'object' && profileImage !== null) {
+ return profileImage.metaData?.blurHash || null
+ }
+ return null
+}
+
const getDisplayName = (user) =>
user?.name ||
[user?.firstName, user?.lastName].filter(Boolean).join(' ') ||
@@ -37,16 +46,39 @@ const avatarCardStyle = { borderRadius: '12px', width: 32, height: 32 }
const UserProfileImage = memo(function UserProfileImage({
profileImageId,
- displayName
+ displayName,
+ blurHash
}) {
const { fetchFileThumbnail } = useContext(ApiServerContext)
const fetchFileThumbnailRef = useRef(fetchFileThumbnail)
fetchFileThumbnailRef.current = fetchFileThumbnail
const [profileImageUrl, setProfileImageUrl] = useState(null)
+ const [blurHashUrl, setBlurHashUrl] = useState(null)
const profileImageUrlRef = useRef(null)
const loadedImageIdRef = useRef(null)
+ useEffect(() => {
+ if (!blurHash) {
+ setBlurHashUrl(null)
+ return
+ }
+
+ try {
+ const pixels = decode(blurHash, 32, 32)
+ const canvas = document.createElement('canvas')
+ canvas.width = 32
+ canvas.height = 32
+ const ctx = canvas.getContext('2d')
+ const imageData = ctx.createImageData(32, 32)
+ imageData.data.set(pixels)
+ ctx.putImageData(imageData, 0, 0)
+ setBlurHashUrl(canvas.toDataURL())
+ } catch {
+ setBlurHashUrl(null)
+ }
+ }, [blurHash])
+
useEffect(() => {
if (!profileImageId) {
if (profileImageUrlRef.current) {
@@ -111,6 +143,17 @@ const UserProfileImage = memo(function UserProfileImage({
)
}
+ if (profileImageId && blurHashUrl) {
+ return (
+
+ )
+ }
+
return (