jiti-meet/react/features/base/avatar/components/Avatar.js

228 lines
5.6 KiB
JavaScript
Raw Normal View History

2019-06-26 14:08:23 +00:00
// @flow
2019-07-03 15:39:39 +00:00
import React, { PureComponent } from 'react';
2019-06-26 14:08:23 +00:00
import { getParticipantById } from '../../participants';
2019-07-03 15:39:39 +00:00
import { connect } from '../../redux';
2019-06-26 14:08:23 +00:00
import { getAvatarColor, getInitials } from '../functions';
2019-07-03 15:39:39 +00:00
import { StatelessAvatar } from '.';
2019-06-26 14:08:23 +00:00
export type Props = {
/**
* Custom avatar backgrounds from branding.
*/
_customAvatarBackgrounds: Array<string>,
2019-06-26 14:08:23 +00:00
/**
* The string we base the initials on (this is generated from a list of precedences).
2019-06-26 14:08:23 +00:00
*/
_initialsBase: ?string,
/**
* An URL that we validated that it can be loaded.
*/
_loadableAvatarUrl: ?string,
2019-07-03 15:39:39 +00:00
/**
* A prop to maintain compatibility with web.
*/
className?: string,
2019-06-26 14:08:23 +00:00
/**
* 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,
2020-09-29 11:00:30 +00:00
/**
* Whether or not to update the background color of the avatar
*/
dynamicColor?: Boolean,
2019-07-03 15:39:39 +00:00
/**
* ID of the element, if any.
*/
id?: string,
2019-06-26 14:08:23 +00:00
/**
* 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,
/**
* TestId of the element, if any.
*/
testId?: string,
2019-06-26 14:08:23 +00:00
/**
2019-07-03 15:39:39 +00:00
* URL of the avatar, if any.
2019-06-26 14:08:23 +00:00
*/
2019-07-03 15:39:39 +00:00
url: ?string,
2019-06-26 14:08:23 +00:00
}
type State = {
avatarFailed: boolean
}
export const DEFAULT_SIZE = 65;
/**
2019-07-03 15:39:39 +00:00
* Implements a class to render avatars in the app.
2019-06-26 14:08:23 +00:00
*/
2019-07-03 15:39:39 +00:00
class Avatar<P: Props> extends PureComponent<P, State> {
2020-09-29 11:00:30 +00:00
/**
* Default values for {@code Avatar} component's properties.
*
* @static
*/
static defaultProps = {
dynamicColor: true
};
2019-06-26 14:08:23 +00:00
/**
* 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) {
2019-07-03 15:39:39 +00:00
if (prevProps.url !== this.props.url) {
2019-06-26 14:08:23 +00:00
// 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 {
_customAvatarBackgrounds,
2019-06-26 14:08:23 +00:00
_initialsBase,
_loadableAvatarUrl,
2019-07-03 15:39:39 +00:00
className,
2019-06-26 14:08:23 +00:00
colorBase,
2020-09-29 11:00:30 +00:00
dynamicColor,
2019-07-03 15:39:39 +00:00
id,
size,
status,
testId,
2019-07-03 15:39:39 +00:00
url
2019-06-26 14:08:23 +00:00
} = this.props;
const { avatarFailed } = this.state;
2019-07-03 15:39:39 +00:00
const avatarProps = {
className,
color: undefined,
id,
initials: undefined,
onAvatarLoadError: undefined,
size,
status,
testId,
2019-07-03 15:39:39 +00:00
url: undefined
};
2019-06-26 14:08:23 +00:00
// _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.
2019-07-03 15:39:39 +00:00
const effectiveURL = (!avatarFailed && url) || _loadableAvatarUrl;
if (effectiveURL) {
avatarProps.onAvatarLoadError = this._onAvatarLoadError;
avatarProps.url = effectiveURL;
}
const initials = getInitials(_initialsBase);
2019-07-03 15:39:39 +00:00
if (initials) {
2020-09-29 11:00:30 +00:00
if (dynamicColor) {
avatarProps.color = getAvatarColor(colorBase || _initialsBase, _customAvatarBackgrounds);
2020-09-29 11:00:30 +00:00
}
avatarProps.initials = initials;
2019-06-26 14:08:23 +00:00
}
2019-07-03 15:39:39 +00:00
return (
<StatelessAvatar
{ ...avatarProps } />
);
2019-06-26 14:08:23 +00:00
}
_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) {
2019-07-16 10:23:01 +00:00
const { colorBase, displayName, participantId } = ownProps;
const _participant: ?Object = participantId && getParticipantById(state, participantId);
const _initialsBase = _participant?.name ?? displayName;
2019-06-26 14:08:23 +00:00
return {
_customAvatarBackgrounds: state['features/dynamic-branding'].avatarBackgrounds,
2019-06-26 14:08:23 +00:00
_initialsBase,
_loadableAvatarUrl: _participant?.loadableAvatarUrl,
2019-07-16 10:23:01 +00:00
colorBase: !colorBase && _participant ? _participant.id : colorBase
2019-06-26 14:08:23 +00:00
};
}
2019-07-03 15:39:39 +00:00
export default connect(_mapStateToProps)(Avatar);