diff --git a/conference.js b/conference.js index 3a45895f6..d73fef644 100644 --- a/conference.js +++ b/conference.js @@ -1263,21 +1263,24 @@ export default { * @returns {void} */ _displayAudioOnlyTooltip(featureName) { + let buttonName = null; let tooltipElementId = null; switch (featureName) { case 'screenShare': - tooltipElementId = '#screenshareWhileAudioOnly'; + buttonName = 'desktop'; + tooltipElementId = 'screenshareWhileAudioOnly'; break; case 'videoMute': - tooltipElementId = '#unmuteWhileAudioOnly'; + buttonName = 'camera'; + tooltipElementId = 'unmuteWhileAudioOnly'; break; } if (tooltipElementId) { APP.UI.showToolbar(6000); APP.UI.showCustomToolbarPopup( - tooltipElementId, true, 5000); + buttonName, tooltipElementId, true, 5000); } }, @@ -1697,7 +1700,8 @@ export default { room.on(ConferenceEvents.TALK_WHILE_MUTED, () => { APP.UI.showToolbar(6000); - APP.UI.showCustomToolbarPopup('#talkWhileMutedPopup', true, 5000); + APP.UI.showCustomToolbarPopup( + 'microphone', 'talkWhileMutedPopup', true, 5000); }); room.on( diff --git a/css/_toolbars.scss b/css/_toolbars.scss index 7dcefd3a5..44dcf763a 100644 --- a/css/_toolbars.scss +++ b/css/_toolbars.scss @@ -130,11 +130,11 @@ @include transform(translateX(-50%)); - .button:first-child { + > div:first-child .button { border-bottom-left-radius: 3px; border-top-left-radius: 3px; } - .button:last-child { + > div:last-child .button { border-bottom-right-radius: 3px; border-top-right-radius: 3px; } diff --git a/lang/main.json b/lang/main.json index 02bd3957e..9ff05492b 100644 --- a/lang/main.json +++ b/lang/main.json @@ -114,8 +114,8 @@ "login": "Login", "logout": "Logout", "dialpad": "Open / Close dialpad", - "sharedVideoMutedPopup": "Your shared video has been muted so
that you can talk to the other participants.", - "micMutedPopup": "Your microphone has been muted so that you
would fully enjoy your shared video.", + "sharedVideoMutedPopup": "Your shared video has been muted so that you can talk to the other participants.", + "micMutedPopup": "Your microphone has been muted so that you would fully enjoy your shared video.", "talkWhileMutedPopup": "Trying to speak? You are muted.", "unableToUnmutePopup": "You cannot un-mute while the shared video is on.", "cameraDisabled": "Camera is not available", diff --git a/modules/UI/UI.js b/modules/UI/UI.js index a5346db67..9e998ff35 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -30,7 +30,9 @@ import { import { openDisplayNamePrompt } from '../../react/features/display-name'; import { checkAutoEnableDesktopSharing, + clearButtonPopup, dockToolbox, + setButtonPopupTimeout, setToolbarButton, showDialPadButton, showEtherpadButton, @@ -609,13 +611,21 @@ UI.inputDisplayNameHandler = function (newDisplayName) { /** * Show custom popup/tooltip for a specified button. - * @param popupSelectorID the selector id of the popup to show - * @param show true or false/show or hide the popup - * @param timeout the time to show the popup + * + * @param {string} buttonName - The name of the button as specified in the + * button configurations for the toolbar. + * @param {string} popupSelectorID - The id of the popup to show as specified in + * the button configurations for the toolbar. + * @param {boolean} show - True or false/show or hide the popup + * @param {number} timeout - The time to show the popup + * @returns {void} */ -UI.showCustomToolbarPopup = function (popupSelectorID, show, timeout) { - eventEmitter.emit(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP, - popupSelectorID, show, timeout); +UI.showCustomToolbarPopup = function (buttonName, popupID, show, timeout) { + const action = show + ? setButtonPopupTimeout(buttonName, popupID, timeout) + : clearButtonPopup(buttonName); + + APP.store.dispatch(action); }; /** diff --git a/modules/UI/recording/Recording.js b/modules/UI/recording/Recording.js index 1524189b5..99295d9bc 100644 --- a/modules/UI/recording/Recording.js +++ b/modules/UI/recording/Recording.js @@ -18,7 +18,6 @@ const logger = require("jitsi-meet-logger").getLogger(__filename); import UIEvents from "../../../service/UI/UIEvents"; import UIUtil from '../util/UIUtil'; -import { setTooltip } from '../util/Tooltip'; import VideoLayout from '../videolayout/VideoLayout'; import { setToolboxEnabled } from '../../../react/features/toolbox'; @@ -324,8 +323,6 @@ var Recording = { initRecordingButton() { const selector = $('#toolbar_button_record'); - setTooltip(selector, 'liveStreaming.buttonTooltip', 'right'); - selector.addClass(this.baseClass); selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip); APP.translation.translateElement(selector); diff --git a/modules/UI/shared_video/SharedVideo.js b/modules/UI/shared_video/SharedVideo.js index 4fb98bd28..e9a34e1ed 100644 --- a/modules/UI/shared_video/SharedVideo.js +++ b/modules/UI/shared_video/SharedVideo.js @@ -558,7 +558,8 @@ export default class SharedVideoManager { if(show) this.showSharedVideoMutedPopup(false); - APP.UI.showCustomToolbarPopup('#micMutedPopup', show, 5000); + APP.UI.showCustomToolbarPopup( + 'microphone', 'micMutedPopup', show, 5000); } /** @@ -571,7 +572,8 @@ export default class SharedVideoManager { if(show) this.showMicMutedPopup(false); - APP.UI.showCustomToolbarPopup('#sharedVideoMutedPopup', show, 5000); + APP.UI.showCustomToolbarPopup( + 'sharedvideo', 'sharedVideoMutedPopup', show, 5000); } } diff --git a/react/features/toolbox/actions.web.js b/react/features/toolbox/actions.web.js index c7b61e993..54d58473b 100644 --- a/react/features/toolbox/actions.web.js +++ b/react/features/toolbox/actions.web.js @@ -20,6 +20,7 @@ import { } from './actions.native'; import { SET_DEFAULT_TOOLBOX_BUTTONS } from './actionTypes'; import { + getButton, getDefaultToolboxButtons, isButtonEnabled } from './functions'; @@ -48,6 +49,23 @@ export function checkAutoEnableDesktopSharing(): Function { }; } +/** + * Dispatches an action to hide any popups displayed by the associated button. + * + * @param {string} buttonName - The name of the button as specified in the + * button configurations for the toolbar. + * @returns {Function} + */ +export function clearButtonPopup(buttonName) { + return (dispatch, getState) => { + _clearPopupTimeout(buttonName, getState()); + + dispatch(setToolbarButton(buttonName, { + popupDisplay: null + })); + }; +} + /** * Docks/undocks the Toolbox. * @@ -195,6 +213,34 @@ export function hideToolbox(force: boolean = false): Function { }; } +/** + * Dispatches an action to show the popup associated with a button. Sets a + * timeout to be fired which will dismiss the popup. + * + * @param {string} buttonName - The name of the button as specified in the + * button configurations for the toolbar. + * @param {string} popupName - The id of the popup to show as specified in + * the button configurations for the toolbar. + * @param {number} timeout - The time in milliseconds to show the popup. + * @returns {Function} + */ +export function setButtonPopupTimeout(buttonName, popupName, timeout) { + return (dispatch, getState) => { + _clearPopupTimeout(buttonName, getState()); + + const newTimeoutId = setTimeout(() => { + dispatch(clearButtonPopup(buttonName)); + }, timeout); + + dispatch(setToolbarButton(buttonName, { + popupDisplay: { + popupID: popupName, + timeoutID: newTimeoutId + } + })); + }; +} + /** * Sets the default toolbar buttons of the Toolbox. * @@ -387,3 +433,20 @@ export function toggleSideToolbarContainer(containerId: string): Function { } }; } + +/** + * Clears the timeout set for hiding a button popup. + * + * @param {string} buttonName - The name of the button as specified in the + * button configurations for the toolbar. + * @param {Object} state - The redux state in which the button is expected to + * be defined. + * @private + * @returns {void} + */ +function _clearPopupTimeout(buttonName, state) { + const { popupDisplay } = getButton(buttonName, state); + const { timeoutID } = popupDisplay || {}; + + clearTimeout(timeoutID); +} diff --git a/react/features/toolbox/components/ToolbarButton.web.js b/react/features/toolbox/components/ToolbarButton.web.js index 763a4013d..897cbfdb3 100644 --- a/react/features/toolbox/components/ToolbarButton.web.js +++ b/react/features/toolbox/components/ToolbarButton.web.js @@ -1,5 +1,6 @@ /* @flow */ +import AKInlineDialog from '@atlaskit/inline-dialog'; import { Tooltip } from '@atlaskit/tooltip'; import React, { Component } from 'react'; @@ -11,6 +12,18 @@ import StatelessToolbarButton from './StatelessToolbarButton'; declare var APP: Object; +/** + * Mapping of tooltip positions to equivalent {@code AKInlineDialog} positions. + * + * @private + */ +const TOOLTIP_TO_POPUP_POSITION = { + bottom: 'bottom center', + left: 'left middle', + top: 'top center', + right: 'right middle' +}; + /** * Represents a button in Toolbar on React. * @@ -127,26 +140,39 @@ class ToolbarButton extends Component { */ render(): ReactElement<*> { const { button, t, tooltipPosition } = this.props; - const popups = button.popups || []; - const props = { ...this.props, onClick: this._onClick, createRefToButton: this._createRefToButton }; - return ( + const buttonComponent = ( // eslint-disable-line no-extra-parens - - { this._renderPopups(popups) } - + ); + + const popupConfig = this._getPopupDisplayConfiguration(); + + if (popupConfig) { + const { dataAttr, dataInterpolate, position } = popupConfig; + + return ( + + { buttonComponent } + + ); + } + + return buttonComponent; } /** @@ -173,6 +199,32 @@ class ToolbarButton extends Component { this.button = element; } + /** + * Parses the props and state to find any popup that should be displayed + * and returns an object describing how the popup should display. + * + * @private + * @returns {Object|null} + */ + _getPopupDisplayConfiguration() { + const { button, tooltipPosition } = this.props; + const { popups, popupDisplay } = button; + + if (!popups || !popupDisplay) { + return null; + } + + const { popupID } = popupDisplay; + const currentPopup = popups.find(popup => popup.id === popupID); + + return Object.assign( + {}, + currentPopup || {}, + { + position: TOOLTIP_TO_POPUP_POSITION[tooltipPosition] + }); + } + /** * If toolbar button should contain children elements * renders them. @@ -188,34 +240,6 @@ class ToolbarButton extends Component { return null; } - /** - * Renders popup element for toolbar button. - * - * @param {Array} popups - Array of popup objects. - * @returns {Array} - * @private - */ - _renderPopups(popups: Array<*> = []): Array<*> { - return popups.map(popup => { - let gravity = 'n'; - - if (popup.dataAttrPosition) { - gravity = popup.dataAttrPosition; - } - - const title = this.props.t(popup.dataAttr, popup.dataInterpolate); - - return ( -
- ); - }); - } - /** * Hides any displayed tooltip. * diff --git a/react/features/toolbox/components/Toolbox.web.js b/react/features/toolbox/components/Toolbox.web.js index 370ca036e..da0023826 100644 --- a/react/features/toolbox/components/Toolbox.web.js +++ b/react/features/toolbox/components/Toolbox.web.js @@ -3,15 +3,12 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; -import UIEvents from '../../../../service/UI/UIEvents'; - import { setDefaultToolboxButtons, setToolboxAlwaysVisible } from '../actions'; import { - abstractMapStateToProps, - showCustomToolbarPopup + abstractMapStateToProps } from '../functions'; import Notice from './Notice'; import PrimaryToolbar from './PrimaryToolbar'; @@ -71,10 +68,6 @@ class Toolbox extends Component { componentDidMount(): void { this.props._setToolboxAlwaysVisible(); - APP.UI.addListener( - UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP, - showCustomToolbarPopup); - // FIXME The redux action SET_DEFAULT_TOOLBOX_BUTTONS and related source // code such as the redux action creator setDefaultToolboxButtons and // _setDefaultToolboxButtons were introduced to solve the following bug @@ -89,17 +82,6 @@ class Toolbox extends Component { this.props._setDefaultToolboxButtons(); } - /** - * Unregisters legacy UI listeners. - * - * @returns {void} - */ - componentWillUnmount(): void { - APP.UI.removeListener( - UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP, - showCustomToolbarPopup); - } - /** * Implements React's {@link Component#render()}. * diff --git a/react/features/toolbox/defaultToolbarButtons.js b/react/features/toolbox/defaultToolbarButtons.js index 0adc72b7f..4393cac3c 100644 --- a/react/features/toolbox/defaultToolbarButtons.js +++ b/react/features/toolbox/defaultToolbarButtons.js @@ -51,7 +51,6 @@ const buttons: Object = { }, popups: [ { - className: 'loginmenu', dataAttr: 'audioOnly.featureToggleDisabled', dataInterpolate: { feature: 'video mute' }, id: 'unmuteWhileAudioOnly' @@ -143,7 +142,6 @@ const buttons: Object = { }, popups: [ { - className: 'loginmenu', dataAttr: 'audioOnly.featureToggleDisabled', dataInterpolate: { feature: 'screen sharing' }, id: 'screenshareWhileAudioOnly' @@ -313,17 +311,14 @@ const buttons: Object = { }, popups: [ { - className: 'loginmenu', dataAttr: 'toolbar.micMutedPopup', id: 'micMutedPopup' }, { - className: 'loginmenu', dataAttr: 'toolbar.unableToUnmutePopup', id: 'unableToUnmutePopup' }, { - className: 'loginmenu', dataAttr: 'toolbar.talkWhileMutedPopup', id: 'talkWhileMutedPopup' } @@ -419,9 +414,7 @@ const buttons: Object = { }, popups: [ { - className: 'loginmenu extendedToolbarPopup', dataAttr: 'toolbar.sharedVideoMutedPopup', - dataAttrPosition: 'w', id: 'sharedVideoMutedPopup' } ], diff --git a/react/features/toolbox/functions.web.js b/react/features/toolbox/functions.web.js index 7c8aa66f5..ae8c0cc41 100644 --- a/react/features/toolbox/functions.web.js +++ b/react/features/toolbox/functions.web.js @@ -7,7 +7,7 @@ declare var $: Function; declare var AJS: Object; declare var interfaceConfig: Object; -export { abstractMapStateToProps } from './functions.native'; +export { abstractMapStateToProps, getButton } from './functions.native'; /** * Returns an object which contains the default buttons for the primary and