* WebRTC Server side

* WebRTC client side

* Bug fixes and improvements

* Pushing UI improvements for webrtc (#3728)

* Pushing UI improvements for webrtc

* Updating webrtc css

* PLT-3943 WebRTC P1: bug fixes and improvements

* Video resolution set to std, reduce volume of ringtone and flip video horizontally

* Fix calling a user B while WebRTC RHS is still opened

* Leave RHS opened when call ends, Fix isBusy on popover and channel_header

* Fix pre-release feature, RHS & System Console

* PLT-3945 - Updating UI for webrtc (#3908)

* PLT-3943 Webrtc p1

* Add ongoing call indicator when RHS is opened

* UI updates to to webrtc notifcation (#3959)
Этот коммит содержится в:
enahum
2016-09-16 15:35:13 -03:00
коммит произвёл GitHub
родитель df2d61d941
Коммит 781ff323db
45 изменённых файлов: 4902 добавлений и 90 удалений

Просмотреть файл

@@ -264,7 +264,7 @@ export default class AdminSidebar extends React.Component {
title={
<FormattedMessage
id='admin.sidebar.webrtc'
defaultMessage='WebRTC'
defaultMessage='WebRTC (Beta)'
/>
}
/>

Просмотреть файл

@@ -15,10 +15,23 @@ export default class WebrtcSettings extends AdminSettings {
constructor(props) {
super(props);
this.canSave = this.canSave.bind(this);
this.handleAgreeChange = this.handleAgreeChange.bind(this);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
}
canSave() {
return !this.state.enableWebrtc || this.state.agree;
}
handleAgreeChange(e) {
this.setState({
agree: e.target.checked
});
}
getConfigFromState(config) {
config.WebrtcSettings.Enable = this.state.enableWebrtc;
config.WebrtcSettings.GatewayWebsocketUrl = this.state.gatewayWebsocketUrl;
@@ -44,7 +57,8 @@ export default class WebrtcSettings extends AdminSettings {
stunURI: settings.StunURI,
turnURI: settings.TurnURI,
turnUsername: settings.TurnUsername,
turnSharedKey: settings.TurnSharedKey
turnSharedKey: settings.TurnSharedKey,
agree: settings.Enable
};
}
@@ -53,13 +67,32 @@ export default class WebrtcSettings extends AdminSettings {
<h3>
<FormattedMessage
id='admin.integrations.webrtc'
defaultMessage='Mattermost WebRTC'
defaultMessage='Mattermost WebRTC (Beta)'
/>
</h3>
);
}
renderSettings() {
const tosCheckbox = (
<div className='form-group'>
<div className='col-sm-4'/>
<div className='col-sm-8'>
<input
type='checkbox'
ref='agree'
checked={this.state.agree}
onChange={this.handleAgreeChange}
disabled={!this.state.enableWebrtc}
/>
<FormattedHTMLMessage
id='admin.webrtc.agree'
defaultMessage=' I understand and accept the Mattermost Hosted WebRTC Service <a href="https://about.mattermost.com/webrtc-terms/" target="_blank">Terms of Service</a> and <a href="https://about.mattermost.com/webrtc-privacy/" target="_blank">Privacy Policy</a>.'
/>
</div>
</div>
);
return (
<SettingsGroup>
<BooleanSetting
@@ -79,6 +112,7 @@ export default class WebrtcSettings extends AdminSettings {
value={this.state.enableWebrtc}
onChange={this.handleChange}
/>
{tosCheckbox}
<TextSetting
id='gatewayWebsocketUrl'
label={
@@ -111,7 +145,7 @@ export default class WebrtcSettings extends AdminSettings {
helpText={
<FormattedMessage
id='admin.webrtc.gatewayAdminUrlDescription'
defaultMessage='Enter https://<mattermost-webrtc-gateway-url>:<port>. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.
defaultMessage='Enter https://<mattermost-webrtc-gateway-url>:<port>/admin. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.
Mattermost WebRTC uses this URL to obtain valid tokens for each peer to establish the connection.'
/>
}
@@ -167,7 +201,7 @@ export default class WebrtcSettings extends AdminSettings {
}
placeholder={Utils.localizeMessage('admin.webrtc.turnUriExample', 'Ex "turn:webrtc.mattermost.com:5349"')}
helpText={
<FormattedHTMLMessage
<FormattedMessage
id='admin.webrtc.turnUriDescription'
defaultMessage='Enter your TURN URI as turn:<your-turn-url>:<port>. TURN is a standardized network protocol to allow an end host to assist devices to establish a connection by using a relay public IP address if it is located behind a symmetric NAT.'
/>
@@ -186,7 +220,7 @@ export default class WebrtcSettings extends AdminSettings {
}
placeholder={Utils.localizeMessage('admin.webrtc.turnUsernameExample', 'Ex "myusername"')}
helpText={
<FormattedHTMLMessage
<FormattedMessage
id='admin.webrtc.turnUsernameDescription'
defaultMessage='Enter your TURN Server Username.'
/>
@@ -205,7 +239,7 @@ export default class WebrtcSettings extends AdminSettings {
}
placeholder={Utils.localizeMessage('admin.webrtc.turnSharedKeyExample', 'Ex "bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg="')}
helpText={
<FormattedHTMLMessage
<FormattedMessage
id='admin.webrtc.turnSharedKeyDescription'
defaultMessage='Enter your TURN Server Shared Key. This is used to created dynamic passwords to establish the connection. Each password is valid for a short period of time.'
/>

Просмотреть файл

@@ -21,21 +21,26 @@ import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as WebrtcActions from 'actions/webrtc_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx';
import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {getFlaggedPosts} from 'actions/post_actions.jsx';
import {ActionTypes, Constants, Preferences} from 'utils/constants.jsx';
import {ActionTypes, Constants, Preferences, UserStatuses} from 'utils/constants.jsx';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import {Tooltip, OverlayTrigger, Popover} from 'react-bootstrap';
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
export default class ChannelHeader extends React.Component {
constructor(props) {
super(props);
@@ -47,6 +52,8 @@ export default class ChannelHeader extends React.Component {
this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
this.openRecentMentions = this.openRecentMentions.bind(this);
this.getFlagged = this.getFlagged.bind(this);
this.initWebrtc = this.initWebrtc.bind(this);
this.onBusy = this.onBusy.bind(this);
const state = this.getStateFromStores();
state.showEditChannelPurposeModal = false;
@@ -64,7 +71,8 @@ export default class ChannelHeader extends React.Component {
users: extraInfo.members,
userCount: extraInfo.member_count,
currentUser: UserStore.getCurrentUser(),
enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true)
enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
isBusy: WebrtcStore.isBusy()
};
}
@@ -85,6 +93,9 @@ export default class ChannelHeader extends React.Component {
SearchStore.addSearchChangeListener(this.onListenerChange);
PreferenceStore.addChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
UserStore.addStatusesChangeListener(this.onListenerChange);
WebrtcStore.addChangedListener(this.onListenerChange);
WebrtcStore.addBusyListener(this.onBusy);
$('.sidebar--left .dropdown-menu').perfectScrollbar();
document.addEventListener('keydown', this.openRecentMentions);
}
@@ -95,6 +106,9 @@ export default class ChannelHeader extends React.Component {
SearchStore.removeSearchChangeListener(this.onListenerChange);
PreferenceStore.removeChangeListener(this.onListenerChange);
UserStore.removeChangeListener(this.onListenerChange);
UserStore.removeStatusesChangeListener(this.onListenerChange);
WebrtcStore.removeChangedListener(this.onListenerChange);
WebrtcStore.removeBusyListener(this.onBusy);
document.removeEventListener('keydown', this.openRecentMentions);
}
@@ -204,6 +218,17 @@ export default class ChannelHeader extends React.Component {
return true;
}
initWebrtc(contactId, isOnline) {
if (isOnline && !this.state.isBusy) {
GlobalActions.emitCloseRightHandSide();
WebrtcActions.initWebrtc(contactId, true);
}
}
onBusy(isBusy) {
this.setState({isBusy});
}
render() {
const flagIcon = Constants.FLAG_ICON_SVG;
@@ -250,10 +275,12 @@ export default class ChannelHeader extends React.Component {
const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
const isDirect = (this.state.channel.type === 'D');
let webrtc;
if (isDirect) {
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
let contact;
if (this.state.users.length > 1) {
let contact;
if (this.state.users[0].id === currentId) {
contact = this.state.users[1];
} else {
@@ -261,6 +288,68 @@ export default class ChannelHeader extends React.Component {
}
channelTitle = Utils.displayUsername(contact.id);
}
const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && global.mm_license.Webrtc === 'true' &&
global.mm_config.EnableDeveloper === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
if (webrtcEnabled) {
const isOffline = UserStore.getStatus(contact.id) === UserStatuses.OFFLINE;
const busy = this.state.isBusy;
let circleClass = '';
let webrtcMessage;
if (isOffline || busy) {
circleClass = 'offline';
if (busy) {
webrtcMessage = (
<FormattedMessage
id='channel_header.webrtc.unavailable'
defaultMessage='New call unavailable until your existing call ends'
/>
);
}
} else {
webrtcMessage = (
<FormattedMessage
id='channel_header.webrtc.call'
defaultMessage='Start Video Call'
/>
);
}
webrtc = (
<div className='webrtc__header'>
<a
href='#'
onClick={() => this.initWebrtc(contact.id, !isOffline)}
disabled={isOffline}
>
<svg
id='webrtc-btn'
className='webrtc__button'
xmlns='http://www.w3.org/2000/svg'
>
<circle
className={circleClass}
cx='16'
cy='16'
r='18'
>
<title>
{webrtcMessage}
</title>
</circle>
<path
className='off'
transform='scale(0.4), translate(17,16)'
d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
fill='white'
/>
</svg>
</a>
</div>
);
}
}
let channelTerm = (
@@ -541,6 +630,7 @@ export default class ChannelHeader extends React.Component {
<tr>
<th>
<div className='channel-header__info'>
{webrtc}
<div className='dropdown'>
<a
href='#'
@@ -564,8 +654,8 @@ export default class ChannelHeader extends React.Component {
<OverlayTrigger
trigger={'click'}
placement='bottom'
overlay={popoverContent}
rootClose={true}
overlay={popoverContent}
ref='headerOverlay'
>
<div

Просмотреть файл

@@ -1,11 +1,11 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
import {ActionTypes, WebrtcActionTypes} from 'utils/constants.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import ModalStore from 'stores/modal_store.jsx';
import UserStore from 'stores/user_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import {intlShape, injectIntl, FormattedMessage} from 'react-intl';
@@ -40,12 +40,18 @@ class LeaveTeamModal extends React.Component {
});
}
handleSubmit() {
GlobalActions.emitLeaveTeam();
handleSubmit(e) {
this.setState({
show: false
});
if (WebrtcStore.isBusy()) {
WebrtcStore.emitChanged({action: WebrtcActionTypes.IN_PROGRESS});
e.preventDefault();
return;
}
GlobalActions.emitLeaveTeam();
}
handleHide() {

Просмотреть файл

@@ -11,6 +11,17 @@ export default class LoadingScreen extends React.Component {
this.state = {};
}
render() {
let message = (
<FormattedMessage
id='loading_screen.loading'
defaultMessage='Loading'
/>
);
if (this.props.message) {
message = this.props.message;
}
return (
<div
className='loading-screen'
@@ -18,10 +29,7 @@ export default class LoadingScreen extends React.Component {
>
<div className='loading__content'>
<h3>
<FormattedMessage
id='loading_screen.loading'
defaultMessage='Loading'
/>
{message}
</h3>
<div className='round round-1'></div>
<div className='round round-2'></div>
@@ -36,5 +44,6 @@ LoadingScreen.defaultProps = {
position: 'relative'
};
LoadingScreen.propTypes = {
position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']),
message: React.PropTypes.node
};

Просмотреть файл

@@ -6,12 +6,13 @@ import * as GlobalActions from 'actions/global_actions.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import AboutBuildModal from './about_build_modal.jsx';
import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import Constants from 'utils/constants.jsx';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router/es6';
@@ -30,6 +31,8 @@ export default class NavbarDropdown extends React.Component {
this.renderCustomEmojiLink = this.renderCustomEmojiLink.bind(this);
this.handleClick = this.handleClick.bind(this);
this.state = {
showUserSettingsModal: false,
showAboutModal: false,
@@ -38,6 +41,13 @@ export default class NavbarDropdown extends React.Component {
};
}
handleClick(e) {
if (WebrtcStore.isBusy()) {
WebrtcStore.emitChanged({action: WebrtcActionTypes.IN_PROGRESS});
e.preventDefault();
}
}
handleAboutModal() {
this.setState({showAboutModal: true});
}
@@ -77,7 +87,10 @@ export default class NavbarDropdown extends React.Component {
return (
<li>
<Link to={'/' + this.props.teamName + '/emoji'}>
<Link
onClick={this.handleClick}
to={'/' + this.props.teamName + '/emoji'}
>
<FormattedMessage
id='navbar_dropdown.emoji'
defaultMessage='Custom Emoji'
@@ -198,7 +211,10 @@ export default class NavbarDropdown extends React.Component {
if (integrationsEnabled && (isAdmin || config.EnableOnlyAdminIntegrations !== 'true')) {
integrationsLink = (
<li>
<Link to={'/' + this.props.teamName + '/integrations'}>
<Link
to={'/' + this.props.teamName + '/integrations'}
onClick={this.handleClick}
>
<FormattedMessage
id='navbar_dropdown.integrations'
defaultMessage='Integrations'
@@ -213,6 +229,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<Link
to={'/admin_console'}
onClick={this.handleClick}
>
<FormattedMessage
id='navbar_dropdown.console'
@@ -231,6 +248,7 @@ export default class NavbarDropdown extends React.Component {
<Link
key='newTeam_a'
to='/create_team'
onClick={this.handleClick}
>
<FormattedMessage
id='navbar_dropdown.create'

Просмотреть файл

@@ -21,6 +21,9 @@ import ErrorBar from 'components/error_bar.jsx';
import SidebarRight from 'components/sidebar_right.jsx';
import SidebarRightMenu from 'components/sidebar_right_menu.jsx';
import Navbar from 'components/navbar.jsx';
import WebrtcSidebar from './webrtc/components/webrtc_sidebar.jsx';
import WebrtcNotification from './webrtc/components/webrtc_notification.jsx';
// Modals
import GetPostLinkModal from 'components/get_post_link_modal.jsx';
@@ -146,9 +149,11 @@ export default class NeedsTeam extends React.Component {
return (
<div className='channel-view'>
<ErrorBar/>
<WebrtcNotification/>
<div className='container-fluid'>
<SidebarRight/>
<SidebarRightMenu teamType={this.state.team.type}/>
<WebrtcSidebar/>
{content}
<GetPostLinkModal/>

Просмотреть файл

@@ -118,6 +118,10 @@ export default class Post extends React.Component {
return true;
}
if (nextProps.isBusy !== this.props.isBusy) {
return true;
}
return false;
}
render() {
@@ -246,6 +250,8 @@ export default class Post extends React.Component {
displayNameType={this.props.displayNameType}
useMilitaryTime={this.props.useMilitaryTime}
isFlagged={this.props.isFlagged}
status={this.props.status}
isBusy={this.props.isBusy}
/>
<PostBody
post={post}
@@ -282,5 +288,6 @@ Post.propTypes = {
isCommentMention: React.PropTypes.bool,
useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool,
status: React.PropTypes.string
status: React.PropTypes.string,
isBusy: React.PropTypes.bool
};

Просмотреть файл

@@ -24,6 +24,8 @@ export default class PostHeader extends React.Component {
<UserProfile
user={this.props.user}
displayNameType={this.props.displayNameType}
status={this.props.status}
isBusy={this.props.isBusy}
/>
);
let botIndicator;
@@ -98,5 +100,7 @@ PostHeader.propTypes = {
compactDisplay: React.PropTypes.bool,
displayNameType: React.PropTypes.string,
useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool.isRequired
isFlagged: React.PropTypes.bool.isRequired,
status: React.PropTypes.string,
isBusy: React.PropTypes.bool
};

Просмотреть файл

@@ -316,6 +316,7 @@ export default class PostList extends React.Component {
useMilitaryTime={this.props.useMilitaryTime}
isFlagged={isFlagged}
status={status}
isBusy={this.props.isBusy}
/>
);
@@ -585,5 +586,6 @@ PostList.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired,
isFocusPost: React.PropTypes.bool,
flaggedPosts: React.PropTypes.object,
statuses: React.PropTypes.object
statuses: React.PropTypes.object,
isBusy: React.PropTypes.bool
};

Просмотреть файл

@@ -9,6 +9,7 @@ import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import Constants from 'utils/constants.jsx';
const ScrollTypes = Constants.ScrollTypes;
@@ -26,6 +27,7 @@ export default class PostFocusView extends React.Component {
this.onStatusChange = this.onStatusChange.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onPostListScroll = this.onPostListScroll.bind(this);
this.onBusy = this.onBusy.bind(this);
const focusedPostId = PostStore.getFocusedPostId();
@@ -38,13 +40,14 @@ export default class PostFocusView extends React.Component {
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
let statuses;
if (channel && channel.type !== Constants.DM_CHANNEL) {
if (channel) {
statuses = Object.assign({}, UserStore.getStatuses());
}
this.state = {
postList: PostStore.filterPosts(focusedPostId, joinLeaveEnabled),
currentUser: UserStore.getCurrentUser(),
isBusy: WebrtcStore.isBusy(),
profiles,
statuses,
scrollType: ScrollTypes.POST,
@@ -64,6 +67,7 @@ export default class PostFocusView extends React.Component {
UserStore.addStatusesChangeListener(this.onStatusChange);
EmojiStore.addChangeListener(this.onEmojiChange);
PreferenceStore.addChangeListener(this.onPreferenceChange);
WebrtcStore.addBusyListener(this.onBusy);
}
componentWillUnmount() {
@@ -73,6 +77,7 @@ export default class PostFocusView extends React.Component {
UserStore.removeStatusesChangeListener(this.onStatusChange);
EmojiStore.removeChangeListener(this.onEmojiChange);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
WebrtcStore.removeBusyListener(this.onBusy);
}
onChannelChange() {
@@ -113,7 +118,7 @@ export default class PostFocusView extends React.Component {
onStatusChange() {
const channel = ChannelStore.getCurrent();
let statuses;
if (channel && channel.type !== Constants.DM_CHANNEL) {
if (channel) {
statuses = Object.assign({}, UserStore.getStatuses());
}
@@ -144,6 +149,10 @@ export default class PostFocusView extends React.Component {
this.setState({scrollType: ScrollTypes.FREE});
}
onBusy(isBusy) {
this.setState({isBusy});
}
render() {
const postsToHighlight = {};
postsToHighlight[this.state.scrollPostId] = true;
@@ -172,6 +181,7 @@ export default class PostFocusView extends React.Component {
emojis={this.state.emojis}
flaggedPosts={this.state.flaggedPosts}
statuses={this.state.statuses}
isBusy={this.state.isBusy}
/>
);
}

Просмотреть файл

@@ -8,6 +8,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PostStore from 'stores/post_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -30,6 +31,7 @@ export default class PostViewController extends React.Component {
this.onPostListScroll = this.onPostListScroll.bind(this);
this.onActivate = this.onActivate.bind(this);
this.onDeactivate = this.onDeactivate.bind(this);
this.onBusy = this.onBusy.bind(this);
const channel = props.channel;
let profiles = UserStore.getProfiles();
@@ -54,6 +56,7 @@ export default class PostViewController extends React.Component {
channel,
postList: PostStore.filterPosts(channel.id, joinLeaveEnabled),
currentUser: UserStore.getCurrentUser(),
isBusy: WebrtcStore.isBusy(),
profiles,
statuses,
atTop: PostStore.getVisibilityAtTop(channel.id),
@@ -140,6 +143,7 @@ export default class PostViewController extends React.Component {
PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostsViewJumpListener(this.onPostsViewJumpRequest);
ChannelStore.addLastViewedListener(this.onSetNewMessageIndicator);
WebrtcStore.addBusyListener(this.onBusy);
}
onDeactivate() {
@@ -149,6 +153,7 @@ export default class PostViewController extends React.Component {
PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostsViewJumpListener(this.onPostsViewJumpRequest);
ChannelStore.removeLastViewedListener(this.onSetNewMessageIndicator);
WebrtcStore.removeBusyListener(this.onBusy);
}
componentWillReceiveProps(nextProps) {
@@ -217,6 +222,10 @@ export default class PostViewController extends React.Component {
}
}
onBusy(isBusy) {
this.setState({isBusy});
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.active !== this.props.active) {
return true;
@@ -294,6 +303,10 @@ export default class PostViewController extends React.Component {
return true;
}
if (nextState.isBusy !== this.state.isBusy) {
return true;
}
return false;
}
@@ -326,6 +339,7 @@ export default class PostViewController extends React.Component {
lastViewed={this.state.lastViewed}
ownNewMessage={this.state.ownNewMessage}
statuses={this.state.statuses}
isBusy={this.state.isBusy}
/>
);
}

Просмотреть файл

@@ -36,7 +36,7 @@ export default class RhsHeaderPost extends React.Component {
handleBack(e) {
e.preventDefault();
if (this.props.fromSearch) {
if (this.props.fromSearch || this.props.isWebrtc) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SEARCH_TERM,
term: this.props.fromSearch,
@@ -82,6 +82,15 @@ export default class RhsHeaderPost extends React.Component {
/>
</Tooltip>
);
} else if (this.props.isWebrtc) {
backToResultsTooltip = (
<Tooltip id='backToResultsTooltip'>
<FormattedMessage
id='rhs_header.backToCallTooltip'
defaultMessage='Back to Call'
/>
</Tooltip>
);
}
const expandSidebarTooltip = (
@@ -102,7 +111,7 @@ export default class RhsHeaderPost extends React.Component {
</Tooltip>
);
if (this.props.fromSearch || this.props.fromFlaggedPosts) {
if (this.props.fromSearch || this.props.fromFlaggedPosts || this.props.isWebrtc) {
back = (
<a
href='#'
@@ -178,6 +187,7 @@ RhsHeaderPost.defaultProps = {
};
RhsHeaderPost.propTypes = {
isMentionSearch: React.PropTypes.bool,
isWebrtc: React.PropTypes.bool,
fromSearch: React.PropTypes.string,
fromFlaggedPosts: React.PropTypes.bool,
toggleSize: React.PropTypes.function,

Просмотреть файл

@@ -282,6 +282,7 @@ export default class RhsThread extends React.Component {
<RhsHeaderPost
fromFlaggedPosts={this.props.fromFlaggedPosts}
fromSearch={this.props.fromSearch}
isWebrtc={this.props.isWebrtc}
isMentionSearch={this.props.isMentionSearch}
toggleSize={this.props.toggleSize}
shrink={this.props.shrink}
@@ -362,6 +363,7 @@ RhsThread.defaultProps = {
RhsThread.propTypes = {
fromSearch: React.PropTypes.string,
fromFlaggedPosts: React.PropTypes.bool,
isWebrtc: React.PropTypes.bool,
isMentionSearch: React.PropTypes.bool,
currentUser: React.PropTypes.object.isRequired,
useMilitaryTime: React.PropTypes.bool.isRequired,

Просмотреть файл

@@ -5,11 +5,11 @@ import $ from 'jquery';
import SearchResults from './search_results.jsx';
import RhsThread from './rhs_thread.jsx';
import SearchStore from 'stores/search_store.jsx';
import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import {getFlaggedPosts} from 'actions/post_actions.jsx';
@@ -101,6 +101,8 @@ export default class SidebarRight extends React.Component {
}
componentDidUpdate() {
const isOpen = this.state.searchVisible || this.state.postRightVisible;
WebrtcStore.emitRhsChanged(isOpen);
this.doStrangeThings();
}
@@ -175,6 +177,7 @@ export default class SidebarRight extends React.Component {
<RhsThread
fromFlaggedPosts={this.state.fromFlaggedPosts}
fromSearch={this.state.fromSearch}
isWebrtc={WebrtcStore.isBusy()}
isMentionSearch={this.state.isMentionSearch}
currentUser={this.state.currentUser}
useMilitaryTime={this.state.useMilitaryTime}

Просмотреть файл

@@ -10,12 +10,13 @@ import AboutBuildModal from './about_build_modal.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {getFlaggedPosts} from 'actions/post_actions.jsx';
import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
const Preferences = Constants.Preferences;
@@ -33,6 +34,7 @@ export default class SidebarRightMenu extends React.Component {
super(props);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleAboutModal = this.handleAboutModal.bind(this);
this.searchMentions = this.searchMentions.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
@@ -47,6 +49,13 @@ export default class SidebarRightMenu extends React.Component {
this.state = state;
}
handleClick(e) {
if (WebrtcStore.isBusy()) {
WebrtcStore.emitChanged({action: WebrtcActionTypes.IN_PROGRESS});
e.preventDefault();
}
}
handleAboutModal() {
this.setState({showAboutModal: true});
}
@@ -254,6 +263,7 @@ export default class SidebarRightMenu extends React.Component {
<li>
<Link
to={'/admin_console'}
onClick={this.handleClick}
>
<i className='icon fa fa-wrench'></i>
<FormattedMessage

Просмотреть файл

@@ -4,22 +4,33 @@
import * as Utils from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
import UserStore from 'stores/user_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as WebrtcActions from 'actions/webrtc_actions.jsx';
import Constants from 'utils/constants.jsx';
const UserStatuses = Constants.UserStatuses;
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
import {Popover, OverlayTrigger} from 'react-bootstrap';
var id = 0;
function nextId() {
id = id + 1;
return id;
}
import {FormattedMessage} from 'react-intl';
import React from 'react';
export default class UserProfile extends React.Component {
constructor(props) {
super(props);
this.uniqueId = nextId();
this.initWebrtc = this.initWebrtc.bind(this);
this.state = {
currentUserId: UserStore.getCurrentId()
};
}
initWebrtc() {
if (this.props.status !== UserStatuses.OFFLINE && !WebrtcStore.isBusy()) {
GlobalActions.emitCloseRightHandSide();
WebrtcActions.initWebrtc(this.props.user.id, true);
}
}
shouldComponentUpdate(nextProps) {
@@ -43,6 +54,14 @@ export default class UserProfile extends React.Component {
return true;
}
if (nextProps.status !== this.props.status) {
return true;
}
if (nextProps.isBusy !== this.props.isBusy) {
return true;
}
return false;
}
@@ -68,6 +87,70 @@ export default class UserProfile extends React.Component {
return <div className='user-popover'>{name}</div>;
}
let webrtc;
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && global.mm_license.Webrtc === 'true' &&
global.mm_config.EnableDeveloper === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
if (webrtcEnabled && this.props.user.id !== this.state.currentUserId) {
const isOnline = this.props.status !== UserStatuses.OFFLINE;
let webrtcMessage;
let circleClass = 'offline';
if (isOnline && !this.props.isBusy) {
circleClass = '';
webrtcMessage = (
<FormattedMessage
id='user_profile.webrtc.call'
defaultMessage='Start Video Call'
/>
);
} else if (this.props.isBusy) {
webrtcMessage = (
<FormattedMessage
id='user_profile.webrtc.unavailable'
defaultMessage='New call unavailable until your existing call ends'
/>
);
}
webrtc = (
<div
className='webrtc__user-profile'
key='makeCall'
>
<a
href='#'
onClick={() => this.initWebrtc()}
disabled={!isOnline}
>
<svg
id='webrtc-btn'
className='webrtc__button'
xmlns='http://www.w3.org/2000/svg'
>
<circle
className={circleClass}
cx='16'
cy='16'
r='18'
>
<title>
{webrtcMessage}
</title>
</circle>
<path
className='off'
transform='scale(0.4), translate(17,16)'
d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
fill='white'
/>
</svg>
</a>
</div>
);
}
var dataContent = [];
dataContent.push(
<img
@@ -97,6 +180,8 @@ export default class UserProfile extends React.Component {
);
}
dataContent.push(webrtc);
if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) {
dataContent.push(
<div
@@ -150,5 +235,7 @@ UserProfile.propTypes = {
overwriteName: React.PropTypes.string,
overwriteImage: React.PropTypes.string,
disablePopover: React.PropTypes.bool,
displayNameType: React.PropTypes.string
displayNameType: React.PropTypes.string,
status: React.PropTypes.string,
isBusy: React.PropTypes.bool
};

Просмотреть файл

@@ -33,7 +33,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
}
getStateFromStores() {
const preReleaseFeaturesKeys = Object.keys(PreReleaseFeatures);
let preReleaseFeaturesKeys = Object.keys(PreReleaseFeatures);
const advancedSettings = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS);
const settings = {
send_on_ctrl_enter: PreferenceStore.get(
@@ -55,6 +55,13 @@ export default class AdvancedSettingsDisplay extends React.Component {
let enabledFeatures = 0;
for (const [name, value] of advancedSettings) {
const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && global.mm_license.Webrtc === 'true' &&
global.mm_config.EnableDeveloper === 'true';
if (!webrtcEnabled) {
preReleaseFeaturesKeys = preReleaseFeaturesKeys.filter((f) => f !== 'WEBRTC_PREVIEW');
}
for (const key of preReleaseFeaturesKeys) {
const feature = PreReleaseFeatures[key];
@@ -329,6 +336,13 @@ export default class AdvancedSettingsDisplay extends React.Component {
defaultMessage='Show experimental previews of link content, when available'
/>
);
case 'WEBRTC_PREVIEW':
return (
<FormattedMessage
id='user.settings.advance.webrtc_preview'
defaultMessage='Enable the ability to make and receive one-on-one WebRTC calls'
/>
);
default:
return null;
}

Просмотреть файл

@@ -0,0 +1,100 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from 'utils/constants.jsx';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import React from 'react';
export default class WebrtcHeader extends React.Component {
render() {
const title = (
<FormattedMessage
id='webrtc.header'
defaultMessage='Call with {username}'
values={{
username: this.props.username
}}
/>
);
const closeSidebarTooltip = (
<Tooltip id='closeSidebarTooltip'>
<FormattedMessage
id='rhs_header.closeTooltip'
defaultMessage='Close Sidebar'
/>
</Tooltip>
);
const expandSidebarTooltip = (
<Tooltip id='expandSidebarTooltip'>
<FormattedMessage
id='rhs_header.expandTooltip'
defaultMessage='Expand Sidebar'
/>
</Tooltip>
);
const shrinkSidebarTooltip = (
<Tooltip id='shrinkSidebarTooltip'>
<FormattedMessage
id='rhs_header.expandTooltip'
defaultMessage='Shrink Sidebar'
/>
</Tooltip>
);
return (
<div className='sidebar--right__header'>
<span className='sidebar--right__title'>{title}</span>
<div className='pull-right'>
<button
type='button'
className='sidebar--right__expand'
aria-label='Expand'
onClick={this.props.toggleSize}
>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={expandSidebarTooltip}
>
<i className='fa fa-expand'/>
</OverlayTrigger>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={shrinkSidebarTooltip}
>
<i className='fa fa-compress'/>
</OverlayTrigger>
</button>
<button
type='button'
className='sidebar--right__close'
aria-label='Close'
title='Close'
onClick={this.props.onClose}
>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={closeSidebarTooltip}
>
<i className='fa fa-sign-out'/>
</OverlayTrigger>
</button>
</div>
</div>
);
}
}
WebrtcHeader.propTypes = {
username: React.PropTypes.string.isRequired,
onClose: React.PropTypes.func.isRequired,
toggleSize: React.PropTypes.function
};

Просмотреть файл

@@ -0,0 +1,320 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Client from 'client/web_client.jsx';
import WebSocketClient from 'client/web_websocket_client.jsx';
import UserStore from 'stores/user_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as WebrtcActions from 'actions/webrtc_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import ring from 'images/ring.mp3';
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
export default class WebrtcNotification extends React.Component {
constructor() {
super();
this.mounted = false;
this.closeNotification = this.closeNotification.bind(this);
this.onIncomingCall = this.onIncomingCall.bind(this);
this.onCancelCall = this.onCancelCall.bind(this);
this.onRhs = this.onRhs.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleAnswer = this.handleAnswer.bind(this);
this.handleTimeout = this.handleTimeout.bind(this);
this.stopRinging = this.stopRinging.bind(this);
this.closeRightHandSide = this.closeRightHandSide.bind(this);
this.state = {
userCalling: null,
rhsOpened: false
};
}
componentDidMount() {
WebrtcStore.addNotifyListener(this.onIncomingCall);
WebrtcStore.addChangedListener(this.onCancelCall);
WebrtcStore.addRhsChangedListener(this.onRhs);
this.mounted = true;
}
componentWillUnmount() {
WebrtcStore.removeNotifyListener(this.onIncomingCall);
WebrtcStore.removeChangedListener(this.onCancelCall);
WebrtcStore.removeRhsChangedListener(this.onRhs);
if (this.refs.ring) {
this.refs.ring.removeListener('ended', this.handleTimeout);
}
this.mounted = false;
}
componentDidUpdate() {
if (this.state.userCalling) {
this.refs.ring.addEventListener('ended', this.handleTimeout);
}
}
closeNotification() {
this.setState({
userCalling: null
});
}
stopRinging() {
if (this.refs.ring) {
this.refs.ring.pause();
this.refs.ring.currentTime = 0;
}
this.setState({userCalling: null});
}
closeRightHandSide(e) {
e.preventDefault();
GlobalActions.emitCloseRightHandSide();
}
onIncomingCall(incoming) {
if (this.mounted) {
const userId = incoming.from_user_id;
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
const featureEnabled = Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
if (featureEnabled) {
if (WebrtcStore.isBusy()) {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.BUSY,
from_user_id: UserStore.getCurrentId(),
to_user_id: userId
});
this.stopRinging();
} else if (userMedia) {
WebrtcStore.setVideoCallWith(userId);
this.setState({
userCalling: UserStore.getProfile(userId)
});
} else {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.UNSUPPORTED,
from_user_id: UserStore.getCurrentId(),
to_user_id: userId
});
this.stopRinging();
}
} else {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.DISABLED,
from_user_id: UserStore.getCurrentId(),
to_user_id: userId
});
this.stopRinging();
}
}
}
onCancelCall(message) {
if (message && message.action !== WebrtcActionTypes.CANCEL) {
return;
} else if (message && message.action === WebrtcActionTypes.CANCEL && this.state.userCalling && message.from_user_id !== this.state.userCalling.id) {
return;
}
WebrtcStore.setVideoCallWith(null);
this.closeNotification();
}
onRhs(rhsOpened) {
this.setState({rhsOpened});
}
handleTimeout() {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.NO_ANSWER,
from_user_id: UserStore.getCurrentId(),
to_user_id: this.state.userCalling.id
});
this.onCancelCall();
}
handleAnswer(e) {
if (e) {
e.preventDefault();
}
const caller = this.state.userCalling;
if (caller) {
const callerId = caller.id;
const currentUserId = UserStore.getCurrentId();
const message = {
action: WebrtcActionTypes.ANSWER,
from_user_id: currentUserId,
to_user_id: callerId
};
GlobalActions.emitCloseRightHandSide();
WebrtcActions.initWebrtc(callerId, false);
WebSocketClient.sendMessage('webrtc', message);
// delay till next tick (this will give time to listen for events
setTimeout(() => {
//we switch from and to user to handle the event locally
message.from_user_id = callerId;
message.to_user_id = currentUserId;
WebrtcActions.handle(message);
}, 0);
this.closeNotification();
}
}
handleClose(e) {
if (e) {
e.preventDefault();
}
if (this.state.userCalling) {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.DECLINE,
from_user_id: UserStore.getCurrentId(),
to_user_id: this.state.userCalling.id
});
}
this.onCancelCall();
}
render() {
const user = this.state.userCalling;
if (user) {
const username = Utils.displayUsername(user.id);
const profileImgSrc = Client.getUsersRoute() + '/' + user.id + '/image?time=' + (user.update_at || new Date().getTime());
const profileImg = (
<img
className='user-popover__image'
src={profileImgSrc}
height='128'
width='128'
key='user-popover-image'
/>
);
const answerBtn = (
<svg
className='webrtc-icons__pickup'
xmlns='http://www.w3.org/2000/svg'
width='48'
height='48'
viewBox='-10 -10 68 68'
onClick={this.handleAnswer}
>
<circle
cx='24'
cy='24'
r='34'
>
<title>
<FormattedMessage
id='webrtc.notification.answer'
defaultMessage='Answer'
/>
</title>
</circle>
<path
transform='translate(-10,-10)'
fill='#fff'
d='M29.854,37.627c1.723,1.904 3.679,3.468 5.793,4.684l3.683,-3.334c0.469,-0.424 1.119,-0.517 1.669,-0.302c1.628,0.63 3.331,1.021 5.056,1.174c0.401,0.026 0.795,0.199 1.09,0.525c0.295,0.326 0.433,0.741 0.407,1.153l-0.279,5.593c-0.02,0.418 -0.199,0.817 -0.525,1.112c-0.326,0.296 -0.741,0.434 -1.159,0.413c-6.704,-0.504 -13.238,-3.491 -18.108,-8.87c-4.869,-5.38 -7.192,-12.179 -7.028,-18.899c0.015,-0.413 0.199,-0.817 0.526,-1.113c0.326,-0.295 0.74,-0.433 1.153,-0.407l5.593,0.279c0.407,0.02 0.812,0.193 1.107,0.519c0.29,0.32 0.428,0.735 0.413,1.137c-0.018,1.732 0.202,3.464 0.667,5.147c0.159,0.569 0.003,1.207 -0.466,1.631l-3.683,3.334c1.005,2.219 2.368,4.32 4.091,6.224Z'
/>
</svg>
);
const rejectBtn = (
<svg
className='webrtc-icons__cancel'
xmlns='http://www.w3.org/2000/svg'
width='48'
height='48'
viewBox='-10 -10 68 68'
onClick={this.handleClose}
>
<circle
cx='24'
cy='24'
r='34'
>
<title>
<FormattedMessage
id='webrtc.notification.decline'
defaultMessage='Decline'
/>
</title>
</circle>
<path
transform='scale(0.7), translate(11,10)'
d='M24 18c-3.21 0-6.3.5-9.2 1.44v6.21c0 .79-.46 1.47-1.12 1.8-1.95.98-3.74 2.23-5.33 3.7-.36.35-.85.57-1.4.57-.55 0-1.05-.22-1.41-.59L.59 26.18c-.37-.37-.59-.87-.59-1.42 0-.55.22-1.05.59-1.42C6.68 17.55 14.93 14 24 14s17.32 3.55 23.41 9.34c.37.36.59.87.59 1.42 0 .55-.22 1.05-.59 1.41l-4.95 4.95c-.36.36-.86.59-1.41.59-.54 0-1.04-.22-1.4-.57-1.59-1.47-3.38-2.72-5.33-3.7-.66-.33-1.12-1.01-1.12-1.8v-6.21C30.3 18.5 27.21 18 24 18z'
fill='white'
/>
</svg>
);
const msg = (
<div>
<FormattedMessage
id='webrtc.notification.incoming_call'
defaultMessage='{username} is calling you.'
values={{
username
}}
/>
<div
className='webrtc-buttons webrtc-icons active'
style={{marginTop: '5px'}}
>
{answerBtn}
{rejectBtn}
</div>
</div>
);
return (
<div className='webrtc-notification'>
<audio
ref='ring'
src={ring}
autoPlay={true}
/>
<div>
{profileImg}
</div>
{msg}
</div>
);
} else if (this.state.rhsOpened && WebrtcStore.isBusy()) {
return (
<div
className='webrtc__notification--rhs'
onClick={this.closeRightHandSide}
>
<i className='fa fa-phone'/>
<FormattedMessage
id='webrtc.notification.returnToCall'
defaultMessage='Return to ongoing call with {username}'
values={{
username: Utils.displayUsername(WebrtcStore.getVideoCallWith())
}}
/>
</div>
);
}
return <div/>;
}
}

Просмотреть файл

@@ -0,0 +1,133 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import WebrtcController from '../webrtc_controller.jsx';
import UserStore from 'stores/user_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
export default class SidebarRight extends React.Component {
constructor(props) {
super(props);
this.plScrolledToBottom = true;
this.onShrink = this.onShrink.bind(this);
this.toggleSize = this.toggleSize.bind(this);
this.onInitializeVideoCall = this.onInitializeVideoCall.bind(this);
this.doStrangeThings = this.doStrangeThings.bind(this);
this.state = {
expanded: false,
currentUser: UserStore.getCurrentUser(),
videoCallVisible: false,
isCaller: false,
videoCallWithUserId: null
};
}
componentDidMount() {
WebrtcStore.addInitListener(this.onInitializeVideoCall);
this.doStrangeThings();
}
componentWillUnmount() {
WebrtcStore.removeInitListener(this.onInitializeVideoCall);
}
shouldComponentUpdate(nextProps, nextState) {
return !Utils.areObjectsEqual(nextState, this.state);
}
doStrangeThings() {
// We should have a better way to do this stuff
// Hence the function name.
$('.app__body .inner-wrap').removeClass('move--right');
$('.app__body .inner-wrap').addClass('webrtc--show');
$('.app__body .sidebar--left').removeClass('move--right');
$('.app__body .webrtc').addClass('webrtc--show');
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
if (!this.state.videoCallVisible) {
$('.app__body .inner-wrap').removeClass('webrtc--show').removeClass('move--right');
$('.app__body .webrtc').removeClass('webrtc--show');
return (
<div></div>
);
}
return null;
}
componentDidUpdate() {
this.doStrangeThings();
}
onShrink() {
this.setState({expanded: false});
}
toggleSize(e) {
if (e) {
e.preventDefault();
}
this.setState({expanded: !this.state.expanded});
}
onInitializeVideoCall(userId, isCaller) {
let expanded = this.state.expanded;
if (userId === null) {
expanded = false;
}
this.setState({
videoCallVisible: (userId !== null),
isCaller,
videoCallWithUserId: userId,
expanded
});
if (userId !== null) {
this.forceUpdate();
}
}
render() {
let content = null;
let expandedClass = '';
if (this.state.expanded) {
expandedClass = 'sidebar--right--expanded';
}
if (this.state.videoCallVisible) {
content = (
<WebrtcController
currentUser={this.state.currentUser}
userId={this.state.videoCallWithUserId}
isCaller={this.state.isCaller}
expanded={this.state.expanded}
toggleSize={this.toggleSize}
/>
);
}
return (
<div
className={'sidebar--right webrtc ' + expandedClass}
id='sidebar-webrtc'
>
<div
onClick={this.onShrink}
className='sidebar--right__bg'
/>
<div className='sidebar-right-container'>
{content}
</div>
</div>
);
}
}

1214
webapp/components/webrtc/webrtc_controller.jsx Обычный файл

Разница между файлами не показана из-за своего большого размера Загрузить разницу