- Change "features/chat" to support listening for new chat messages and storing them, removing that logic from conference.js. - Combine chat.scss and side_toolbar_container.css, and remove unused scss files. Chat is the only side panel so the two concepts have been merged. - Remove direct access to the chat feature from non-react and non-redux flows. - Modify the i18n translate function to take in an options object. By default the option "wait" is set to true, but that causes components to mount after the parent has been notified of an update, which means autoscrolling down to the latest rendered messages does not work. With "wait" set to false, the children will mount and then the parent will trigger componentDidUpdate. - Create react components for chat. Chat is the side panel plus the entiren chat feature. ChatInput is a child of Chat and is used for composing messages. ChatMessage displays one message and extends PureComponent to limit re-renders. - Fix a bug where the toolbar was not showing automatically when chat is closed and a new message is received. - Import react-transition-group to time the animation of the side panel showing/hiding and unmounting the Chat component. This gets around the issue of having to control autofocus if the component were always mounted and visibility toggled, but introduces not being able to store previous scroll state (without additional work or re-work).
308 lines
8.2 KiB
JavaScript
308 lines
8.2 KiB
JavaScript
// @flow
|
|
|
|
import _ from 'lodash';
|
|
import React, { Component } from 'react';
|
|
import { connect as reactReduxConnect } from 'react-redux';
|
|
|
|
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
|
|
|
|
import { obtainConfig } from '../../base/config';
|
|
import { connect, disconnect } from '../../base/connection';
|
|
import { translate } from '../../base/i18n';
|
|
import { Chat } from '../../chat';
|
|
import { Filmstrip } from '../../filmstrip';
|
|
import { CalleeInfoContainer } from '../../invite';
|
|
import { LargeVideo } from '../../large-video';
|
|
import { NotificationsContainer } from '../../notifications';
|
|
import {
|
|
LAYOUTS,
|
|
getCurrentLayout,
|
|
shouldDisplayTileView
|
|
} from '../../video-layout';
|
|
|
|
import { default as Notice } from './Notice';
|
|
import {
|
|
Toolbox,
|
|
fullScreenChanged,
|
|
setToolboxAlwaysVisible,
|
|
showToolbox
|
|
} from '../../toolbox';
|
|
|
|
import { maybeShowSuboptimalExperienceNotification } from '../functions';
|
|
|
|
declare var APP: Object;
|
|
declare var config: Object;
|
|
declare var interfaceConfig: Object;
|
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
|
|
/**
|
|
* DOM events for when full screen mode has changed. Different browsers need
|
|
* different vendor prefixes.
|
|
*
|
|
* @private
|
|
* @type {Array<string>}
|
|
*/
|
|
const FULL_SCREEN_EVENTS = [
|
|
'webkitfullscreenchange',
|
|
'mozfullscreenchange',
|
|
'fullscreenchange'
|
|
];
|
|
|
|
/**
|
|
* The CSS class to apply to the root element of the conference so CSS can
|
|
* modify the app layout.
|
|
*
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
const LAYOUT_CLASSNAMES = {
|
|
[LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip',
|
|
[LAYOUTS.TILE_VIEW]: 'tile-view',
|
|
[LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip'
|
|
};
|
|
|
|
/**
|
|
* The type of the React {@code Component} props of {@link Conference}.
|
|
*/
|
|
type Props = {
|
|
|
|
/**
|
|
* Whether the local participant is recording the conference.
|
|
*/
|
|
_iAmRecorder: boolean,
|
|
|
|
/**
|
|
* The CSS class to apply to the root of {@link Conference} to modify the
|
|
* application layout.
|
|
*/
|
|
_layoutClassName: string,
|
|
|
|
/**
|
|
* Conference room name.
|
|
*/
|
|
_room: string,
|
|
|
|
/**
|
|
* Whether or not the current UI layout should be in tile view.
|
|
*/
|
|
_shouldDisplayTileView: boolean,
|
|
|
|
dispatch: Function,
|
|
t: Function
|
|
}
|
|
|
|
/**
|
|
* The conference page of the Web application.
|
|
*/
|
|
class Conference extends Component<Props> {
|
|
_onFullScreenChange: Function;
|
|
_onShowToolbar: Function;
|
|
_originalOnShowToolbar: Function;
|
|
|
|
/**
|
|
* Initializes a new Conference instance.
|
|
*
|
|
* @param {Object} props - The read-only properties with which the new
|
|
* instance is to be initialized.
|
|
*/
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
// Throttle and bind this component's mousemove handler to prevent it
|
|
// from firing too often.
|
|
this._originalOnShowToolbar = this._onShowToolbar;
|
|
this._onShowToolbar = _.throttle(
|
|
() => this._originalOnShowToolbar(),
|
|
100,
|
|
{
|
|
leading: true,
|
|
trailing: false
|
|
});
|
|
|
|
// Bind event handler so it is only bound once for every instance.
|
|
this._onFullScreenChange = this._onFullScreenChange.bind(this);
|
|
}
|
|
|
|
/**
|
|
* Start the connection and get the UI ready for the conference.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
componentDidMount() {
|
|
const { configLocation } = config;
|
|
|
|
if (configLocation) {
|
|
obtainConfig(configLocation, this.props._room)
|
|
.then(() => {
|
|
const now = window.performance.now();
|
|
|
|
APP.connectionTimes['configuration.fetched'] = now;
|
|
logger.log('(TIME) configuration fetched:\t', now);
|
|
|
|
this._start();
|
|
})
|
|
.catch(err => {
|
|
logger.log(err);
|
|
|
|
// Show obtain config error.
|
|
APP.UI.messageHandler.showError({
|
|
descriptionKey: 'dialog.connectError',
|
|
titleKey: 'connection.CONNFAIL'
|
|
});
|
|
});
|
|
} else {
|
|
this._start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calls into legacy UI to update the application layout, if necessary.
|
|
*
|
|
* @inheritdoc
|
|
* returns {void}
|
|
*/
|
|
componentDidUpdate(prevProps) {
|
|
if (this.props._shouldDisplayTileView
|
|
=== prevProps._shouldDisplayTileView) {
|
|
return;
|
|
}
|
|
|
|
// TODO: For now VideoLayout is being called as LargeVideo and Filmstrip
|
|
// sizing logic is still handled outside of React. Once all components
|
|
// are in react they should calculate size on their own as much as
|
|
// possible and pass down sizings.
|
|
VideoLayout.refreshLayout();
|
|
}
|
|
|
|
/**
|
|
* Disconnect from the conference when component will be
|
|
* unmounted.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
componentWillUnmount() {
|
|
APP.UI.unregisterListeners();
|
|
APP.UI.unbindEvents();
|
|
|
|
FULL_SCREEN_EVENTS.forEach(name =>
|
|
document.removeEventListener(name, this._onFullScreenChange));
|
|
|
|
APP.conference.isJoined() && this.props.dispatch(disconnect());
|
|
}
|
|
|
|
/**
|
|
* Implements React's {@link Component#render()}.
|
|
*
|
|
* @inheritdoc
|
|
* @returns {ReactElement}
|
|
*/
|
|
render() {
|
|
const {
|
|
VIDEO_QUALITY_LABEL_DISABLED,
|
|
|
|
// XXX The character casing of the name filmStripOnly utilized by
|
|
// interfaceConfig is obsolete but legacy support is required.
|
|
filmStripOnly: filmstripOnly
|
|
} = interfaceConfig;
|
|
const hideVideoQualityLabel
|
|
= filmstripOnly
|
|
|| VIDEO_QUALITY_LABEL_DISABLED
|
|
|| this.props._iAmRecorder;
|
|
|
|
return (
|
|
<div
|
|
className = { this.props._layoutClassName }
|
|
id = 'videoconference_page'
|
|
onMouseMove = { this._onShowToolbar }>
|
|
<Notice />
|
|
<div id = 'videospace'>
|
|
<LargeVideo
|
|
hideVideoQualityLabel = { hideVideoQualityLabel } />
|
|
<Filmstrip filmstripOnly = { filmstripOnly } />
|
|
</div>
|
|
|
|
{ filmstripOnly || <Toolbox /> }
|
|
{ filmstripOnly || <Chat /> }
|
|
|
|
<NotificationsContainer />
|
|
|
|
<CalleeInfoContainer />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Updates the Redux state when full screen mode has been enabled or
|
|
* disabled.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onFullScreenChange() {
|
|
this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen()));
|
|
}
|
|
|
|
/**
|
|
* Displays the toolbar.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onShowToolbar() {
|
|
this.props.dispatch(showToolbox());
|
|
}
|
|
|
|
/**
|
|
* Until we don't rewrite UI using react components
|
|
* we use UI.start from old app. Also method translates
|
|
* component right after it has been mounted.
|
|
*
|
|
* @inheritdoc
|
|
*/
|
|
_start() {
|
|
APP.UI.start();
|
|
|
|
APP.UI.registerListeners();
|
|
APP.UI.bindEvents();
|
|
|
|
FULL_SCREEN_EVENTS.forEach(name =>
|
|
document.addEventListener(name, this._onFullScreenChange));
|
|
|
|
const { dispatch, t } = this.props;
|
|
|
|
dispatch(connect());
|
|
|
|
maybeShowSuboptimalExperienceNotification(dispatch, t);
|
|
|
|
interfaceConfig.filmStripOnly
|
|
&& dispatch(setToolboxAlwaysVisible(true));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps (parts of) the Redux state to the associated props for the
|
|
* {@code Conference} component.
|
|
*
|
|
* @param {Object} state - The Redux state.
|
|
* @private
|
|
* @returns {{
|
|
* _iAmRecorder: boolean,
|
|
* _layoutClassName: string,
|
|
* _room: ?string,
|
|
* _shouldDisplayTileView: boolean
|
|
* }}
|
|
*/
|
|
function _mapStateToProps(state) {
|
|
const currentLayout = getCurrentLayout(state);
|
|
|
|
return {
|
|
_iAmRecorder: state['features/base/config'].iAmRecorder,
|
|
_layoutClassName: LAYOUT_CLASSNAMES[currentLayout],
|
|
_room: state['features/base/conference'].room,
|
|
_shouldDisplayTileView: shouldDisplayTileView(state)
|
|
};
|
|
}
|
|
|
|
export default reactReduxConnect(_mapStateToProps)(translate(Conference));
|