You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
3.8 KiB
TypeScript
142 lines
3.8 KiB
TypeScript
import React, { useCallback, useState } from 'react';
|
|
import classNames from 'classnames';
|
|
|
|
import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder';
|
|
import { ConversationAvatar } from './session/usingClosedConversationDetails';
|
|
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch';
|
|
import _ from 'underscore';
|
|
|
|
export enum AvatarSize {
|
|
XS = 28,
|
|
S = 36,
|
|
M = 48,
|
|
L = 64,
|
|
XL = 80,
|
|
HUGE = 300,
|
|
}
|
|
|
|
type Props = {
|
|
avatarPath?: string | null;
|
|
name?: string; // display name, profileName or pubkey, whatever is set first
|
|
pubkey?: string;
|
|
size: AvatarSize;
|
|
base64Data?: string; // if this is not empty, it will be used to render the avatar with base64 encoded data
|
|
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
|
|
onAvatarClick?: () => void;
|
|
};
|
|
|
|
const Identicon = (props: Props) => {
|
|
const { size, name, pubkey } = props;
|
|
const userName = name || '0';
|
|
|
|
return (
|
|
<AvatarPlaceHolder
|
|
diameter={size}
|
|
name={userName}
|
|
pubkey={pubkey}
|
|
colors={['#5ff8b0', '#26cdb9', '#f3c615', '#fcac5a']}
|
|
borderColor={'#00000059'}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const NoImage = (props: {
|
|
memberAvatars?: Array<ConversationAvatar>;
|
|
name?: string;
|
|
pubkey?: string;
|
|
size: AvatarSize;
|
|
onAvatarClick?: () => void;
|
|
}) => {
|
|
const { name, memberAvatars, size, pubkey } = props;
|
|
// if no image but we have conversations set for the group, renders group members avatars
|
|
if (memberAvatars) {
|
|
return (
|
|
<ClosedGroupAvatar
|
|
size={size}
|
|
memberAvatars={memberAvatars}
|
|
onAvatarClick={props.onAvatarClick}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return <Identicon size={size} name={name} pubkey={pubkey} />;
|
|
};
|
|
|
|
const AvatarImage = (props: {
|
|
avatarPath?: string;
|
|
base64Data?: string;
|
|
name?: string; // display name, profileName or pubkey, whatever is set first
|
|
imageBroken: boolean;
|
|
handleImageError: () => any;
|
|
}) => {
|
|
const { avatarPath, base64Data, name, imageBroken, handleImageError } = props;
|
|
|
|
const onDragStart = useCallback((e: any) => {
|
|
e.preventDefault();
|
|
return false;
|
|
}, []);
|
|
|
|
if ((!avatarPath && !base64Data) || imageBroken) {
|
|
return null;
|
|
}
|
|
const dataToDisplay = base64Data ? `data:image/jpeg;base64,${base64Data}` : avatarPath;
|
|
|
|
return (
|
|
<img
|
|
onError={handleImageError}
|
|
onDragStart={onDragStart}
|
|
alt={window.i18n('contactAvatarAlt', [name])}
|
|
src={dataToDisplay}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const AvatarInner = (props: Props) => {
|
|
const { avatarPath, base64Data, size, memberAvatars, name } = props;
|
|
const [imageBroken, setImageBroken] = useState(false);
|
|
// contentType is not important
|
|
const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', '');
|
|
const handleImageError = () => {
|
|
window.log.warn(
|
|
'Avatar: Image failed to load; failing over to placeholder',
|
|
urlToLoad,
|
|
avatarPath
|
|
);
|
|
setImageBroken(true);
|
|
};
|
|
|
|
const isClosedGroupAvatar = Boolean(memberAvatars?.length);
|
|
const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroupAvatar;
|
|
|
|
const isClickable = !!props.onAvatarClick;
|
|
return (
|
|
<div
|
|
className={classNames(
|
|
'module-avatar',
|
|
`module-avatar--${size}`,
|
|
hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image',
|
|
isClickable && 'module-avatar-clickable'
|
|
)}
|
|
onClick={e => {
|
|
e.stopPropagation();
|
|
props.onAvatarClick?.();
|
|
}}
|
|
role="button"
|
|
>
|
|
{hasImage ? (
|
|
<AvatarImage
|
|
avatarPath={urlToLoad}
|
|
base64Data={base64Data}
|
|
imageBroken={imageBroken}
|
|
name={name}
|
|
handleImageError={handleImageError}
|
|
/>
|
|
) : (
|
|
<NoImage {...props} />
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const Avatar = React.memo(AvatarInner, _.isEqual);
|