mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 14:45:29 +00:00
Bug 972017 Part 3 - Finish the view flow transition for direct calling for Loop. r=nperriault
This commit is contained in:
parent
ad583709b0
commit
85b2d189ea
@ -483,7 +483,8 @@ loop.conversation = (function(mozL10n) {
|
|||||||
sdk: React.PropTypes.object.isRequired,
|
sdk: React.PropTypes.object.isRequired,
|
||||||
|
|
||||||
// XXX New types for OutgoingConversationView
|
// XXX New types for OutgoingConversationView
|
||||||
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired
|
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@ -504,7 +505,8 @@ loop.conversation = (function(mozL10n) {
|
|||||||
|
|
||||||
if (this.state.outgoing) {
|
if (this.state.outgoing) {
|
||||||
return (OutgoingConversationView({
|
return (OutgoingConversationView({
|
||||||
store: this.props.store}
|
store: this.props.store,
|
||||||
|
dispatcher: this.props.dispatcher}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,7 +519,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel initialisation.
|
* Conversation initialisation.
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
// Do the initial L10n setup, we do this before anything
|
// Do the initial L10n setup, we do this before anything
|
||||||
@ -574,6 +576,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
store: conversationStore,
|
store: conversationStore,
|
||||||
client: client,
|
client: client,
|
||||||
conversation: conversation,
|
conversation: conversation,
|
||||||
|
dispatcher: dispatcher,
|
||||||
sdk: window.OT}
|
sdk: window.OT}
|
||||||
), document.querySelector('#main'));
|
), document.querySelector('#main'));
|
||||||
|
|
||||||
|
@ -483,7 +483,8 @@ loop.conversation = (function(mozL10n) {
|
|||||||
sdk: React.PropTypes.object.isRequired,
|
sdk: React.PropTypes.object.isRequired,
|
||||||
|
|
||||||
// XXX New types for OutgoingConversationView
|
// XXX New types for OutgoingConversationView
|
||||||
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired
|
store: React.PropTypes.instanceOf(loop.store.ConversationStore).isRequired,
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@ -505,6 +506,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
if (this.state.outgoing) {
|
if (this.state.outgoing) {
|
||||||
return (<OutgoingConversationView
|
return (<OutgoingConversationView
|
||||||
store={this.props.store}
|
store={this.props.store}
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,7 +519,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel initialisation.
|
* Conversation initialisation.
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
// Do the initial L10n setup, we do this before anything
|
// Do the initial L10n setup, we do this before anything
|
||||||
@ -574,6 +576,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
store={conversationStore}
|
store={conversationStore}
|
||||||
client={client}
|
client={client}
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
|
dispatcher={dispatcher}
|
||||||
sdk={window.OT}
|
sdk={window.OT}
|
||||||
/>, document.querySelector('#main'));
|
/>, document.querySelector('#main'));
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ var loop = loop || {};
|
|||||||
loop.conversationViews = (function(mozL10n) {
|
loop.conversationViews = (function(mozL10n) {
|
||||||
|
|
||||||
var CALL_STATES = loop.store.CALL_STATES;
|
var CALL_STATES = loop.store.CALL_STATES;
|
||||||
|
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays details of the incoming/outgoing conversation
|
* Displays details of the incoming/outgoing conversation
|
||||||
@ -41,11 +44,24 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
*/
|
*/
|
||||||
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
|
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
callState: React.PropTypes.string,
|
callState: React.PropTypes.string,
|
||||||
calleeId: React.PropTypes.string,
|
calleeId: React.PropTypes.string,
|
||||||
|
enableCancelButton: React.PropTypes.bool
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
enableCancelButton: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelCall: function() {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var cx = React.addons.classSet;
|
||||||
var pendingStateString;
|
var pendingStateString;
|
||||||
if (this.props.callState === CALL_STATES.ALERTING) {
|
if (this.props.callState === CALL_STATES.ALERTING) {
|
||||||
pendingStateString = mozL10n.get("call_progress_ringing_description");
|
pendingStateString = mozL10n.get("call_progress_ringing_description");
|
||||||
@ -53,6 +69,12 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
pendingStateString = mozL10n.get("call_progress_connecting_description");
|
pendingStateString = mozL10n.get("call_progress_connecting_description");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var btnCancelStyles = cx({
|
||||||
|
"btn": true,
|
||||||
|
"btn-cancel": true,
|
||||||
|
"disabled": !this.props.enableCancelButton
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
ConversationDetailView({calleeId: this.props.calleeId},
|
ConversationDetailView({calleeId: this.props.calleeId},
|
||||||
|
|
||||||
@ -60,7 +82,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
|
|
||||||
React.DOM.div({className: "btn-group call-action-group"},
|
React.DOM.div({className: "btn-group call-action-group"},
|
||||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||||
React.DOM.button({className: "btn btn-cancel"},
|
React.DOM.button({className: btnCancelStyles,
|
||||||
|
onClick: this.cancelCall},
|
||||||
mozL10n.get("initiate_call_cancel_button")
|
mozL10n.get("initiate_call_cancel_button")
|
||||||
),
|
),
|
||||||
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
||||||
@ -75,10 +98,115 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
* Call failed view. Displayed when a call fails.
|
* Call failed view. Displayed when a call fails.
|
||||||
*/
|
*/
|
||||||
var CallFailedView = React.createClass({displayName: 'CallFailedView',
|
var CallFailedView = React.createClass({displayName: 'CallFailedView',
|
||||||
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
retryCall: function() {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.RetryCall());
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelCall: function() {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
React.DOM.div({className: "call-window"},
|
React.DOM.div({className: "call-window"},
|
||||||
React.DOM.h2(null, mozL10n.get("generic_failure_title"))
|
React.DOM.h2(null, mozL10n.get("generic_failure_title")),
|
||||||
|
|
||||||
|
React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_no_reason2")),
|
||||||
|
|
||||||
|
React.DOM.div({className: "btn-group call-action-group"},
|
||||||
|
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||||
|
React.DOM.button({className: "btn btn-accept btn-retry",
|
||||||
|
onClick: this.retryCall},
|
||||||
|
mozL10n.get("retry_call_button")
|
||||||
|
),
|
||||||
|
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||||
|
React.DOM.button({className: "btn btn-cancel",
|
||||||
|
onClick: this.cancelCall},
|
||||||
|
mozL10n.get("cancel_button")
|
||||||
|
),
|
||||||
|
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var OngoingConversationView = React.createClass({displayName: 'OngoingConversationView',
|
||||||
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
video: React.PropTypes.object,
|
||||||
|
audio: React.PropTypes.object
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
video: {enabled: true, visible: true},
|
||||||
|
audio: {enabled: true, visible: true}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
/**
|
||||||
|
* OT inserts inline styles into the markup. Using a listener for
|
||||||
|
* resize events helps us trigger a full width/height on the element
|
||||||
|
* so that they update to the correct dimensions.
|
||||||
|
* XXX: this should be factored as a mixin.
|
||||||
|
*/
|
||||||
|
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||||
|
window.addEventListener('resize', this.updateVideoContainer);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
window.removeEventListener('orientationchange', this.updateVideoContainer);
|
||||||
|
window.removeEventListener('resize', this.updateVideoContainer);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateVideoContainer: function() {
|
||||||
|
var localStreamParent = document.querySelector('.local .OT_publisher');
|
||||||
|
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
||||||
|
if (localStreamParent) {
|
||||||
|
localStreamParent.style.width = "100%";
|
||||||
|
}
|
||||||
|
if (remoteStreamParent) {
|
||||||
|
remoteStreamParent.style.height = "100%";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hangup: function() {
|
||||||
|
this.props.dispatcher.dispatch(
|
||||||
|
new sharedActions.HangupCall());
|
||||||
|
},
|
||||||
|
|
||||||
|
publishStream: function(type, enabled) {
|
||||||
|
// XXX Add this as part of bug 972017.
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var localStreamClasses = React.addons.classSet({
|
||||||
|
local: true,
|
||||||
|
"local-stream": true,
|
||||||
|
"local-stream-audio": !this.props.video.enabled
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
React.DOM.div({className: "video-layout-wrapper"},
|
||||||
|
React.DOM.div({className: "conversation"},
|
||||||
|
React.DOM.div({className: "media nested"},
|
||||||
|
React.DOM.div({className: "video_wrapper remote_wrapper"},
|
||||||
|
React.DOM.div({className: "video_inner remote"})
|
||||||
|
),
|
||||||
|
React.DOM.div({className: localStreamClasses})
|
||||||
|
),
|
||||||
|
loop.shared.views.ConversationToolbar({
|
||||||
|
video: this.props.video,
|
||||||
|
audio: this.props.audio,
|
||||||
|
publishStream: this.publishStream,
|
||||||
|
hangup: this.hangup})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -90,6 +218,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
*/
|
*/
|
||||||
var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView',
|
var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
store: React.PropTypes.instanceOf(
|
store: React.PropTypes.instanceOf(
|
||||||
loop.store.ConversationStore).isRequired
|
loop.store.ConversationStore).isRequired
|
||||||
},
|
},
|
||||||
@ -104,22 +233,83 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
}, this);
|
}, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
_closeWindow: function() {
|
||||||
if (this.state.callState === CALL_STATES.TERMINATED) {
|
window.close();
|
||||||
return (CallFailedView(null));
|
},
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the call is in a cancellable state, during call setup.
|
||||||
|
*/
|
||||||
|
_isCancellable: function() {
|
||||||
|
return this.state.callState !== CALL_STATES.INIT &&
|
||||||
|
this.state.callState !== CALL_STATES.GATHER;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to setup and render the feedback view.
|
||||||
|
*/
|
||||||
|
_renderFeedbackView: function() {
|
||||||
|
document.title = mozL10n.get("conversation_has_ended");
|
||||||
|
|
||||||
|
// XXX Bug 1076754 Feedback view should be redone in the Flux style.
|
||||||
|
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
|
||||||
|
"feedback.baseUrl");
|
||||||
|
|
||||||
|
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||||
|
|
||||||
|
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||||
|
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
|
||||||
|
platform: appVersionInfo.OS,
|
||||||
|
channel: appVersionInfo.channel,
|
||||||
|
version: appVersionInfo.version
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
sharedViews.FeedbackView({
|
||||||
|
feedbackApiClient: feedbackClient,
|
||||||
|
onAfterFeedbackReceived: this._closeWindow.bind(this)}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
switch (this.state.callState) {
|
||||||
|
case CALL_STATES.CLOSE: {
|
||||||
|
this._closeWindow();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case CALL_STATES.TERMINATED: {
|
||||||
|
return (CallFailedView({
|
||||||
|
dispatcher: this.props.dispatcher}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
case CALL_STATES.ONGOING: {
|
||||||
|
return (OngoingConversationView({
|
||||||
|
dispatcher: this.props.dispatcher,
|
||||||
|
video: {enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case CALL_STATES.FINISHED: {
|
||||||
|
return this._renderFeedbackView();
|
||||||
|
}
|
||||||
|
default: {
|
||||||
return (PendingConversationView({
|
return (PendingConversationView({
|
||||||
|
dispatcher: this.props.dispatcher,
|
||||||
callState: this.state.callState,
|
callState: this.state.callState,
|
||||||
calleeId: this.state.calleeId}
|
calleeId: this.state.calleeId,
|
||||||
|
enableCancelButton: this._isCancellable()}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
PendingConversationView: PendingConversationView,
|
PendingConversationView: PendingConversationView,
|
||||||
ConversationDetailView: ConversationDetailView,
|
ConversationDetailView: ConversationDetailView,
|
||||||
CallFailedView: CallFailedView,
|
CallFailedView: CallFailedView,
|
||||||
|
OngoingConversationView: OngoingConversationView,
|
||||||
OutgoingConversationView: OutgoingConversationView
|
OutgoingConversationView: OutgoingConversationView
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ var loop = loop || {};
|
|||||||
loop.conversationViews = (function(mozL10n) {
|
loop.conversationViews = (function(mozL10n) {
|
||||||
|
|
||||||
var CALL_STATES = loop.store.CALL_STATES;
|
var CALL_STATES = loop.store.CALL_STATES;
|
||||||
|
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays details of the incoming/outgoing conversation
|
* Displays details of the incoming/outgoing conversation
|
||||||
@ -41,11 +44,24 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
*/
|
*/
|
||||||
var PendingConversationView = React.createClass({
|
var PendingConversationView = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
callState: React.PropTypes.string,
|
callState: React.PropTypes.string,
|
||||||
calleeId: React.PropTypes.string,
|
calleeId: React.PropTypes.string,
|
||||||
|
enableCancelButton: React.PropTypes.bool
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
enableCancelButton: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelCall: function() {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var cx = React.addons.classSet;
|
||||||
var pendingStateString;
|
var pendingStateString;
|
||||||
if (this.props.callState === CALL_STATES.ALERTING) {
|
if (this.props.callState === CALL_STATES.ALERTING) {
|
||||||
pendingStateString = mozL10n.get("call_progress_ringing_description");
|
pendingStateString = mozL10n.get("call_progress_ringing_description");
|
||||||
@ -53,6 +69,12 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
pendingStateString = mozL10n.get("call_progress_connecting_description");
|
pendingStateString = mozL10n.get("call_progress_connecting_description");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var btnCancelStyles = cx({
|
||||||
|
"btn": true,
|
||||||
|
"btn-cancel": true,
|
||||||
|
"disabled": !this.props.enableCancelButton
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConversationDetailView calleeId={this.props.calleeId}>
|
<ConversationDetailView calleeId={this.props.calleeId}>
|
||||||
|
|
||||||
@ -60,7 +82,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
|
|
||||||
<div className="btn-group call-action-group">
|
<div className="btn-group call-action-group">
|
||||||
<div className="fx-embedded-call-button-spacer"></div>
|
<div className="fx-embedded-call-button-spacer"></div>
|
||||||
<button className="btn btn-cancel">
|
<button className={btnCancelStyles}
|
||||||
|
onClick={this.cancelCall}>
|
||||||
{mozL10n.get("initiate_call_cancel_button")}
|
{mozL10n.get("initiate_call_cancel_button")}
|
||||||
</button>
|
</button>
|
||||||
<div className="fx-embedded-call-button-spacer"></div>
|
<div className="fx-embedded-call-button-spacer"></div>
|
||||||
@ -75,10 +98,115 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
* Call failed view. Displayed when a call fails.
|
* Call failed view. Displayed when a call fails.
|
||||||
*/
|
*/
|
||||||
var CallFailedView = React.createClass({
|
var CallFailedView = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
retryCall: function() {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.RetryCall());
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelCall: function() {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="call-window">
|
<div className="call-window">
|
||||||
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
||||||
|
|
||||||
|
<p className="btn-label">{mozL10n.get("generic_failure_no_reason2")}</p>
|
||||||
|
|
||||||
|
<div className="btn-group call-action-group">
|
||||||
|
<div className="fx-embedded-call-button-spacer"></div>
|
||||||
|
<button className="btn btn-accept btn-retry"
|
||||||
|
onClick={this.retryCall}>
|
||||||
|
{mozL10n.get("retry_call_button")}
|
||||||
|
</button>
|
||||||
|
<div className="fx-embedded-call-button-spacer"></div>
|
||||||
|
<button className="btn btn-cancel"
|
||||||
|
onClick={this.cancelCall}>
|
||||||
|
{mozL10n.get("cancel_button")}
|
||||||
|
</button>
|
||||||
|
<div className="fx-embedded-call-button-spacer"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var OngoingConversationView = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
video: React.PropTypes.object,
|
||||||
|
audio: React.PropTypes.object
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
video: {enabled: true, visible: true},
|
||||||
|
audio: {enabled: true, visible: true}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
/**
|
||||||
|
* OT inserts inline styles into the markup. Using a listener for
|
||||||
|
* resize events helps us trigger a full width/height on the element
|
||||||
|
* so that they update to the correct dimensions.
|
||||||
|
* XXX: this should be factored as a mixin.
|
||||||
|
*/
|
||||||
|
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||||
|
window.addEventListener('resize', this.updateVideoContainer);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
window.removeEventListener('orientationchange', this.updateVideoContainer);
|
||||||
|
window.removeEventListener('resize', this.updateVideoContainer);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateVideoContainer: function() {
|
||||||
|
var localStreamParent = document.querySelector('.local .OT_publisher');
|
||||||
|
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
||||||
|
if (localStreamParent) {
|
||||||
|
localStreamParent.style.width = "100%";
|
||||||
|
}
|
||||||
|
if (remoteStreamParent) {
|
||||||
|
remoteStreamParent.style.height = "100%";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hangup: function() {
|
||||||
|
this.props.dispatcher.dispatch(
|
||||||
|
new sharedActions.HangupCall());
|
||||||
|
},
|
||||||
|
|
||||||
|
publishStream: function(type, enabled) {
|
||||||
|
// XXX Add this as part of bug 972017.
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var localStreamClasses = React.addons.classSet({
|
||||||
|
local: true,
|
||||||
|
"local-stream": true,
|
||||||
|
"local-stream-audio": !this.props.video.enabled
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="video-layout-wrapper">
|
||||||
|
<div className="conversation">
|
||||||
|
<div className="media nested">
|
||||||
|
<div className="video_wrapper remote_wrapper">
|
||||||
|
<div className="video_inner remote"></div>
|
||||||
|
</div>
|
||||||
|
<div className={localStreamClasses}></div>
|
||||||
|
</div>
|
||||||
|
<loop.shared.views.ConversationToolbar
|
||||||
|
video={this.props.video}
|
||||||
|
audio={this.props.audio}
|
||||||
|
publishStream={this.publishStream}
|
||||||
|
hangup={this.hangup} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -90,6 +218,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
*/
|
*/
|
||||||
var OutgoingConversationView = React.createClass({
|
var OutgoingConversationView = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
store: React.PropTypes.instanceOf(
|
store: React.PropTypes.instanceOf(
|
||||||
loop.store.ConversationStore).isRequired
|
loop.store.ConversationStore).isRequired
|
||||||
},
|
},
|
||||||
@ -104,22 +233,83 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
}, this);
|
}, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
_closeWindow: function() {
|
||||||
if (this.state.callState === CALL_STATES.TERMINATED) {
|
window.close();
|
||||||
return (<CallFailedView />);
|
},
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the call is in a cancellable state, during call setup.
|
||||||
|
*/
|
||||||
|
_isCancellable: function() {
|
||||||
|
return this.state.callState !== CALL_STATES.INIT &&
|
||||||
|
this.state.callState !== CALL_STATES.GATHER;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to setup and render the feedback view.
|
||||||
|
*/
|
||||||
|
_renderFeedbackView: function() {
|
||||||
|
document.title = mozL10n.get("conversation_has_ended");
|
||||||
|
|
||||||
|
// XXX Bug 1076754 Feedback view should be redone in the Flux style.
|
||||||
|
var feebackAPIBaseUrl = navigator.mozLoop.getLoopCharPref(
|
||||||
|
"feedback.baseUrl");
|
||||||
|
|
||||||
|
var appVersionInfo = navigator.mozLoop.appVersionInfo;
|
||||||
|
|
||||||
|
var feedbackClient = new loop.FeedbackAPIClient(feebackAPIBaseUrl, {
|
||||||
|
product: navigator.mozLoop.getLoopCharPref("feedback.product"),
|
||||||
|
platform: appVersionInfo.OS,
|
||||||
|
channel: appVersionInfo.channel,
|
||||||
|
version: appVersionInfo.version
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<sharedViews.FeedbackView
|
||||||
|
feedbackApiClient={feedbackClient}
|
||||||
|
onAfterFeedbackReceived={this._closeWindow.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
switch (this.state.callState) {
|
||||||
|
case CALL_STATES.CLOSE: {
|
||||||
|
this._closeWindow();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case CALL_STATES.TERMINATED: {
|
||||||
|
return (<CallFailedView
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
case CALL_STATES.ONGOING: {
|
||||||
|
return (<OngoingConversationView
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
|
video={{enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case CALL_STATES.FINISHED: {
|
||||||
|
return this._renderFeedbackView();
|
||||||
|
}
|
||||||
|
default: {
|
||||||
return (<PendingConversationView
|
return (<PendingConversationView
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
callState={this.state.callState}
|
callState={this.state.callState}
|
||||||
calleeId={this.state.calleeId}
|
calleeId={this.state.calleeId}
|
||||||
|
enableCancelButton={this._isCancellable()}
|
||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
PendingConversationView: PendingConversationView,
|
PendingConversationView: PendingConversationView,
|
||||||
ConversationDetailView: ConversationDetailView,
|
ConversationDetailView: ConversationDetailView,
|
||||||
CallFailedView: CallFailedView,
|
CallFailedView: CallFailedView,
|
||||||
|
OngoingConversationView: OngoingConversationView,
|
||||||
OutgoingConversationView: OutgoingConversationView
|
OutgoingConversationView: OutgoingConversationView
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,6 +47,12 @@ loop.shared.actions = (function() {
|
|||||||
CancelCall: Action.define("cancelCall", {
|
CancelCall: Action.define("cancelCall", {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to retry a failed call.
|
||||||
|
*/
|
||||||
|
RetryCall: Action.define("retryCall", {
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to initiate connecting of a call with the relevant
|
* Used to initiate connecting of a call with the relevant
|
||||||
* sessionData.
|
* sessionData.
|
||||||
@ -57,14 +63,20 @@ loop.shared.actions = (function() {
|
|||||||
sessionData: Object
|
sessionData: Object
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for hanging up the call at the end of a successful call.
|
||||||
|
*/
|
||||||
|
HangupCall: Action.define("hangupCall", {
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for notifying of connection progress state changes.
|
* Used for notifying of connection progress state changes.
|
||||||
* The connection refers to the overall connection flow as indicated
|
* The connection refers to the overall connection flow as indicated
|
||||||
* on the websocket.
|
* on the websocket.
|
||||||
*/
|
*/
|
||||||
ConnectionProgress: Action.define("connectionProgress", {
|
ConnectionProgress: Action.define("connectionProgress", {
|
||||||
// The new connection state
|
// The connection state from the websocket.
|
||||||
state: String
|
wsState: String
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,19 +10,46 @@ loop.store = (function() {
|
|||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedUtils = loop.shared.utils;
|
var sharedUtils = loop.shared.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Websocket states taken from:
|
||||||
|
* https://docs.services.mozilla.com/loop/apis.html#call-progress-state-change-progress
|
||||||
|
*/
|
||||||
|
var WS_STATES = {
|
||||||
|
// The call is starting, and the remote party is not yet being alerted.
|
||||||
|
INIT: "init",
|
||||||
|
// The called party is being alerted.
|
||||||
|
ALERTING: "alerting",
|
||||||
|
// The call is no longer being set up and has been aborted for some reason.
|
||||||
|
TERMINATED: "terminated",
|
||||||
|
// The called party has indicated that he has answered the call,
|
||||||
|
// but the media is not yet confirmed.
|
||||||
|
CONNECTING: "connecting",
|
||||||
|
// One of the two parties has indicated successful media set up,
|
||||||
|
// but the other has not yet.
|
||||||
|
HALF_CONNECTED: "half-connected",
|
||||||
|
// Both endpoints have reported successfully establishing media.
|
||||||
|
CONNECTED: "connected"
|
||||||
|
};
|
||||||
|
|
||||||
var CALL_STATES = {
|
var CALL_STATES = {
|
||||||
// The initial state of the view.
|
// The initial state of the view.
|
||||||
INIT: "init",
|
INIT: "cs-init",
|
||||||
// The store is gathering the call data from the server.
|
// The store is gathering the call data from the server.
|
||||||
GATHER: "gather",
|
GATHER: "cs-gather",
|
||||||
// The websocket has connected to the server and is waiting
|
// The initial data has been gathered, the websocket is connecting, or has
|
||||||
// for the other peer to connect to the websocket.
|
// connected, and waiting for the other side to connect to the server.
|
||||||
CONNECTING: "connecting",
|
CONNECTING: "cs-connecting",
|
||||||
// The websocket has received information that we're now alerting
|
// The websocket has received information that we're now alerting
|
||||||
// the peer.
|
// the peer.
|
||||||
ALERTING: "alerting",
|
ALERTING: "cs-alerting",
|
||||||
|
// The call is ongoing.
|
||||||
|
ONGOING: "cs-ongoing",
|
||||||
|
// The call ended successfully.
|
||||||
|
FINISHED: "cs-finished",
|
||||||
|
// The user has finished with the window.
|
||||||
|
CLOSE: "cs-close",
|
||||||
// The call was terminated due to an issue during connection.
|
// The call was terminated due to an issue during connection.
|
||||||
TERMINATED: "terminated"
|
TERMINATED: "cs-terminated"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -85,7 +112,10 @@ loop.store = (function() {
|
|||||||
"connectionFailure",
|
"connectionFailure",
|
||||||
"connectionProgress",
|
"connectionProgress",
|
||||||
"gatherCallData",
|
"gatherCallData",
|
||||||
"connectCall"
|
"connectCall",
|
||||||
|
"hangupCall",
|
||||||
|
"cancelCall",
|
||||||
|
"retryCall"
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -109,19 +139,29 @@ loop.store = (function() {
|
|||||||
* @param {sharedActions.ConnectionProgress} actionData The action data.
|
* @param {sharedActions.ConnectionProgress} actionData The action data.
|
||||||
*/
|
*/
|
||||||
connectionProgress: function(actionData) {
|
connectionProgress: function(actionData) {
|
||||||
// XXX Turn this into a state machine?
|
var callState = this.get("callState");
|
||||||
if (actionData.state === "alerting" &&
|
|
||||||
(this.get("callState") === CALL_STATES.CONNECTING ||
|
switch(actionData.wsState) {
|
||||||
this.get("callState") === CALL_STATES.GATHER)) {
|
case WS_STATES.INIT: {
|
||||||
this.set({
|
if (callState === CALL_STATES.GATHER) {
|
||||||
callState: CALL_STATES.ALERTING
|
this.set({callState: CALL_STATES.CONNECTING});
|
||||||
});
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WS_STATES.ALERTING: {
|
||||||
|
this.set({callState: CALL_STATES.ALERTING});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WS_STATES.CONNECTING:
|
||||||
|
case WS_STATES.HALF_CONNECTED:
|
||||||
|
case WS_STATES.CONNECTED: {
|
||||||
|
this.set({callState: CALL_STATES.ONGOING});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.error("Unexpected websocket state passed to connectionProgress:",
|
||||||
|
actionData.wsState);
|
||||||
}
|
}
|
||||||
if (actionData.state === "connecting" &&
|
|
||||||
this.get("callState") === CALL_STATES.GATHER) {
|
|
||||||
this.set({
|
|
||||||
callState: CALL_STATES.CONNECTING
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -156,6 +196,63 @@ loop.store = (function() {
|
|||||||
this._connectWebSocket();
|
this._connectWebSocket();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hangs up an ongoing call.
|
||||||
|
*/
|
||||||
|
hangupCall: function() {
|
||||||
|
// XXX Stop the SDK once we add it.
|
||||||
|
|
||||||
|
// Ensure the websocket has been disconnected.
|
||||||
|
if (this._websocket) {
|
||||||
|
// Let the server know the user has hung up.
|
||||||
|
this._websocket.mediaFail();
|
||||||
|
this._ensureWebSocketDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set({callState: CALL_STATES.FINISHED});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a call
|
||||||
|
*/
|
||||||
|
cancelCall: function() {
|
||||||
|
var callState = this.get("callState");
|
||||||
|
if (callState === CALL_STATES.TERMINATED) {
|
||||||
|
// All we need to do is close the window.
|
||||||
|
this.set({callState: CALL_STATES.CLOSE});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callState === CALL_STATES.CONNECTING ||
|
||||||
|
callState === CALL_STATES.ALERTING) {
|
||||||
|
if (this._websocket) {
|
||||||
|
// Let the server know the user has hung up.
|
||||||
|
this._websocket.cancel();
|
||||||
|
this._ensureWebSocketDisconnected();
|
||||||
|
}
|
||||||
|
this.set({callState: CALL_STATES.CLOSE});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Unsupported cancel in state", callState);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries a call
|
||||||
|
*/
|
||||||
|
retryCall: function() {
|
||||||
|
var callState = this.get("callState");
|
||||||
|
if (callState !== CALL_STATES.TERMINATED) {
|
||||||
|
console.error("Unexpected retry in state", callState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set({callState: CALL_STATES.GATHER});
|
||||||
|
if (this.get("outgoing")) {
|
||||||
|
this._setupOutgoingCall();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the outgoing call data from the server and handles the
|
* Obtains the outgoing call data from the server and handles the
|
||||||
* result.
|
* result.
|
||||||
@ -192,11 +289,11 @@ loop.store = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._websocket.promiseConnect().then(
|
this._websocket.promiseConnect().then(
|
||||||
function() {
|
function(progressState) {
|
||||||
this.dispatcher.dispatch(new sharedActions.ConnectionProgress({
|
this.dispatcher.dispatch(new sharedActions.ConnectionProgress({
|
||||||
// This is the websocket call state, i.e. waiting for the
|
// This is the websocket call state, i.e. waiting for the
|
||||||
// other end to connect to the server.
|
// other end to connect to the server.
|
||||||
state: "connecting"
|
wsState: progressState
|
||||||
}));
|
}));
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
function(error) {
|
function(error) {
|
||||||
@ -207,7 +304,18 @@ loop.store = (function() {
|
|||||||
}.bind(this)
|
}.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
this._websocket.on("progress", this._handleWebSocketProgress, this);
|
this.listenTo(this._websocket, "progress", this._handleWebSocketProgress);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the websocket gets disconnected.
|
||||||
|
*/
|
||||||
|
_ensureWebSocketDisconnected: function() {
|
||||||
|
this.stopListening(this._websocket);
|
||||||
|
|
||||||
|
// Now close the websocket.
|
||||||
|
this._websocket.close();
|
||||||
|
delete this._websocket;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,19 +326,18 @@ loop.store = (function() {
|
|||||||
var action;
|
var action;
|
||||||
|
|
||||||
switch(progressData.state) {
|
switch(progressData.state) {
|
||||||
case "terminated":
|
case WS_STATES.TERMINATED: {
|
||||||
action = new sharedActions.ConnectionFailure({
|
action = new sharedActions.ConnectionFailure({
|
||||||
reason: progressData.reason
|
reason: progressData.reason
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "alerting":
|
}
|
||||||
|
default: {
|
||||||
action = new sharedActions.ConnectionProgress({
|
action = new sharedActions.ConnectionProgress({
|
||||||
state: progressData.state
|
wsState: progressData.state
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
console.warn("Received unexpected state in _handleWebSocketProgress", progressData.state);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatcher.dispatch(action);
|
this.dispatcher.dispatch(action);
|
||||||
@ -239,6 +346,7 @@ loop.store = (function() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
CALL_STATES: CALL_STATES,
|
CALL_STATES: CALL_STATES,
|
||||||
ConversationStore: ConversationStore
|
ConversationStore: ConversationStore,
|
||||||
|
WS_STATES: WS_STATES
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
@ -174,6 +174,17 @@ loop.CallConnectionWebSocket = (function() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the server that something failed during setup.
|
||||||
|
*/
|
||||||
|
mediaFail: function() {
|
||||||
|
this._send({
|
||||||
|
messageType: "action",
|
||||||
|
event: "terminate",
|
||||||
|
reason: "media-fail"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends data on the websocket.
|
* Sends data on the websocket.
|
||||||
*
|
*
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
|
|
||||||
describe("loop.conversationViews", function () {
|
describe("loop.conversationViews", function () {
|
||||||
var sandbox, oldTitle, view;
|
var sandbox, oldTitle, view, dispatcher;
|
||||||
|
|
||||||
var CALL_STATES = loop.store.CALL_STATES;
|
var CALL_STATES = loop.store.CALL_STATES;
|
||||||
|
|
||||||
@ -15,6 +15,9 @@ describe("loop.conversationViews", function () {
|
|||||||
sandbox.stub(document.mozL10n, "get", function(x) {
|
sandbox.stub(document.mozL10n, "get", function(x) {
|
||||||
return x;
|
return x;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatcher = new loop.Dispatcher();
|
||||||
|
sandbox.stub(dispatcher, "dispatch");
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
@ -53,7 +56,8 @@ describe("loop.conversationViews", function () {
|
|||||||
function() {
|
function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callState: CALL_STATES.CONNECTING,
|
callState: CALL_STATES.CONNECTING,
|
||||||
calleeId: "mrsmith"
|
calleeId: "mrsmith",
|
||||||
|
dispatcher: dispatcher
|
||||||
});
|
});
|
||||||
|
|
||||||
var label = TestUtils.findRenderedDOMComponentWithClass(
|
var label = TestUtils.findRenderedDOMComponentWithClass(
|
||||||
@ -66,7 +70,8 @@ describe("loop.conversationViews", function () {
|
|||||||
function() {
|
function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
callState: CALL_STATES.ALERTING,
|
callState: CALL_STATES.ALERTING,
|
||||||
calleeId: "mrsmith"
|
calleeId: "mrsmith",
|
||||||
|
dispatcher: dispatcher
|
||||||
});
|
});
|
||||||
|
|
||||||
var label = TestUtils.findRenderedDOMComponentWithClass(
|
var label = TestUtils.findRenderedDOMComponentWithClass(
|
||||||
@ -74,6 +79,86 @@ describe("loop.conversationViews", function () {
|
|||||||
|
|
||||||
expect(label).to.have.string("ringing");
|
expect(label).to.have.string("ringing");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should disable the cancel button if enableCancelButton is false",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
callState: CALL_STATES.CONNECTING,
|
||||||
|
calleeId: "mrsmith",
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
enableCancelButton: false
|
||||||
|
});
|
||||||
|
|
||||||
|
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||||
|
|
||||||
|
expect(cancelBtn.classList.contains("disabled")).eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should enable the cancel button if enableCancelButton is false",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
callState: CALL_STATES.CONNECTING,
|
||||||
|
calleeId: "mrsmith",
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
enableCancelButton: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||||
|
|
||||||
|
expect(cancelBtn.classList.contains("disabled")).eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a cancelCall action when the cancel button is pressed",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
callState: CALL_STATES.CONNECTING,
|
||||||
|
calleeId: "mrsmith",
|
||||||
|
dispatcher: dispatcher
|
||||||
|
});
|
||||||
|
|
||||||
|
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||||
|
|
||||||
|
React.addons.TestUtils.Simulate.click(cancelBtn);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "cancelCall"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("CallFailedView", function() {
|
||||||
|
function mountTestComponent(props) {
|
||||||
|
return TestUtils.renderIntoDocument(
|
||||||
|
loop.conversationViews.CallFailedView({
|
||||||
|
dispatcher: dispatcher
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should dispatch a retryCall action when the retry button is pressed",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent();
|
||||||
|
|
||||||
|
var retryBtn = view.getDOMNode().querySelector('.btn-retry');
|
||||||
|
|
||||||
|
React.addons.TestUtils.Simulate.click(retryBtn);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "retryCall"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a cancelCall action when the cancel button is pressed",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent();
|
||||||
|
|
||||||
|
var cancelBtn = view.getDOMNode().querySelector('.btn-cancel');
|
||||||
|
|
||||||
|
React.addons.TestUtils.Simulate.click(cancelBtn);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "cancelCall"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("OutgoingConversationView", function() {
|
describe("OutgoingConversationView", function() {
|
||||||
@ -88,9 +173,18 @@ describe("loop.conversationViews", function () {
|
|||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
store = new loop.store.ConversationStore({}, {
|
store = new loop.store.ConversationStore({}, {
|
||||||
dispatcher: new loop.Dispatcher(),
|
dispatcher: dispatcher,
|
||||||
client: {}
|
client: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
navigator.mozLoop = {
|
||||||
|
getLoopCharPref: function() { return "fake"; },
|
||||||
|
appVersionInfo: sinon.spy()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
delete navigator.mozLoop;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the CallFailedView when the call state is 'terminated'",
|
it("should render the CallFailedView when the call state is 'terminated'",
|
||||||
@ -103,9 +197,9 @@ describe("loop.conversationViews", function () {
|
|||||||
loop.conversationViews.CallFailedView);
|
loop.conversationViews.CallFailedView);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the PendingConversationView when the call state is connecting",
|
it("should render the PendingConversationView when the call state is 'init'",
|
||||||
function() {
|
function() {
|
||||||
store.set({callState: CALL_STATES.CONNECTING});
|
store.set({callState: CALL_STATES.INIT});
|
||||||
|
|
||||||
view = mountTestComponent();
|
view = mountTestComponent();
|
||||||
|
|
||||||
@ -113,9 +207,29 @@ describe("loop.conversationViews", function () {
|
|||||||
loop.conversationViews.PendingConversationView);
|
loop.conversationViews.PendingConversationView);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should render the OngoingConversationView when the call state is 'ongoing'",
|
||||||
|
function() {
|
||||||
|
store.set({callState: CALL_STATES.ONGOING});
|
||||||
|
|
||||||
|
view = mountTestComponent();
|
||||||
|
|
||||||
|
TestUtils.findRenderedComponentWithType(view,
|
||||||
|
loop.conversationViews.OngoingConversationView);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the FeedbackView when the call state is 'finished'",
|
||||||
|
function() {
|
||||||
|
store.set({callState: CALL_STATES.FINISHED});
|
||||||
|
|
||||||
|
view = mountTestComponent();
|
||||||
|
|
||||||
|
TestUtils.findRenderedComponentWithType(view,
|
||||||
|
loop.shared.views.FeedbackView);
|
||||||
|
});
|
||||||
|
|
||||||
it("should update the rendered views when the state is changed.",
|
it("should update the rendered views when the state is changed.",
|
||||||
function() {
|
function() {
|
||||||
store.set({callState: CALL_STATES.CONNECTING});
|
store.set({callState: CALL_STATES.INIT});
|
||||||
|
|
||||||
view = mountTestComponent();
|
view = mountTestComponent();
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ describe("loop.ConversationStore", function () {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var CALL_STATES = loop.store.CALL_STATES;
|
var CALL_STATES = loop.store.CALL_STATES;
|
||||||
|
var WS_STATES = loop.store.WS_STATES;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedUtils = loop.shared.utils;
|
var sharedUtils = loop.shared.utils;
|
||||||
var sandbox, dispatcher, client, store, fakeSessionData;
|
var sandbox, dispatcher, client, store, fakeSessionData;
|
||||||
@ -86,36 +87,69 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("#connectionProgress", function() {
|
describe("#connectionProgress", function() {
|
||||||
describe("progress: connecting", function() {
|
describe("progress: init", function() {
|
||||||
it("should change the state from 'gather' to 'connecting'", function() {
|
it("should change the state from 'gather' to 'connecting'", function() {
|
||||||
store.set({callState: CALL_STATES.GATHER});
|
store.set({callState: CALL_STATES.GATHER});
|
||||||
|
|
||||||
dispatcher.dispatch(
|
dispatcher.dispatch(
|
||||||
new sharedActions.ConnectionProgress({state: "connecting"}));
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.INIT}));
|
||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.CONNECTING);
|
expect(store.get("callState")).eql(CALL_STATES.CONNECTING);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("progress: alerting", function() {
|
describe("progress: alerting", function() {
|
||||||
it("should set the state from 'gather' to 'alerting'", function() {
|
it("should change the state from 'gather' to 'alerting'", function() {
|
||||||
store.set({callState: CALL_STATES.GATHER});
|
store.set({callState: CALL_STATES.GATHER});
|
||||||
|
|
||||||
dispatcher.dispatch(
|
dispatcher.dispatch(
|
||||||
new sharedActions.ConnectionProgress({state: "alerting"}));
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set the state from 'connecting' to 'alerting'", function() {
|
it("should change the state from 'init' to 'alerting'", function() {
|
||||||
store.set({callState: CALL_STATES.CONNECTING});
|
store.set({callState: CALL_STATES.INIT});
|
||||||
|
|
||||||
dispatcher.dispatch(
|
dispatcher.dispatch(
|
||||||
new sharedActions.ConnectionProgress({state: "alerting"}));
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
expect(store.get("callState")).eql(CALL_STATES.ALERTING);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("progress: connecting", function() {
|
||||||
|
it("should change the state to 'ongoing'", function() {
|
||||||
|
store.set({callState: CALL_STATES.ALERTING});
|
||||||
|
|
||||||
|
dispatcher.dispatch(
|
||||||
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("progress: half-connected", function() {
|
||||||
|
it("should change the state to 'ongoing'", function() {
|
||||||
|
store.set({callState: CALL_STATES.ALERTING});
|
||||||
|
|
||||||
|
dispatcher.dispatch(
|
||||||
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.HALF_CONNECTED}));
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("progress: connecting", function() {
|
||||||
|
it("should change the state to 'ongoing'", function() {
|
||||||
|
store.set({callState: CALL_STATES.ALERTING});
|
||||||
|
|
||||||
|
dispatcher.dispatch(
|
||||||
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTED}));
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#gatherCallData", function() {
|
describe("#gatherCallData", function() {
|
||||||
@ -250,7 +284,7 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch a connection progress action on success", function(done) {
|
it("should dispatch a connection progress action on success", function(done) {
|
||||||
resolveConnectPromise();
|
resolveConnectPromise(WS_STATES.INIT);
|
||||||
|
|
||||||
connectPromise.then(function() {
|
connectPromise.then(function() {
|
||||||
checkFailures(done, function() {
|
checkFailures(done, function() {
|
||||||
@ -259,7 +293,7 @@ describe("loop.ConversationStore", function () {
|
|||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("name", "connectionProgress"));
|
sinon.match.hasOwn("name", "connectionProgress"));
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("state", "connecting"));
|
sinon.match.hasOwn("wsState", WS_STATES.INIT));
|
||||||
});
|
});
|
||||||
}, function() {
|
}, function() {
|
||||||
done(new Error("Promise should have been resolve, not rejected"));
|
done(new Error("Promise should have been resolve, not rejected"));
|
||||||
@ -282,7 +316,105 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#hangupCall", function() {
|
||||||
|
var wsMediaFailSpy, wsCloseSpy;
|
||||||
|
beforeEach(function() {
|
||||||
|
wsMediaFailSpy = sinon.spy();
|
||||||
|
wsCloseSpy = sinon.spy();
|
||||||
|
|
||||||
|
store._websocket = {
|
||||||
|
mediaFail: wsMediaFailSpy,
|
||||||
|
close: wsCloseSpy
|
||||||
|
};
|
||||||
|
store.set({callState: CALL_STATES.ONGOING});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send a media-fail message to the websocket if it is open", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(wsMediaFailSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ensure the websocket is closed", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(wsCloseSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the callState to finished", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#cancelCall", function() {
|
||||||
|
it("should set the state to close if the call has terminated already", function() {
|
||||||
|
store.set({callState: CALL_STATES.TERMINATED});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("whilst connecting", function() {
|
||||||
|
var wsCancelSpy, wsCloseSpy;
|
||||||
|
beforeEach(function() {
|
||||||
|
wsCancelSpy = sinon.spy();
|
||||||
|
wsCloseSpy = sinon.spy();
|
||||||
|
|
||||||
|
store._websocket = {
|
||||||
|
cancel: wsCancelSpy,
|
||||||
|
close: wsCloseSpy
|
||||||
|
};
|
||||||
|
store.set({callState: CALL_STATES.CONNECTING});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send a cancel message to the websocket if it is open", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(wsCancelSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ensure the websocket is closed", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(wsCloseSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the state to close if the call is connecting", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#retryCall", function() {
|
||||||
|
it("should set the state to gather", function() {
|
||||||
|
store.set({callState: CALL_STATES.TERMINATED});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.RetryCall());
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.GATHER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should request the outgoing call data", function() {
|
||||||
|
store.set({
|
||||||
|
callState: CALL_STATES.TERMINATED,
|
||||||
|
outgoing: true,
|
||||||
|
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
|
||||||
|
calleeId: "fake"
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.RetryCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(client.setupOutgoingCall);
|
||||||
|
sinon.assert.calledWith(client.setupOutgoingCall,
|
||||||
|
["fake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -296,7 +428,10 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch a connection failure action on 'terminate'", function() {
|
it("should dispatch a connection failure action on 'terminate'", function() {
|
||||||
store._websocket.trigger("progress", {state: "terminated", reason: "reject"});
|
store._websocket.trigger("progress", {
|
||||||
|
state: WS_STATES.TERMINATED,
|
||||||
|
reason: "reject"
|
||||||
|
});
|
||||||
|
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
// Can't use instanceof here, as that matches any action
|
// Can't use instanceof here, as that matches any action
|
||||||
@ -307,14 +442,14 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch a connection progress action on 'alerting'", function() {
|
it("should dispatch a connection progress action on 'alerting'", function() {
|
||||||
store._websocket.trigger("progress", {state: "alerting"});
|
store._websocket.trigger("progress", {state: WS_STATES.ALERTING});
|
||||||
|
|
||||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
// Can't use instanceof here, as that matches any action
|
// Can't use instanceof here, as that matches any action
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("name", "connectionProgress"));
|
sinon.match.hasOwn("name", "connectionProgress"));
|
||||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
sinon.match.hasOwn("state", "alerting"));
|
sinon.match.hasOwn("wsState", WS_STATES.ALERTING));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -206,6 +206,22 @@ describe("loop.CallConnectionWebSocket", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#mediaFail", function() {
|
||||||
|
it("should send a terminate message to the server with a reason of media-fail",
|
||||||
|
function() {
|
||||||
|
callWebSocket.promiseConnect();
|
||||||
|
|
||||||
|
callWebSocket.mediaFail();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dummySocket.send);
|
||||||
|
sinon.assert.calledWithExactly(dummySocket.send, JSON.stringify({
|
||||||
|
messageType: "action",
|
||||||
|
event: "terminate",
|
||||||
|
reason: "media-fail"
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Events", function() {
|
describe("Events", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox.stub(callWebSocket, "trigger");
|
sandbox.stub(callWebSocket, "trigger");
|
||||||
|
@ -10,12 +10,17 @@
|
|||||||
(function() {
|
(function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
// Stop the default init functions running to avoid conflicts.
|
||||||
|
document.removeEventListener('DOMContentLoaded', loop.panel.init);
|
||||||
|
document.removeEventListener('DOMContentLoaded', loop.conversation.init);
|
||||||
|
|
||||||
// 1. Desktop components
|
// 1. Desktop components
|
||||||
// 1.1 Panel
|
// 1.1 Panel
|
||||||
var PanelView = loop.panel.PanelView;
|
var PanelView = loop.panel.PanelView;
|
||||||
// 1.2. Conversation Window
|
// 1.2. Conversation Window
|
||||||
var IncomingCallView = loop.conversation.IncomingCallView;
|
var IncomingCallView = loop.conversation.IncomingCallView;
|
||||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||||
|
var CallFailedView = loop.conversationViews.CallFailedView;
|
||||||
|
|
||||||
// 2. Standalone webapp
|
// 2. Standalone webapp
|
||||||
var HomeView = loop.webapp.HomeView;
|
var HomeView = loop.webapp.HomeView;
|
||||||
@ -249,6 +254,15 @@
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
Section({name: "CallFailedView"},
|
||||||
|
Example({summary: "Call Failed", dashed: "true",
|
||||||
|
style: {width: "260px", height: "265px"}},
|
||||||
|
React.DOM.div({className: "fx-embedded"},
|
||||||
|
CallFailedView(null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
Section({name: "StartConversationView"},
|
Section({name: "StartConversationView"},
|
||||||
Example({summary: "Start conversation view", dashed: "true"},
|
Example({summary: "Start conversation view", dashed: "true"},
|
||||||
React.DOM.div({className: "standalone"},
|
React.DOM.div({className: "standalone"},
|
||||||
|
@ -10,12 +10,17 @@
|
|||||||
(function() {
|
(function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
// Stop the default init functions running to avoid conflicts.
|
||||||
|
document.removeEventListener('DOMContentLoaded', loop.panel.init);
|
||||||
|
document.removeEventListener('DOMContentLoaded', loop.conversation.init);
|
||||||
|
|
||||||
// 1. Desktop components
|
// 1. Desktop components
|
||||||
// 1.1 Panel
|
// 1.1 Panel
|
||||||
var PanelView = loop.panel.PanelView;
|
var PanelView = loop.panel.PanelView;
|
||||||
// 1.2. Conversation Window
|
// 1.2. Conversation Window
|
||||||
var IncomingCallView = loop.conversation.IncomingCallView;
|
var IncomingCallView = loop.conversation.IncomingCallView;
|
||||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||||
|
var CallFailedView = loop.conversationViews.CallFailedView;
|
||||||
|
|
||||||
// 2. Standalone webapp
|
// 2. Standalone webapp
|
||||||
var HomeView = loop.webapp.HomeView;
|
var HomeView = loop.webapp.HomeView;
|
||||||
@ -249,6 +254,15 @@
|
|||||||
</Example>
|
</Example>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
<Section name="CallFailedView">
|
||||||
|
<Example summary="Call Failed" dashed="true"
|
||||||
|
style={{width: "260px", height: "265px"}}>
|
||||||
|
<div className="fx-embedded">
|
||||||
|
<CallFailedView />
|
||||||
|
</div>
|
||||||
|
</Example>
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Section name="StartConversationView">
|
<Section name="StartConversationView">
|
||||||
<Example summary="Start conversation view" dashed="true">
|
<Example summary="Start conversation view" dashed="true">
|
||||||
<div className="standalone">
|
<div className="standalone">
|
||||||
|
Loading…
Reference in New Issue
Block a user