Skip to content

Commit

Permalink
feat(branding): Add ability to customize logo & background
Browse files Browse the repository at this point in the history
  • Loading branch information
vp8x8 authored and saghul committed Jun 10, 2020
1 parent 29dc63f commit 8758c22
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 53 deletions.
17 changes: 17 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,23 @@ var config = {
// If set to true all muting operations of remote participants will be disabled.
// disableRemoteMute: true,

/**
External API url used to receive branding specific information.
If there is no url set or there are missing fields, the defaults are applied.
None of the fieds are mandatory and the response must have the shape:
{
// The hex value for the colour used as background
backgroundColor: '#fff',
// The url for the image used as background
backgroundImageUrl: 'https://example.com/background-img.png',
// The anchor url used when clicking the logo image
logoClickUrl: 'https://example-company.org',
// The url used for the image used as logo
logoImageUrl: 'https://example.com/logo-img.png'
}
*/
// brandingDataUrl: '',

// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
Expand Down
3 changes: 2 additions & 1 deletion css/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ form {
.leftwatermark {
left: 32px;
top: 32px;
background-image: url($defaultWatermarkLink);
background-position: center left;
background-repeat: no-repeat;
background-size: contain;
}

.rightwatermark {
Expand Down
2 changes: 0 additions & 2 deletions css/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ $sidebarWidth: 375px;
* Misc.
*/
$borderRadius: 4px;
$defaultWatermarkLink: '../images/watermark.png';
$popoverMenuPadding: 13px;
$happySoftwareBackground: transparent;
$desktopAppDragBarHeight: 25px;
Expand Down Expand Up @@ -270,4 +269,3 @@ $chromeExtensionBannerTop: 80px;
$chromeExtensionBannerRight: 16px;
$chromeExtensionBannerTopInMeeting: 10px;
$chromeExtensionBannerRightInMeeeting: 10px;

3 changes: 0 additions & 3 deletions css/filmstrip/_tile_view_overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@
#remotePresenceMessage {
display: none !important;
}
#largeVideoContainer {
background-color: $defaultBackground !important;
}

/**
* Thumbnail popover menus can overlap other thumbnails. Setting an auto
Expand Down
3 changes: 1 addition & 2 deletions interface_config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable no-unused-vars, no-var, max-len */

var interfaceConfig = {
// TO FIX: this needs to be handled from SASS variables. There are some
// methods allowing to use variables both in css and js.
DEFAULT_BACKGROUND: '#474747',
DEFAULT_LOGO_URL: '../images/watermark.png',

/**
* Whether or not the blurred video background for large video should be
Expand Down
26 changes: 0 additions & 26 deletions modules/UI/videolayout/VideoContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,6 @@ export class VideoContainer extends LargeContainer {
});

this._updateBackground();

// Reset the large video background depending on the stream.
this.setLargeVideoBackground(this.avatarDisplayed);
}

/**
Expand Down Expand Up @@ -533,14 +530,6 @@ export class VideoContainer extends LargeContainer {
* @param {boolean} show
*/
showAvatar(show) {
// TO FIX: Video background need to be black, so that we don't have a
// flickering effect when scrolling between videos and have the screen
// move to grey before going back to video. Avatars though can have the
// default background set.
// In order to fix this code we need to introduce video background or
// find a workaround for the video flickering.
this.setLargeVideoBackground(show);

this.$avatar.css('visibility', show ? 'visible' : 'hidden');
this.avatarDisplayed = show;

Expand Down Expand Up @@ -596,21 +585,6 @@ export class VideoContainer extends LargeContainer {
return false;
}

/**
* Sets the large video container background depending on the container
* type and the parameter indicating if an avatar is currently shown on
* large.
*
* @param {boolean} isAvatar - Indicates if the avatar is currently shown
* on the large video.
* @returns {void}
*/
setLargeVideoBackground(isAvatar) {
$('#largeVideoContainer').css('background',
this.videoType === VIDEO_CONTAINER_TYPE && !isAvatar
? '#000' : interfaceConfig.DEFAULT_BACKGROUND);
}

/**
* Callback invoked when the video element changes dimensions.
*
Expand Down
1 change: 1 addition & 0 deletions react/features/base/config/interfaceConfigWhitelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default [
'CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT',
'CONNECTION_INDICATOR_DISABLED',
'DEFAULT_BACKGROUND',
'DEFAULT_LOGO_URL',
'DISABLE_PRESENCE_STATUS',
'DISABLE_JOIN_LEAVE_NOTIFICATIONS',
'DEFAULT_LOCAL_DISPLAY_NAME',
Expand Down
75 changes: 61 additions & 14 deletions react/features/base/react/components/web/Watermarks.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,27 @@ const _RIGHT_WATERMARK_STYLE = {
*/
type Props = {

/**
* The user selected url used to navigate to on logo click.
*/
_customLogoLink: string,

/**
* The url of the user selected logo.
*/
_customLogoUrl: string,

/**
* Whether or not the current user is logged in through a JWT.
*/
_isGuest: boolean,

/**
* Flag used to signal that the logo can be displayed.
* It becomes true after the user customization options are fetched.
*/
_readyToDisplayJitsiWatermark: boolean,

/**
* Invoked to obtain translated strings.
*/
Expand Down Expand Up @@ -133,6 +149,26 @@ class Watermarks extends Component<Props, State> {
);
}

/**
* Returns true if the watermark is ready to be displayed.
*
* @private
* @returns {boolean}
*/
_canDisplayJitsiWatermark() {
const {
showJitsiWatermark,
showJitsiWatermarkForGuests
} = this.state;
const {
_isGuest,
_readyToDisplayJitsiWatermark
} = this.props;

return _readyToDisplayJitsiWatermark
&& (showJitsiWatermark || (_isGuest && showJitsiWatermarkForGuests));
}

/**
* Renders a brand watermark if it is enabled.
*
Expand Down Expand Up @@ -173,18 +209,27 @@ class Watermarks extends Component<Props, State> {
*/
_renderJitsiWatermark() {
let reactElement = null;

if (this.state.showJitsiWatermark
|| (this.props._isGuest
&& this.state.showJitsiWatermarkForGuests)) {
reactElement = <div className = 'watermark leftwatermark' />;

const { jitsiWatermarkLink } = this.state;

if (jitsiWatermarkLink) {
const {
_customLogoUrl,
_customLogoLink
} = this.props;

if (this._canDisplayJitsiWatermark()) {
const link = _customLogoLink || this.state.jitsiWatermarkLink;
const style = {
backgroundImage: `url(${_customLogoUrl || interfaceConfig.DEFAULT_LOGO_URL})`,
maxWidth: 140,
maxHeight: 70
};

reactElement = (<div
className = 'watermark leftwatermark'
style = { style } />);

if (link) {
reactElement = (
<a
href = { jitsiWatermarkLink }
href = { link }
target = '_new'>
{ reactElement }
</a>
Expand Down Expand Up @@ -223,12 +268,11 @@ class Watermarks extends Component<Props, State> {
* Maps parts of Redux store to component prop types.
*
* @param {Object} state - Snapshot of Redux store.
* @returns {{
* _isGuest: boolean
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const { isGuest } = state['features/base/jwt'];
const { customizationReady, logoClickUrl, logoImageUrl } = state['features/dynamic-branding'];

return {
/**
Expand All @@ -238,7 +282,10 @@ function _mapStateToProps(state) {
* @private
* @type {boolean}
*/
_isGuest: isGuest
_customLogoLink: logoClickUrl,
_customLogoUrl: logoImageUrl,
_isGuest: isGuest,
_readyToDisplayJitsiWatermark: customizationReady
};
}

Expand Down
9 changes: 9 additions & 0 deletions react/features/dynamic-branding/actionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Action used to set custom user properties.
*/
export const SET_DYNAMIC_BRANDING_DATA = 'SET_DYNAMIC_BRANDING_DATA';

/**
* Action used to signal the branding elements are ready to be displayed
*/
export const SET_DYNAMIC_BRANDING_READY = 'SET_DYNAMIC_BRANDING_READY';
66 changes: 66 additions & 0 deletions react/features/dynamic-branding/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// @flow

import { getLogger } from 'jitsi-meet-logger';

import { doGetJSON } from '../base/util';

import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';
import { extractFqnFromPath } from './functions';

const logger = getLogger(__filename);

/**
* Fetches custom branding data.
* If there is no data or the request fails, sets the `customizationReady` flag
* so the defaults can be displayed.
*
* @returns {Function}
*/
export function fetchCustomBrandingData() {
return async function(dispatch: Function, getState: Function) {
const state = getState();
const baseUrl = state['features/base/config'].brandingDataUrl;
const { customizationReady } = state['features/dynamic-branding'];

if (!customizationReady) {
const fqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);

if (baseUrl && fqn) {
try {
const res = await doGetJSON(`${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`);

return dispatch(setDynamicBrandingData(res));
} catch (err) {
logger.error('Error fetching branding data', err);
}
}

dispatch(setDynamicBrandingReady());
}
};
}

/**
* Action used to set the user customizations.
*
* @param {Object} value - The custom data to be set.
* @returns {Object}
*/
function setDynamicBrandingData(value) {
return {
type: SET_DYNAMIC_BRANDING_DATA,
value
};
}


/**
* Action used to signal the branding elements are ready to be displayed.
*
* @returns {Object}
*/
function setDynamicBrandingReady() {
return {
type: SET_DYNAMIC_BRANDING_READY
};
}
15 changes: 15 additions & 0 deletions react/features/dynamic-branding/functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @flow

/**
* Extracts the fqn part from a path, where fqn represents
* tenant/roomName.
*
* @param {string} path - The URL path.
* @returns {string}
*/
export function extractFqnFromPath(path: string) {
const parts = path.split('/');
const len = parts.length;

return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : '';
}
4 changes: 4 additions & 0 deletions react/features/dynamic-branding/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './actions';
export * from './functions';

import './reducer';
46 changes: 46 additions & 0 deletions react/features/dynamic-branding/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// @flow

import { ReducerRegistry } from '../base/redux';

import { SET_DYNAMIC_BRANDING_DATA, SET_DYNAMIC_BRANDING_READY } from './actionTypes';

/**
* The name of the redux store/state property which is the root of the redux
* state of the feature {@code dynamic-branding}.
*/
const STORE_NAME = 'features/dynamic-branding';

const DEFAULT_STATE = {
backgroundColor: '',
backgroundImageUrl: '',
customizationReady: false,
logoClickUrl: '',
logoImageUrl: ''
};

/**
* Reduces redux actions for the purposes of the feature {@code dynamic-branding}.
*/
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_DYNAMIC_BRANDING_DATA: {
const { backgroundColor, backgroundImageUrl, logoClickUrl, logoImageUrl } = action.value;

return {
backgroundColor,
backgroundImageUrl,
logoClickUrl,
logoImageUrl,
customizationReady: true
};
}
case SET_DYNAMIC_BRANDING_READY:
return {
...state,
customizationReady: true
};

}

return state;
});
Loading

0 comments on commit 8758c22

Please sign in to comment.