Bug 1020449 Loop should show caller information on incoming calls. Patch originally by Andrei, updated and polished by Standard8. r=nperriault
This commit is contained in:
@@ -15,6 +15,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
var sharedMixins = loop.shared.mixins;
|
var sharedMixins = loop.shared.mixins;
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
||||||
|
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
|
||||||
|
|
||||||
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin],
|
||||||
@@ -94,9 +95,14 @@ loop.conversation = (function(mozL10n) {
|
|||||||
"conversation-window-dropdown": true,
|
"conversation-window-dropdown": true,
|
||||||
"visually-hidden": !this.state.showMenu
|
"visually-hidden": !this.state.showMenu
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
React.DOM.div({className: "call-window"},
|
React.DOM.div({className: "call-window"},
|
||||||
React.DOM.h2(null, mozL10n.get("incoming_call_title2")),
|
CallIdentifierView({video: this.props.video,
|
||||||
|
peerIdentifier: this.props.model.getCallIdentifier(),
|
||||||
|
urlCreationDate: this.props.model.get("urlCreationDate"),
|
||||||
|
showIcons: true}),
|
||||||
|
|
||||||
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"}),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ loop.conversation = (function(mozL10n) {
|
|||||||
var sharedMixins = loop.shared.mixins;
|
var sharedMixins = loop.shared.mixins;
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
|
||||||
|
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
|
||||||
|
|
||||||
var IncomingCallView = React.createClass({
|
var IncomingCallView = React.createClass({
|
||||||
mixins: [sharedMixins.DropdownMenuMixin],
|
mixins: [sharedMixins.DropdownMenuMixin],
|
||||||
@@ -94,9 +95,14 @@ loop.conversation = (function(mozL10n) {
|
|||||||
"conversation-window-dropdown": true,
|
"conversation-window-dropdown": true,
|
||||||
"visually-hidden": !this.state.showMenu
|
"visually-hidden": !this.state.showMenu
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="call-window">
|
<div className="call-window">
|
||||||
<h2>{mozL10n.get("incoming_call_title2")}</h2>
|
<CallIdentifierView video={this.props.video}
|
||||||
|
peerIdentifier={this.props.model.getCallIdentifier()}
|
||||||
|
urlCreationDate={this.props.model.get("urlCreationDate")}
|
||||||
|
showIcons={true} />
|
||||||
|
|
||||||
<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>
|
||||||
|
|||||||
@@ -14,6 +14,73 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays information about the call
|
||||||
|
* Caller avatar, name & conversation creation date
|
||||||
|
*/
|
||||||
|
var CallIdentifierView = React.createClass({displayName: 'CallIdentifierView',
|
||||||
|
propTypes: {
|
||||||
|
peerIdentifier: React.PropTypes.string,
|
||||||
|
showIcons: React.PropTypes.bool.isRequired,
|
||||||
|
urlCreationDate: React.PropTypes.string,
|
||||||
|
video: React.PropTypes.bool
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
peerIdentifier: "",
|
||||||
|
showLinkDetail: true,
|
||||||
|
urlCreationDate: "",
|
||||||
|
video: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {timestamp: 0};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and formats the incoming call creation date
|
||||||
|
*/
|
||||||
|
formatCreationDate: function() {
|
||||||
|
if (!this.props.urlCreationDate) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp = this.props.urlCreationDate;
|
||||||
|
return "(" + loop.shared.utils.formatDate(timestamp) + ")";
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var iconVideoClasses = React.addons.classSet({
|
||||||
|
"fx-embedded-tiny-video-icon": true,
|
||||||
|
"muted": !this.props.video
|
||||||
|
});
|
||||||
|
var callDetailClasses = React.addons.classSet({
|
||||||
|
"fx-embedded-call-detail": true,
|
||||||
|
"hide": !this.props.showIcons
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
React.DOM.div({className: "fx-embedded-call-identifier"},
|
||||||
|
React.DOM.div({className: "fx-embedded-call-identifier-avatar fx-embedded-call-identifier-item"}),
|
||||||
|
React.DOM.div({className: "fx-embedded-call-identifier-info fx-embedded-call-identifier-item"},
|
||||||
|
React.DOM.div({className: "fx-embedded-call-identifier-text overflow-text-ellipsis font-bold"},
|
||||||
|
this.props.peerIdentifier
|
||||||
|
),
|
||||||
|
React.DOM.div({className: callDetailClasses},
|
||||||
|
React.DOM.span({className: "fx-embedded-tiny-audio-icon"}),
|
||||||
|
React.DOM.span({className: iconVideoClasses}),
|
||||||
|
React.DOM.span({className: "fx-embedded-conversation-timestamp"},
|
||||||
|
this.formatCreationDate()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays details of the incoming/outgoing conversation
|
* Displays details of the incoming/outgoing conversation
|
||||||
* (name, link, audio/video type etc).
|
* (name, link, audio/video type etc).
|
||||||
@@ -51,7 +118,9 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
React.DOM.div({className: "call-window"},
|
React.DOM.div({className: "call-window"},
|
||||||
React.DOM.h2(null, contactName),
|
CallIdentifierView({
|
||||||
|
peerIdentifier: contactName,
|
||||||
|
showIcons: false}),
|
||||||
React.DOM.div(null, this.props.children)
|
React.DOM.div(null, this.props.children)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -382,6 +451,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
PendingConversationView: PendingConversationView,
|
PendingConversationView: PendingConversationView,
|
||||||
|
CallIdentifierView: CallIdentifierView,
|
||||||
ConversationDetailView: ConversationDetailView,
|
ConversationDetailView: ConversationDetailView,
|
||||||
CallFailedView: CallFailedView,
|
CallFailedView: CallFailedView,
|
||||||
OngoingConversationView: OngoingConversationView,
|
OngoingConversationView: OngoingConversationView,
|
||||||
|
|||||||
@@ -14,6 +14,73 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays information about the call
|
||||||
|
* Caller avatar, name & conversation creation date
|
||||||
|
*/
|
||||||
|
var CallIdentifierView = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
peerIdentifier: React.PropTypes.string,
|
||||||
|
showIcons: React.PropTypes.bool.isRequired,
|
||||||
|
urlCreationDate: React.PropTypes.string,
|
||||||
|
video: React.PropTypes.bool
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
peerIdentifier: "",
|
||||||
|
showLinkDetail: true,
|
||||||
|
urlCreationDate: "",
|
||||||
|
video: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {timestamp: 0};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and formats the incoming call creation date
|
||||||
|
*/
|
||||||
|
formatCreationDate: function() {
|
||||||
|
if (!this.props.urlCreationDate) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp = this.props.urlCreationDate;
|
||||||
|
return "(" + loop.shared.utils.formatDate(timestamp) + ")";
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var iconVideoClasses = React.addons.classSet({
|
||||||
|
"fx-embedded-tiny-video-icon": true,
|
||||||
|
"muted": !this.props.video
|
||||||
|
});
|
||||||
|
var callDetailClasses = React.addons.classSet({
|
||||||
|
"fx-embedded-call-detail": true,
|
||||||
|
"hide": !this.props.showIcons
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fx-embedded-call-identifier">
|
||||||
|
<div className="fx-embedded-call-identifier-avatar fx-embedded-call-identifier-item"/>
|
||||||
|
<div className="fx-embedded-call-identifier-info fx-embedded-call-identifier-item">
|
||||||
|
<div className="fx-embedded-call-identifier-text overflow-text-ellipsis>
|
||||||
|
{this.props.peerIdentifier}
|
||||||
|
</div>
|
||||||
|
<div className={callDetailClasses}>
|
||||||
|
<span className="fx-embedded-tiny-audio-icon"></span>
|
||||||
|
<span className={iconVideoClasses}></span>
|
||||||
|
<span className="fx-embedded-conversation-timestamp">
|
||||||
|
{this.formatCreationDate()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays details of the incoming/outgoing conversation
|
* Displays details of the incoming/outgoing conversation
|
||||||
* (name, link, audio/video type etc).
|
* (name, link, audio/video type etc).
|
||||||
@@ -51,7 +118,9 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="call-window">
|
<div className="call-window">
|
||||||
<h2>{contactName}</h2>
|
<CallIdentifierView
|
||||||
|
peerIdentifier={contactName}
|
||||||
|
showIcons={false} />
|
||||||
<div>{this.props.children}</div>
|
<div>{this.props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -382,6 +451,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
PendingConversationView: PendingConversationView,
|
PendingConversationView: PendingConversationView,
|
||||||
|
CallIdentifierView: CallIdentifierView,
|
||||||
ConversationDetailView: ConversationDetailView,
|
ConversationDetailView: ConversationDetailView,
|
||||||
CallFailedView: CallFailedView,
|
CallFailedView: CallFailedView,
|
||||||
OngoingConversationView: OngoingConversationView,
|
OngoingConversationView: OngoingConversationView,
|
||||||
|
|||||||
@@ -308,15 +308,6 @@ loop.panel = (function(_, mozL10n) {
|
|||||||
this._fetchCallUrl();
|
this._fetchCallUrl();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a random 5 character string used to identify
|
|
||||||
* the conversation.
|
|
||||||
* XXX this will go away once the backend changes
|
|
||||||
*/
|
|
||||||
conversationIdentifier: function() {
|
|
||||||
return Math.random().toString(36).substring(5);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
// If we've already got a callURL, don't bother requesting a new one.
|
// If we've already got a callURL, don't bother requesting a new one.
|
||||||
// As of this writing, only used for visual testing in the UI showcase.
|
// As of this writing, only used for visual testing in the UI showcase.
|
||||||
@@ -332,7 +323,9 @@ loop.panel = (function(_, mozL10n) {
|
|||||||
*/
|
*/
|
||||||
_fetchCallUrl: function() {
|
_fetchCallUrl: function() {
|
||||||
this.setState({pending: true});
|
this.setState({pending: true});
|
||||||
this.props.client.requestCallUrl(this.conversationIdentifier(),
|
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
|
||||||
|
// a user-set string.
|
||||||
|
this.props.client.requestCallUrl("",
|
||||||
this._onCallUrlReceived);
|
this._onCallUrlReceived);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -308,15 +308,6 @@ loop.panel = (function(_, mozL10n) {
|
|||||||
this._fetchCallUrl();
|
this._fetchCallUrl();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a random 5 character string used to identify
|
|
||||||
* the conversation.
|
|
||||||
* XXX this will go away once the backend changes
|
|
||||||
*/
|
|
||||||
conversationIdentifier: function() {
|
|
||||||
return Math.random().toString(36).substring(5);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
// If we've already got a callURL, don't bother requesting a new one.
|
// If we've already got a callURL, don't bother requesting a new one.
|
||||||
// As of this writing, only used for visual testing in the UI showcase.
|
// As of this writing, only used for visual testing in the UI showcase.
|
||||||
@@ -332,7 +323,9 @@ loop.panel = (function(_, mozL10n) {
|
|||||||
*/
|
*/
|
||||||
_fetchCallUrl: function() {
|
_fetchCallUrl: function() {
|
||||||
this.setState({pending: true});
|
this.setState({pending: true});
|
||||||
this.props.client.requestCallUrl(this.conversationIdentifier(),
|
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
|
||||||
|
// a user-set string.
|
||||||
|
this.props.client.requestCallUrl("",
|
||||||
this._onCallUrlReceived);
|
this._onCallUrlReceived);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -94,12 +94,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fx-embedded-btn-icon-video,
|
.fx-embedded-btn-icon-video,
|
||||||
.fx-embedded-btn-video-small {
|
.fx-embedded-btn-video-small,
|
||||||
|
.fx-embedded-tiny-video-icon {
|
||||||
background-image: url("../img/video-inverse-14x14.png");
|
background-image: url("../img/video-inverse-14x14.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
.fx-embedded-btn-icon-audio,
|
.fx-embedded-btn-icon-audio,
|
||||||
.fx-embedded-btn-audio-small {
|
.fx-embedded-btn-audio-small,
|
||||||
|
.fx-embedded-tiny-audio-icon {
|
||||||
background-image: url("../img/audio-inverse-14x14.png");
|
background-image: url("../img/audio-inverse-14x14.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +486,74 @@
|
|||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fx-embedded-call-identifier {
|
||||||
|
display: inline;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-call-identifier-item {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-call-identifier-avatar {
|
||||||
|
max-width: 50px;
|
||||||
|
min-width: 50px;
|
||||||
|
background: #ccc;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-image: url("../img/audio-call-avatar.svg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-color: #4ba6e7;
|
||||||
|
background-size: contain;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
|
||||||
|
float: left;
|
||||||
|
-moz-margin-end: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-call-identifier-text {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-call-identifier-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
-moz-margin-start: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-conversation-timestamp {
|
||||||
|
font-size: .6rem;
|
||||||
|
line-height: 17px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-call-detail {
|
||||||
|
padding-top: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-tiny-video-icon {
|
||||||
|
margin: 0 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-tiny-audio-icon,
|
||||||
|
.fx-embedded-tiny-video-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background-size: 12px 12px;
|
||||||
|
background-color: #4ba6e7;
|
||||||
|
display: inline-block;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fx-embedded-tiny-video-icon.muted {
|
||||||
|
background-color: rgba(0,0,0,.2)
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width:640px) {
|
@media screen and (min-width:640px) {
|
||||||
|
|
||||||
/* Force full height on all parents up to the video elements
|
/* Force full height on all parents up to the video elements
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ loop.shared.models = (function(l10n) {
|
|||||||
selectedCallType: "audio-video", // The selected type for the call that was
|
selectedCallType: "audio-video", // The selected type for the call that was
|
||||||
// initiated ("audio" or "audio-video")
|
// initiated ("audio" or "audio-video")
|
||||||
callToken: undefined, // Incoming call token.
|
callToken: undefined, // Incoming call token.
|
||||||
|
callUrl: undefined, // Incoming call url
|
||||||
// Used for blocking a call url
|
// Used for blocking a call url
|
||||||
subscribedStream: false, // Used to indicate that a stream has been
|
subscribedStream: false, // Used to indicate that a stream has been
|
||||||
// subscribed to
|
// subscribed to
|
||||||
@@ -142,15 +143,18 @@ loop.shared.models = (function(l10n) {
|
|||||||
setIncomingSessionData: function(sessionData) {
|
setIncomingSessionData: function(sessionData) {
|
||||||
// Explicit property assignment to prevent later "surprises"
|
// Explicit property assignment to prevent later "surprises"
|
||||||
this.set({
|
this.set({
|
||||||
sessionId: sessionData.sessionId,
|
sessionId: sessionData.sessionId,
|
||||||
sessionToken: sessionData.sessionToken,
|
sessionToken: sessionData.sessionToken,
|
||||||
sessionType: sessionData.sessionType,
|
sessionType: sessionData.sessionType,
|
||||||
apiKey: sessionData.apiKey,
|
apiKey: sessionData.apiKey,
|
||||||
callId: sessionData.callId,
|
callId: sessionData.callId,
|
||||||
progressURL: sessionData.progressURL,
|
callerId: sessionData.callerId,
|
||||||
websocketToken: sessionData.websocketToken.toString(16),
|
urlCreationDate: sessionData.urlCreationDate,
|
||||||
callType: sessionData.callType || "audio-video",
|
progressURL: sessionData.progressURL,
|
||||||
callToken: sessionData.callToken
|
websocketToken: sessionData.websocketToken.toString(16),
|
||||||
|
callType: sessionData.callType || "audio-video",
|
||||||
|
callToken: sessionData.callToken,
|
||||||
|
callUrl: sessionData.callUrl
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -198,6 +202,23 @@ loop.shared.models = (function(l10n) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to remove the scheme from a url.
|
||||||
|
*/
|
||||||
|
_removeScheme: function(url) {
|
||||||
|
if (!url) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return url.replace(/^https?:\/\//, "");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a conversation identifier for the incoming call view
|
||||||
|
*/
|
||||||
|
getCallIdentifier: function() {
|
||||||
|
return this.get("callerId") || this._removeScheme(this.get("callUrl"));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes a local stream.
|
* Publishes a local stream.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,6 +17,18 @@ loop.shared.utils = (function() {
|
|||||||
AUDIO_ONLY: "audio"
|
AUDIO_ONLY: "audio"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a given date into an l10n-friendly string.
|
||||||
|
*
|
||||||
|
* @param {Integer} The timestamp in seconds to format.
|
||||||
|
* @return {String} The formatted string.
|
||||||
|
*/
|
||||||
|
function formatDate(timestamp) {
|
||||||
|
var date = (new Date(timestamp * 1000));
|
||||||
|
var options = {year: "numeric", month: "long", day: "numeric"};
|
||||||
|
return date.toLocaleDateString(navigator.language, options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for adding different styles to the panel
|
* Used for adding different styles to the panel
|
||||||
* @returns {String} Corresponds to the client platform
|
* @returns {String} Corresponds to the client platform
|
||||||
@@ -87,6 +99,7 @@ loop.shared.utils = (function() {
|
|||||||
return {
|
return {
|
||||||
CALL_TYPES: CALL_TYPES,
|
CALL_TYPES: CALL_TYPES,
|
||||||
Helper: Helper,
|
Helper: Helper,
|
||||||
|
formatDate: formatDate,
|
||||||
getTargetPlatform: getTargetPlatform,
|
getTargetPlatform: getTargetPlatform,
|
||||||
getBoolPreference: getBoolPreference
|
getBoolPreference: getBoolPreference
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -446,10 +446,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
||||||
} else {
|
} else {
|
||||||
var date = (new Date(callUrlInfo.urlCreationDate * 1000));
|
this.setState({
|
||||||
var options = {year: "numeric", month: "long", day: "numeric"};
|
urlCreationDateString: sharedUtils.formatDate(callUrlInfo.urlCreationDate)
|
||||||
var timestamp = date.toLocaleDateString(navigator.language, options);
|
});
|
||||||
this.setState({urlCreationDateString: timestamp});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -446,10 +446,9 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
||||||
} else {
|
} else {
|
||||||
var date = (new Date(callUrlInfo.urlCreationDate * 1000));
|
this.setState({
|
||||||
var options = {year: "numeric", month: "long", day: "numeric"};
|
urlCreationDateString: sharedUtils.formatDate(callUrlInfo.urlCreationDate)
|
||||||
var timestamp = date.toLocaleDateString(navigator.language, options);
|
});
|
||||||
this.setState({urlCreationDateString: timestamp});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,67 @@ describe("loop.conversationViews", function () {
|
|||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("CallIdentifierView", function() {
|
||||||
|
function mountTestComponent(props) {
|
||||||
|
return TestUtils.renderIntoDocument(
|
||||||
|
loop.conversationViews.CallIdentifierView(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should set display the peer identifer", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
showIcons: false,
|
||||||
|
peerIdentifier: "mrssmith"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(TestUtils.findRenderedDOMComponentWithClass(
|
||||||
|
view, "fx-embedded-call-identifier-text").props.children).eql("mrssmith");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not display the icons if showIcons is false", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
showIcons: false,
|
||||||
|
peerIdentifier: "mrssmith"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(TestUtils.findRenderedDOMComponentWithClass(
|
||||||
|
view, "fx-embedded-call-detail").props.className).to.contain("hide");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display the icons if showIcons is true", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
showIcons: true,
|
||||||
|
peerIdentifier: "mrssmith"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(TestUtils.findRenderedDOMComponentWithClass(
|
||||||
|
view, "fx-embedded-call-detail").props.className).to.not.contain("hide");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display the url timestamp", function() {
|
||||||
|
sandbox.stub(loop.shared.utils, "formatDate").returns(("October 9, 2014"));
|
||||||
|
|
||||||
|
view = mountTestComponent({
|
||||||
|
showIcons: true,
|
||||||
|
peerIdentifier: "mrssmith",
|
||||||
|
urlCreationDate: (new Date() / 1000).toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(TestUtils.findRenderedDOMComponentWithClass(
|
||||||
|
view, "fx-embedded-conversation-timestamp").props.children).eql("(October 9, 2014)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show video as muted if video is false", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
showIcons: true,
|
||||||
|
peerIdentifier: "mrssmith",
|
||||||
|
video: false
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(TestUtils.findRenderedDOMComponentWithClass(
|
||||||
|
view, "fx-embedded-tiny-video-icon").props.className).to.contain("muted");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("ConversationDetailView", function() {
|
describe("ConversationDetailView", function() {
|
||||||
function mountTestComponent(props) {
|
function mountTestComponent(props) {
|
||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
@@ -47,23 +108,14 @@ describe("loop.conversationViews", function () {
|
|||||||
expect(document.title).eql("mrsmith");
|
expect(document.title).eql("mrsmith");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set display the calledId", function() {
|
|
||||||
view = mountTestComponent({contact: contact});
|
|
||||||
|
|
||||||
expect(TestUtils.findRenderedDOMComponentWithTag(
|
|
||||||
view, "h2").props.children).eql("mrsmith");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fallback to the email if the contact name is not defined",
|
it("should fallback to the email if the contact name is not defined",
|
||||||
function() {
|
function() {
|
||||||
delete contact.name;
|
delete contact.name;
|
||||||
|
|
||||||
view = mountTestComponent({contact: contact});
|
mountTestComponent({contact: contact});
|
||||||
|
|
||||||
expect(TestUtils.findRenderedDOMComponentWithTag(
|
expect(document.title).eql("fakeEmail");
|
||||||
view, "h2").props.children).eql("fakeEmail");
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("PendingConversationView", function() {
|
describe("PendingConversationView", function() {
|
||||||
|
|||||||
@@ -696,7 +696,9 @@ describe("loop.conversation", function() {
|
|||||||
var view, model;
|
var view, model;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
var Model = Backbone.Model.extend({});
|
var Model = Backbone.Model.extend({
|
||||||
|
getCallIdentifier: function() {return "fakeId";}
|
||||||
|
});
|
||||||
model = new Model();
|
model = new Model();
|
||||||
sandbox.spy(model, "trigger");
|
sandbox.spy(model, "trigger");
|
||||||
sandbox.stub(model, "set");
|
sandbox.stub(model, "set");
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ describe("loop.shared.models", function() {
|
|||||||
apiKey: "apiKey",
|
apiKey: "apiKey",
|
||||||
callType: "callType",
|
callType: "callType",
|
||||||
websocketToken: 123,
|
websocketToken: 123,
|
||||||
callToken: "callToken"
|
callToken: "callToken",
|
||||||
|
callUrl: "http://invalid/callToken",
|
||||||
|
callerId: "mrssmith"
|
||||||
};
|
};
|
||||||
fakeSession = _.extend({
|
fakeSession = _.extend({
|
||||||
connect: function () {},
|
connect: function () {},
|
||||||
@@ -360,6 +362,28 @@ describe("loop.shared.models", function() {
|
|||||||
expect(model.hasVideoStream("outgoing")).to.eql(true);
|
expect(model.hasVideoStream("outgoing")).to.eql(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#getCallIdentifier", function() {
|
||||||
|
var model;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||||
|
sdk: fakeSDK
|
||||||
|
});
|
||||||
|
model.startSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the callerId", function() {
|
||||||
|
expect(model.getCallIdentifier()).to.eql("mrssmith");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the shorted callUrl if the callerId does not exist",
|
||||||
|
function() {
|
||||||
|
model.set({"callerId": ""});
|
||||||
|
|
||||||
|
expect(model.getCallIdentifier()).to.eql("invalid/callToken");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,26 @@ describe("loop.shared.utils", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#formatDate", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
sandbox.stub(Date.prototype, "toLocaleDateString").returns("fake result");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call toLocaleDateString with arguments", function() {
|
||||||
|
sharedUtils.formatDate(1000);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(Date.prototype.toLocaleDateString);
|
||||||
|
sinon.assert.calledWithExactly(Date.prototype.toLocaleDateString,
|
||||||
|
navigator.language,
|
||||||
|
{year: "numeric", month: "long", day: "numeric"}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the formatted string", function() {
|
||||||
|
expect(sharedUtils.formatDate(1000)).eql("fake result");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#getBoolPreference", function() {
|
describe("#getBoolPreference", function() {
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
navigator.mozLoop = undefined;
|
navigator.mozLoop = undefined;
|
||||||
|
|||||||
@@ -78,7 +78,10 @@
|
|||||||
|
|
||||||
var mockSDK = {};
|
var mockSDK = {};
|
||||||
|
|
||||||
var mockConversationModel = new loop.shared.models.ConversationModel({}, {
|
var mockConversationModel = new loop.shared.models.ConversationModel({
|
||||||
|
callerId: "Mrs Jones",
|
||||||
|
urlCreationDate: (new Date() / 1000).toString()
|
||||||
|
}, {
|
||||||
sdk: mockSDK
|
sdk: mockSDK
|
||||||
});
|
});
|
||||||
mockConversationModel.startSession = noop;
|
mockConversationModel.startSession = noop;
|
||||||
|
|||||||
@@ -78,7 +78,10 @@
|
|||||||
|
|
||||||
var mockSDK = {};
|
var mockSDK = {};
|
||||||
|
|
||||||
var mockConversationModel = new loop.shared.models.ConversationModel({}, {
|
var mockConversationModel = new loop.shared.models.ConversationModel({
|
||||||
|
callerId: "Mrs Jones",
|
||||||
|
urlCreationDate: (new Date() / 1000).toString()
|
||||||
|
}, {
|
||||||
sdk: mockSDK
|
sdk: mockSDK
|
||||||
});
|
});
|
||||||
mockConversationModel.startSession = noop;
|
mockConversationModel.startSession = noop;
|
||||||
|
|||||||
Reference in New Issue
Block a user