- Removed unnecessary state and effects related to image loading in ActivityIndicator, replacing them with the new Thumbnail component for better performance and simplicity. - Updated ObjectCard to conditionally render a Thumbnail based on the presence of a thumbnail property, enhancing the visual representation of objects. - Introduced a new Thumbnail component to handle image fetching and rendering, including support for blurHash decoding and fallback UI.
201 lines
5.1 KiB
JavaScript
201 lines
5.1 KiB
JavaScript
import { useContext, memo } from 'react'
|
|
import { Flex, Popover, Divider } from 'antd'
|
|
import { UserOutlined } from '@ant-design/icons'
|
|
import PropTypes from 'prop-types'
|
|
import EditIcon from '../../Icons/EditIcon'
|
|
import { AuthContext } from '../context/AuthContext'
|
|
import SpotlightTooltip from './SpotlightTooltip'
|
|
import Thumbnail from './Thumbnail'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { getModelByName } from '../../../database/ObjectModels'
|
|
|
|
const getUserId = (user) => user?._id || user
|
|
|
|
const getProfileImageId = (user) => {
|
|
const profileImage = user?.profileImage
|
|
return (
|
|
profileImage?._id ||
|
|
(typeof profileImage === 'string' ? profileImage : null)
|
|
)
|
|
}
|
|
|
|
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(' ') ||
|
|
user?.username ||
|
|
'User'
|
|
|
|
const avatarStyle = {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: '12px',
|
|
objectFit: 'cover',
|
|
border: '1px solid rgb(128, 128, 128, 0.4)'
|
|
}
|
|
|
|
const UserProfileImage = memo(function UserProfileImage({
|
|
profileImageId,
|
|
displayName,
|
|
blurHash
|
|
}) {
|
|
const file = profileImageId
|
|
? { _id: profileImageId, metaData: blurHash ? { blurHash } : undefined }
|
|
: null
|
|
|
|
return (
|
|
<Thumbnail
|
|
file={file}
|
|
size={64}
|
|
alt={displayName}
|
|
style={avatarStyle}
|
|
fallback={
|
|
<UserOutlined
|
|
style={{
|
|
fontSize: 14,
|
|
color: 'var(--color-text-secondary)'
|
|
}}
|
|
/>
|
|
}
|
|
/>
|
|
)
|
|
})
|
|
|
|
UserProfileImage.propTypes = {
|
|
profileImageId: PropTypes.string,
|
|
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()
|
|
|
|
const model = getModelByName('user')
|
|
|
|
// Get hyperlink URL from model's default actions
|
|
var hyperlink = null
|
|
const defaultModelActions =
|
|
model.actions?.filter((action) => action.default == true) || []
|
|
|
|
if (defaultModelActions.length >= 1 && getUserId(user)) {
|
|
hyperlink = defaultModelActions[0].url(getUserId(user))
|
|
}
|
|
|
|
return (
|
|
<Popover
|
|
content={<SpotlightTooltip query={`USR:${user?._id}`} type='user' />}
|
|
placement='bottomLeft'
|
|
arrow={false}
|
|
style={{ padding: 0 }}
|
|
>
|
|
<div
|
|
style={{
|
|
position: 'relative',
|
|
width: 32,
|
|
height: 32,
|
|
cursor: 'pointer',
|
|
borderRadius: '12px'
|
|
}}
|
|
onClick={() => {
|
|
navigate(hyperlink)
|
|
}}
|
|
>
|
|
<UserProfileImage
|
|
profileImageId={profileImageId}
|
|
displayName={displayName}
|
|
blurHash={blurHash}
|
|
/>
|
|
{mode === 'editing' && (
|
|
<div
|
|
style={{
|
|
position: 'absolute',
|
|
right: -4,
|
|
bottom: -4,
|
|
width: 14,
|
|
height: 14,
|
|
borderRadius: '50%',
|
|
background: 'var(--color-warning)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
boxShadow: '0 0 0 1px var(--color-border)'
|
|
}}
|
|
>
|
|
<EditIcon style={{ fontSize: 8, color: '#ffffff' }} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Popover>
|
|
)
|
|
},
|
|
(prevProps, nextProps) => {
|
|
return (
|
|
prevProps.mode === nextProps.mode &&
|
|
getUserId(prevProps.user) === getUserId(nextProps.user) &&
|
|
getProfileImageId(prevProps.user) === getProfileImageId(nextProps.user) &&
|
|
getProfileImageBlurHash(prevProps.user) ===
|
|
getProfileImageBlurHash(nextProps.user)
|
|
)
|
|
}
|
|
)
|
|
|
|
UserActivityAvatar.propTypes = {
|
|
user: PropTypes.object,
|
|
mode: PropTypes.string
|
|
}
|
|
|
|
const ActivityIndicator = ({
|
|
activities = [],
|
|
showStartingDivider = false
|
|
}) => {
|
|
const { userProfile } = useContext(AuthContext)
|
|
const currentUserId = userProfile?._id
|
|
|
|
const otherActivities = activities.filter((activity) => {
|
|
if (!activity?.mode) return false
|
|
const userId = getUserId(activity.user)
|
|
return userId && userId !== currentUserId
|
|
})
|
|
|
|
if (otherActivities.length === 0) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<Flex gap='small' align='center'>
|
|
{showStartingDivider && otherActivities.length >= 1 && (
|
|
<Divider type='vertical' style={{ margin: '0 4px', height: '18px' }} />
|
|
)}
|
|
{otherActivities.map((activity) => {
|
|
const userId = getUserId(activity.user)
|
|
return (
|
|
<UserActivityAvatar
|
|
key={userId}
|
|
user={activity.user}
|
|
mode={activity.mode}
|
|
/>
|
|
)
|
|
})}
|
|
</Flex>
|
|
)
|
|
}
|
|
|
|
ActivityIndicator.propTypes = {
|
|
activities: PropTypes.array,
|
|
showStartingDivider: PropTypes.bool
|
|
}
|
|
|
|
export default ActivityIndicator
|