From 5f5468995f13d75dae1d90d7892df7c1a62b3b0b Mon Sep 17 00:00:00 2001 From: Mihai Uscat Date: Wed, 11 Mar 2020 11:45:42 +0200 Subject: [PATCH] feat(chat): Make chat push content to the side in large view --- css/_chat.scss | 7 +-- css/_toolbars.scss | 5 +++ css/_videolayout_default.scss | 7 +++ modules/UI/videolayout/LargeVideoManager.js | 14 +++++- react/features/chat/actions.js | 14 +++--- react/features/chat/components/web/Chat.js | 28 +++++------- react/features/chat/constants.js | 5 +++ .../conference/components/web/Conference.js | 4 -- .../conference/components/web/index.js | 2 + .../large-video/components/LargeVideo.web.js | 13 +++++- .../toolbox/components/web/Toolbox.js | 45 +++++++++++-------- react/features/toolbox/functions.web.js | 5 ++- 12 files changed, 94 insertions(+), 55 deletions(-) diff --git a/css/_chat.scss b/css/_chat.scss index 9b9444843..3c28d1758 100644 --- a/css/_chat.scss +++ b/css/_chat.scss @@ -4,16 +4,11 @@ color: #FFF; display: flex; flex-direction: column; - /** - * Make the sidebar flush with the top of the toolbar. Take the size of - * the toolbar and subtract from 100%. - */ - height: calc(100% - #{$newToolbarSizeWithPadding}); + height: 100%; left: -$sidebarWidth; overflow: hidden; position: absolute; top: 0; - transition: left 0.5s; width: $sidebarWidth; z-index: $sideToolbarContainerZ; diff --git a/css/_toolbars.scss b/css/_toolbars.scss index 9ec65f25b..928a68d28 100644 --- a/css/_toolbars.scss +++ b/css/_toolbars.scss @@ -42,6 +42,11 @@ display: none; } + &.shift-right { + margin-left: $sidebarWidth; + width: calc(100% - #{$sidebarWidth}); + } + .toolbox-background { background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0)); transition: bottom .3s ease-in; diff --git a/css/_videolayout_default.scss b/css/_videolayout_default.scss index 67078ea20..c9d538fcb 100644 --- a/css/_videolayout_default.scss +++ b/css/_videolayout_default.scss @@ -181,6 +181,13 @@ visibility: hidden; z-index: $zindex2; } + + &.shift-right { + &#largeVideoContainer { + margin-left: $sidebarWidth; + width: calc(100% - #{$sidebarWidth}); + } + } } #localVideoWrapper { diff --git a/modules/UI/videolayout/LargeVideoManager.js b/modules/UI/videolayout/LargeVideoManager.js index ed4cfd7b9..da16533ea 100644 --- a/modules/UI/videolayout/LargeVideoManager.js +++ b/modules/UI/videolayout/LargeVideoManager.js @@ -12,6 +12,7 @@ import { JitsiParticipantConnectionStatus } from '../../../react/features/base/lib-jitsi-meet'; import { VIDEO_TYPE } from '../../../react/features/base/media'; +import { CHAT_SIZE } from '../../../react/features/chat'; import { updateKnownLargeVideoResolution } from '../../../react/features/large-video'; @@ -323,7 +324,18 @@ export default class LargeVideoManager { * Update container size. */ updateContainerSize() { - this.width = UIUtil.getAvailableVideoWidth(); + let widthToUse = UIUtil.getAvailableVideoWidth(); + const { isOpen } = APP.store.getState()['features/chat']; + + if (isOpen) { + /** + * If chat state is open, we re-compute the container width + * by subtracting the default width of the chat. + */ + widthToUse -= CHAT_SIZE; + } + + this.width = widthToUse; this.height = window.innerHeight; } diff --git a/react/features/chat/actions.js b/react/features/chat/actions.js index d519d9ac4..99935c1db 100644 --- a/react/features/chat/actions.js +++ b/react/features/chat/actions.js @@ -1,5 +1,7 @@ // @flow +import VideoLayout from '../../../modules/UI/videolayout/VideoLayout'; + import { ADD_MESSAGE, CLEAR_MESSAGES, @@ -86,14 +88,14 @@ export function setPrivateMessageRecipient(participant: Object) { } /** - * Toggles display of the chat side panel. + * Toggles display of the chat side panel while also taking window + * resize into account. * - * @returns {{ - * type: TOGGLE_CHAT - * }} + * @returns {Function} */ export function toggleChat() { - return { - type: TOGGLE_CHAT + return function(dispatch: (Object) => Object) { + dispatch({ type: TOGGLE_CHAT }); + VideoLayout.onResize(); }; } diff --git a/react/features/chat/components/web/Chat.js b/react/features/chat/components/web/Chat.js index f96dbcc4c..22e1e2465 100644 --- a/react/features/chat/components/web/Chat.js +++ b/react/features/chat/components/web/Chat.js @@ -1,7 +1,6 @@ // @flow import React from 'react'; -import Transition from 'react-transition-group/Transition'; import { translate } from '../../../base/i18n'; import { Icon, IconClose } from '../../../base/icons'; @@ -84,11 +83,9 @@ class Chat extends AbstractChat { */ render() { return ( - - { this._renderPanelContent } - + <> + { this._renderPanelContent() } + ); } @@ -145,30 +142,25 @@ class Chat extends AbstractChat { ); } - _renderPanelContent: (string) => React$Node | null; + _renderPanelContent: () => React$Node | null; /** - * Renders the contents of the chat panel, depending on the current - * animation state provided by {@code Transition}. + * Renders the contents of the chat panel. * - * @param {string} state - The current display transition state of the - * {@code Chat} component, as provided by {@code Transition}. * @private * @returns {ReactElement | null} */ - _renderPanelContent(state) { - this._isExited = state === 'exited'; - + _renderPanelContent() { const { _isOpen, _showNamePrompt } = this.props; - const ComponentToRender = !_isOpen && state === 'exited' - ? null - : ( + const ComponentToRender = _isOpen + ? ( <> { this._renderChatHeader() } { _showNamePrompt ? : this._renderChat() } - ); + ) + : null; let className = ''; if (_isOpen) { diff --git a/react/features/chat/constants.js b/react/features/chat/constants.js index b7681532a..5d872d410 100644 --- a/react/features/chat/constants.js +++ b/react/features/chat/constants.js @@ -2,6 +2,11 @@ export const CHAT_VIEW_MODAL_ID = 'chatView'; +/** + * The size of the chat. + */ +export const CHAT_SIZE = 375; + /** * The audio ID of the audio element for which the {@link playAudio} action is * triggered when new chat message is received. diff --git a/react/features/conference/components/web/Conference.js b/react/features/conference/components/web/Conference.js index 3b49cdfd4..c4a47e8ed 100644 --- a/react/features/conference/components/web/Conference.js +++ b/react/features/conference/components/web/Conference.js @@ -28,10 +28,8 @@ import { } from '../AbstractConference'; import type { AbstractProps } from '../AbstractConference'; -import InviteMore from './InviteMore'; import Labels from './Labels'; import { default as Notice } from './Notice'; -import { default as Subject } from './Subject'; declare var APP: Object; declare var config: Object; @@ -201,8 +199,6 @@ class Conference extends AbstractConference { onMouseMove = { this._onShowToolbar }> - -
diff --git a/react/features/conference/components/web/index.js b/react/features/conference/components/web/index.js index 9e4a6107c..360e48e5d 100644 --- a/react/features/conference/components/web/index.js +++ b/react/features/conference/components/web/index.js @@ -3,3 +3,5 @@ export { default as Conference } from './Conference'; export { default as renderConferenceTimer } from './ConferenceTimerDisplay'; export { default as InsecureRoomNameLabel } from './InsecureRoomNameLabel'; +export { default as InviteMore } from './InviteMore'; +export { default as Subject } from './Subject'; diff --git a/react/features/large-video/components/LargeVideo.web.js b/react/features/large-video/components/LargeVideo.web.js index 4db151b52..aed30eb54 100644 --- a/react/features/large-video/components/LargeVideo.web.js +++ b/react/features/large-video/components/LargeVideo.web.js @@ -4,6 +4,7 @@ import React, { Component } from 'react'; import { Watermarks } from '../../base/react'; import { connect } from '../../base/redux'; +import { InviteMore, Subject } from '../../conference'; import { fetchCustomBrandingData } from '../../dynamic-branding'; import { Captions } from '../../subtitles/'; @@ -26,6 +27,11 @@ type Props = { */ _fetchCustomBrandingData: Function, + /** + * Prop that indicates whether the chat is open. + */ + _isChatOpen: boolean, + /** * Used to determine the value of the autoplay attribute of the underlying * video element. @@ -57,12 +63,15 @@ class LargeVideo extends Component { */ render() { const style = this._getCustomSyles(); + const className = `videocontainer${this.props._isChatOpen ? ' shift-right' : ''}`; return (
+ +
@@ -133,10 +142,12 @@ class LargeVideo extends Component { function _mapStateToProps(state) { const testingConfig = state['features/base/config'].testing; const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding']; + const { isOpen: isChatOpen } = state['features/chat']; return { _customBackgroundColor: backgroundColor, _customBackgroundImageUrl: backgroundImageUrl, + _isChatOpen: isChatOpen, _noAutoPlayVideo: testingConfig?.noAutoPlayVideo }; } diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js index 08c3f1f5c..2a80fb2cf 100644 --- a/react/features/toolbox/components/web/Toolbox.js +++ b/react/features/toolbox/components/web/Toolbox.js @@ -32,7 +32,7 @@ import { connect, equals } from '../../../base/redux'; import { OverflowMenuItem } from '../../../base/toolbox'; import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks'; import { VideoBlurButton } from '../../../blur'; -import { ChatCounter, toggleChat } from '../../../chat'; +import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat'; import { SharedDocumentButton } from '../../../etherpad'; import { openFeedbackDialog } from '../../../feedback'; import { beginAddPeople } from '../../../invite'; @@ -322,6 +322,10 @@ class Toolbox extends Component { this._onSetOverflowVisible(false); this.props.dispatch(setToolbarHovered(false)); } + + if (this.props._chatOpen !== prevProps._chatOpen) { + this._onResize(); + } } /** @@ -344,9 +348,9 @@ class Toolbox extends Component { * @returns {ReactElement} */ render() { - const { _visible, _visibleButtons } = this.props; + const { _chatOpen, _visible, _visibleButtons } = this.props; const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${ - _visibleButtons.size ? '' : 'no-buttons'}`; + _visibleButtons.size ? '' : 'no-buttons'} ${_chatOpen ? 'shift-right' : ''}`; return (
{ * @returns {void} */ _onResize() { - const width = window.innerWidth; + let widthToUse = window.innerWidth; - if (this.state.windowWidth !== width) { - this.setState({ windowWidth: width }); + // Take chat size into account when resizing toolbox. + if (this.props._chatOpen) { + widthToUse -= CHAT_SIZE; + } + + if (this.state.windowWidth !== widthToUse) { + this.setState({ windowWidth: widthToUse }); } } @@ -1174,6 +1183,9 @@ class Toolbox extends Component { / 2 // divide by the number of groups(left and right group) ); + if (this._shouldShowButton('chat')) { + buttonsLeft.push('chat'); + } if (this._shouldShowButton('desktop') && this._isDesktopSharingButtonVisible()) { buttonsLeft.push('desktop'); @@ -1181,9 +1193,6 @@ class Toolbox extends Component { if (this._shouldShowButton('raisehand')) { buttonsLeft.push('raisehand'); } - if (this._shouldShowButton('chat')) { - buttonsLeft.push('chat'); - } if (this._shouldShowButton('closedcaptions')) { buttonsLeft.push('closedcaptions'); } @@ -1239,15 +1248,6 @@ class Toolbox extends Component { return (
- { buttonsLeft.indexOf('desktop') !== -1 - && this._renderDesktopSharingButton() } - { buttonsLeft.indexOf('raisehand') !== -1 - && } { buttonsLeft.indexOf('chat') !== -1 &&
{ tooltip = { t('toolbar.chat') } />
} + { buttonsLeft.indexOf('desktop') !== -1 + && this._renderDesktopSharingButton() } + { buttonsLeft.indexOf('raisehand') !== -1 + && } { buttonsLeft.indexOf('closedcaptions') !== -1 && diff --git a/react/features/toolbox/functions.web.js b/react/features/toolbox/functions.web.js index 624f82ae3..3f3f87053 100644 --- a/react/features/toolbox/functions.web.js +++ b/react/features/toolbox/functions.web.js @@ -1,6 +1,7 @@ // @flow import { hasAvailableDevices } from '../base/devices'; +import { isMobileBrowser } from '../base/environment/utils'; declare var interfaceConfig: Object; @@ -43,8 +44,10 @@ export function isToolboxVisible(state: Object) { visible } = state['features/toolbox']; const { audioSettingsVisible, videoSettingsVisible } = state['features/settings']; + const { isOpen } = state['features/chat']; + const isMobileChatOpen = isMobileBrowser() && isOpen; - return Boolean(!iAmSipGateway && (timeoutID || visible || alwaysVisible + return Boolean(!isMobileChatOpen && !iAmSipGateway && (timeoutID || visible || alwaysVisible || audioSettingsVisible || videoSettingsVisible)); }