fix: AlwaysOnTop avatar
This commit is contained in:
parent
658679f89e
commit
a04982fd96
|
@ -2,6 +2,11 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
// We need to reference these files directly to avoid loading things that are not available
|
||||
// in this environment (e.g. JitsiMeetJS or interfaceConfig)
|
||||
import StatelessAvatar from '../base/avatar/components/web/StatelessAvatar';
|
||||
import { getInitials } from '../base/avatar/functions';
|
||||
|
||||
import Toolbar from './Toolbar';
|
||||
|
||||
const { api } = window.alwaysOnTop;
|
||||
|
@ -17,6 +22,7 @@ const TOOLBAR_TIMEOUT = 4000;
|
|||
type State = {
|
||||
avatarURL: string,
|
||||
displayName: string,
|
||||
formattedDisplayName: string,
|
||||
isVideoDisplayed: boolean,
|
||||
visible: boolean
|
||||
};
|
||||
|
@ -42,6 +48,7 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
this.state = {
|
||||
avatarURL: '',
|
||||
displayName: '',
|
||||
formattedDisplayName: '',
|
||||
isVideoDisplayed: true,
|
||||
visible: true
|
||||
};
|
||||
|
@ -78,10 +85,15 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_displayNameChangedListener({ formattedDisplayName, id }) {
|
||||
_displayNameChangedListener({ displayname, formattedDisplayName, id }) {
|
||||
if (api._getOnStageParticipant() === id
|
||||
&& formattedDisplayName !== this.state.displayName) {
|
||||
this.setState({ displayName: formattedDisplayName });
|
||||
&& (formattedDisplayName !== this.state.formattedDisplayName
|
||||
|| displayname !== this.state.displayName)) {
|
||||
// I think the API has a typo using lowercase n for the displayname
|
||||
this.setState({
|
||||
displayName: displayname,
|
||||
formattedDisplayName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,12 +124,14 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
_largeVideoChangedListener() {
|
||||
const userID = api._getOnStageParticipant();
|
||||
const avatarURL = api.getAvatarURL(userID);
|
||||
const displayName = api._getFormattedDisplayName(userID);
|
||||
const displayName = api.getDisplayName(userID);
|
||||
const formattedDisplayName = api._getFormattedDisplayName(userID);
|
||||
const isVideoDisplayed = Boolean(api._getLargeVideo());
|
||||
|
||||
this.setState({
|
||||
avatarURL,
|
||||
displayName,
|
||||
formattedDisplayName,
|
||||
isVideoDisplayed
|
||||
});
|
||||
}
|
||||
|
@ -161,7 +175,7 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderVideoNotAvailableScreen() {
|
||||
const { avatarURL, displayName, isVideoDisplayed } = this.state;
|
||||
const { avatarURL, displayName, formattedDisplayName, isVideoDisplayed } = this.state;
|
||||
|
||||
if (isVideoDisplayed) {
|
||||
return null;
|
||||
|
@ -169,19 +183,16 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
|
||||
return (
|
||||
<div id = 'videoNotAvailableScreen'>
|
||||
{
|
||||
avatarURL
|
||||
? <div id = 'avatarContainer'>
|
||||
<img
|
||||
id = 'avatar'
|
||||
src = { avatarURL } />
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div id = 'avatarContainer'>
|
||||
<StatelessAvatar
|
||||
id = 'avatar'
|
||||
initials = { getInitials(displayName) }
|
||||
url = { avatarURL } />)
|
||||
</div>
|
||||
<div
|
||||
className = 'displayname'
|
||||
id = 'displayname'>
|
||||
{ displayName }
|
||||
{ formattedDisplayName }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Color of the (initials based) avatar, if needed.
|
||||
*/
|
||||
color?: string,
|
||||
|
||||
/**
|
||||
* Initials to be used to render the initials based avatars.
|
||||
*/
|
||||
initials?: string,
|
||||
|
||||
/**
|
||||
* Callback to signal the failure of the loading of the URL.
|
||||
*/
|
||||
onAvatarLoadError?: Function,
|
||||
|
||||
/**
|
||||
* Expected size of the avatar.
|
||||
*/
|
||||
size?: number;
|
||||
|
||||
/**
|
||||
* The URL of the avatar to render.
|
||||
*/
|
||||
url?: ?string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an abstract stateless avatar component that renders an avatar purely from what gets passed through
|
||||
* props.
|
||||
*/
|
||||
export default class AbstractStatelessAvatar<P: Props> extends PureComponent<P> {}
|
|
@ -1,11 +1,14 @@
|
|||
// @flow
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { getParticipantById } from '../../participants';
|
||||
import { connect } from '../../redux';
|
||||
|
||||
import { getAvatarColor, getInitials } from '../functions';
|
||||
|
||||
import { StatelessAvatar } from '.';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
|
@ -18,6 +21,11 @@ export type Props = {
|
|||
*/
|
||||
_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.
|
||||
|
@ -30,6 +38,11 @@ export type Props = {
|
|||
*/
|
||||
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).
|
||||
*/
|
||||
|
@ -41,9 +54,9 @@ export type Props = {
|
|||
size: number,
|
||||
|
||||
/**
|
||||
* URI of the avatar, if any.
|
||||
* URL of the avatar, if any.
|
||||
*/
|
||||
uri: ?string,
|
||||
url: ?string,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
@ -53,9 +66,9 @@ type State = {
|
|||
export const DEFAULT_SIZE = 65;
|
||||
|
||||
/**
|
||||
* Implements an abstract class to render avatars in the app.
|
||||
* Implements a class to render avatars in the app.
|
||||
*/
|
||||
export default class AbstractAvatar<P: Props> extends PureComponent<P, State> {
|
||||
class Avatar<P: Props> extends PureComponent<P, State> {
|
||||
/**
|
||||
* Instantiates a new {@code Component}.
|
||||
*
|
||||
|
@ -77,7 +90,7 @@ export default class AbstractAvatar<P: Props> extends PureComponent<P, State> {
|
|||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps: P) {
|
||||
if (prevProps.uri !== this.props.uri) {
|
||||
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
|
||||
|
@ -99,25 +112,45 @@ export default class AbstractAvatar<P: Props> extends PureComponent<P, State> {
|
|||
const {
|
||||
_initialsBase,
|
||||
_loadableAvatarUrl,
|
||||
className,
|
||||
colorBase,
|
||||
uri
|
||||
id,
|
||||
size,
|
||||
url
|
||||
} = this.props;
|
||||
const { avatarFailed } = this.state;
|
||||
|
||||
const avatarProps = {
|
||||
className,
|
||||
color: undefined,
|
||||
id,
|
||||
initials: undefined,
|
||||
onAvatarLoadError: undefined,
|
||||
size,
|
||||
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.
|
||||
if ((uri && !avatarFailed) || _loadableAvatarUrl) {
|
||||
return this._renderURLAvatar((!avatarFailed && uri) || _loadableAvatarUrl);
|
||||
const effectiveURL = (!avatarFailed && url) || _loadableAvatarUrl;
|
||||
|
||||
if (effectiveURL) {
|
||||
avatarProps.onAvatarLoadError = this._onAvatarLoadError;
|
||||
avatarProps.url = effectiveURL;
|
||||
} else {
|
||||
const initials = getInitials(_initialsBase);
|
||||
|
||||
if (initials) {
|
||||
avatarProps.color = getAvatarColor(colorBase || _initialsBase);
|
||||
avatarProps.initials = initials;
|
||||
}
|
||||
}
|
||||
|
||||
const _initials = getInitials(_initialsBase);
|
||||
|
||||
if (_initials) {
|
||||
return this._renderInitialsAvatar(_initials, getAvatarColor(colorBase || _initialsBase));
|
||||
}
|
||||
|
||||
return this._renderDefaultAvatar();
|
||||
return (
|
||||
<StatelessAvatar
|
||||
{ ...avatarProps } />
|
||||
);
|
||||
}
|
||||
|
||||
_onAvatarLoadError: () => void;
|
||||
|
@ -132,30 +165,6 @@ export default class AbstractAvatar<P: Props> extends PureComponent<P, State> {
|
|||
avatarFailed: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to render the actual, platform specific default avatar component.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderDefaultAvatar: () => React$Element<*>
|
||||
|
||||
/**
|
||||
* Function to render the actual, platform specific initials-based avatar component.
|
||||
*
|
||||
* @param {string} initials - The initials to use.
|
||||
* @param {string} color - The color to use.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderInitialsAvatar: (string, string) => React$Element<*>
|
||||
|
||||
/**
|
||||
* Function to render the actual, platform specific URL-based avatar component.
|
||||
*
|
||||
* @param {string} uri - The URI of the avatar.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderURLAvatar: ?string => React$Element<*>
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,10 +177,12 @@ export default class AbstractAvatar<P: Props> extends PureComponent<P, State> {
|
|||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||
const { displayName, participantId } = ownProps;
|
||||
const _participant = participantId && getParticipantById(state, participantId);
|
||||
const _initialsBase = (_participant && (_participant.name || _participant.email)) || displayName;
|
||||
const _initialsBase = (_participant && _participant.name) || displayName;
|
||||
|
||||
return {
|
||||
_initialsBase,
|
||||
_loadableAvatarUrl: _participant && _participant.loadableAvatarUrl
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(Avatar);
|
|
@ -1,3 +1,4 @@
|
|||
// @flow
|
||||
|
||||
export * from './native';
|
||||
export { default as Avatar } from './Avatar';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// @flow
|
||||
|
||||
export * from './web';
|
||||
export { default as Avatar } from './Avatar';
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
|
||||
import { connect } from '../../../redux';
|
||||
import { type StyleType } from '../../../styles';
|
||||
|
||||
import AbstractAvatar, {
|
||||
_mapStateToProps,
|
||||
type Props as AbstractProps,
|
||||
DEFAULT_SIZE
|
||||
} from '../AbstractAvatar';
|
||||
|
||||
import RemoteAvatar, { DEFAULT_AVATAR } from './RemoteAvatar';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* External style of the component.
|
||||
*/
|
||||
style?: StyleType
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an avatar component that has 4 ways to render an avatar:
|
||||
*
|
||||
* - Based on an explicit avatar URI, if provided
|
||||
* - Gravatar, if there is any
|
||||
* - Based on initials generated from name or email
|
||||
* - Default avatar icon, if any of the above fails
|
||||
*/
|
||||
class Avatar extends AbstractAvatar<Props> {
|
||||
|
||||
_onAvatarLoadError: () => void;
|
||||
|
||||
/**
|
||||
* Implements {@code AbstractAvatar#_renderDefaultAvatar}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderDefaultAvatar() {
|
||||
return this._wrapAvatar(
|
||||
<Image
|
||||
source = { DEFAULT_AVATAR }
|
||||
style = { [
|
||||
styles.avatarContent(this.props.size || DEFAULT_SIZE),
|
||||
styles.staticAvatar
|
||||
] } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code AbstractAvatar#_renderGravatar}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderInitialsAvatar(initials, color) {
|
||||
return this._wrapAvatar(
|
||||
<View
|
||||
style = { [
|
||||
styles.initialsContainer,
|
||||
{
|
||||
backgroundColor: color
|
||||
}
|
||||
] }>
|
||||
<Text style = { styles.initialsText(this.props.size || DEFAULT_SIZE) }> { initials } </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code AbstractAvatar#_renderGravatar}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderURLAvatar(uri) {
|
||||
return this._wrapAvatar(
|
||||
<RemoteAvatar
|
||||
onError = { this._onAvatarLoadError }
|
||||
size = { this.props.size || DEFAULT_SIZE }
|
||||
uri = { uri } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an avatar into a common wrapper.
|
||||
*
|
||||
* @param {React#Component} avatar - The avatar component.
|
||||
* @returns {React#Component}
|
||||
*/
|
||||
_wrapAvatar(avatar) {
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.avatarContainer(this.props.size || DEFAULT_SIZE),
|
||||
this.props.style
|
||||
] }>
|
||||
{ avatar }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(Avatar);
|
|
@ -1,50 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Image } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
export const DEFAULT_AVATAR = require('../../../../../../images/avatar.png');
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback for load errors.
|
||||
*/
|
||||
onError: Function,
|
||||
|
||||
/**
|
||||
* Size of the avatar.
|
||||
*/
|
||||
size: number,
|
||||
|
||||
/**
|
||||
* URI of the avatar to load.
|
||||
*/
|
||||
uri: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a private class that is used to fetch and render remote avatars based on an URI.
|
||||
*/
|
||||
export default class RemoteAvatar extends PureComponent<Props> {
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { onError, size, uri } = this.props;
|
||||
|
||||
return (
|
||||
<Image
|
||||
defaultSource = { DEFAULT_AVATAR }
|
||||
onError = { onError }
|
||||
resizeMode = 'cover'
|
||||
source = {{ uri }}
|
||||
style = { styles.avatarContent(size) } />
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
|
||||
import { type StyleType } from '../../../styles';
|
||||
|
||||
import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* External style passed to the componant.
|
||||
*/
|
||||
style?: StyleType
|
||||
};
|
||||
|
||||
const DEFAULT_AVATAR = require('../../../../../../images/avatar.png');
|
||||
|
||||
/**
|
||||
* Implements a stateless avatar component that renders an avatar purely from what gets passed through
|
||||
* props.
|
||||
*/
|
||||
export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { initials, size, style, url } = this.props;
|
||||
|
||||
let avatar;
|
||||
|
||||
if (url) {
|
||||
avatar = this._renderURLAvatar();
|
||||
} else if (initials) {
|
||||
avatar = this._renderInitialsAvatar();
|
||||
} else {
|
||||
avatar = this._renderDefaultAvatar();
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.avatarContainer(size),
|
||||
style
|
||||
] }>
|
||||
{ avatar }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the default avatar.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderDefaultAvatar() {
|
||||
const { size } = this.props;
|
||||
|
||||
return (
|
||||
<Image
|
||||
source = { DEFAULT_AVATAR }
|
||||
style = { [
|
||||
styles.avatarContent(size),
|
||||
styles.staticAvatar
|
||||
] } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the initials-based avatar.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderInitialsAvatar() {
|
||||
const { color, initials, size } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.initialsContainer,
|
||||
{
|
||||
backgroundColor: color
|
||||
}
|
||||
] }>
|
||||
<Text style = { styles.initialsText(size) }> { initials } </Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the url-based avatar.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderURLAvatar() {
|
||||
const { onAvatarLoadError, size, url } = this.props;
|
||||
|
||||
return (
|
||||
<Image
|
||||
defaultSource = { DEFAULT_AVATAR }
|
||||
onError = { onAvatarLoadError }
|
||||
resizeMode = 'cover'
|
||||
source = {{ uri: url }}
|
||||
style = { styles.avatarContent(size) } />
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export { default as Avatar } from './Avatar';
|
||||
export { default as StatelessAvatar } from './StatelessAvatar';
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
import { ColorPalette } from '../../../styles';
|
||||
|
||||
const DEFAULT_SIZE = 65;
|
||||
|
||||
/**
|
||||
* The styles of the feature base/participants.
|
||||
*/
|
||||
export default {
|
||||
|
||||
avatarContainer: (size: number) => {
|
||||
avatarContainer: (size: number = DEFAULT_SIZE) => {
|
||||
return {
|
||||
alignItems: 'center',
|
||||
borderRadius: size / 2,
|
||||
|
@ -18,7 +20,7 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
avatarContent: (size: number) => {
|
||||
avatarContent: (size: number = DEFAULT_SIZE) => {
|
||||
return {
|
||||
height: size,
|
||||
width: size
|
||||
|
@ -32,7 +34,7 @@ export default {
|
|||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
initialsText: (size: number) => {
|
||||
initialsText: (size: number = DEFAULT_SIZE) => {
|
||||
return {
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
fontSize: size * 0.5,
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from '../../../redux';
|
||||
|
||||
import AbstractAvatar, {
|
||||
_mapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractAvatar';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
className?: string,
|
||||
id: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an avatar as a React/Web {@link Component}.
|
||||
*/
|
||||
class Avatar extends AbstractAvatar<Props> {
|
||||
/**
|
||||
* Constructs a style object to be used on the avatars.
|
||||
*
|
||||
* @param {string?} color - The desired background color.
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getAvatarStyle(color) {
|
||||
const { size } = this.props;
|
||||
|
||||
return {
|
||||
backgroundColor: color || undefined,
|
||||
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}
|
||||
*/
|
||||
_getAvatarClassName(additional) {
|
||||
return `avatar ${additional || ''} ${this.props.className || ''}`;
|
||||
}
|
||||
|
||||
_onAvatarLoadError: () => void;
|
||||
|
||||
/**
|
||||
* Implements {@code AbstractAvatar#_renderDefaultAvatar}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderDefaultAvatar() {
|
||||
return (
|
||||
<img
|
||||
className = { this._getAvatarClassName('defaultAvatar') }
|
||||
id = { this.props.id }
|
||||
src = 'images/avatar.png'
|
||||
style = { this._getAvatarStyle() } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code AbstractAvatar#_renderGravatar}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderInitialsAvatar(initials, color) {
|
||||
return (
|
||||
<div
|
||||
className = { this._getAvatarClassName() }
|
||||
id = { this.props.id }
|
||||
style = { this._getAvatarStyle(color) }>
|
||||
{ initials }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code AbstractAvatar#_renderGravatar}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderURLAvatar(uri) {
|
||||
return (
|
||||
<img
|
||||
className = { this._getAvatarClassName() }
|
||||
id = { this.props.id }
|
||||
onError = { this._onAvatarLoadError }
|
||||
src = { uri }
|
||||
style = { this._getAvatarStyle() } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(Avatar);
|
|
@ -0,0 +1,96 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* External class name passed through props.
|
||||
*/
|
||||
className?: string,
|
||||
|
||||
/**
|
||||
* The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop)
|
||||
*/
|
||||
defaultAvatar?: string,
|
||||
|
||||
/**
|
||||
* ID of the component to be rendered.
|
||||
*/
|
||||
id?: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a stateless avatar component that renders an avatar purely from what gets passed through
|
||||
* props.
|
||||
*/
|
||||
export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { initials, url } = this.props;
|
||||
|
||||
if (url) {
|
||||
return (
|
||||
<img
|
||||
className = { this._getAvatarClassName() }
|
||||
id = { this.props.id }
|
||||
onError = { this.props.onAvatarLoadError }
|
||||
src = { url }
|
||||
style = { this._getAvatarStyle() } />
|
||||
);
|
||||
}
|
||||
|
||||
if (initials) {
|
||||
return (
|
||||
<div
|
||||
className = { this._getAvatarClassName() }
|
||||
id = { this.props.id }
|
||||
style = { this._getAvatarStyle(this.props.color) }>
|
||||
{ initials }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// default avatar
|
||||
return (
|
||||
<img
|
||||
className = { this._getAvatarClassName('defaultAvatar') }
|
||||
id = { this.props.id }
|
||||
src = { this.props.defaultAvatar || 'images/avatar.png' }
|
||||
style = { this._getAvatarStyle() } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a style object to be used on the avatars.
|
||||
*
|
||||
* @param {string?} color - The desired background color.
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getAvatarStyle(color) {
|
||||
const { size } = this.props;
|
||||
|
||||
return {
|
||||
backgroundColor: color || undefined,
|
||||
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}
|
||||
*/
|
||||
_getAvatarClassName(additional) {
|
||||
return `avatar ${additional || ''} ${this.props.className || ''}`;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export { default as Avatar } from './Avatar';
|
||||
export { default as StatelessAvatar } from './StatelessAvatar';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// @flow
|
||||
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
|
|
@ -91,7 +91,7 @@ export default class AvatarListItem extends Component<Props> {
|
|||
displayName = { title }
|
||||
size = { avatarSize }
|
||||
style = { avatarStyle }
|
||||
uri = { avatar } />
|
||||
url = { avatar } />
|
||||
<Container style = { styles.listItemDetails }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
|
|
|
@ -18,6 +18,25 @@ export function createDeferred(): Object {
|
|||
return deferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base URL of the app.
|
||||
*
|
||||
* @param {Object} w - Window object to use instead of the built in one.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getBaseUrl(w: Object = window) {
|
||||
const doc = w.document;
|
||||
const base = doc.querySelector('base');
|
||||
|
||||
if (base && base.href) {
|
||||
return base.href;
|
||||
}
|
||||
|
||||
const { protocol, host } = w.location;
|
||||
|
||||
return `${protocol}//${host}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace for all global variables, functions, etc that we need.
|
||||
*
|
||||
|
|
|
@ -10,11 +10,11 @@ import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
|
|||
import {
|
||||
PARTICIPANT_KICKED,
|
||||
SET_LOADABLE_AVATAR_URL,
|
||||
getAvatarURLByParticipantId,
|
||||
getLocalParticipant,
|
||||
getParticipantById
|
||||
} from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { getBaseUrl } from '../base/util';
|
||||
import { appendSuffix } from '../display-name';
|
||||
import { SUBMIT_FEEDBACK } from '../feedback';
|
||||
import { SET_FILMSTRIP_VISIBLE } from '../filmstrip';
|
||||
|
@ -39,11 +39,26 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
const result = next(action);
|
||||
|
||||
if (participant && (participant.loadableAvatarUrl !== loadableAvatarUrl)) {
|
||||
APP.API.notifyAvatarChanged(
|
||||
id,
|
||||
loadableAvatarUrl
|
||||
);
|
||||
if (participant) {
|
||||
if (loadableAvatarUrl) {
|
||||
participant.loadableAvatarUrl !== loadableAvatarUrl && APP.API.notifyAvatarChanged(
|
||||
id,
|
||||
loadableAvatarUrl
|
||||
);
|
||||
} else {
|
||||
// There is no loadable explicit URL. In this case the Avatar component would
|
||||
// decide to render initials or the default avatar, but the external API needs
|
||||
// a URL when it needs to be rendered, so if there is no initials, we return the default
|
||||
// Avatar URL as if it was a usual avatar URL. If there are (or may be) initials
|
||||
// we send undefined to signal the api user that it's not an URL that needs to be rendered.
|
||||
//
|
||||
// NOTE: we may implement a special URL format later to signal that the avatar is based
|
||||
// on initials, that API consumers can handle as they want, e.g. initials://jm
|
||||
APP.API.notifyAvatarChanged(
|
||||
id,
|
||||
participant.name ? undefined : _getDefaultAvatarUrl()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -65,7 +80,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
case CONFERENCE_JOINED: {
|
||||
const state = store.getState();
|
||||
const { room } = state['features/base/conference'];
|
||||
const { name, id } = getLocalParticipant(state);
|
||||
const { loadableAvatarUrl, name, id } = getLocalParticipant(state);
|
||||
|
||||
APP.API.notifyConferenceJoined(
|
||||
room,
|
||||
|
@ -76,7 +91,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
name,
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME
|
||||
),
|
||||
avatarURL: getAvatarURLByParticipantId(state, id)
|
||||
avatarURL: loadableAvatarUrl
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
@ -125,3 +140,12 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the absolute URL of the default avatar.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getDefaultAvatarUrl() {
|
||||
return new URL('images/avatar.png', getBaseUrl()).href;
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ class IncomingCallPage extends Component<Props> {
|
|||
<View style = { styles.avatar }>
|
||||
<Avatar
|
||||
size = { CALLER_AVATAR_SIZE }
|
||||
uri = { this.props._callerAvatarURL } />
|
||||
url = { this.props._callerAvatarURL } />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue