// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { FunctionComponent, ReactNode, useEffect, useState, } from 'react'; import classNames from 'classnames'; import { noop } from 'lodash'; import { Spinner } from './Spinner'; import { getInitials } from '../util/getInitials'; import { LocalizerType } from '../types/Util'; import { AvatarColorType } from '../types/Colors'; import * as log from '../logging/log'; import { assert } from '../util/assert'; import { shouldBlurAvatar } from '../util/shouldBlurAvatar'; export enum AvatarBlur { NoBlur, BlurPicture, BlurPictureWithClickToView, } export enum AvatarSize { TWENTY_EIGHT = 28, THIRTY_TWO = 32, THIRTY_SIX = 36, FIFTY_TWO = 52, EIGHTY = 80, NINETY_SIX = 96, ONE_HUNDRED_TWELVE = 112, } export type Props = { avatarPath?: string; blur?: AvatarBlur; color?: AvatarColorType; loading?: boolean; acceptedMessageRequest: boolean; conversationType: 'group' | 'direct'; isMe: boolean; name?: string; noteToSelf?: boolean; phoneNumber?: string; profileName?: string; sharedGroupNames: Array; size: AvatarSize; title: string; unblurredAvatarPath?: string; onClick?: () => unknown; // Matches Popper's RefHandler type innerRef?: React.Ref; i18n: LocalizerType; } & Pick, 'className'>; const getDefaultBlur = ( ...args: Parameters ): AvatarBlur => shouldBlurAvatar(...args) ? AvatarBlur.BlurPicture : AvatarBlur.NoBlur; export const Avatar: FunctionComponent = ({ acceptedMessageRequest, avatarPath, className, color, conversationType, i18n, isMe, innerRef, loading, noteToSelf, onClick, sharedGroupNames, size, title, unblurredAvatarPath, blur = getDefaultBlur({ acceptedMessageRequest, avatarPath, isMe, sharedGroupNames, unblurredAvatarPath, }), }) => { const [imageBroken, setImageBroken] = useState(false); useEffect(() => { setImageBroken(false); }, [avatarPath]); useEffect(() => { if (!avatarPath) { return noop; } const image = new Image(); image.src = avatarPath; image.onerror = () => { log.warn('Avatar: Image failed to load; failing over to placeholder'); setImageBroken(true); }; return () => { image.onerror = noop; }; }, [avatarPath]); const initials = getInitials(title); const hasImage = !noteToSelf && avatarPath && !imageBroken; const shouldUseInitials = !hasImage && conversationType === 'direct' && Boolean(initials); let contents: ReactNode; if (loading) { const svgSize = size < 40 ? 'small' : 'normal'; contents = (
); } else if (hasImage) { assert(avatarPath, 'avatarPath should be defined here'); assert( blur !== AvatarBlur.BlurPictureWithClickToView || size >= 100, 'Rendering "click to view" for a small avatar. This may not render correctly' ); const isBlurred = blur === AvatarBlur.BlurPicture || blur === AvatarBlur.BlurPictureWithClickToView; contents = ( <>
{blur === AvatarBlur.BlurPictureWithClickToView && (
{i18n('view')}
)} ); } else if (noteToSelf) { contents = (
); } else if (shouldUseInitials) { contents = ( ); } else { contents = (
); } if (onClick) { contents = ( ); } return (
{contents}
); };