// Copyright 2018-2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import classNames from 'classnames'; import { getInitials } from '../util/getInitials'; import { LocalizerType } from '../types/Util'; import { ColorType } from '../types/Colors'; export enum AvatarSize { TWENTY_EIGHT = 28, THIRTY_TWO = 32, FIFTY_TWO = 52, EIGHTY = 80, NINETY_SIX = 96, ONE_HUNDRED_TWELVE = 112, } export type Props = { avatarPath?: string; color?: ColorType; conversationType: 'group' | 'direct'; noteToSelf?: boolean; title: string; name?: string; phoneNumber?: string; profileName?: string; size: AvatarSize; onClick?: () => unknown; // Matches Popper's RefHandler type innerRef?: React.Ref; i18n: LocalizerType; } & Pick, 'className'>; interface State { imageBroken: boolean; lastAvatarPath?: string; } export class Avatar extends React.Component { public handleImageErrorBound: () => void; public constructor(props: Props) { super(props); this.handleImageErrorBound = this.handleImageError.bind(this); this.state = { lastAvatarPath: props.avatarPath, imageBroken: false, }; } public static getDerivedStateFromProps(props: Props, state: State): State { if (props.avatarPath !== state.lastAvatarPath) { return { ...state, lastAvatarPath: props.avatarPath, imageBroken: false, }; } return state; } public handleImageError(): void { window.log.info( 'Avatar: Image failed to load; failing over to placeholder' ); this.setState({ imageBroken: true, }); } public renderImage(): JSX.Element | null { const { avatarPath, i18n, title } = this.props; const { imageBroken } = this.state; if (!avatarPath || imageBroken) { return null; } return ( {i18n('contactAvatarAlt', ); } public renderNoImage(): JSX.Element { const { conversationType, noteToSelf, size, title } = this.props; const initials = getInitials(title); const isGroup = conversationType === 'group'; if (noteToSelf) { return (
); } if (!isGroup && initials) { return (
{initials}
); } return (
); } public render(): JSX.Element { const { avatarPath, color, innerRef, noteToSelf, onClick, size, className, } = this.props; const { imageBroken } = this.state; const hasImage = !noteToSelf && avatarPath && !imageBroken; if (![28, 32, 52, 80, 96, 112].includes(size)) { throw new Error(`Size ${size} is not supported!`); } let contents; if (onClick) { contents = ( ); } else { contents = hasImage ? this.renderImage() : this.renderNoImage(); } return (
{contents}
); } }