2022-09-13 07:36:00 +00:00
|
|
|
import { withStyles } from '@mui/styles';
|
2022-03-15 10:27:40 +00:00
|
|
|
import clsx from 'clsx';
|
2019-07-03 15:39:39 +00:00
|
|
|
import React from 'react';
|
|
|
|
|
2022-08-26 09:54:03 +00:00
|
|
|
import Icon from '../../../icons/components/Icon';
|
2019-07-03 15:39:39 +00:00
|
|
|
import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
|
2022-03-15 10:27:40 +00:00
|
|
|
import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
|
2019-07-03 15:39:39 +00:00
|
|
|
|
|
|
|
type Props = AbstractProps & {
|
|
|
|
|
2022-03-15 10:27:40 +00:00
|
|
|
/**
|
2022-08-26 09:54:03 +00:00
|
|
|
* External class name passed through props.
|
2022-03-15 10:27:40 +00:00
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
className?: string;
|
2022-03-15 10:27:40 +00:00
|
|
|
|
2019-07-03 15:39:39 +00:00
|
|
|
/**
|
2022-08-26 09:54:03 +00:00
|
|
|
* An object containing the CSS classes.
|
2019-07-03 15:39:39 +00:00
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
classes: any;
|
2019-07-03 15:39:39 +00:00
|
|
|
|
|
|
|
/**
|
2021-11-04 21:10:43 +00:00
|
|
|
* The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop).
|
2019-07-03 15:39:39 +00:00
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
defaultAvatar?: string;
|
2019-07-03 15:39:39 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* ID of the component to be rendered.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
id?: string;
|
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.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
status?: string;
|
2020-07-10 15:28:57 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* TestId of the element, if any.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
testId?: string;
|
2021-12-17 00:16:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates whether to load the avatar using CORS or not.
|
|
|
|
*/
|
2022-09-08 09:52:36 +00:00
|
|
|
useCORS?: boolean;
|
2019-07-03 15:39:39 +00:00
|
|
|
};
|
|
|
|
|
2022-03-15 10:27:40 +00:00
|
|
|
/**
|
|
|
|
* Creates the styles for the component.
|
|
|
|
*
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
|
|
|
const styles = () => {
|
|
|
|
return {
|
|
|
|
avatar: {
|
|
|
|
backgroundColor: '#AAA',
|
|
|
|
borderRadius: '50%',
|
|
|
|
color: 'rgba(255, 255, 255, 1)',
|
|
|
|
fontWeight: '100',
|
2022-09-13 07:36:00 +00:00
|
|
|
objectFit: 'cover' as const,
|
|
|
|
textAlign: 'center' as const,
|
2022-03-15 10:27:40 +00:00
|
|
|
|
|
|
|
'&.avatar-small': {
|
|
|
|
height: '28px !important',
|
|
|
|
width: '28px !important'
|
|
|
|
},
|
|
|
|
|
|
|
|
'&.avatar-xsmall': {
|
|
|
|
height: '16px !important',
|
|
|
|
width: '16px !important'
|
|
|
|
},
|
|
|
|
|
|
|
|
'& .jitsi-icon': {
|
|
|
|
transform: 'translateY(50%)'
|
|
|
|
},
|
|
|
|
|
|
|
|
'& .avatar-svg': {
|
|
|
|
height: '100%',
|
|
|
|
width: '100%'
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
badge: {
|
2022-09-13 07:36:00 +00:00
|
|
|
position: 'relative' as const,
|
2022-03-15 10:27:40 +00:00
|
|
|
|
|
|
|
'&.avatar-badge:after': {
|
|
|
|
borderRadius: '50%',
|
|
|
|
content: '""',
|
|
|
|
display: 'block',
|
|
|
|
height: '35%',
|
|
|
|
position: 'absolute',
|
|
|
|
bottom: 0,
|
|
|
|
width: '35%'
|
|
|
|
},
|
|
|
|
|
|
|
|
'&.avatar-badge-available:after': {
|
|
|
|
backgroundColor: PRESENCE_AVAILABLE_COLOR
|
|
|
|
},
|
|
|
|
|
|
|
|
'&.avatar-badge-away:after': {
|
|
|
|
backgroundColor: PRESENCE_AWAY_COLOR
|
|
|
|
},
|
|
|
|
|
|
|
|
'&.avatar-badge-busy:after': {
|
|
|
|
backgroundColor: PRESENCE_BUSY_COLOR
|
|
|
|
},
|
|
|
|
|
|
|
|
'&.avatar-badge-idle:after': {
|
|
|
|
backgroundColor: PRESENCE_IDLE_COLOR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2019-07-03 15:39:39 +00:00
|
|
|
/**
|
|
|
|
* Implements a stateless avatar component that renders an avatar purely from what gets passed through
|
|
|
|
* props.
|
|
|
|
*/
|
2022-03-15 10:27:40 +00:00
|
|
|
class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
2021-12-17 00:16:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Instantiates a new {@code Component}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
constructor(props: Props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this._onAvatarLoadError = this._onAvatarLoadError.bind(this);
|
|
|
|
}
|
|
|
|
|
2019-07-03 15:39:39 +00:00
|
|
|
/**
|
|
|
|
* Implements {@code Component#render}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
render() {
|
2021-12-17 00:16:24 +00:00
|
|
|
const { initials, url, useCORS } = this.props;
|
2019-07-04 08:31:23 +00:00
|
|
|
|
2019-08-30 16:39:06 +00:00
|
|
|
if (this._isIcon(url)) {
|
2019-07-04 08:31:23 +00:00
|
|
|
return (
|
|
|
|
<div
|
2022-03-15 10:27:40 +00:00
|
|
|
className = { clsx(this._getAvatarClassName(), this._getBadgeClassName()) }
|
2020-07-10 15:28:57 +00:00
|
|
|
data-testid = { this.props.testId }
|
2019-07-04 08:31:23 +00:00
|
|
|
id = { this.props.id }
|
|
|
|
style = { this._getAvatarStyle(this.props.color) }>
|
2019-11-27 09:39:00 +00:00
|
|
|
<Icon
|
|
|
|
size = '50%'
|
|
|
|
src = { url } />
|
2019-07-04 08:31:23 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2019-07-03 15:39:39 +00:00
|
|
|
|
|
|
|
if (url) {
|
|
|
|
return (
|
2019-12-06 15:02:51 +00:00
|
|
|
<div className = { this._getBadgeClassName() }>
|
|
|
|
<img
|
2021-07-15 11:41:57 +00:00
|
|
|
alt = 'avatar'
|
2019-12-06 15:02:51 +00:00
|
|
|
className = { this._getAvatarClassName() }
|
2021-12-17 00:16:24 +00:00
|
|
|
crossOrigin = { useCORS ? '' : undefined }
|
2020-07-10 15:28:57 +00:00
|
|
|
data-testid = { this.props.testId }
|
2019-12-06 15:02:51 +00:00
|
|
|
id = { this.props.id }
|
2021-12-17 00:16:24 +00:00
|
|
|
onError = { this._onAvatarLoadError }
|
2019-12-06 15:02:51 +00:00
|
|
|
src = { url }
|
|
|
|
style = { this._getAvatarStyle() } />
|
|
|
|
</div>
|
2019-07-03 15:39:39 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (initials) {
|
|
|
|
return (
|
|
|
|
<div
|
2022-03-15 10:27:40 +00:00
|
|
|
className = { clsx(this._getAvatarClassName(), this._getBadgeClassName()) }
|
2020-07-10 15:28:57 +00:00
|
|
|
data-testid = { this.props.testId }
|
2019-07-03 15:39:39 +00:00
|
|
|
id = { this.props.id }
|
|
|
|
style = { this._getAvatarStyle(this.props.color) }>
|
2019-07-03 16:23:00 +00:00
|
|
|
<svg
|
|
|
|
className = 'avatar-svg'
|
|
|
|
viewBox = '0 0 100 100'
|
|
|
|
xmlns = 'http://www.w3.org/2000/svg'
|
|
|
|
xmlnsXlink = 'http://www.w3.org/1999/xlink'>
|
2020-04-20 23:14:11 +00:00
|
|
|
<text
|
|
|
|
dominantBaseline = 'central'
|
2021-06-10 12:48:44 +00:00
|
|
|
fill = 'rgba(255,255,255,1)'
|
2020-04-20 23:14:11 +00:00
|
|
|
fontSize = '40pt'
|
|
|
|
textAnchor = 'middle'
|
|
|
|
x = '50'
|
|
|
|
y = '50'>
|
|
|
|
{ initials }
|
|
|
|
</text>
|
2019-07-03 16:23:00 +00:00
|
|
|
</svg>
|
2019-07-03 15:39:39 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// default avatar
|
|
|
|
return (
|
2019-12-06 15:02:51 +00:00
|
|
|
<div className = { this._getBadgeClassName() }>
|
|
|
|
<img
|
2021-07-15 11:41:57 +00:00
|
|
|
alt = 'avatar'
|
2019-12-06 15:02:51 +00:00
|
|
|
className = { this._getAvatarClassName('defaultAvatar') }
|
2020-07-10 15:28:57 +00:00
|
|
|
data-testid = { this.props.testId }
|
2019-12-06 15:02:51 +00:00
|
|
|
id = { this.props.id }
|
|
|
|
src = { this.props.defaultAvatar || 'images/avatar.png' }
|
|
|
|
style = { this._getAvatarStyle() } />
|
|
|
|
</div>
|
2019-07-03 15:39:39 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a style object to be used on the avatars.
|
|
|
|
*
|
2022-08-26 09:54:03 +00:00
|
|
|
* @param {string} color - The desired background color.
|
2019-07-03 15:39:39 +00:00
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2022-08-26 09:54:03 +00:00
|
|
|
_getAvatarStyle(color?: string) {
|
2019-07-03 15:39:39 +00:00
|
|
|
const { size } = this.props;
|
|
|
|
|
|
|
|
return {
|
2021-07-20 11:37:22 +00:00
|
|
|
background: color || undefined,
|
2019-07-03 15:39:39 +00:00
|
|
|
fontSize: size ? size * 0.5 : '180%',
|
|
|
|
height: size || '100%',
|
|
|
|
width: size || '100%'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a list of class names required for the avatar component.
|
|
|
|
*
|
|
|
|
* @param {string} additional - Any additional class to add.
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
2022-08-26 09:54:03 +00:00
|
|
|
_getAvatarClassName(additional?: string) {
|
2022-03-15 10:27:40 +00:00
|
|
|
return clsx('avatar', additional, this.props.className, this.props.classes.avatar);
|
2019-07-03 15:39:39 +00:00
|
|
|
}
|
2019-07-04 08:31:23 +00:00
|
|
|
|
2019-12-06 15:02:51 +00:00
|
|
|
/**
|
|
|
|
* Generates a class name to render a badge on the avatar, if necessary.
|
|
|
|
*
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
_getBadgeClassName() {
|
|
|
|
const { status } = this.props;
|
|
|
|
|
|
|
|
if (status) {
|
2022-03-15 10:27:40 +00:00
|
|
|
return clsx('avatar-badge', `avatar-badge-${status}`, this.props.classes.badge);
|
2019-12-06 15:02:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2021-12-17 00:16:24 +00:00
|
|
|
/**
|
|
|
|
* Handles avatar load errors.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onAvatarLoadError() {
|
|
|
|
const { onAvatarLoadError, onAvatarLoadErrorParams } = this.props;
|
|
|
|
|
|
|
|
if (typeof onAvatarLoadError === 'function') {
|
|
|
|
onAvatarLoadError(onAvatarLoadErrorParams);
|
|
|
|
}
|
|
|
|
}
|
2019-07-03 15:39:39 +00:00
|
|
|
}
|
2022-03-15 10:27:40 +00:00
|
|
|
|
|
|
|
export default withStyles(styles)(StatelessAvatar);
|