зеркало из https://github.com/mozilla/gecko-dev.git
Bug 990678 - Add ability to make audio only calls in Loop standalone and desktop. r=Standard8
This commit is contained in:
Родитель
e8f465a4f5
Коммит
0f09159ecf
|
@ -32,13 +32,13 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
window.addEventListener('click', this.clickHandler);
|
||||
window.addEventListener('blur', this._hideDeclineMenu);
|
||||
window.addEventListener("click", this.clickHandler);
|
||||
window.addEventListener("blur", this._hideDeclineMenu);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener('click', this.clickHandler);
|
||||
window.removeEventListener('blur', this._hideDeclineMenu);
|
||||
window.removeEventListener("click", this.clickHandler);
|
||||
window.removeEventListener("blur", this._hideDeclineMenu);
|
||||
},
|
||||
|
||||
clickHandler: function(e) {
|
||||
|
@ -48,8 +48,11 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
_handleAccept: function() {
|
||||
this.props.model.trigger("accept");
|
||||
_handleAccept: function(callType) {
|
||||
return () => {
|
||||
this.props.model.set("selectedCallType", callType);
|
||||
this.props.model.trigger("accept");
|
||||
};
|
||||
},
|
||||
|
||||
_handleDecline: function() {
|
||||
|
@ -74,15 +77,15 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
var btnClassAccept = "btn btn-success btn-accept";
|
||||
var btnClassAccept = "btn btn-success btn-accept call-audio-video";
|
||||
var btnClassBlock = "btn btn-error btn-block";
|
||||
var btnClassDecline = "btn btn-error btn-decline";
|
||||
var conversationPanelClass = "incoming-call " +
|
||||
loop.shared.utils.getTargetPlatform();
|
||||
var cx = React.addons.classSet;
|
||||
var declineDropdownMenuClasses = cx({
|
||||
var dropdownMenuClassesDecline = cx({
|
||||
"native-dropdown-menu": true,
|
||||
"decline-block-menu": true,
|
||||
"conversation-window-dropdown": true,
|
||||
"visually-hidden": !this.state.showDeclineMenu
|
||||
});
|
||||
return (
|
||||
|
@ -92,22 +95,36 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
React.DOM.div({className: "button-chevron-menu-group"},
|
||||
React.DOM.div({className: "button-group-chevron"},
|
||||
React.DOM.div({className: "button-group"},
|
||||
React.DOM.button({className: btnClassDecline, onClick: this._handleDecline},
|
||||
|
||||
React.DOM.button({className: btnClassDecline,
|
||||
onClick: this._handleDecline},
|
||||
__("incoming_call_decline_button")
|
||||
),
|
||||
React.DOM.div({className: "btn-chevron",
|
||||
onClick: this._toggleDeclineMenu}
|
||||
onClick: this._toggleDeclineMenu}
|
||||
)
|
||||
),
|
||||
React.DOM.ul({className: declineDropdownMenuClasses},
|
||||
|
||||
React.DOM.ul({className: dropdownMenuClassesDecline},
|
||||
React.DOM.li({className: "btn-block", onClick: this._handleDeclineBlock},
|
||||
__("incoming_call_decline_and_block_button")
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
),
|
||||
React.DOM.button({className: btnClassAccept, onClick: this._handleAccept},
|
||||
__("incoming_call_answer_button")
|
||||
|
||||
React.DOM.div({className: "button-chevron-menu-group"},
|
||||
React.DOM.div({className: "button-group"},
|
||||
React.DOM.button({className: btnClassAccept,
|
||||
onClick: this._handleAccept("audio-video")},
|
||||
__("incoming_call_answer_button")
|
||||
),
|
||||
React.DOM.div({className: "call-audio-only",
|
||||
onClick: this._handleAccept("audio"),
|
||||
title: __("incoming_call_answer_audio_only_tooltip")}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -181,9 +198,10 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
// We'll probably really want to be getting this data from the
|
||||
// background worker on the desktop client.
|
||||
// Bug 1032700 should fix this.
|
||||
this._conversation.setSessionData(sessionData[0]);
|
||||
this._conversation.setIncomingSessionData(sessionData[0]);
|
||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
||||
model: this._conversation
|
||||
model: this._conversation,
|
||||
video: {enabled: this._conversation.hasVideoStream("incoming")}
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
@ -213,7 +231,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
*/
|
||||
declineAndBlock: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
var token = navigator.mozLoop.getLoopCharPref('loopToken');
|
||||
var token = navigator.mozLoop.getLoopCharPref("loopToken");
|
||||
this._client.deleteCallUrl(token, function(error) {
|
||||
// XXX The conversation window will be closed when this cb is triggered
|
||||
// figure out if there is a better way to report the error to the user
|
||||
|
@ -235,10 +253,14 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
return;
|
||||
}
|
||||
|
||||
var callType = this._conversation.get("selectedCallType");
|
||||
var videoStream = callType === "audio" ? false : true;
|
||||
|
||||
/*jshint newcap:false*/
|
||||
this.loadReactComponent(sharedViews.ConversationView({
|
||||
sdk: OT,
|
||||
model: this._conversation
|
||||
model: this._conversation,
|
||||
video: {enabled: videoStream}
|
||||
}));
|
||||
},
|
||||
|
||||
|
|
|
@ -32,13 +32,13 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
window.addEventListener('click', this.clickHandler);
|
||||
window.addEventListener('blur', this._hideDeclineMenu);
|
||||
window.addEventListener("click", this.clickHandler);
|
||||
window.addEventListener("blur", this._hideDeclineMenu);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener('click', this.clickHandler);
|
||||
window.removeEventListener('blur', this._hideDeclineMenu);
|
||||
window.removeEventListener("click", this.clickHandler);
|
||||
window.removeEventListener("blur", this._hideDeclineMenu);
|
||||
},
|
||||
|
||||
clickHandler: function(e) {
|
||||
|
@ -48,8 +48,11 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
_handleAccept: function() {
|
||||
this.props.model.trigger("accept");
|
||||
_handleAccept: function(callType) {
|
||||
return () => {
|
||||
this.props.model.set("selectedCallType", callType);
|
||||
this.props.model.trigger("accept");
|
||||
};
|
||||
},
|
||||
|
||||
_handleDecline: function() {
|
||||
|
@ -74,15 +77,15 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
var btnClassAccept = "btn btn-success btn-accept";
|
||||
var btnClassAccept = "btn btn-success btn-accept call-audio-video";
|
||||
var btnClassBlock = "btn btn-error btn-block";
|
||||
var btnClassDecline = "btn btn-error btn-decline";
|
||||
var conversationPanelClass = "incoming-call " +
|
||||
loop.shared.utils.getTargetPlatform();
|
||||
var cx = React.addons.classSet;
|
||||
var declineDropdownMenuClasses = cx({
|
||||
var dropdownMenuClassesDecline = cx({
|
||||
"native-dropdown-menu": true,
|
||||
"decline-block-menu": true,
|
||||
"conversation-window-dropdown": true,
|
||||
"visually-hidden": !this.state.showDeclineMenu
|
||||
});
|
||||
return (
|
||||
|
@ -92,23 +95,37 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
<div className="button-chevron-menu-group">
|
||||
<div className="button-group-chevron">
|
||||
<div className="button-group">
|
||||
<button className={btnClassDecline} onClick={this._handleDecline}>
|
||||
|
||||
<button className={btnClassDecline}
|
||||
onClick={this._handleDecline}>
|
||||
{__("incoming_call_decline_button")}
|
||||
</button>
|
||||
<div className="btn-chevron"
|
||||
onClick={this._toggleDeclineMenu}>
|
||||
onClick={this._toggleDeclineMenu}>
|
||||
</div>
|
||||
</div>
|
||||
<ul className={declineDropdownMenuClasses}>
|
||||
|
||||
<ul className={dropdownMenuClassesDecline}>
|
||||
<li className="btn-block" onClick={this._handleDeclineBlock}>
|
||||
{__("incoming_call_decline_and_block_button")}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="button-chevron-menu-group">
|
||||
<div className="button-group">
|
||||
<button className={btnClassAccept}
|
||||
onClick={this._handleAccept("audio-video")}>
|
||||
{__("incoming_call_answer_button")}
|
||||
</button>
|
||||
<div className="call-audio-only"
|
||||
onClick={this._handleAccept("audio")}
|
||||
title={__("incoming_call_answer_audio_only_tooltip")} >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button className={btnClassAccept} onClick={this._handleAccept}>
|
||||
{__("incoming_call_answer_button")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -181,9 +198,10 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
// We'll probably really want to be getting this data from the
|
||||
// background worker on the desktop client.
|
||||
// Bug 1032700 should fix this.
|
||||
this._conversation.setSessionData(sessionData[0]);
|
||||
this._conversation.setIncomingSessionData(sessionData[0]);
|
||||
this.loadReactComponent(loop.conversation.IncomingCallView({
|
||||
model: this._conversation
|
||||
model: this._conversation,
|
||||
video: {enabled: this._conversation.hasVideoStream("incoming")}
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
@ -213,7 +231,7 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
*/
|
||||
declineAndBlock: function() {
|
||||
navigator.mozLoop.stopAlerting();
|
||||
var token = navigator.mozLoop.getLoopCharPref('loopToken');
|
||||
var token = navigator.mozLoop.getLoopCharPref("loopToken");
|
||||
this._client.deleteCallUrl(token, function(error) {
|
||||
// XXX The conversation window will be closed when this cb is triggered
|
||||
// figure out if there is a better way to report the error to the user
|
||||
|
@ -235,10 +253,14 @@ loop.conversation = (function(OT, mozL10n) {
|
|||
return;
|
||||
}
|
||||
|
||||
var callType = this._conversation.get("selectedCallType");
|
||||
var videoStream = callType === "audio" ? false : true;
|
||||
|
||||
/*jshint newcap:false*/
|
||||
this.loadReactComponent(sharedViews.ConversationView({
|
||||
sdk: OT,
|
||||
model: this._conversation
|
||||
model: this._conversation,
|
||||
video: {enabled: videoStream}
|
||||
}));
|
||||
},
|
||||
|
||||
|
|
|
@ -109,6 +109,11 @@ h1, h2, h3 {
|
|||
height: auto;
|
||||
}
|
||||
|
||||
.btn-large + .btn-chevron {
|
||||
padding: 1rem;
|
||||
height: 100%; /* match full height of button */
|
||||
}
|
||||
|
||||
/*
|
||||
* Left / Right padding elements
|
||||
* used to center components
|
||||
|
@ -133,17 +138,20 @@ h1, h2, h3 {
|
|||
border: 1px solid #006b9d;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
.btn-success,
|
||||
.btn-success + .btn-chevron {
|
||||
background-color: #74bf43;
|
||||
border: 1px solid #74bf43;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
.btn-success:hover,
|
||||
.btn-success + .btn-chevron:hover {
|
||||
background-color: #6cb23e;
|
||||
border: 1px solid #6cb23e;
|
||||
}
|
||||
|
||||
.btn-success:active {
|
||||
.btn-success:active,
|
||||
.btn-success + .btn-chevron:active {
|
||||
background-color: #64a43a;
|
||||
border: 1px solid #64a43a;
|
||||
}
|
||||
|
@ -234,6 +242,8 @@ h1, h2, h3 {
|
|||
|
||||
.button-group .btn {
|
||||
flex: 1;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
|
@ -292,24 +302,36 @@ h1, h2, h3 {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
.btn-large .icon {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
.icon,
|
||||
.icon-small,
|
||||
.icon-audio,
|
||||
.icon-video {
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
vertical-align: top;
|
||||
margin-left: 10px;
|
||||
background-position: 80% center;
|
||||
}
|
||||
|
||||
.icon-small {
|
||||
background-size: 10px;
|
||||
}
|
||||
|
||||
.icon-video {
|
||||
background-image: url("../img/video-inverse-14x14.png");
|
||||
}
|
||||
|
||||
.icon-audio {
|
||||
background-image: url("../img/audio-default-16x16@1.5x.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.icon-video {
|
||||
background-image: url("../img/video-inverse-14x14@2x.png");
|
||||
}
|
||||
|
||||
.icon-audio {
|
||||
background-image: url("../img/audio-default-16x16@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -194,6 +194,41 @@
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
.call-audio-only {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-left: 1px solid rgba(255,255,255,.4);
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
background-color: #74BF43;
|
||||
background-image: url("../img/audio-inverse-14x14.png");
|
||||
background-size: 1rem;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.call-audio-only:hover {
|
||||
background-color: #6cb23e;
|
||||
}
|
||||
|
||||
|
||||
.call-audio-video {
|
||||
background-image: url("../img/video-inverse-14x14.png");
|
||||
background-position: 96% center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1rem;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.call-audio-only {
|
||||
background-image: url("../img/audio-inverse-14x14@2x.png");
|
||||
}
|
||||
.call-audio-video {
|
||||
background-image: url("../img/video-inverse-14x14@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
/* Expired call url page */
|
||||
|
||||
.expired-url-info {
|
||||
|
@ -212,9 +247,16 @@
|
|||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* Block incoming call */
|
||||
/*
|
||||
* Dropdown menu hidden behind a chevron
|
||||
*
|
||||
* .native-dropdown-menu[-large-parent] Generic class, contains common styles
|
||||
* .standalone-dropdown-menu Initiate call dropdown menu
|
||||
* .conversation-window-dropdown Dropdown menu for answer/decline/block options
|
||||
*/
|
||||
|
||||
.native-dropdown-menu {
|
||||
.native-dropdown-menu,
|
||||
.native-dropdown-large-parent {
|
||||
/* Should match a native select menu */
|
||||
padding: 0;
|
||||
position: absolute; /* element can be wider than the parent */
|
||||
|
@ -226,19 +268,35 @@
|
|||
border-color: #aaa #111 #111 #aaa;
|
||||
}
|
||||
|
||||
.decline-block-menu li {
|
||||
padding: 0 10px 0 5px;
|
||||
list-style: none;
|
||||
font-size: .9em;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.decline-block-menu li:hover {
|
||||
color: #FFF;
|
||||
background: #111;
|
||||
/*
|
||||
* If the component is smaller than the parent
|
||||
* we need it to display block to occupy full width
|
||||
* Same as above but overrides apropriate styles
|
||||
*/
|
||||
.native-dropdown-large-parent {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.native-dropdown-menu li,
|
||||
.native-dropdown-large-parent li {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.native-dropdown-menu li:hover,
|
||||
.native-dropdown-large-parent li:hover,
|
||||
.native-dropdown-large-parent li:hover button {
|
||||
color: #fff;
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
.conversation-window-dropdown li {
|
||||
padding: 0 10px 0 5px;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
/* Expired call url page */
|
||||
|
||||
.expired-url-info {
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 424 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 536 B |
|
@ -14,17 +14,21 @@ loop.shared.models = (function() {
|
|||
*/
|
||||
var ConversationModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
connected: false, // Session connected flag
|
||||
ongoing: false, // Ongoing call flag
|
||||
callerId: undefined, // Loop caller id
|
||||
loopToken: undefined, // Loop conversation token
|
||||
loopVersion: undefined, // Loop version for /calls/ information. This
|
||||
// is the version received from the push
|
||||
// notification and is used by the server to
|
||||
// determine the pending calls
|
||||
sessionId: undefined, // OT session id
|
||||
sessionToken: undefined, // OT session token
|
||||
apiKey: undefined // OT api key
|
||||
connected: false, // Session connected flag
|
||||
ongoing: false, // Ongoing call flag
|
||||
callerId: undefined, // Loop caller id
|
||||
loopToken: undefined, // Loop conversation token
|
||||
loopVersion: undefined, // Loop version for /calls/ information. This
|
||||
// is the version received from the push
|
||||
// notification and is used by the server to
|
||||
// determine the pending calls
|
||||
sessionId: undefined, // OT session id
|
||||
sessionToken: undefined, // OT session token
|
||||
apiKey: undefined, // OT api key
|
||||
callType: undefined, // The type of incoming call selected by
|
||||
// other peer ("audio" or "audio-video")
|
||||
selectedCallType: undefined // The selected type for the call that was
|
||||
// initiated ("audio" or "audio-video")
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -114,7 +118,7 @@ loop.shared.models = (function() {
|
|||
this._pendingCallTimer = setTimeout(
|
||||
handleOutgoingCallTimeout.bind(this), this.pendingCallTimeout);
|
||||
|
||||
this.setSessionData(sessionData);
|
||||
this.setOutgoingSessionData(sessionData);
|
||||
this.trigger("call:outgoing");
|
||||
},
|
||||
|
||||
|
@ -129,10 +133,11 @@ loop.shared.models = (function() {
|
|||
|
||||
/**
|
||||
* Sets session information.
|
||||
* Session data received by creating an outgoing call.
|
||||
*
|
||||
* @param {Object} sessionData Conversation session information.
|
||||
*/
|
||||
setSessionData: function(sessionData) {
|
||||
setOutgoingSessionData: function(sessionData) {
|
||||
// Explicit property assignment to prevent later "surprises"
|
||||
this.set({
|
||||
sessionId: sessionData.sessionId,
|
||||
|
@ -141,6 +146,21 @@ loop.shared.models = (function() {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets session information about the incoming call.
|
||||
*
|
||||
* @param {Object} sessionData Conversation session information.
|
||||
*/
|
||||
setIncomingSessionData: function(sessionData) {
|
||||
// Explicit property assignment to prevent later "surprises"
|
||||
this.set({
|
||||
sessionId: sessionData.sessionId,
|
||||
sessionToken: sessionData.sessionToken,
|
||||
apiKey: sessionData.apiKey,
|
||||
callType: sessionData.callType || "audio-video"
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a SDK session and subscribe to call events.
|
||||
*/
|
||||
|
@ -169,6 +189,22 @@ loop.shared.models = (function() {
|
|||
.once("session:ended", this.stopListening, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to determine if video stream is available for the
|
||||
* incoming or outgoing call
|
||||
*
|
||||
* @param {string} callType Incoming or outgoing call
|
||||
*/
|
||||
hasVideoStream: function(callType) {
|
||||
if (callType === "incoming") {
|
||||
return this.get("callType") === "audio-video";
|
||||
}
|
||||
if (callType === "outgoing") {
|
||||
return this.get("selectedCallType") === "audio-video";
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a loop-server error, which has an optional `errno` property which
|
||||
* is server error identifier.
|
||||
|
|
|
@ -217,13 +217,24 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
}
|
||||
},
|
||||
|
||||
getInitialProps: function() {
|
||||
return {
|
||||
video: {enabled: true},
|
||||
audio: {enabled: true}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
video: {enabled: false},
|
||||
audio: {enabled: false}
|
||||
video: this.props.video,
|
||||
audio: this.props.audio
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.publisherConfig.publishVideo = this.props.video.enabled;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.listenTo(this.props.model, "session:connected",
|
||||
this.startPublishing);
|
||||
|
|
|
@ -217,13 +217,24 @@ loop.shared.views = (function(_, OT, l10n) {
|
|||
}
|
||||
},
|
||||
|
||||
getInitialProps: function() {
|
||||
return {
|
||||
video: {enabled: true},
|
||||
audio: {enabled: true}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
video: {enabled: false},
|
||||
audio: {enabled: false}
|
||||
video: this.props.video,
|
||||
audio: this.props.audio
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.publisherConfig.publishVideo = this.props.video.enabled;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.listenTo(this.props.model, "session:connected",
|
||||
this.startPublishing);
|
||||
|
|
|
@ -124,3 +124,39 @@ header {
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
.start-audio-only-call,
|
||||
.start-audio-video-call {
|
||||
background-color: none;
|
||||
background-image: url("../shared/img/audio-default-16x16@1.5x.png");
|
||||
background-position: 80% center;
|
||||
background-size: 10px;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.start-audio-only-call {
|
||||
border: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.start-audio-only-call:hover {
|
||||
background-image: url("../shared/img/audio-inverse-14x14.png");
|
||||
}
|
||||
|
||||
.start-audio-video-call {
|
||||
background-size: 20px;
|
||||
background-image: url("../shared/img/video-inverse-14x14.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.start-audio-only-call {
|
||||
background-image: url("../shared/img/audio-default-16x16@2x.png");
|
||||
}
|
||||
.start-audio-only-call:hover {
|
||||
background-image: url("../shared/img/audio-inverse-14x14@2x.png");
|
||||
}
|
||||
.start-audio-video-call {
|
||||
background-image: url("../shared/img/video-inverse-14x14@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,7 +144,8 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
getInitialState: function() {
|
||||
return {
|
||||
urlCreationDateString: '',
|
||||
disableCallButton: false
|
||||
disableCallButton: false,
|
||||
showCallOptionsMenu: false
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -157,6 +158,8 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// Listen for events & hide dropdown menu if user clicks away
|
||||
window.addEventListener("click", this.clickHandler);
|
||||
this.props.model.listenTo(this.props.model, "session:error",
|
||||
this._onSessionError);
|
||||
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
|
||||
|
@ -173,10 +176,18 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
|
||||
/**
|
||||
* Initiates the call.
|
||||
* Takes in a call type parameter "audio" or "audio-video" and returns
|
||||
* a function that initiates the call. React click handler requires a function
|
||||
* to be called when that event happenes.
|
||||
*
|
||||
* @param {string} User call type choice "audio" or "audio-video"
|
||||
*/
|
||||
_initiateOutgoingCall: function() {
|
||||
this.setState({disableCallButton: true});
|
||||
this.props.model.setupOutgoingCall();
|
||||
_initiateOutgoingCall: function(callType) {
|
||||
return function() {
|
||||
this.props.model.set("selectedCallType", callType);
|
||||
this.setState({disableCallButton: true});
|
||||
this.props.model.setupOutgoingCall();
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
_setConversationTimestamp: function(err, callUrlInfo) {
|
||||
|
@ -191,6 +202,22 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("click", this.clickHandler);
|
||||
},
|
||||
|
||||
clickHandler: function(e) {
|
||||
if (!e.target.classList.contains('btn-chevron') &&
|
||||
this.state.showCallOptionsMenu) {
|
||||
this._toggleCallOptionsMenu();
|
||||
}
|
||||
},
|
||||
|
||||
_toggleCallOptionsMenu: function() {
|
||||
var state = this.state.showCallOptionsMenu;
|
||||
this.setState({showCallOptionsMenu: !state});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var tos_link_name = __("terms_of_use_link_text");
|
||||
var privacy_notice_name = __("privacy_notice_link_text");
|
||||
|
@ -202,8 +229,14 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
"https://www.mozilla.org/privacy/'>" + privacy_notice_name + "</a>"
|
||||
});
|
||||
|
||||
var callButtonClasses = "btn btn-success btn-large " +
|
||||
var btnClassStartCall = "btn btn-large btn-success " +
|
||||
"start-audio-video-call " +
|
||||
loop.shared.utils.getTargetPlatform();
|
||||
var dropdownMenuClasses = React.addons.classSet({
|
||||
"native-dropdown-large-parent": true,
|
||||
"standalone-dropdown-menu": true,
|
||||
"visually-hidden": !this.state.showCallOptionsMenu
|
||||
});
|
||||
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
|
@ -221,11 +254,37 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
|
||||
React.DOM.div({className: "button-group"},
|
||||
React.DOM.div({className: "flex-padding-1"}),
|
||||
React.DOM.button({ref: "submitButton", onClick: this._initiateOutgoingCall,
|
||||
className: callButtonClasses,
|
||||
disabled: this.state.disableCallButton},
|
||||
__("initiate_call_button"),
|
||||
React.DOM.i({className: "icon icon-video"})
|
||||
React.DOM.div({className: "button-chevron-menu-group"},
|
||||
React.DOM.div({className: "button-group-chevron"},
|
||||
React.DOM.div({className: "button-group"},
|
||||
|
||||
React.DOM.button({className: btnClassStartCall,
|
||||
onClick: this._initiateOutgoingCall("audio-video"),
|
||||
disabled: this.state.disableCallButton,
|
||||
title: __("initiate_audio_video_call_tooltip")},
|
||||
__("initiate_audio_video_call_button")
|
||||
),
|
||||
|
||||
React.DOM.div({className: "btn-chevron",
|
||||
onClick: this._toggleCallOptionsMenu}
|
||||
)
|
||||
|
||||
),
|
||||
|
||||
React.DOM.ul({className: dropdownMenuClasses},
|
||||
React.DOM.li(null,
|
||||
/*
|
||||
Button required for disabled state.
|
||||
*/
|
||||
React.DOM.button({className: "start-audio-only-call",
|
||||
onClick: this._initiateOutgoingCall("audio"),
|
||||
disabled: this.state.disableCallButton},
|
||||
__("initiate_audio_call_button")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
),
|
||||
React.DOM.div({className: "flex-padding-1"})
|
||||
),
|
||||
|
@ -280,12 +339,12 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
var callType = this._conversation.get("selectedCallType");
|
||||
|
||||
this._conversation.once("call:outgoing", this.startCall, this);
|
||||
|
||||
// XXX For now, we assume both audio and video as there is no
|
||||
// other option to select (bug 1048333)
|
||||
this._client.requestCallInfo(this._conversation.get("loopToken"), "audio-video",
|
||||
function(err, sessionData) {
|
||||
this._client.requestCallInfo(this._conversation.get("loopToken"),
|
||||
callType, function(err, sessionData) {
|
||||
if (err) {
|
||||
switch (err.errno) {
|
||||
// loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
|
||||
|
@ -389,7 +448,8 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
}
|
||||
this.loadReactComponent(sharedViews.ConversationView({
|
||||
sdk: OT,
|
||||
model: this._conversation
|
||||
model: this._conversation,
|
||||
video: {enabled: this._conversation.hasVideoStream("outgoing")}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -144,7 +144,8 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
getInitialState: function() {
|
||||
return {
|
||||
urlCreationDateString: '',
|
||||
disableCallButton: false
|
||||
disableCallButton: false,
|
||||
showCallOptionsMenu: false
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -157,6 +158,8 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// Listen for events & hide dropdown menu if user clicks away
|
||||
window.addEventListener("click", this.clickHandler);
|
||||
this.props.model.listenTo(this.props.model, "session:error",
|
||||
this._onSessionError);
|
||||
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
|
||||
|
@ -173,10 +176,18 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
|
||||
/**
|
||||
* Initiates the call.
|
||||
* Takes in a call type parameter "audio" or "audio-video" and returns
|
||||
* a function that initiates the call. React click handler requires a function
|
||||
* to be called when that event happenes.
|
||||
*
|
||||
* @param {string} User call type choice "audio" or "audio-video"
|
||||
*/
|
||||
_initiateOutgoingCall: function() {
|
||||
this.setState({disableCallButton: true});
|
||||
this.props.model.setupOutgoingCall();
|
||||
_initiateOutgoingCall: function(callType) {
|
||||
return function() {
|
||||
this.props.model.set("selectedCallType", callType);
|
||||
this.setState({disableCallButton: true});
|
||||
this.props.model.setupOutgoingCall();
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
_setConversationTimestamp: function(err, callUrlInfo) {
|
||||
|
@ -191,6 +202,22 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("click", this.clickHandler);
|
||||
},
|
||||
|
||||
clickHandler: function(e) {
|
||||
if (!e.target.classList.contains('btn-chevron') &&
|
||||
this.state.showCallOptionsMenu) {
|
||||
this._toggleCallOptionsMenu();
|
||||
}
|
||||
},
|
||||
|
||||
_toggleCallOptionsMenu: function() {
|
||||
var state = this.state.showCallOptionsMenu;
|
||||
this.setState({showCallOptionsMenu: !state});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var tos_link_name = __("terms_of_use_link_text");
|
||||
var privacy_notice_name = __("privacy_notice_link_text");
|
||||
|
@ -202,8 +229,14 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
"https://www.mozilla.org/privacy/'>" + privacy_notice_name + "</a>"
|
||||
});
|
||||
|
||||
var callButtonClasses = "btn btn-success btn-large " +
|
||||
var btnClassStartCall = "btn btn-large btn-success " +
|
||||
"start-audio-video-call " +
|
||||
loop.shared.utils.getTargetPlatform();
|
||||
var dropdownMenuClasses = React.addons.classSet({
|
||||
"native-dropdown-large-parent": true,
|
||||
"standalone-dropdown-menu": true,
|
||||
"visually-hidden": !this.state.showCallOptionsMenu
|
||||
});
|
||||
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
|
@ -221,12 +254,38 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
|
||||
<div className="button-group">
|
||||
<div className="flex-padding-1"></div>
|
||||
<button ref="submitButton" onClick={this._initiateOutgoingCall}
|
||||
className={callButtonClasses}
|
||||
disabled={this.state.disableCallButton}>
|
||||
{__("initiate_call_button")}
|
||||
<i className="icon icon-video"></i>
|
||||
</button>
|
||||
<div className="button-chevron-menu-group">
|
||||
<div className="button-group-chevron">
|
||||
<div className="button-group">
|
||||
|
||||
<button className={btnClassStartCall}
|
||||
onClick={this._initiateOutgoingCall("audio-video")}
|
||||
disabled={this.state.disableCallButton}
|
||||
title={__("initiate_audio_video_call_tooltip")} >
|
||||
{__("initiate_audio_video_call_button")}
|
||||
</button>
|
||||
|
||||
<div className="btn-chevron"
|
||||
onClick={this._toggleCallOptionsMenu}>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ul className={dropdownMenuClasses}>
|
||||
<li>
|
||||
{/*
|
||||
Button required for disabled state.
|
||||
*/}
|
||||
<button className="start-audio-only-call"
|
||||
onClick={this._initiateOutgoingCall("audio")}
|
||||
disabled={this.state.disableCallButton} >
|
||||
{__("initiate_audio_call_button")}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-padding-1"></div>
|
||||
</div>
|
||||
|
||||
|
@ -280,12 +339,12 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
var callType = this._conversation.get("selectedCallType");
|
||||
|
||||
this._conversation.once("call:outgoing", this.startCall, this);
|
||||
|
||||
// XXX For now, we assume both audio and video as there is no
|
||||
// other option to select (bug 1048333)
|
||||
this._client.requestCallInfo(this._conversation.get("loopToken"), "audio-video",
|
||||
function(err, sessionData) {
|
||||
this._client.requestCallInfo(this._conversation.get("loopToken"),
|
||||
callType, function(err, sessionData) {
|
||||
if (err) {
|
||||
switch (err.errno) {
|
||||
// loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
|
||||
|
@ -389,7 +448,8 @@ loop.webapp = (function($, _, OT, webL10n) {
|
|||
}
|
||||
this.loadReactComponent(sharedViews.ConversationView({
|
||||
sdk: OT,
|
||||
model: this._conversation
|
||||
model: this._conversation,
|
||||
video: {enabled: this._conversation.hasVideoStream("outgoing")}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -25,7 +25,9 @@ promote_firefox_hello_heading=Download Firefox to make free audio and video call
|
|||
get_firefox_button=Get Firefox
|
||||
call_url_unavailable_notification=This URL is unavailable.
|
||||
initiate_call_button_label=Click Call to start a video chat
|
||||
initiate_call_button=Call
|
||||
initiate_audio_video_call_button=Call
|
||||
initiate_audio_video_call_tooltip=Start a video call
|
||||
initiate_audio_call_button=Voice call
|
||||
## LOCALIZATION NOTE (legal_text_and_links): In this item, don't translate the
|
||||
## part between {{..}}
|
||||
legal_text_and_links=By using this product you agree to the {{terms_of_use_url}} and {{privacy_notice_url}}
|
||||
|
|
|
@ -119,7 +119,8 @@ describe("loop.conversation", function() {
|
|||
pendingCallTimeout: 1000,
|
||||
});
|
||||
sandbox.stub(client, "requestCallsInfo");
|
||||
sandbox.stub(conversation, "setSessionData");
|
||||
sandbox.stub(conversation, "setIncomingSessionData");
|
||||
sandbox.stub(conversation, "setOutgoingSessionData");
|
||||
});
|
||||
|
||||
describe("Routes", function() {
|
||||
|
@ -192,7 +193,8 @@ describe("loop.conversation", function() {
|
|||
fakeSessionData = {
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey"
|
||||
apiKey: "apiKey",
|
||||
callType: "callType"
|
||||
};
|
||||
|
||||
client.requestCallsInfo.callsArgWith(1, null, [fakeSessionData]);
|
||||
|
@ -201,17 +203,31 @@ describe("loop.conversation", function() {
|
|||
it("should store the session data", function() {
|
||||
router.incoming(42);
|
||||
|
||||
sinon.assert.calledOnce(conversation.setSessionData);
|
||||
sinon.assert.calledWithExactly(conversation.setSessionData,
|
||||
sinon.assert.calledOnce(conversation.setIncomingSessionData);
|
||||
sinon.assert.calledWithExactly(conversation.setIncomingSessionData,
|
||||
fakeSessionData);
|
||||
});
|
||||
|
||||
it("should call the view with video.enabled=false", function() {
|
||||
sandbox.stub(conversation, "get").withArgs("callType").returns("audio");
|
||||
router.incoming("fakeVersion");
|
||||
|
||||
sinon.assert.calledOnce(conversation.get);
|
||||
sinon.assert.calledOnce(loop.conversation.IncomingCallView);
|
||||
sinon.assert.calledWithExactly(loop.conversation.IncomingCallView,
|
||||
{model: conversation,
|
||||
video: {enabled: false}});
|
||||
});
|
||||
|
||||
it("should display the incoming call view", function() {
|
||||
sandbox.stub(conversation, "get").withArgs("callType")
|
||||
.returns("audio-video");
|
||||
router.incoming("fakeVersion");
|
||||
|
||||
sinon.assert.calledOnce(loop.conversation.IncomingCallView);
|
||||
sinon.assert.calledWithExactly(loop.conversation.IncomingCallView,
|
||||
{model: conversation});
|
||||
{model: conversation,
|
||||
video: {enabled: true}});
|
||||
sinon.assert.calledOnce(router.loadReactComponent);
|
||||
sinon.assert.calledWith(router.loadReactComponent,
|
||||
sinon.match(function(value) {
|
||||
|
@ -439,9 +455,32 @@ describe("loop.conversation", function() {
|
|||
|
||||
TestUtils.Simulate.click(buttonAccept);
|
||||
|
||||
sinon.assert.calledOnce(model.trigger);
|
||||
/* Setting a model property triggers 2 events */
|
||||
sinon.assert.calledThrice(model.trigger);
|
||||
sinon.assert.calledWith(model.trigger, "accept");
|
||||
});
|
||||
sinon.assert.calledWith(model.trigger, "change:selectedCallType");
|
||||
sinon.assert.calledWith(model.trigger, "change");
|
||||
});
|
||||
|
||||
it("should set selectedCallType to audio-video", function() {
|
||||
var buttonAccept = view.getDOMNode().querySelector(".call-audio-video");
|
||||
sandbox.stub(model, "set");
|
||||
|
||||
TestUtils.Simulate.click(buttonAccept);
|
||||
|
||||
sinon.assert.calledOnce(model.set);
|
||||
sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio-video");
|
||||
});
|
||||
|
||||
it("should set selectedCallType to audio", function() {
|
||||
var buttonAccept = view.getDOMNode().querySelector(".call-audio-only");
|
||||
sandbox.stub(model, "set");
|
||||
|
||||
TestUtils.Simulate.click(buttonAccept);
|
||||
|
||||
sinon.assert.calledOnce(model.set);
|
||||
sinon.assert.calledWithExactly(model.set, "selectedCallType", "audio");
|
||||
});
|
||||
});
|
||||
|
||||
describe("click event on .btn-decline", function() {
|
||||
|
|
|
@ -24,7 +24,8 @@ describe("loop.shared.models", function() {
|
|||
fakeSessionData = {
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
apiKey: "apiKey"
|
||||
apiKey: "apiKey",
|
||||
callType: "callType"
|
||||
};
|
||||
fakeSession = _.extend({
|
||||
connect: function () {},
|
||||
|
@ -101,13 +102,14 @@ describe("loop.shared.models", function() {
|
|||
describe("#outgoing", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(conversation, "endSession");
|
||||
sandbox.stub(conversation, "setSessionData");
|
||||
sandbox.stub(conversation, "setOutgoingSessionData");
|
||||
sandbox.stub(conversation, "setIncomingSessionData");
|
||||
});
|
||||
|
||||
it("should save the sessionData", function() {
|
||||
it("should save the outgoing sessionData", function() {
|
||||
conversation.outgoing(fakeSessionData);
|
||||
|
||||
sinon.assert.calledOnce(conversation.setSessionData);
|
||||
sinon.assert.calledOnce(conversation.setOutgoingSessionData);
|
||||
});
|
||||
|
||||
it("should trigger a `call:outgoing` event", function(done) {
|
||||
|
@ -139,13 +141,24 @@ describe("loop.shared.models", function() {
|
|||
});
|
||||
|
||||
describe("#setSessionData", function() {
|
||||
it("should update conversation session information", function() {
|
||||
conversation.setSessionData(fakeSessionData);
|
||||
it("should update outgoing conversation session information",
|
||||
function() {
|
||||
conversation.setOutgoingSessionData(fakeSessionData);
|
||||
|
||||
expect(conversation.get("sessionId")).eql("sessionId");
|
||||
expect(conversation.get("sessionToken")).eql("sessionToken");
|
||||
expect(conversation.get("apiKey")).eql("apiKey");
|
||||
});
|
||||
expect(conversation.get("sessionId")).eql("sessionId");
|
||||
expect(conversation.get("sessionToken")).eql("sessionToken");
|
||||
expect(conversation.get("apiKey")).eql("apiKey");
|
||||
});
|
||||
|
||||
it("should update incoming conversation session information",
|
||||
function() {
|
||||
conversation.setIncomingSessionData(fakeSessionData);
|
||||
|
||||
expect(conversation.get("sessionId")).eql("sessionId");
|
||||
expect(conversation.get("sessionToken")).eql("sessionToken");
|
||||
expect(conversation.get("apiKey")).eql("apiKey");
|
||||
expect(conversation.get("callType")).eql("callType");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#startSession", function() {
|
||||
|
@ -359,6 +372,30 @@ describe("loop.shared.models", function() {
|
|||
sinon.assert.calledOnce(model.stopListening);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#hasVideoStream", function() {
|
||||
var model;
|
||||
|
||||
beforeEach(function() {
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
|
||||
it("should return true for incoming callType", function() {
|
||||
model.set("callType", "audio-video");
|
||||
|
||||
expect(model.hasVideoStream("incoming")).to.eql(true);
|
||||
});
|
||||
|
||||
it("should return true for outgoing callType", function() {
|
||||
model.set("selectedCallType", "audio-video");
|
||||
|
||||
expect(model.hasVideoStream("outgoing")).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -212,17 +212,37 @@ describe("loop.shared.views", function() {
|
|||
it("should start a session", function() {
|
||||
sandbox.stub(model, "startSession");
|
||||
|
||||
mountTestComponent({sdk: fakeSDK, model: model});
|
||||
mountTestComponent({
|
||||
sdk: fakeSDK,
|
||||
model: model,
|
||||
video: {enabled: true}
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(model.startSession);
|
||||
});
|
||||
|
||||
it("should set the correct stream publish options", function() {
|
||||
|
||||
var component = mountTestComponent({
|
||||
sdk: fakeSDK,
|
||||
model: model,
|
||||
video: {enabled: false}
|
||||
});
|
||||
|
||||
expect(component.publisherConfig.publishVideo).to.eql(false);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("constructed", function() {
|
||||
var comp;
|
||||
|
||||
beforeEach(function() {
|
||||
comp = mountTestComponent({sdk: fakeSDK, model: model});
|
||||
comp = mountTestComponent({
|
||||
sdk: fakeSDK,
|
||||
model: model,
|
||||
video: {enabled: false}
|
||||
});
|
||||
});
|
||||
|
||||
describe("#hangup", function() {
|
||||
|
@ -293,7 +313,11 @@ describe("loop.shared.views", function() {
|
|||
var comp;
|
||||
|
||||
beforeEach(function() {
|
||||
comp = mountTestComponent({sdk: fakeSDK, model: model});
|
||||
comp = mountTestComponent({
|
||||
sdk: fakeSDK,
|
||||
model: model,
|
||||
video: {enabled: false}
|
||||
});
|
||||
comp.startPublishing();
|
||||
});
|
||||
|
||||
|
|
|
@ -302,6 +302,7 @@ describe("loop.webapp", function() {
|
|||
describe("Has loop token", function() {
|
||||
beforeEach(function() {
|
||||
conversation.set("loopToken", "fakeToken");
|
||||
conversation.set("selectedCallType", "audio-video");
|
||||
sandbox.stub(conversation, "outgoing");
|
||||
});
|
||||
|
||||
|
@ -400,21 +401,59 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
|
||||
it("should start the conversation establishment process", function() {
|
||||
var button = view.getDOMNode().querySelector("button");
|
||||
var button = view.getDOMNode().querySelector(".start-audio-video-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(setupOutgoingCall);
|
||||
sinon.assert.calledWithExactly(setupOutgoingCall);
|
||||
});
|
||||
|
||||
it("should disable current form once session is initiated", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
var button = view.getDOMNode().querySelector("button");
|
||||
it("should start the conversation establishment process", function() {
|
||||
var button = view.getDOMNode().querySelector(".start-audio-only-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
expect(button.disabled).to.eql(true);
|
||||
sinon.assert.calledOnce(setupOutgoingCall);
|
||||
sinon.assert.calledWithExactly(setupOutgoingCall);
|
||||
});
|
||||
|
||||
it("should disable audio-video button once session is initiated",
|
||||
function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".start-audio-video-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
expect(button.disabled).to.eql(true);
|
||||
});
|
||||
|
||||
it("should disable audio-only button once session is initiated",
|
||||
function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".start-audio-only-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
expect(button.disabled).to.eql(true);
|
||||
});
|
||||
|
||||
it("should set selectedCallType to audio", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".start-audio-only-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
expect(conversation.get("selectedCallType")).to.eql("audio");
|
||||
});
|
||||
|
||||
it("should set selectedCallType to audio-video", function() {
|
||||
conversation.set("loopToken", "fake");
|
||||
|
||||
var button = view.getDOMNode().querySelector(".start-audio-video-call");
|
||||
React.addons.TestUtils.Simulate.click(button);
|
||||
|
||||
expect(conversation.get("selectedCallType")).to.eql("audio-video");
|
||||
});
|
||||
|
||||
it("should set state.urlCreationDateString to a locale date string",
|
||||
function() {
|
||||
// wrap in a jquery object because text is broken up
|
||||
|
|
|
@ -17,6 +17,7 @@ unable_retrieve_url=Sorry, we were unable to retrieve a call url.
|
|||
incoming_call_title=Incoming Call…
|
||||
incoming_call=Incoming call
|
||||
incoming_call_answer_button=Answer
|
||||
incoming_call_answer_audio_only_tooltip=Answer with voice
|
||||
incoming_call_decline_button=Decline
|
||||
incoming_call_decline_and_block_button=Decline and Block
|
||||
incoming_call_block_button=Block
|
||||
|
|
Загрузка…
Ссылка в новой задаче