Add blurhash support for user profile images in ActivityIndicator component
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Integrated the blurhash library to decode and display blurred placeholders for user profile images. - Updated UserProfileImage component to accept a blurHash prop and render the decoded image when available. - Enhanced UserActivityAvatar to retrieve and pass the blurHash from user data, improving the visual experience during image loading.
This commit is contained in:
parent
8d0f92af45
commit
28dfb645ef
@ -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",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
@ -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 (
|
||||
<img
|
||||
src={blurHashUrl}
|
||||
alt=''
|
||||
aria-hidden
|
||||
style={avatarImageStyle}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
style={avatarCardStyle}
|
||||
@ -130,12 +173,14 @@ const UserProfileImage = memo(function UserProfileImage({
|
||||
|
||||
UserProfileImage.propTypes = {
|
||||
profileImageId: PropTypes.string,
|
||||
displayName: PropTypes.string.isRequired
|
||||
displayName: PropTypes.string.isRequired,
|
||||
blurHash: PropTypes.string
|
||||
}
|
||||
|
||||
const UserActivityAvatar = memo(
|
||||
function UserActivityAvatar({ user, mode }) {
|
||||
const profileImageId = getProfileImageId(user)
|
||||
const blurHash = getProfileImageBlurHash(user)
|
||||
const displayName = getDisplayName(user)
|
||||
|
||||
const navigate = useNavigate()
|
||||
@ -173,6 +218,7 @@ const UserActivityAvatar = memo(
|
||||
<UserProfileImage
|
||||
profileImageId={profileImageId}
|
||||
displayName={displayName}
|
||||
blurHash={blurHash}
|
||||
/>
|
||||
{mode === 'editing' && (
|
||||
<div
|
||||
@ -201,7 +247,9 @@ const UserActivityAvatar = memo(
|
||||
return (
|
||||
prevProps.mode === nextProps.mode &&
|
||||
getUserId(prevProps.user) === getUserId(nextProps.user) &&
|
||||
getProfileImageId(prevProps.user) === getProfileImageId(nextProps.user)
|
||||
getProfileImageId(prevProps.user) === getProfileImageId(nextProps.user) &&
|
||||
getProfileImageBlurHash(prevProps.user) ===
|
||||
getProfileImageBlurHash(nextProps.user)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user