Implements a filmstrip-only mode for the toolbox

This commit is contained in:
yanas 2017-04-06 13:43:36 -05:00 committed by Lyubo Marinov
parent 031f2dfeb8
commit 77b789e26a
9 changed files with 123 additions and 36 deletions

View File

@ -186,6 +186,28 @@
} }
} }
/**
* Styles the toolbar in filmstrip-only mode.
*/
&_filmstrip-only {
border-radius: 3px;
bottom: 0;
display: inline-block;
height: auto;
position: absolute;
right: 0;
width: $defaultToolbarSize;
.button:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.button:last-child {
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
}
/** /**
* Toolbar specific round badge. * Toolbar specific round badge.
*/ */

View File

@ -233,22 +233,23 @@ export function showSIPCallButton(show: boolean): Function {
*/ */
export function showToolbox(timeout: number = 0): Object { export function showToolbox(timeout: number = 0): Object {
return (dispatch: Dispatch<*>, getState: Function) => { return (dispatch: Dispatch<*>, getState: Function) => {
if (interfaceConfig.filmStripOnly) {
return;
}
const state = getState(); const state = getState();
const { timeoutMS, visible } = state['features/toolbox']; const { alwaysVisible, timeoutMS, visible } = state['features/toolbox'];
if (!visible) { if (!visible) {
dispatch(setToolboxVisible(true)); dispatch(setToolboxVisible(true));
dispatch(setSubjectSlideIn(true)); dispatch(setSubjectSlideIn(true));
// If the Toolbox is always visible, there's no need for a timeout
// to toggle its visibility.
if (!alwaysVisible) {
dispatch( dispatch(
setToolboxTimeout( setToolboxTimeout(
() => dispatch(hideToolbox()), () => dispatch(hideToolbox()),
timeout || timeoutMS)); timeout || timeoutMS));
dispatch(setToolboxTimeoutMS(interfaceConfig.TOOLBAR_TIMEOUT)); dispatch(setToolboxTimeoutMS(interfaceConfig.TOOLBAR_TIMEOUT));
} }
}
}; };
} }

View File

@ -101,9 +101,17 @@ class PrimaryToolbar extends Component {
* *
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render(): ReactElement<*> | null {
const { buttonHandlers, splitterIndex } = this.state;
const { _primaryToolbarButtons } = this.props; const { _primaryToolbarButtons } = this.props;
// The number of buttons to show in the toolbar isn't fixed, it depends
// on availability of features and configuration parameters, so if we
// don't have anything to render we exit here.
if (_primaryToolbarButtons.size === 0) {
return null;
}
const { buttonHandlers, splitterIndex } = this.state;
const { primaryToolbarClassName } = getToolbarClassNames(this.props); const { primaryToolbarClassName } = getToolbarClassNames(this.props);
return ( return (

View File

@ -150,9 +150,17 @@ class SecondaryToolbar extends Component {
* *
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render(): ReactElement<*> { render(): ReactElement<*> | null {
const { buttonHandlers } = this.state;
const { _secondaryToolbarButtons } = this.props; const { _secondaryToolbarButtons } = this.props;
// The number of buttons to show in the toolbar isn't fixed, it depends
// on availability of features and configuration parameters, so if we
// don't have anything to render we exit here.
if (_secondaryToolbarButtons.size === 0) {
return null;
}
const { buttonHandlers } = this.state;
const { secondaryToolbarClassName } = getToolbarClassNames(this.props); const { secondaryToolbarClassName } = getToolbarClassNames(this.props);
return ( return (

View File

@ -8,10 +8,6 @@ import {
} from '../actions'; } from '../actions';
import ToolbarButton from './ToolbarButton'; import ToolbarButton from './ToolbarButton';
declare var APP: Object;
declare var config: Object;
declare var interfaceConfig: Object;
/** /**
* Implements a toolbar in React/Web. It is a strip that contains a set of * Implements a toolbar in React/Web. It is a strip that contains a set of
* toolbar items such as buttons. Toolbar is commonly placed inside of a * toolbar items such as buttons. Toolbar is commonly placed inside of a
@ -153,6 +149,15 @@ class Toolbar extends Component {
toolbarButtons toolbarButtons
} = this.props; } = this.props;
// Only a few buttons have custom button handlers defined, so this
// list may be undefined or empty depending on the buttons we're
// rendering.
// TODO: merge the buttonHandlers and onClick properties and come up
// with a consistent event handling property.
if (!buttonHandlers) {
return;
}
Object.keys(buttonHandlers).forEach(key => { Object.keys(buttonHandlers).forEach(key => {
let button = toolbarButtons.get(key); let button = toolbarButtons.get(key);

View File

@ -31,6 +31,11 @@ class Toolbox extends Component {
* @static * @static
*/ */
static propTypes = { static propTypes = {
/**
* Indicates if the toolbox should always be visible.
*/
_alwaysVisible: React.PropTypes.bool,
/** /**
* Handler dispatching setting default buttons action. * Handler dispatching setting default buttons action.
*/ */
@ -159,8 +164,9 @@ class Toolbox extends Component {
* @private * @private
*/ */
_renderToolbars(): ReactElement<*> | null { _renderToolbars(): ReactElement<*> | null {
// The toolbars should not be shown until timeoutID is initialized. // In case we're not in alwaysVisible mode the toolbox should not be
if (this.props._timeoutID === null) { // shown until timeoutID is initialized.
if (!this.props._alwaysVisible && this.props._timeoutID === null) {
return null; return null;
} }
@ -202,8 +208,9 @@ function _mapDispatchToProps(dispatch: Function): Object {
* @returns {Object} Dispatched action. * @returns {Object} Dispatched action.
*/ */
_setToolboxAlwaysVisible() { _setToolboxAlwaysVisible() {
dispatch( dispatch(setToolboxAlwaysVisible(
setToolboxAlwaysVisible(config.alwaysVisibleToolbar === true)); config.alwaysVisibleToolbar === true
|| interfaceConfig.filmStripOnly));
} }
}; };
} }
@ -214,6 +221,7 @@ function _mapDispatchToProps(dispatch: Function): Object {
* @param {Object} state - Redux state. * @param {Object} state - Redux state.
* @private * @private
* @returns {{ * @returns {{
* _alwaysVisible: boolean,
* _audioMuted: boolean, * _audioMuted: boolean,
* _locked: boolean, * _locked: boolean,
* _subjectSlideIn: boolean, * _subjectSlideIn: boolean,
@ -222,6 +230,7 @@ function _mapDispatchToProps(dispatch: Function): Object {
*/ */
function _mapStateToProps(state: Object): Object { function _mapStateToProps(state: Object): Object {
const { const {
alwaysVisible,
subject, subject,
subjectSlideIn, subjectSlideIn,
timeoutID timeoutID
@ -230,10 +239,18 @@ function _mapStateToProps(state: Object): Object {
return { return {
...abstractMapStateToProps(state), ...abstractMapStateToProps(state),
/**
* Indicates if the toolbox should always be visible.
*
* @private
* @type {boolean}
*/
_alwaysVisible: alwaysVisible,
/** /**
* Property containing conference subject. * Property containing conference subject.
* *
* @protected * @private
* @type {string} * @type {string}
*/ */
_subject: subject, _subject: subject,
@ -241,7 +258,7 @@ function _mapStateToProps(state: Object): Object {
/** /**
* Flag showing whether to set subject slide in animation. * Flag showing whether to set subject slide in animation.
* *
* @protected * @private
* @type {boolean} * @type {boolean}
*/ */
_subjectSlideIn: subjectSlideIn, _subjectSlideIn: subjectSlideIn,
@ -249,7 +266,7 @@ function _mapStateToProps(state: Object): Object {
/** /**
* Property containing toolbox timeout id. * Property containing toolbox timeout id.
* *
* @protected * @private
* @type {number} * @type {number}
*/ */
_timeoutID: timeoutID _timeoutID: timeoutID

View File

@ -46,6 +46,7 @@ export default {
camera: { camera: {
classNames: [ 'button', 'icon-camera' ], classNames: [ 'button', 'icon-camera' ],
enabled: true, enabled: true,
filmstripOnlyEnabled: true,
id: 'toolbar_button_camera', id: 'toolbar_button_camera',
onClick() { onClick() {
if (APP.conference.videoMuted) { if (APP.conference.videoMuted) {
@ -203,6 +204,7 @@ export default {
hangup: { hangup: {
classNames: [ 'button', 'icon-hangup', 'button_hangup' ], classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
enabled: true, enabled: true,
filmstripOnlyEnabled: true,
id: 'toolbar_button_hangup', id: 'toolbar_button_hangup',
onClick() { onClick() {
JitsiMeetJS.analytics.sendEvent('toolbar.hangup'); JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
@ -231,6 +233,7 @@ export default {
microphone: { microphone: {
classNames: [ 'button', 'icon-microphone' ], classNames: [ 'button', 'icon-microphone' ],
enabled: true, enabled: true,
filmstripOnlyEnabled: true,
id: 'toolbar_button_mute', id: 'toolbar_button_mute',
onClick() { onClick() {
const sharedVideoManager = APP.UI.getSharedVideoManager(); const sharedVideoManager = APP.UI.getSharedVideoManager();

View File

@ -167,6 +167,8 @@ export function getDefaultToolboxButtons(): Object {
if (typeof interfaceConfig !== 'undefined' if (typeof interfaceConfig !== 'undefined'
&& interfaceConfig.TOOLBAR_BUTTONS) { && interfaceConfig.TOOLBAR_BUTTONS) {
const { filmStripOnly } = interfaceConfig;
toolbarButtons toolbarButtons
= interfaceConfig.TOOLBAR_BUTTONS.reduce( = interfaceConfig.TOOLBAR_BUTTONS.reduce(
(acc, buttonName) => { (acc, buttonName) => {
@ -176,8 +178,13 @@ export function getDefaultToolboxButtons(): Object {
const place = _getToolbarButtonPlace(buttonName); const place = _getToolbarButtonPlace(buttonName);
button.buttonName = buttonName; button.buttonName = buttonName;
// In filmstrip-only mode we only add a button if it's
// filmstrip-only enabled.
if (!filmStripOnly || button.filmstripOnlyEnabled) {
acc[place].set(buttonName, button); acc[place].set(buttonName, button);
} }
}
return acc; return acc;
}, },
@ -210,7 +217,11 @@ function _getToolbarButtonPlace(btn) {
* @private * @private
*/ */
export function getToolbarClassNames(props: Object) { export function getToolbarClassNames(props: Object) {
const primaryToolbarClassNames = [ 'toolbar_primary' ]; const primaryToolbarClassNames = [
interfaceConfig.filmStripOnly
? 'toolbar_filmstrip-only'
: 'toolbar_primary'
];
const secondaryToolbarClassNames = [ 'toolbar_secondary' ]; const secondaryToolbarClassNames = [ 'toolbar_secondary' ];
if (props._visible) { if (props._visible) {

View File

@ -207,6 +207,18 @@ function _setButton(state, { button, buttonName }): Object {
...button ...button
}; };
// In filmstrip-only mode we only show buttons if they're filmstrip-only
// enabled, so we don't need to update if this isn't the case.
// FIXME A reducer should be a pure function of the current state and the
// specified action so it should not use the global variable
// interfaceConfig. Anyway, we'll move interfaceConfig into the (redux)
// store so we'll surely revisit the source code bellow.
if (interfaceConfig.filmStripOnly && !selectedButton.filmstripOnlyEnabled) {
return {
...state
};
}
const updatedToolbar = state[place].set(buttonName, selectedButton); const updatedToolbar = state[place].set(buttonName, selectedButton);
return { return {