diff --git a/modules/API/API.js b/modules/API/API.js index 4c4fcbb01e..fb9d2dc6bc 100644 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -574,6 +574,12 @@ function initCommands() { }); break; } + case 'get-custom-avatar-backgrounds' : { + callback({ + avatarBackgrounds: APP.store.getState()['features/dynamic-branding'].avatarBackgrounds + }); + break; + } default: return false; } diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index 1cd3884ae5..f43fc40805 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -769,6 +769,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter { return getCurrentDevices(this._transport); } + /** + * Returns any custom avatars backgrounds. + * + * @returns {Promise} - Resolves with the list of custom avatar backgrounds. + */ + getCustomAvatarBackgrounds() { + return this._transport.sendRequest({ + name: 'get-custom-avatar-backgrounds' + }); + } + /** * Returns the current livestream url. * diff --git a/react/features/always-on-top/AlwaysOnTop.js b/react/features/always-on-top/AlwaysOnTop.js index 875b2805ba..6fd843b370 100644 --- a/react/features/always-on-top/AlwaysOnTop.js +++ b/react/features/always-on-top/AlwaysOnTop.js @@ -21,6 +21,7 @@ const TOOLBAR_TIMEOUT = 4000; */ type State = { avatarURL: string, + customAvatarBackgrounds: Array, displayName: string, formattedDisplayName: string, isVideoDisplayed: boolean, @@ -48,6 +49,7 @@ export default class AlwaysOnTop extends Component<*, State> { this.state = { avatarURL: '', + customAvatarBackgrounds: [], displayName: '', formattedDisplayName: '', isVideoDisplayed: true, @@ -178,7 +180,14 @@ export default class AlwaysOnTop extends Component<*, State> { * @returns {ReactElement} */ _renderVideoNotAvailableScreen() { - const { avatarURL, displayName, formattedDisplayName, isVideoDisplayed, userID } = this.state; + const { + avatarURL, + customAvatarBackgrounds, + displayName, + formattedDisplayName, + isVideoDisplayed, + userID + } = this.state; if (isVideoDisplayed) { return null; @@ -188,7 +197,7 @@ export default class AlwaysOnTop extends Component<*, State> {
) @@ -218,6 +227,12 @@ export default class AlwaysOnTop extends Component<*, State> { window.addEventListener('mousemove', this._mouseMove); this._hideToolbarAfterTimeout(); + api.getCustomAvatarBackgrounds() + .then(res => + this.setState({ + customAvatarBackgrounds: res.avatarBackgrounds || [] + })) + .catch(console.error); } /** diff --git a/react/features/base/avatar/components/Avatar.js b/react/features/base/avatar/components/Avatar.js index 4f739ef062..fadf551532 100644 --- a/react/features/base/avatar/components/Avatar.js +++ b/react/features/base/avatar/components/Avatar.js @@ -10,6 +10,11 @@ import { StatelessAvatar } from '.'; export type Props = { + /** + * Custom avatar backgrounds from branding. + */ + _customAvatarBackgrounds: Array, + /** * The string we base the initials on (this is generated from a list of precedences). */ @@ -133,6 +138,7 @@ class Avatar extends PureComponent { */ render() { const { + _customAvatarBackgrounds, _initialsBase, _loadableAvatarUrl, className, @@ -172,7 +178,7 @@ class Avatar extends PureComponent { if (initials) { if (dynamicColor) { - avatarProps.color = getAvatarColor(colorBase || _initialsBase); + avatarProps.color = getAvatarColor(colorBase || _initialsBase, _customAvatarBackgrounds); } avatarProps.initials = initials; @@ -211,6 +217,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) { const _initialsBase = _participant?.name ?? displayName; return { + _customAvatarBackgrounds: state['features/dynamic-branding'].avatarBackgrounds, _initialsBase, _loadableAvatarUrl: _participant?.loadableAvatarUrl, colorBase: !colorBase && _participant ? _participant.id : colorBase diff --git a/react/features/base/avatar/components/web/StatelessAvatar.js b/react/features/base/avatar/components/web/StatelessAvatar.js index fd9309f0fa..6e509c566c 100644 --- a/react/features/base/avatar/components/web/StatelessAvatar.js +++ b/react/features/base/avatar/components/web/StatelessAvatar.js @@ -125,7 +125,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar { const { size } = this.props; return { - backgroundColor: color || undefined, + background: color || undefined, fontSize: size ? size * 0.5 : '180%', height: size || '100%', width: size || '100%' diff --git a/react/features/base/avatar/functions.js b/react/features/base/avatar/functions.js index 5cf687964d..e8d3f16ba2 100644 --- a/react/features/base/avatar/functions.js +++ b/react/features/base/avatar/functions.js @@ -16,9 +16,13 @@ const AVATAR_OPACITY = 0.4; * Generates the background color of an initials based avatar. * * @param {string?} initials - The initials of the avatar. + * @param {Array} customAvatarBackgrounds - Custom avatar background values. * @returns {string} */ -export function getAvatarColor(initials: ?string) { +export function getAvatarColor(initials: ?string, customAvatarBackgrounds: Array) { + const hasCustomAvatarBackgronds = customAvatarBackgrounds && customAvatarBackgrounds.length; + const colorsBase = hasCustomAvatarBackgronds ? customAvatarBackgrounds : AVATAR_COLORS; + let colorIndex = 0; if (initials) { @@ -28,10 +32,10 @@ export function getAvatarColor(initials: ?string) { nameHash += s.codePointAt(0); } - colorIndex = nameHash % AVATAR_COLORS.length; + colorIndex = nameHash % colorsBase.length; } - return `rgba(${AVATAR_COLORS[colorIndex]}, ${AVATAR_OPACITY})`; + return hasCustomAvatarBackgronds ? colorsBase[colorIndex] : `rgba(${colorsBase[colorIndex]}, ${AVATAR_OPACITY})`; } /** diff --git a/react/features/dynamic-branding/reducer.js b/react/features/dynamic-branding/reducer.js index 3921ecdf2b..802e045007 100644 --- a/react/features/dynamic-branding/reducer.js +++ b/react/features/dynamic-branding/reducer.js @@ -15,6 +15,15 @@ import { const STORE_NAME = 'features/dynamic-branding'; const DEFAULT_STATE = { + + /** + * The pool of avatar backgrounds. + * + * @public + * @type {Array} + */ + avatarBackgrounds: [], + /** * The custom background color for the LargeVideo. * @@ -112,10 +121,12 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => { didPageUrl, inviteDomain, logoClickUrl, - logoImageUrl + logoImageUrl, + avatarBackgrounds } = action.value; return { + avatarBackgrounds, backgroundColor, backgroundImageUrl, defaultBranding,