|  |  |  | 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 phoneNumber, 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 phoneNumber, 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); |