From a9d82a79eafb4873686120c0ebb0f346438848d3 Mon Sep 17 00:00:00 2001 From: Hristo Terezov Date: Tue, 5 Mar 2019 14:26:45 +0000 Subject: [PATCH] fix(toolbar): Move buttons to overflow menu when the space isn't enough --- css/_atlaskit_overrides.scss | 8 + css/_toolbars.scss | 8 + css/modals/invite/_info.scss | 3 +- lang/main.json | 15 +- .../base/toolbox/components/AbstractButton.js | 5 +- .../components/OverflowMenuItem.native.js | 0 .../components/OverflowMenuItem.web.js} | 0 .../features/base/toolbox/components/index.js | 1 + .../invite/components/InfoDialogButton.web.js | 41 ++- .../components/info-dialog/InfoDialog.web.js | 29 +- .../info-dialog/PasswordForm.web.js | 19 ++ .../components/ClosedCaptionButton.web.js | 2 + .../toolbox/components/web/Toolbox.js | 274 ++++++++++++++++-- .../video-layout/components/TileViewButton.js | 3 +- 14 files changed, 378 insertions(+), 30 deletions(-) create mode 100644 react/features/base/toolbox/components/OverflowMenuItem.native.js rename react/features/{toolbox/components/web/OverflowMenuItem.js => base/toolbox/components/OverflowMenuItem.web.js} (100%) diff --git a/css/_atlaskit_overrides.scss b/css/_atlaskit_overrides.scss index 0dc5b5a13..0b110c0a3 100644 --- a/css/_atlaskit_overrides.scss +++ b/css/_atlaskit_overrides.scss @@ -40,3 +40,11 @@ .videocontainer .tOoji { background: none; } + +/** + * Override @atlaskit/InlineDialog styling for the overflowmenu so it displays + * with the correct height. + */ +.toolbox-button-wth-dialog .eYJELv { + max-height: initial; +} diff --git a/css/_toolbars.scss b/css/_toolbars.scss index 4b330b58b..ae6dd8742 100644 --- a/css/_toolbars.scss +++ b/css/_toolbars.scss @@ -190,6 +190,14 @@ cursor: initial; color: #3b475c; } + + i.toggled { + background: inherit; + } + + i.toggled:hover { + background: inherit; + } } .beta-tag { diff --git a/css/modals/invite/_info.scss b/css/modals/invite/_info.scss index 3a642f46b..6105caf4e 100644 --- a/css/modals/invite/_info.scss +++ b/css/modals/invite/_info.scss @@ -66,7 +66,7 @@ } .info-dialog-dial-in { - white-space: nowrap; + word-break: break-all; .conference-id, .phone-number { @@ -77,6 +77,7 @@ .info-dialog-icon { color: #6453C0; font-size: 16px; + min-width: 30px; } .info-dialog-url-text, diff --git a/lang/main.json b/lang/main.json index 44fbdad88..ee8b078b5 100644 --- a/lang/main.json +++ b/lang/main.json @@ -362,7 +362,8 @@ "numbers": "Dial-in Numbers", "password": "Password:", "title": "Share", - "tooltip": "Share link and dial-in info for this meeting" + "tooltip": "Share link and dial-in info for this meeting", + "label": "Meeting info" }, "inviteDialog": { "alertOk": "Ok", @@ -631,11 +632,14 @@ "callQuality": "Manage call quality", "cameraDisabled": "Camera is not available", "chat": "Open / Close chat", + "closeChat": "Close chat", "documentClose": "Close shared document", "documentOpen": "Open shared document", "enterFullScreen": "View full screen", + "enterTileView": "Enter tile view", "etherpad": "Open / Close shared document", "exitFullScreen": "Exit full screen", + "exitTileView": "Exit tile view", "feedback": "Leave feedback", "filmstrip": "Show / Hide videos", "fullscreen": "View / Exit full screen", @@ -644,13 +648,16 @@ "lock": "Lock / Unlock room", "login": "Login", "logout": "Logout", + "lowerYourHand": "Lower your hand", "micDisabled": "Microphone is not available", "micMutedPopup": "Your microphone has been muted so that you would fully enjoy your shared video.", "moreActions": "More actions", "mute": "Mute / Unmute", + "openChat": "Open chat", "pip": "Enter Picture-in-Picture mode", "profile": "Edit your profile", "raiseHand": "Raise / Lower your hand", + "raiseYourHand": "Raise your hand", "Settings": "Settings", "sharedvideo": "Share a YouTube video", "sharedVideoMutedPopup": "Your shared video has been muted so that you can talk to the other members.", @@ -658,6 +665,10 @@ "shortcuts": "View shortcuts", "sip": "Call SIP number", "speakerStats": "Speaker stats", + "startScreenSharing": "Start screen sharing", + "startSubtitles": "Start subtitles", + "stopScreenSharing": "Stop screen sharing", + "stopSubtitles": "Stop subtitles", "stopSharedVideo": "Stop YouTube video", "talkWhileMutedPopup": "Trying to speak? You are muted.", "tileViewToggle": "Toggle tile view", @@ -666,7 +677,7 @@ "videomute": "Start / Stop camera" }, "transcribing": { - "ccButtonTooltip": "Start / Stop showing subtitles", + "ccButtonTooltip": "Start / Stop subtitles", "error": "Transcribing failed. Please try again.", "expandedLabel": "Transcribing is currently on", "failedToStart": "Transcribing failed to start", diff --git a/react/features/base/toolbox/components/AbstractButton.js b/react/features/base/toolbox/components/AbstractButton.js index f3364ca5d..9d54188a6 100644 --- a/react/features/base/toolbox/components/AbstractButton.js +++ b/react/features/base/toolbox/components/AbstractButton.js @@ -160,8 +160,9 @@ export default class AbstractButton extends Component { * @returns {string} */ _getIconName() { - return (this._isToggled() ? this.toggledIconName : this.iconName) - || this.iconName; + return ( + this._isToggled() ? this.toggledIconName : this.iconName + ) || this.iconName; } /** diff --git a/react/features/base/toolbox/components/OverflowMenuItem.native.js b/react/features/base/toolbox/components/OverflowMenuItem.native.js new file mode 100644 index 000000000..e69de29bb diff --git a/react/features/toolbox/components/web/OverflowMenuItem.js b/react/features/base/toolbox/components/OverflowMenuItem.web.js similarity index 100% rename from react/features/toolbox/components/web/OverflowMenuItem.js rename to react/features/base/toolbox/components/OverflowMenuItem.web.js diff --git a/react/features/base/toolbox/components/index.js b/react/features/base/toolbox/components/index.js index 945aeadcd..ddceba413 100644 --- a/react/features/base/toolbox/components/index.js +++ b/react/features/base/toolbox/components/index.js @@ -5,3 +5,4 @@ export { default as AbstractButton } from './AbstractButton'; export type { Props as AbstractButtonProps } from './AbstractButton'; export { default as AbstractHangupButton } from './AbstractHangupButton'; export { default as AbstractVideoMuteButton } from './AbstractVideoMuteButton'; +export { default as OverflowMenuItem } from './OverflowMenuItem'; diff --git a/react/features/invite/components/InfoDialogButton.web.js b/react/features/invite/components/InfoDialogButton.web.js index 916232b05..f3380bda2 100644 --- a/react/features/invite/components/InfoDialogButton.web.js +++ b/react/features/invite/components/InfoDialogButton.web.js @@ -5,12 +5,13 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createToolbarEvent, sendAnalytics } from '../../analytics'; +import { openDialog } from '../../base/dialog'; import { translate } from '../../base/i18n'; import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet'; import { getParticipantCount } from '../../base/participants'; +import { OverflowMenuItem } from '../../base/toolbox'; import { getActiveSession } from '../../recording'; import { ToolbarButton } from '../../toolbox'; - import { updateDialInNumbers } from '../actions'; import { InfoDialog } from './info-dialog'; @@ -59,6 +60,11 @@ type Props = { */ dispatch: Dispatch<*>, + /** + * Whether to show the label or not. + */ + showLabel: boolean, + /** * Invoked to obtain translated strings. */ @@ -122,6 +128,8 @@ class InfoDialogButton extends Component { // Bind event handlers so they are only bound once for every instance. this._onDialogClose = this._onDialogClose.bind(this); this._onDialogToggle = this._onDialogToggle.bind(this); + this._onClickOverflowMenuButton + = this._onClickOverflowMenuButton.bind(this); } /** @@ -142,16 +150,28 @@ class InfoDialogButton extends Component { * @returns {ReactElement} */ render() { - const { _dialIn, _liveStreamViewURL, t } = this.props; + const { _dialIn, _liveStreamViewURL, showLabel, t } = this.props; const { showDialog } = this.state; const iconClass = `icon-info ${showDialog ? 'toggled' : ''}`; + if (showLabel) { + return ( + + ); + } + return (
} isOpen = { showDialog } @@ -179,6 +199,23 @@ class InfoDialogButton extends Component { this.setState({ showDialog: false }); } + _onClickOverflowMenuButton: () => void; + + /** + * Opens the Info dialog. + * + * @returns {void} + */ + _onClickOverflowMenuButton() { + const { _dialIn, _liveStreamViewURL } = this.props; + + this.props.dispatch(openDialog(InfoDialog, { + dialIn: _dialIn, + liveStreamViewURL: _liveStreamViewURL, + isInlineDialog: false + })); + } + _onDialogToggle: () => void; /** diff --git a/react/features/invite/components/info-dialog/InfoDialog.web.js b/react/features/invite/components/info-dialog/InfoDialog.web.js index 7fc27f315..ba997c72d 100644 --- a/react/features/invite/components/info-dialog/InfoDialog.web.js +++ b/react/features/invite/components/info-dialog/InfoDialog.web.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { setPassword } from '../../../base/conference'; import { getInviteURL } from '../../../base/connection'; +import { Dialog } from '../../../base/dialog'; import { translate } from '../../../base/i18n'; import { isLocalParticipantModerator } from '../../../base/participants'; @@ -66,6 +67,11 @@ type Props = { */ dispatch: Dispatch<*>, + /** + * Whether is Atlaskit InlineDialog or a normal dialog. + */ + isInlineDialog: boolean, + /** * The current known URL for a live stream in progress. */ @@ -187,9 +193,14 @@ class InfoDialog extends Component { * @returns {ReactElement} */ render() { - const { liveStreamViewURL, onMouseOver, t } = this.props; + const { + isInlineDialog, + liveStreamViewURL, + onMouseOver, + t + } = this.props; - return ( + const inlineDialog = (
@@ -246,6 +257,20 @@ class InfoDialog extends Component { value = { this._getTextToCopy() } />
); + + if (isInlineDialog) { + return inlineDialog; + } + + return ( + + { inlineDialog } + + ); } /** diff --git a/react/features/invite/components/info-dialog/PasswordForm.web.js b/react/features/invite/components/info-dialog/PasswordForm.web.js index e75faea57..7578ac5d2 100644 --- a/react/features/invite/components/info-dialog/PasswordForm.web.js +++ b/react/features/invite/components/info-dialog/PasswordForm.web.js @@ -83,6 +83,7 @@ class PasswordForm extends Component { this._onEnteredPasswordChange = this._onEnteredPasswordChange.bind(this); this._onPasswordSubmit = this._onPasswordSubmit.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); } /** @@ -119,6 +120,7 @@ class PasswordForm extends Component { return (
{ */ _onPasswordSubmit(event) { event.preventDefault(); + event.stopPropagation(); this.props.onSubmit(this.state.enteredPassword); } + + _onKeyDown: (Object) => void; + + /** + * Stops the the EnterKey for propagation in order to prevent the dialog + * to close. + * + * @param {Object} event - The key event. + * @private + * @returns {void} + */ + _onKeyDown(event) { + if (event.key === 'Enter') { + event.stopPropagation(); + } + } } export default translate(PasswordForm); diff --git a/react/features/subtitles/components/ClosedCaptionButton.web.js b/react/features/subtitles/components/ClosedCaptionButton.web.js index 045488895..6df9f22a2 100644 --- a/react/features/subtitles/components/ClosedCaptionButton.web.js +++ b/react/features/subtitles/components/ClosedCaptionButton.web.js @@ -19,6 +19,8 @@ class ClosedCaptionButton iconName = 'icon-closed_caption'; toggledIconName = 'icon-closed_caption toggled'; tooltip = 'transcribing.ccButtonTooltip'; + label = 'toolbar.startSubtitles'; + toggledLabel = 'toolbar.stopSubtitles'; } export default translate(connect(_abstractMapStateToProps)( diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js index f6b8f04c8..31c418f00 100644 --- a/react/features/toolbox/components/web/Toolbox.js +++ b/react/features/toolbox/components/web/Toolbox.js @@ -16,6 +16,7 @@ import { getParticipants, participantUpdated } from '../../../base/participants'; +import { OverflowMenuItem } from '../../../base/toolbox'; import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks'; import { ChatCounter, toggleChat } from '../../../chat'; import { toggleDocument } from '../../../etherpad'; @@ -56,7 +57,6 @@ import { import AudioMuteButton from '../AudioMuteButton'; import HangupButton from '../HangupButton'; import OverflowMenuButton from './OverflowMenuButton'; -import OverflowMenuItem from './OverflowMenuItem'; import OverflowMenuProfileItem from './OverflowMenuProfileItem'; import ToolbarButton from './ToolbarButton'; import VideoMuteButton from '../VideoMuteButton'; @@ -177,6 +177,17 @@ type Props = { t: Function }; +/** + * The type of the React {@code Component} state of {@link Toolbox}. + */ +type State = { + + /** + * The width of the browser's window. + */ + windowWidth: number +}; + declare var APP: Object; declare var interfaceConfig: Object; @@ -185,7 +196,7 @@ declare var interfaceConfig: Object; * * @extends Component */ -class Toolbox extends Component { +class Toolbox extends Component { /** * Initializes a new {@code Toolbox} instance. * @@ -198,6 +209,7 @@ class Toolbox extends Component { // Bind event handlers so they are only bound once per instance. this._onMouseOut = this._onMouseOut.bind(this); this._onMouseOver = this._onMouseOver.bind(this); + this._onResize = this._onResize.bind(this); this._onSetOverflowVisible = this._onSetOverflowVisible.bind(this); this._onShortcutToggleChat = this._onShortcutToggleChat.bind(this); @@ -232,6 +244,10 @@ class Toolbox extends Component { = this._onToolbarToggleSharedVideo.bind(this); this._onToolbarOpenLocalRecordingInfoDialog = this._onToolbarOpenLocalRecordingInfoDialog.bind(this); + + this.state = { + windowWidth: window.innerWidth + }; } /** @@ -273,6 +289,8 @@ class Toolbox extends Component { shortcut.helpDescription); } }); + + window.addEventListener('resize', this._onResize); } /** @@ -303,6 +321,8 @@ class Toolbox extends Component { componentWillUnmount() { [ 'C', 'D', 'R', 'S' ].forEach(letter => APP.keyboardshortcut.unregisterShortcut(letter)); + + window.removeEventListener('resize', this._onResize); } /** @@ -482,6 +502,24 @@ class Toolbox extends Component { this.props.dispatch(setToolbarHovered(true)); } + _onResize: () => void; + + /** + * A window resize handler used to calculate the number of buttons we can + * fit in the toolbar. + * + * @private + * @returns {void} + */ + _onResize() { + const width = window.innerWidth; + + if (this.state.windowWidth !== width) { + this.setState({ windowWidth: width }); + } + } + + _onSetOverflowVisible: (boolean) => void; /** @@ -788,13 +826,30 @@ class Toolbox extends Component { this.props.dispatch(openDialog(LocalRecordingInfoDialog)); } + /** + * Returns true if the the desktop sharing button should be visible and + * false otherwise. + * + * @returns {boolean} + */ + _isDesktopSharingButtonVisible() { + const { + _desktopSharingEnabled, + _desktopSharingDisabledTooltipKey + } = this.props; + + return _desktopSharingEnabled || _desktopSharingDisabledTooltipKey; + } + /** * Renders a button for toggleing screen sharing. * * @private + * @param {boolean} isInOverflowMenu - True if the button is moved to the + * overflow menu. * @returns {ReactElement|null} */ - _renderDesktopSharingButton() { + _renderDesktopSharingButton(isInOverflowMenu = false) { const { _desktopSharingEnabled, _desktopSharingDisabledTooltipKey, @@ -802,13 +857,28 @@ class Toolbox extends Component { t } = this.props; - const visible - = _desktopSharingEnabled || _desktopSharingDisabledTooltipKey; - - if (!visible) { + if (!this._isDesktopSharingButtonVisible()) { return null; } + if (isInOverflowMenu) { + return ( + + ); + } + const classNames = `icon-share-desktop ${ _screensharing ? 'toggled' : ''} ${ _desktopSharingEnabled ? '' : 'disabled'}`; @@ -826,6 +896,15 @@ class Toolbox extends Component { ); } + /** + * Returns true if the profile button is visible and false otherwise. + * + * @returns {boolean} + */ + _isProfileVisible() { + return this.props._isGuest && this._shouldShowButton('profile'); + } + /** * Renders the list elements of the overflow menu. * @@ -838,14 +917,12 @@ class Toolbox extends Component { _etherpadInitialized, _feedbackConfigured, _fullScreen, - _isGuest, _sharingVideo, t } = this.props; return [ - _isGuest - && this._shouldShowButton('profile') + this._isProfileVisible() && , @@ -924,6 +1001,88 @@ class Toolbox extends Component { ]; } + /** + * Renders a list of buttons that are moved to the overflow menu. + * + * @private + * @param {Array} movedButtons - The names of the buttons to be + * moved. + * @returns {Array} + */ + _renderMovedButtons(movedButtons) { + const { + _chatOpen, + _raisedHand, + t + } = this.props; + + return movedButtons.map(buttonName => { + switch (buttonName) { + case 'desktop': + return this._renderDesktopSharingButton(true); + case 'raisehand': + return ( + + ); + case 'chat': + return ( + + ); + case 'closedcaptions': + return ; + case 'info': + return ; + case 'invite': + return ( + + ); + case 'tileview': + return ; + case 'localrecording': + return ( + + ); + default: + return null; + } + }); + } + /** * Renders the toolbox content. * @@ -941,13 +1100,86 @@ class Toolbox extends Component { const overflowHasItems = Boolean(overflowMenuContent.filter( child => child).length); const toolbarAccLabel = 'toolbar.accessibilityLabel.moreActionsMenu'; + const buttonsLeft = []; + const buttonsRight = []; + + const maxNumberOfButtonsPerGroup = Math.floor( + ( + this.state.windowWidth + - 168 // the width of the central group by design + - 48 // the minimum space between the button groups + ) + / 56 // the width + padding of a button + / 2 // divide by the number of groups(left and right group) + ); + + if (this._shouldShowButton('desktop') + && this._isDesktopSharingButtonVisible()) { + buttonsLeft.push('desktop'); + } + if (this._shouldShowButton('raisehand')) { + buttonsLeft.push('raisehand'); + } + if (this._shouldShowButton('chat')) { + buttonsLeft.push('chat'); + } + if (this._shouldShowButton('closedcaptions')) { + buttonsLeft.push('closedcaptions'); + } + if (overflowHasItems) { + buttonsRight.push('overflowmenu'); + } + if (this._shouldShowButton('info')) { + buttonsRight.push('info'); + } + if (this._shouldShowButton('invite') && !_hideInviteButton) { + buttonsRight.push('invite'); + } + if (this._shouldShowButton('tileview')) { + buttonsRight.push('tileview'); + } + if (this._shouldShowButton('localrecording')) { + buttonsRight.push('localrecording'); + } + + const movedButtons = []; + + if (buttonsLeft.length > maxNumberOfButtonsPerGroup) { + movedButtons.push(...buttonsLeft.splice( + maxNumberOfButtonsPerGroup, + buttonsLeft.length - maxNumberOfButtonsPerGroup)); + if (buttonsRight.indexOf('overflowmenu') === -1) { + buttonsRight.unshift('overflowmenu'); + } + } + + if (buttonsRight.length > maxNumberOfButtonsPerGroup) { + if (buttonsRight.indexOf('overflowmenu') === -1) { + buttonsRight.unshift('overflowmenu'); + } + + let numberOfButtons = maxNumberOfButtonsPerGroup; + + // make sure the more button will be displayed when we move buttons. + if (numberOfButtons === 0) { + numberOfButtons++; + } + + movedButtons.push(...buttonsRight.splice( + numberOfButtons, + buttonsRight.length - numberOfButtons)); + + } + + overflowMenuContent.splice( + 1, 0, ...this._renderMovedButtons(movedButtons)); return (
- { this._shouldShowButton('desktop') + { buttonsLeft.indexOf('desktop') !== -1 && this._renderDesktopSharingButton() } - { this._shouldShowButton('raisehand') + { buttonsLeft.indexOf('raisehand') !== -1 && { : 'icon-raised-hand' } onClick = { this._onToolbarToggleRaiseHand } tooltip = { t('toolbar.raiseHand') } /> } - { this._shouldShowButton('chat') + { buttonsLeft.indexOf('chat') !== -1 &&
{
} { - this._shouldShowButton('closedcaptions') + buttonsLeft.indexOf('closedcaptions') !== -1 && }
@@ -984,24 +1216,26 @@ class Toolbox extends Component { visible = { this._shouldShowButton('camera') } />
- { this._shouldShowButton('localrecording') + { buttonsRight.indexOf('localrecording') !== -1 && } - { this._shouldShowButton('tileview') + { buttonsRight.indexOf('tileview') !== -1 && } - { this._shouldShowButton('invite') - && !_hideInviteButton + { buttonsRight.indexOf('invite') !== -1 && } - { this._shouldShowButton('info') && } - { overflowHasItems + { + buttonsRight.indexOf('info') !== -1 + && + } + { buttonsRight.indexOf('overflowmenu') !== -1 && diff --git a/react/features/video-layout/components/TileViewButton.js b/react/features/video-layout/components/TileViewButton.js index 113b001f0..cb0fcfc5a 100644 --- a/react/features/video-layout/components/TileViewButton.js +++ b/react/features/video-layout/components/TileViewButton.js @@ -38,7 +38,8 @@ type Props = AbstractButtonProps & { class TileViewButton extends AbstractButton { accessibilityLabel = 'toolbar.accessibilityLabel.tileView'; iconName = 'icon-tiles-many'; - label = 'toolbar.tileViewToggle'; + label = 'toolbar.enterTileView'; + toggledLabel = 'toolbar.exitTileView'; toggledIconName = 'icon-tiles-many toggled'; tooltip = 'toolbar.tileViewToggle';