Implements a filmstrip-only mode for the toolbox
This commit is contained in:
parent
031f2dfeb8
commit
77b789e26a
|
@ -19,9 +19,9 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toolbar button styles.
|
* Toolbar button styles.
|
||||||
*/
|
*/
|
||||||
.button {
|
.button {
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -97,8 +97,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common toolbar styles.
|
* Common toolbar styles.
|
||||||
*/
|
*/
|
||||||
.toolbar {
|
.toolbar {
|
||||||
background-color: $toolbarBackground;
|
background-color: $toolbarBackground;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -119,8 +119,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary toolbar styles.
|
* Primary toolbar styles.
|
||||||
*/
|
*/
|
||||||
&_primary {
|
&_primary {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
@ -148,8 +148,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Secondary toolbar styles.
|
* Secondary toolbar styles.
|
||||||
*/
|
*/
|
||||||
&_secondary {
|
&_secondary {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -233,21 +233,22 @@ 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));
|
||||||
dispatch(
|
|
||||||
setToolboxTimeout(
|
// If the Toolbox is always visible, there's no need for a timeout
|
||||||
() => dispatch(hideToolbox()),
|
// to toggle its visibility.
|
||||||
timeout || timeoutMS));
|
if (!alwaysVisible) {
|
||||||
dispatch(setToolboxTimeoutMS(interfaceConfig.TOOLBAR_TIMEOUT));
|
dispatch(
|
||||||
|
setToolboxTimeout(
|
||||||
|
() => dispatch(hideToolbox()),
|
||||||
|
timeout || timeoutMS));
|
||||||
|
dispatch(setToolboxTimeoutMS(interfaceConfig.TOOLBAR_TIMEOUT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,7 +178,12 @@ export function getDefaultToolboxButtons(): Object {
|
||||||
const place = _getToolbarButtonPlace(buttonName);
|
const place = _getToolbarButtonPlace(buttonName);
|
||||||
|
|
||||||
button.buttonName = buttonName;
|
button.buttonName = buttonName;
|
||||||
acc[place].set(buttonName, button);
|
|
||||||
|
// In filmstrip-only mode we only add a button if it's
|
||||||
|
// filmstrip-only enabled.
|
||||||
|
if (!filmStripOnly || button.filmstripOnlyEnabled) {
|
||||||
|
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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue