Backed out changeset c99243d545e2 (bug 1132301) Backed out changeset a2075595f6fd (bug 1132301) Backed out changeset a2b8f0ddd738 (bug 1132301) Backed out changeset e5d7fd205107 (bug 1132301) Backed out changeset 47a5d0ecb127 (bug 1132301)
300 lines
10 KiB
JavaScript
300 lines
10 KiB
JavaScript
/** @jsx React.DOM */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* jshint newcap:false */
|
|
/* global loop:true, React */
|
|
|
|
var loop = loop || {};
|
|
loop.roomViews = (function(mozL10n) {
|
|
"use strict";
|
|
|
|
var sharedActions = loop.shared.actions;
|
|
var sharedMixins = loop.shared.mixins;
|
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
|
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
|
|
var sharedViews = loop.shared.views;
|
|
|
|
/**
|
|
* ActiveRoomStore mixin.
|
|
* @type {Object}
|
|
*/
|
|
var ActiveRoomStoreMixin = {
|
|
mixins: [Backbone.Events],
|
|
|
|
propTypes: {
|
|
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
|
|
},
|
|
|
|
componentWillMount: function() {
|
|
this.listenTo(this.props.roomStore, "change:activeRoom",
|
|
this._onActiveRoomStateChanged);
|
|
},
|
|
|
|
componentWillUnmount: function() {
|
|
this.stopListening(this.props.roomStore);
|
|
},
|
|
|
|
_onActiveRoomStateChanged: function() {
|
|
// Only update the state if we're mounted, to avoid the problem where
|
|
// stopListening doesn't nuke the active listeners during a event
|
|
// processing.
|
|
if (this.isMounted()) {
|
|
this.setState(this.props.roomStore.getStoreState("activeRoom"));
|
|
}
|
|
},
|
|
|
|
getInitialState: function() {
|
|
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
|
return _.extend({
|
|
// Used by the UI showcase.
|
|
roomState: this.props.roomState || storeState.roomState
|
|
}, storeState);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Desktop room invitation view (overlay).
|
|
*/
|
|
var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView",
|
|
mixins: [ActiveRoomStoreMixin, React.addons.LinkedStateMixin],
|
|
|
|
propTypes: {
|
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
|
},
|
|
|
|
getInitialState: function() {
|
|
return {
|
|
copiedUrl: false,
|
|
newRoomName: "",
|
|
error: null,
|
|
};
|
|
},
|
|
|
|
componentWillMount: function() {
|
|
this.listenTo(this.props.roomStore, "change:error",
|
|
this.onRoomError);
|
|
},
|
|
|
|
componentWillUnmount: function() {
|
|
this.stopListening(this.props.roomStore);
|
|
},
|
|
|
|
handleTextareaKeyDown: function(event) {
|
|
// Submit the form as soon as the user press Enter in that field
|
|
// Note: We're using a textarea instead of a simple text input to display
|
|
// placeholder and entered text on two lines, to circumvent l10n
|
|
// rendering/UX issues for some locales.
|
|
if (event.which === 13) {
|
|
this.handleFormSubmit(event);
|
|
}
|
|
},
|
|
|
|
handleFormSubmit: function(event) {
|
|
event.preventDefault();
|
|
|
|
this.props.dispatcher.dispatch(new sharedActions.RenameRoom({
|
|
roomToken: this.state.roomToken,
|
|
newRoomName: this.state.newRoomName
|
|
}));
|
|
},
|
|
|
|
handleEmailButtonClick: function(event) {
|
|
event.preventDefault();
|
|
|
|
this.props.dispatcher.dispatch(
|
|
new sharedActions.EmailRoomUrl({roomUrl: this.state.roomUrl}));
|
|
},
|
|
|
|
handleCopyButtonClick: function(event) {
|
|
event.preventDefault();
|
|
|
|
this.props.dispatcher.dispatch(
|
|
new sharedActions.CopyRoomUrl({roomUrl: this.state.roomUrl}));
|
|
|
|
this.setState({copiedUrl: true});
|
|
},
|
|
|
|
onRoomError: function() {
|
|
// Only update the state if we're mounted, to avoid the problem where
|
|
// stopListening doesn't nuke the active listeners during a event
|
|
// processing.
|
|
if (this.isMounted()) {
|
|
this.setState({error: this.props.roomStore.getStoreState("error")});
|
|
}
|
|
},
|
|
|
|
render: function() {
|
|
var cx = React.addons.classSet;
|
|
return (
|
|
React.createElement("div", {className: "room-invitation-overlay"},
|
|
React.createElement("p", {className: cx({"error": !!this.state.error,
|
|
"error-display-area": true})},
|
|
mozL10n.get("rooms_name_change_failed_label")
|
|
),
|
|
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
|
React.createElement("textarea", {rows: "2", type: "text", className: "input-room-name",
|
|
valueLink: this.linkState("newRoomName"),
|
|
onBlur: this.handleFormSubmit,
|
|
onKeyDown: this.handleTextareaKeyDown,
|
|
placeholder: mozL10n.get("rooms_name_this_room_label")})
|
|
),
|
|
React.createElement("p", null, mozL10n.get("invite_header_text")),
|
|
React.createElement("div", {className: "btn-group call-action-group"},
|
|
React.createElement("button", {className: "btn btn-info btn-email",
|
|
onClick: this.handleEmailButtonClick},
|
|
mozL10n.get("email_link_button")
|
|
),
|
|
React.createElement("button", {className: "btn btn-info btn-copy",
|
|
onClick: this.handleCopyButtonClick},
|
|
this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
|
mozL10n.get("copy_url_button2")
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Desktop room conversation view.
|
|
*/
|
|
var DesktopRoomConversationView = React.createClass({displayName: "DesktopRoomConversationView",
|
|
mixins: [
|
|
ActiveRoomStoreMixin,
|
|
sharedMixins.DocumentTitleMixin,
|
|
sharedMixins.MediaSetupMixin,
|
|
sharedMixins.RoomsAudioMixin,
|
|
sharedMixins.WindowCloseMixin
|
|
],
|
|
|
|
propTypes: {
|
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
|
},
|
|
|
|
_renderInvitationOverlay: function() {
|
|
if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
|
|
return (
|
|
React.createElement(DesktopRoomInvitationView, {
|
|
roomStore: this.props.roomStore,
|
|
dispatcher: this.props.dispatcher})
|
|
);
|
|
}
|
|
return null;
|
|
},
|
|
|
|
componentWillUpdate: function(nextProps, nextState) {
|
|
// The SDK needs to know about the configuration and the elements to use
|
|
// for display. So the best way seems to pass the information here - ideally
|
|
// the sdk wouldn't need to know this, but we can't change that.
|
|
if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
|
|
nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
|
|
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
|
publisherConfig: this.getDefaultPublisherConfig({
|
|
publishVideo: !this.state.videoMuted
|
|
}),
|
|
getLocalElementFunc: this._getElement.bind(this, ".local"),
|
|
getScreenShareElementFunc: this._getElement.bind(this, ".screen"),
|
|
getRemoteElementFunc: this._getElement.bind(this, ".remote")
|
|
}));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* User clicked on the "Leave" button.
|
|
*/
|
|
leaveRoom: function() {
|
|
if (this.state.used) {
|
|
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
|
} else {
|
|
this.closeWindow();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Used to control publishing a stream - i.e. to mute a stream
|
|
*
|
|
* @param {String} type The type of stream, e.g. "audio" or "video".
|
|
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
|
*/
|
|
publishStream: function(type, enabled) {
|
|
this.props.dispatcher.dispatch(
|
|
new sharedActions.SetMute({
|
|
type: type,
|
|
enabled: enabled
|
|
}));
|
|
},
|
|
|
|
render: function() {
|
|
if (this.state.roomName) {
|
|
this.setTitle(this.state.roomName);
|
|
}
|
|
|
|
var localStreamClasses = React.addons.classSet({
|
|
local: true,
|
|
"local-stream": true,
|
|
"local-stream-audio": this.state.videoMuted,
|
|
"room-preview": this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS
|
|
});
|
|
|
|
var screenShareData = {
|
|
state: this.state.screenSharingState,
|
|
visible: true
|
|
};
|
|
|
|
switch(this.state.roomState) {
|
|
case ROOM_STATES.FAILED:
|
|
case ROOM_STATES.FULL: {
|
|
// Note: While rooms are set to hold a maximum of 2 participants, the
|
|
// FULL case should never happen on desktop.
|
|
return (
|
|
React.createElement(loop.conversationViews.GenericFailureView, {
|
|
cancelCall: this.closeWindow,
|
|
failureReason: this.state.failureReason})
|
|
);
|
|
}
|
|
case ROOM_STATES.ENDED: {
|
|
return (
|
|
React.createElement(sharedViews.FeedbackView, {
|
|
onAfterFeedbackReceived: this.closeWindow})
|
|
);
|
|
}
|
|
default: {
|
|
return (
|
|
React.createElement("div", {className: "room-conversation-wrapper"},
|
|
this._renderInvitationOverlay(),
|
|
React.createElement("div", {className: "video-layout-wrapper"},
|
|
React.createElement("div", {className: "conversation room-conversation"},
|
|
React.createElement("div", {className: "media nested"},
|
|
React.createElement("div", {className: "video_wrapper remote_wrapper"},
|
|
React.createElement("div", {className: "video_inner remote focus-stream"})
|
|
),
|
|
React.createElement("div", {className: localStreamClasses}),
|
|
React.createElement("div", {className: "screen hide"})
|
|
),
|
|
React.createElement(sharedViews.ConversationToolbar, {
|
|
dispatcher: this.props.dispatcher,
|
|
video: {enabled: !this.state.videoMuted, visible: true},
|
|
audio: {enabled: !this.state.audioMuted, visible: true},
|
|
publishStream: this.publishStream,
|
|
hangup: this.leaveRoom,
|
|
screenShare: screenShareData})
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
|
|
DesktopRoomConversationView: DesktopRoomConversationView,
|
|
DesktopRoomInvitationView: DesktopRoomInvitationView
|
|
};
|
|
|
|
})(document.mozL10n || navigator.mozL10n);
|