| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 | // @flow
import React, { PureComponent } from 'react';
import { IconShareDesktop } from '../../icons';
import { getParticipantById } from '../../participants';
import { connect } from '../../redux';
import { getAvatarColor, getInitials } from '../functions';
import { StatelessAvatar } from '.';
export type Props = {
    /**
     * The string we base the initials on (this is generated from a list of precendences).
     */
    _initialsBase: ?string,
    /**
     * An URL that we validated that it can be loaded.
     */
    _loadableAvatarUrl: ?string,
    /**
     * A prop to maintain compatibility with web.
     */
    className?: string,
    /**
     * A string to override the initials to generate a color of. This is handy if you don't want to make
     * the background color match the string that the initials are generated from.
     */
    colorBase?: string,
    /**
     * Display name of the entity to render an avatar for (if any). This is handy when we need
     * an avatar for a non-participasnt entity (e.g. a recent list item).
     */
    displayName?: string,
    /**
     * ID of the element, if any.
     */
    id?: string,
    /**
     * The ID of the participant to render an avatar for (if it's a participant avatar).
     */
    participantId?: string,
    /**
     * The size of the avatar.
     */
    size: number,
    /**
     * One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
     */
    status?: ?string,
    /**
     * URL of the avatar, if any.
     */
    url: ?string,
}
type State = {
    avatarFailed: boolean
}
export const DEFAULT_SIZE = 65;
/**
 * Implements a class to render avatars in the app.
 */
class Avatar<P: Props> extends PureComponent<P, State> {
    /**
     * Instantiates a new {@code Component}.
     *
     * @inheritdoc
     */
    constructor(props: P) {
        super(props);
        this.state = {
            avatarFailed: false
        };
        this._onAvatarLoadError = this._onAvatarLoadError.bind(this);
    }
    /**
     * Implements {@code Component#componentDidUpdate}.
     *
     * @inheritdoc
     */
    componentDidUpdate(prevProps: P) {
        if (prevProps.url !== this.props.url) {
            // URI changed, so we need to try to fetch it again.
            // Eslint doesn't like this statement, but based on the React doc, it's safe if it's
            // wrapped in a condition: https://reactjs.org/docs/react-component.html#componentdidupdate
            // eslint-disable-next-line react/no-did-update-set-state
            this.setState({
                avatarFailed: false
            });
        }
    }
    /**
     * Implements {@code Componenr#render}.
     *
     * @inheritdoc
     */
    render() {
        const {
            _initialsBase,
            _loadableAvatarUrl,
            className,
            colorBase,
            id,
            size,
            status,
            url
        } = this.props;
        const { avatarFailed } = this.state;
        const avatarProps = {
            className,
            color: undefined,
            id,
            initials: undefined,
            onAvatarLoadError: undefined,
            size,
            status,
            url: undefined
        };
        // _loadableAvatarUrl is validated that it can be loaded, but uri (if present) is not, so
        // we still need to do a check for that. And an explicitly provided URI is higher priority than
        // an avatar URL anyhow.
        const effectiveURL = (!avatarFailed && url) || _loadableAvatarUrl;
        if (effectiveURL) {
            avatarProps.onAvatarLoadError = this._onAvatarLoadError;
            avatarProps.url = effectiveURL;
        }
        const initials = getInitials(_initialsBase);
        if (initials) {
            avatarProps.color = getAvatarColor(colorBase || _initialsBase);
            avatarProps.initials = initials;
        }
        return (
            <StatelessAvatar
                { ...avatarProps } />
        );
    }
    _onAvatarLoadError: () => void;
    /**
     * Callback to handle the error while loading of the avatar URI.
     *
     * @returns {void}
     */
    _onAvatarLoadError() {
        this.setState({
            avatarFailed: true
        });
    }
}
/**
 * Maps part of the Redux state to the props of this component.
 *
 * @param {Object} state - The Redux state.
 * @param {Props} ownProps - The own props of the component.
 * @returns {Props}
 */
export function _mapStateToProps(state: Object, ownProps: Props) {
    const { colorBase, displayName, participantId } = ownProps;
    const _participant: ?Object = participantId && getParticipantById(state, participantId);
    const _initialsBase = _participant?.name ?? displayName;
    const screenShares = state['features/video-layout'].screenShares || [];
    let _loadableAvatarUrl = _participant?.loadableAvatarUrl;
    if (participantId && screenShares.includes(participantId)) {
        _loadableAvatarUrl = IconShareDesktop;
    }
    return {
        _initialsBase,
        _loadableAvatarUrl,
        colorBase: !colorBase && _participant ? _participant.id : colorBase
    };
}
export default connect(_mapStateToProps)(Avatar);
 |