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';
|
2021-12-17 00:16:24 +00:00
|
|
|
import { getAvatarColor, getInitials, isCORSAvatarURL } from '../functions';
|
2019-06-26 14:08:23 +00:00
|
|
|
|
2022-01-06 12:48:08 +00:00
|
|
|
import { StatelessAvatar } from './';
|
2019-07-03 15:39:39 +00:00
|
|
|
|
2019-06-26 14:08:23 +00:00
|
|
|
export type Props = {
|
|
|
|
|
2021-12-17 00:16:24 +00:00
|
|
|
/**
|
|
|
|
* The URL patterns for URLs that needs to be handled with CORS.
|
|
|
|
*/
|
|
|
|
_corsAvatarURLs: Array<string>,
|
|
|
|
|
2021-07-20 11:37:22 +00:00
|
|
|
/**
|
|
|
|
* Custom avatar backgrounds from branding.
|
|
|
|
*/
|
|
|
|
_customAvatarBackgrounds: Array<string>,
|
|
|
|
|
2019-06-26 14:08:23 +00:00
|
|
|
/**
|
2021-03-16 15:59:33 +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,
|
|
|
|
|
2021-12-17 00:16:24 +00:00
|
|
|
/**
|
|
|
|
* Indicates whether _loadableAvatarUrl should use CORS or not.
|
|
|
|
*/
|
|
|
|
_loadableAvatarUrlUseCORS: ?boolean,
|
|
|
|
|
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
|
2021-11-04 21:10:43 +00:00
|
|
|
* an avatar for a non-participasnt entity (e.g. A recent list item).
|
2019-06-26 14:08:23 +00:00
|
|
|
*/
|
|
|
|
displayName?: string,
|
|
|
|
|
2020-09-29 11:00:30 +00:00
|
|
|
/**
|
2021-11-04 21:10:43 +00:00
|
|
|
* Whether or not to update the background color of the avatar.
|
2020-09-29 11:00:30 +00:00
|
|
|
*/
|
|
|
|
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,
|
|
|
|
|
2019-12-06 15:02:51 +00:00
|
|
|
/**
|
|
|
|
* One of the expected status strings (e.g. 'available') to render a badge on the avatar, if necessary.
|
|
|
|
*/
|
|
|
|
status?: ?string,
|
|
|
|
|
2020-07-10 15:28:57 +00:00
|
|
|
/**
|
|
|
|
* 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,
|
2021-12-17 00:16:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates whether to load the avatar using CORS or not.
|
|
|
|
*/
|
|
|
|
useCORS?: ?boolean
|
2019-06-26 14:08:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type State = {
|
2021-12-17 00:16:24 +00:00
|
|
|
avatarFailed: boolean,
|
|
|
|
isUsingCORS: boolean
|
2019-06-26 14:08:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2021-12-17 00:16:24 +00:00
|
|
|
const {
|
|
|
|
_corsAvatarURLs,
|
|
|
|
url,
|
|
|
|
useCORS
|
|
|
|
} = props;
|
|
|
|
|
2019-06-26 14:08:23 +00:00
|
|
|
this.state = {
|
2021-12-17 00:16:24 +00:00
|
|
|
avatarFailed: false,
|
|
|
|
isUsingCORS: Boolean(useCORS) || Boolean(url && isCORSAvatarURL(url, _corsAvatarURLs))
|
2019-06-26 14:08:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this._onAvatarLoadError = this._onAvatarLoadError.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements {@code Component#componentDidUpdate}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
componentDidUpdate(prevProps: P) {
|
2021-12-17 00:16:24 +00:00
|
|
|
const { _corsAvatarURLs, url } = this.props;
|
|
|
|
|
|
|
|
if (prevProps.url !== 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({
|
2021-12-17 00:16:24 +00:00
|
|
|
avatarFailed: false,
|
|
|
|
isUsingCORS: Boolean(this.props.useCORS) || Boolean(url && isCORSAvatarURL(url, _corsAvatarURLs))
|
2019-06-26 14:08:23 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements {@code Componenr#render}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
render() {
|
|
|
|
const {
|
2021-07-20 11:37:22 +00:00
|
|
|
_customAvatarBackgrounds,
|
2019-06-26 14:08:23 +00:00
|
|
|
_initialsBase,
|
|
|
|
_loadableAvatarUrl,
|
2021-12-17 00:16:24 +00:00
|
|
|
_loadableAvatarUrlUseCORS,
|
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,
|
2019-12-06 15:02:51 +00:00
|
|
|
status,
|
2020-07-10 15:28:57 +00:00
|
|
|
testId,
|
2019-07-03 15:39:39 +00:00
|
|
|
url
|
2019-06-26 14:08:23 +00:00
|
|
|
} = this.props;
|
2021-12-17 00:16:24 +00:00
|
|
|
const { avatarFailed, isUsingCORS } = this.state;
|
2019-06-26 14:08:23 +00:00
|
|
|
|
2019-07-03 15:39:39 +00:00
|
|
|
const avatarProps = {
|
|
|
|
className,
|
|
|
|
color: undefined,
|
|
|
|
id,
|
|
|
|
initials: undefined,
|
|
|
|
onAvatarLoadError: undefined,
|
2021-12-17 00:16:24 +00:00
|
|
|
onAvatarLoadErrorParams: undefined,
|
2019-07-03 15:39:39 +00:00
|
|
|
size,
|
2019-12-06 15:02:51 +00:00
|
|
|
status,
|
2020-07-10 15:28:57 +00:00
|
|
|
testId,
|
2021-12-17 00:16:24 +00:00
|
|
|
url: undefined,
|
|
|
|
useCORS: isUsingCORS
|
2019-07-03 15:39:39 +00:00
|
|
|
};
|
|
|
|
|
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.
|
2021-12-17 00:16:24 +00:00
|
|
|
const useReduxLoadableAvatarURL = avatarFailed || !url;
|
|
|
|
const effectiveURL = useReduxLoadableAvatarURL ? _loadableAvatarUrl : url;
|
2019-07-03 15:39:39 +00:00
|
|
|
|
|
|
|
if (effectiveURL) {
|
|
|
|
avatarProps.onAvatarLoadError = this._onAvatarLoadError;
|
2021-12-17 00:16:24 +00:00
|
|
|
if (useReduxLoadableAvatarURL) {
|
|
|
|
avatarProps.onAvatarLoadErrorParams = { dontRetry: true };
|
|
|
|
avatarProps.useCORS = _loadableAvatarUrlUseCORS;
|
|
|
|
}
|
2019-07-03 15:39:39 +00:00
|
|
|
avatarProps.url = effectiveURL;
|
2019-07-04 08:31:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const initials = getInitials(_initialsBase);
|
2019-07-03 15:39:39 +00:00
|
|
|
|
2019-07-04 08:31:23 +00:00
|
|
|
if (initials) {
|
2020-09-29 11:00:30 +00:00
|
|
|
if (dynamicColor) {
|
2021-07-20 11:37:22 +00:00
|
|
|
avatarProps.color = getAvatarColor(colorBase || _initialsBase, _customAvatarBackgrounds);
|
2020-09-29 11:00:30 +00:00
|
|
|
}
|
|
|
|
|
2019-07-04 08:31:23 +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.
|
|
|
|
*
|
2021-12-17 00:16:24 +00:00
|
|
|
* @param {Object} params - An object with parameters.
|
|
|
|
* @param {boolean} params.dontRetry - If false we will retry to load the Avatar with different CORS mode.
|
2019-06-26 14:08:23 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2021-12-17 00:16:24 +00:00
|
|
|
_onAvatarLoadError(params = {}) {
|
|
|
|
const { dontRetry = false } = params;
|
|
|
|
|
|
|
|
if (Boolean(this.props.useCORS) === this.state.isUsingCORS && !dontRetry) {
|
|
|
|
// try different mode of loading the avatar.
|
|
|
|
this.setState({
|
|
|
|
isUsingCORS: !this.state.isUsingCORS
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// we already have tried loading the avatar with and without CORS and it failed.
|
|
|
|
this.setState({
|
|
|
|
avatarFailed: true
|
|
|
|
});
|
|
|
|
}
|
2019-06-26 14:08:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
2019-08-14 12:26:46 +00:00
|
|
|
const _participant: ?Object = participantId && getParticipantById(state, participantId);
|
|
|
|
const _initialsBase = _participant?.name ?? displayName;
|
2021-12-17 00:16:24 +00:00
|
|
|
const { corsAvatarURLs } = state['features/base/config'];
|
2019-06-26 14:08:23 +00:00
|
|
|
|
|
|
|
return {
|
2021-07-20 11:37:22 +00:00
|
|
|
_customAvatarBackgrounds: state['features/dynamic-branding'].avatarBackgrounds,
|
2021-12-17 00:16:24 +00:00
|
|
|
_corsAvatarURLs: corsAvatarURLs,
|
2019-06-26 14:08:23 +00:00
|
|
|
_initialsBase,
|
2020-07-16 15:03:15 +00:00
|
|
|
_loadableAvatarUrl: _participant?.loadableAvatarUrl,
|
2021-12-17 00:16:24 +00:00
|
|
|
_loadableAvatarUrlUseCORS: _participant?.loadableAvatarUrlUseCORS,
|
2021-11-08 09:32:21 +00:00
|
|
|
colorBase
|
2019-06-26 14:08:23 +00:00
|
|
|
};
|
|
|
|
}
|
2019-07-03 15:39:39 +00:00
|
|
|
|
|
|
|
export default connect(_mapStateToProps)(Avatar);
|