diff --git a/react/features/always-on-top/AlwaysOnTop.js b/react/features/always-on-top/AlwaysOnTop.js index dffccbcf90..31c74d9a10 100644 --- a/react/features/always-on-top/AlwaysOnTop.js +++ b/react/features/always-on-top/AlwaysOnTop.js @@ -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 (
{}
diff --git a/react/features/base/avatar/components/AbstractAvatar.js b/react/features/base/avatar/components/Avatar.js
similarity index 68%
rename from react/features/base/avatar/components/AbstractAvatar.js
rename to react/features/base/avatar/components/Avatar.js
index 5dede94bcd..521b433e47 100644
--- a/react/features/base/avatar/components/AbstractAvatar.js
+++ b/react/features/base/avatar/components/Avatar.js
@@ -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 {
+class Avatar {
/**
* Instantiates a new {@code Component}.
*
@@ -77,7 +90,7 @@ export default class AbstractAvatar {
* @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 {
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 (
+ {
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 {
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);
diff --git a/react/features/base/avatar/components/index.native.js b/react/features/base/avatar/components/index.native.js
index a32ec60612..62d202f325 100644
--- a/react/features/base/avatar/components/index.native.js
+++ b/react/features/base/avatar/components/index.native.js
@@ -1,3 +1,4 @@
// @flow
export * from './native';
+export { default as Avatar } from './Avatar';
diff --git a/react/features/base/avatar/components/index.web.js b/react/features/base/avatar/components/index.web.js
index 40d5f46528..bdc40ea0a5 100644
--- a/react/features/base/avatar/components/index.web.js
+++ b/react/features/base/avatar/components/index.web.js
@@ -1,3 +1,4 @@
// @flow
export * from './web';
+export { default as Avatar } from './Avatar';
diff --git a/react/features/base/avatar/components/native/Avatar.js b/react/features/base/avatar/components/native/Avatar.js
deleted file mode 100644
index d3f02fd2df..0000000000
--- a/react/features/base/avatar/components/native/Avatar.js
+++ /dev/null
@@ -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
- );
- }
-
- /**
- * Implements {@code AbstractAvatar#_renderGravatar}.
- *
- * @inheritdoc
- */
- _renderInitialsAvatar(initials, color) {
- return (
-
- );
- }
-}
-
-export default connect(_mapStateToProps)(Avatar);
diff --git a/react/features/base/avatar/components/web/StatelessAvatar.js b/react/features/base/avatar/components/web/StatelessAvatar.js
new file mode 100644
index 0000000000..63f06d4b42
--- /dev/null
+++ b/react/features/base/avatar/components/web/StatelessAvatar.js
@@ -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
+ );
+ }
+
+ if (initials) {
+ return (
+
+ );
+ }
+
+ /**
+ * 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 || ''}`;
+ }
+}
diff --git a/react/features/base/avatar/components/web/index.js b/react/features/base/avatar/components/web/index.js
index 2fda91a5d7..82ca95fd84 100644
--- a/react/features/base/avatar/components/web/index.js
+++ b/react/features/base/avatar/components/web/index.js
@@ -1,3 +1,3 @@
// @flow
-export { default as Avatar } from './Avatar';
+export { default as StatelessAvatar } from './StatelessAvatar';
diff --git a/react/features/base/avatar/index.js b/react/features/base/avatar/index.js
index 68ddef9ba1..359d82e99d 100644
--- a/react/features/base/avatar/index.js
+++ b/react/features/base/avatar/index.js
@@ -1,3 +1,4 @@
// @flow
export * from './components';
+export * from './functions';
diff --git a/react/features/base/react/components/native/AvatarListItem.js b/react/features/base/react/components/native/AvatarListItem.js
index 64d1e0e113..2b0d458b32 100644
--- a/react/features/base/react/components/native/AvatarListItem.js
+++ b/react/features/base/react/components/native/AvatarListItem.js
@@ -91,7 +91,7 @@ export default class AvatarListItem extends Component