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.
		
		
		
		
		
			
		
			
				
	
	
		
			146 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			146 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
| import React from 'react';
 | |
| import classNames from 'classnames';
 | |
| 
 | |
| import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder';
 | |
| import { ConversationAvatar } from './session/usingClosedConversationDetails';
 | |
| 
 | |
| interface Props {
 | |
|   avatarPath?: string;
 | |
|   name?: string; // display name, profileName or phoneNumber, whatever is set first
 | |
|   pubkey?: string;
 | |
|   size: number;
 | |
|   memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
 | |
|   onAvatarClick?: () => void;
 | |
| }
 | |
| 
 | |
| interface State {
 | |
|   imageBroken: boolean;
 | |
| }
 | |
| 
 | |
| export class Avatar extends React.PureComponent<Props, State> {
 | |
|   public handleImageErrorBound: () => void;
 | |
|   public onAvatarClickBound: (e: any) => void;
 | |
| 
 | |
|   public constructor(props: Props) {
 | |
|     super(props);
 | |
| 
 | |
|     this.handleImageErrorBound = this.handleImageError.bind(this);
 | |
|     this.onAvatarClickBound = this.onAvatarClick.bind(this);
 | |
| 
 | |
|     this.state = {
 | |
|       imageBroken: false,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   public handleImageError() {
 | |
|     window.log.warn(
 | |
|       'Avatar: Image failed to load; failing over to placeholder'
 | |
|     );
 | |
|     this.setState({
 | |
|       imageBroken: true,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   public renderIdenticon() {
 | |
|     const { size, name, pubkey } = this.props;
 | |
| 
 | |
|     const userName = name || '0';
 | |
| 
 | |
|     return (
 | |
|       <AvatarPlaceHolder
 | |
|         diameter={size}
 | |
|         name={userName}
 | |
|         pubkey={pubkey}
 | |
|         colors={this.getAvatarColors()}
 | |
|         borderColor={this.getAvatarBorderColor()}
 | |
|       />
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   public renderImage() {
 | |
|     const { avatarPath, name } = this.props;
 | |
|     const { imageBroken } = this.state;
 | |
| 
 | |
|     if (!avatarPath || imageBroken) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     return (
 | |
|       <img
 | |
|         onError={this.handleImageErrorBound}
 | |
|         alt={window.i18n('contactAvatarAlt', [name])}
 | |
|         src={avatarPath}
 | |
|       />
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   public renderNoImage() {
 | |
|     const { memberAvatars, size } = this.props;
 | |
|     // if no image but we have conversations set for the group, renders group members avatars
 | |
|     if (memberAvatars) {
 | |
|       return (
 | |
|         <ClosedGroupAvatar
 | |
|           size={size}
 | |
|           memberAvatars={memberAvatars}
 | |
|           i18n={window.i18n}
 | |
|         />
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return this.renderIdenticon();
 | |
|   }
 | |
| 
 | |
|   public render() {
 | |
|     const { avatarPath, size, memberAvatars } = this.props;
 | |
|     const { imageBroken } = this.state;
 | |
|     const isClosedGroupAvatar = memberAvatars && memberAvatars.length;
 | |
|     const hasImage = avatarPath && !imageBroken && !isClosedGroupAvatar;
 | |
| 
 | |
|     if (
 | |
|       size !== 28 &&
 | |
|       size !== 36 &&
 | |
|       size !== 48 &&
 | |
|       size !== 64 &&
 | |
|       size !== 80 &&
 | |
|       size !== 300
 | |
|     ) {
 | |
|       throw new Error(`Size ${size} is not supported!`);
 | |
|     }
 | |
|     const isClickable = !!this.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 => {
 | |
|           this.onAvatarClickBound(e);
 | |
|         }}
 | |
|         role="button"
 | |
|       >
 | |
|         {hasImage ? this.renderImage() : this.renderNoImage()}
 | |
|       </div>
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   private onAvatarClick(e: any) {
 | |
|     if (this.props.onAvatarClick) {
 | |
|       e.stopPropagation();
 | |
|       this.props.onAvatarClick();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private getAvatarColors(): Array<string> {
 | |
|     // const theme = window.Events.getThemedSettings();
 | |
|     // defined in session-android as `profile_picture_placeholder_colors`
 | |
|     return ['#5ff8b0', '#26cdb9', '#f3c615', '#fcac5a'];
 | |
|   }
 | |
| 
 | |
|   private getAvatarBorderColor(): string {
 | |
|     return '#00000059'; // borderAvatarColor in themes.scss
 | |
|   }
 | |
| }
 |