feat(chat): Make chat push content to the side in large view

This commit is contained in:
Mihai Uscat 2020-03-11 11:45:42 +02:00 committed by vp8x8
parent bf7aa39947
commit 5f5468995f
12 changed files with 94 additions and 55 deletions

View File

@ -4,16 +4,11 @@
color: #FFF; color: #FFF;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/** height: 100%;
* Make the sidebar flush with the top of the toolbar. Take the size of
* the toolbar and subtract from 100%.
*/
height: calc(100% - #{$newToolbarSizeWithPadding});
left: -$sidebarWidth; left: -$sidebarWidth;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
top: 0; top: 0;
transition: left 0.5s;
width: $sidebarWidth; width: $sidebarWidth;
z-index: $sideToolbarContainerZ; z-index: $sideToolbarContainerZ;

View File

@ -42,6 +42,11 @@
display: none; display: none;
} }
&.shift-right {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
.toolbox-background { .toolbox-background {
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0)); background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
transition: bottom .3s ease-in; transition: bottom .3s ease-in;

View File

@ -181,6 +181,13 @@
visibility: hidden; visibility: hidden;
z-index: $zindex2; z-index: $zindex2;
} }
&.shift-right {
&#largeVideoContainer {
margin-left: $sidebarWidth;
width: calc(100% - #{$sidebarWidth});
}
}
} }
#localVideoWrapper { #localVideoWrapper {

View File

@ -12,6 +12,7 @@ import {
JitsiParticipantConnectionStatus JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet'; } from '../../../react/features/base/lib-jitsi-meet';
import { VIDEO_TYPE } from '../../../react/features/base/media'; import { VIDEO_TYPE } from '../../../react/features/base/media';
import { CHAT_SIZE } from '../../../react/features/chat';
import { import {
updateKnownLargeVideoResolution updateKnownLargeVideoResolution
} from '../../../react/features/large-video'; } from '../../../react/features/large-video';
@ -323,7 +324,18 @@ export default class LargeVideoManager {
* Update container size. * Update container size.
*/ */
updateContainerSize() { 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; this.height = window.innerHeight;
} }

View File

@ -1,5 +1,7 @@
// @flow // @flow
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { import {
ADD_MESSAGE, ADD_MESSAGE,
CLEAR_MESSAGES, 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 {{ * @returns {Function}
* type: TOGGLE_CHAT
* }}
*/ */
export function toggleChat() { export function toggleChat() {
return { return function(dispatch: (Object) => Object) {
type: TOGGLE_CHAT dispatch({ type: TOGGLE_CHAT });
VideoLayout.onResize();
}; };
} }

View File

@ -1,7 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import Transition from 'react-transition-group/Transition';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { Icon, IconClose } from '../../../base/icons'; import { Icon, IconClose } from '../../../base/icons';
@ -84,11 +83,9 @@ class Chat extends AbstractChat<Props> {
*/ */
render() { render() {
return ( return (
<Transition <>
in = { this.props._isOpen } { this._renderPanelContent() }
timeout = { 500 }> </>
{ this._renderPanelContent }
</Transition>
); );
} }
@ -145,30 +142,25 @@ class Chat extends AbstractChat<Props> {
); );
} }
_renderPanelContent: (string) => React$Node | null; _renderPanelContent: () => React$Node | null;
/** /**
* Renders the contents of the chat panel, depending on the current * Renders the contents of the chat panel.
* animation state provided by {@code Transition}.
* *
* @param {string} state - The current display transition state of the
* {@code Chat} component, as provided by {@code Transition}.
* @private * @private
* @returns {ReactElement | null} * @returns {ReactElement | null}
*/ */
_renderPanelContent(state) { _renderPanelContent() {
this._isExited = state === 'exited';
const { _isOpen, _showNamePrompt } = this.props; const { _isOpen, _showNamePrompt } = this.props;
const ComponentToRender = !_isOpen && state === 'exited' const ComponentToRender = _isOpen
? null ? (
: (
<> <>
{ this._renderChatHeader() } { this._renderChatHeader() }
{ _showNamePrompt { _showNamePrompt
? <DisplayNameForm /> : this._renderChat() } ? <DisplayNameForm /> : this._renderChat() }
</> </>
); )
: null;
let className = ''; let className = '';
if (_isOpen) { if (_isOpen) {

View File

@ -2,6 +2,11 @@
export const CHAT_VIEW_MODAL_ID = 'chatView'; 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 * The audio ID of the audio element for which the {@link playAudio} action is
* triggered when new chat message is received. * triggered when new chat message is received.

View File

@ -28,10 +28,8 @@ import {
} from '../AbstractConference'; } from '../AbstractConference';
import type { AbstractProps } from '../AbstractConference'; import type { AbstractProps } from '../AbstractConference';
import InviteMore from './InviteMore';
import Labels from './Labels'; import Labels from './Labels';
import { default as Notice } from './Notice'; import { default as Notice } from './Notice';
import { default as Subject } from './Subject';
declare var APP: Object; declare var APP: Object;
declare var config: Object; declare var config: Object;
@ -201,8 +199,6 @@ class Conference extends AbstractConference<Props, *> {
onMouseMove = { this._onShowToolbar }> onMouseMove = { this._onShowToolbar }>
<Notice /> <Notice />
<Subject />
<InviteMore />
<div id = 'videospace'> <div id = 'videospace'>
<LargeVideo /> <LargeVideo />
<KnockingParticipantList /> <KnockingParticipantList />

View File

@ -3,3 +3,5 @@
export { default as Conference } from './Conference'; export { default as Conference } from './Conference';
export { default as renderConferenceTimer } from './ConferenceTimerDisplay'; export { default as renderConferenceTimer } from './ConferenceTimerDisplay';
export { default as InsecureRoomNameLabel } from './InsecureRoomNameLabel'; export { default as InsecureRoomNameLabel } from './InsecureRoomNameLabel';
export { default as InviteMore } from './InviteMore';
export { default as Subject } from './Subject';

View File

@ -4,6 +4,7 @@ import React, { Component } from 'react';
import { Watermarks } from '../../base/react'; import { Watermarks } from '../../base/react';
import { connect } from '../../base/redux'; import { connect } from '../../base/redux';
import { InviteMore, Subject } from '../../conference';
import { fetchCustomBrandingData } from '../../dynamic-branding'; import { fetchCustomBrandingData } from '../../dynamic-branding';
import { Captions } from '../../subtitles/'; import { Captions } from '../../subtitles/';
@ -26,6 +27,11 @@ type Props = {
*/ */
_fetchCustomBrandingData: Function, _fetchCustomBrandingData: Function,
/**
* Prop that indicates whether the chat is open.
*/
_isChatOpen: boolean,
/** /**
* Used to determine the value of the autoplay attribute of the underlying * Used to determine the value of the autoplay attribute of the underlying
* video element. * video element.
@ -57,12 +63,15 @@ class LargeVideo extends Component<Props> {
*/ */
render() { render() {
const style = this._getCustomSyles(); const style = this._getCustomSyles();
const className = `videocontainer${this.props._isChatOpen ? ' shift-right' : ''}`;
return ( return (
<div <div
className = 'videocontainer' className = { className }
id = 'largeVideoContainer' id = 'largeVideoContainer'
style = { style }> style = { style }>
<Subject />
<InviteMore />
<div id = 'sharedVideo'> <div id = 'sharedVideo'>
<div id = 'sharedVideoIFrame' /> <div id = 'sharedVideoIFrame' />
</div> </div>
@ -133,10 +142,12 @@ class LargeVideo extends Component<Props> {
function _mapStateToProps(state) { function _mapStateToProps(state) {
const testingConfig = state['features/base/config'].testing; const testingConfig = state['features/base/config'].testing;
const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding']; const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
const { isOpen: isChatOpen } = state['features/chat'];
return { return {
_customBackgroundColor: backgroundColor, _customBackgroundColor: backgroundColor,
_customBackgroundImageUrl: backgroundImageUrl, _customBackgroundImageUrl: backgroundImageUrl,
_isChatOpen: isChatOpen,
_noAutoPlayVideo: testingConfig?.noAutoPlayVideo _noAutoPlayVideo: testingConfig?.noAutoPlayVideo
}; };
} }

View File

@ -32,7 +32,7 @@ import { connect, equals } from '../../../base/redux';
import { OverflowMenuItem } from '../../../base/toolbox'; import { OverflowMenuItem } from '../../../base/toolbox';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks'; import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
import { VideoBlurButton } from '../../../blur'; import { VideoBlurButton } from '../../../blur';
import { ChatCounter, toggleChat } from '../../../chat'; import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
import { SharedDocumentButton } from '../../../etherpad'; import { SharedDocumentButton } from '../../../etherpad';
import { openFeedbackDialog } from '../../../feedback'; import { openFeedbackDialog } from '../../../feedback';
import { beginAddPeople } from '../../../invite'; import { beginAddPeople } from '../../../invite';
@ -322,6 +322,10 @@ class Toolbox extends Component<Props, State> {
this._onSetOverflowVisible(false); this._onSetOverflowVisible(false);
this.props.dispatch(setToolbarHovered(false)); this.props.dispatch(setToolbarHovered(false));
} }
if (this.props._chatOpen !== prevProps._chatOpen) {
this._onResize();
}
} }
/** /**
@ -344,9 +348,9 @@ class Toolbox extends Component<Props, State> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { _visible, _visibleButtons } = this.props; const { _chatOpen, _visible, _visibleButtons } = this.props;
const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${ const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${
_visibleButtons.size ? '' : 'no-buttons'}`; _visibleButtons.size ? '' : 'no-buttons'} ${_chatOpen ? 'shift-right' : ''}`;
return ( return (
<div <div
@ -534,10 +538,15 @@ class Toolbox extends Component<Props, State> {
* @returns {void} * @returns {void}
*/ */
_onResize() { _onResize() {
const width = window.innerWidth; let widthToUse = window.innerWidth;
if (this.state.windowWidth !== width) { // Take chat size into account when resizing toolbox.
this.setState({ windowWidth: width }); if (this.props._chatOpen) {
widthToUse -= CHAT_SIZE;
}
if (this.state.windowWidth !== widthToUse) {
this.setState({ windowWidth: widthToUse });
} }
} }
@ -1174,6 +1183,9 @@ class Toolbox extends Component<Props, State> {
/ 2 // divide by the number of groups(left and right group) / 2 // divide by the number of groups(left and right group)
); );
if (this._shouldShowButton('chat')) {
buttonsLeft.push('chat');
}
if (this._shouldShowButton('desktop') if (this._shouldShowButton('desktop')
&& this._isDesktopSharingButtonVisible()) { && this._isDesktopSharingButtonVisible()) {
buttonsLeft.push('desktop'); buttonsLeft.push('desktop');
@ -1181,9 +1193,6 @@ class Toolbox extends Component<Props, State> {
if (this._shouldShowButton('raisehand')) { if (this._shouldShowButton('raisehand')) {
buttonsLeft.push('raisehand'); buttonsLeft.push('raisehand');
} }
if (this._shouldShowButton('chat')) {
buttonsLeft.push('chat');
}
if (this._shouldShowButton('closedcaptions')) { if (this._shouldShowButton('closedcaptions')) {
buttonsLeft.push('closedcaptions'); buttonsLeft.push('closedcaptions');
} }
@ -1239,15 +1248,6 @@ class Toolbox extends Component<Props, State> {
return ( return (
<div className = 'toolbox-content'> <div className = 'toolbox-content'>
<div className = 'button-group-left'> <div className = 'button-group-left'>
{ buttonsLeft.indexOf('desktop') !== -1
&& this._renderDesktopSharingButton() }
{ buttonsLeft.indexOf('raisehand') !== -1
&& <ToolbarButton
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
icon = { IconRaisedHand }
onClick = { this._onToolbarToggleRaiseHand }
toggled = { _raisedHand }
tooltip = { t('toolbar.raiseHand') } /> }
{ buttonsLeft.indexOf('chat') !== -1 { buttonsLeft.indexOf('chat') !== -1
&& <div className = 'toolbar-button-with-badge'> && <div className = 'toolbar-button-with-badge'>
<ToolbarButton <ToolbarButton
@ -1258,6 +1258,15 @@ class Toolbox extends Component<Props, State> {
tooltip = { t('toolbar.chat') } /> tooltip = { t('toolbar.chat') } />
<ChatCounter /> <ChatCounter />
</div> } </div> }
{ buttonsLeft.indexOf('desktop') !== -1
&& this._renderDesktopSharingButton() }
{ buttonsLeft.indexOf('raisehand') !== -1
&& <ToolbarButton
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
icon = { IconRaisedHand }
onClick = { this._onToolbarToggleRaiseHand }
toggled = { _raisedHand }
tooltip = { t('toolbar.raiseHand') } /> }
{ {
buttonsLeft.indexOf('closedcaptions') !== -1 buttonsLeft.indexOf('closedcaptions') !== -1
&& <ClosedCaptionButton /> && <ClosedCaptionButton />

View File

@ -1,6 +1,7 @@
// @flow // @flow
import { hasAvailableDevices } from '../base/devices'; import { hasAvailableDevices } from '../base/devices';
import { isMobileBrowser } from '../base/environment/utils';
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
@ -43,8 +44,10 @@ export function isToolboxVisible(state: Object) {
visible visible
} = state['features/toolbox']; } = state['features/toolbox'];
const { audioSettingsVisible, videoSettingsVisible } = state['features/settings']; 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)); || audioSettingsVisible || videoSettingsVisible));
} }