Bug 1033841: Ported Loop panel views to React. r=Standard8
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
/** @jsx React.DOM */
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* global loop:true */
|
||||
/*jshint newcap:false*/
|
||||
/*global loop:true, React */
|
||||
|
||||
var loop = loop || {};
|
||||
loop.panel = (function(_, mozL10n) {
|
||||
@@ -21,162 +24,188 @@ loop.panel = (function(_, mozL10n) {
|
||||
/**
|
||||
* Do not disturb panel subview.
|
||||
*/
|
||||
var DoNotDisturbView = sharedViews.BaseView.extend({
|
||||
template: _.template([
|
||||
'<label>',
|
||||
' <input type="checkbox" <%- checked %>>',
|
||||
' <span data-l10n-id="do_not_disturb"></span>',
|
||||
'</label>',
|
||||
].join('')),
|
||||
|
||||
events: {
|
||||
"click input[type=checkbox]": "toggle"
|
||||
var DoNotDisturb = React.createClass({displayName: 'DoNotDisturb',
|
||||
getInitialState: function() {
|
||||
return {doNotDisturb: navigator.mozLoop.doNotDisturb};
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles mozLoop activation status.
|
||||
*/
|
||||
toggle: function() {
|
||||
handleCheckboxChange: function() {
|
||||
// Note: side effect!
|
||||
navigator.mozLoop.doNotDisturb = !navigator.mozLoop.doNotDisturb;
|
||||
this.render();
|
||||
this.setState({doNotDisturb: navigator.mozLoop.doNotDisturb});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.template({
|
||||
checked: navigator.mozLoop.doNotDisturb ? "checked" : ""
|
||||
}));
|
||||
return this;
|
||||
// XXX https://github.com/facebook/react/issues/310 for === htmlFor
|
||||
return (
|
||||
React.DOM.p( {className:"dnd"},
|
||||
React.DOM.input( {type:"checkbox", checked:this.state.doNotDisturb,
|
||||
id:"dnd-component", onChange:this.handleCheckboxChange} ),
|
||||
React.DOM.label( {htmlFor:"dnd-component"}, __("do_not_disturb"))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var ToSView = sharedViews.BaseView.extend({
|
||||
template: _.template([
|
||||
'<p data-l10n-id="legal_text_and_links"',
|
||||
' data-l10n-args=\'',
|
||||
' {"terms_of_use_url": "https://accounts.firefox.com/legal/terms",',
|
||||
' "privacy_notice_url": "www.mozilla.org/privacy/"',
|
||||
' }\'></p>'
|
||||
].join('')),
|
||||
var ToSView = React.createClass({displayName: 'ToSView',
|
||||
getInitialState: function() {
|
||||
return {seenToS: navigator.mozLoop.getLoopCharPref('seenToS')};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (navigator.mozLoop.getLoopCharPref('seenToS') === null) {
|
||||
this.$el.html(this.template());
|
||||
var tosHTML = __("legal_text_and_links", {
|
||||
"terms_of_use_url": "https://accounts.firefox.com/legal/terms",
|
||||
"privacy_notice_url": "www.mozilla.org/privacy/"
|
||||
});
|
||||
|
||||
if (!this.state.seenToS) {
|
||||
navigator.mozLoop.setLoopCharPref('seenToS', 'seen');
|
||||
return React.DOM.p( {className:"tos",
|
||||
dangerouslySetInnerHTML:{__html: tosHTML}});
|
||||
} else {
|
||||
return React.DOM.div(null );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
var PanelLayout = React.createClass({displayName: 'PanelLayout',
|
||||
propTypes: {
|
||||
summary: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div( {className:"share generate-url"},
|
||||
React.DOM.div( {className:"description"},
|
||||
React.DOM.p(null, this.props.summary)
|
||||
),
|
||||
React.DOM.div( {className:"action"},
|
||||
this.props.children
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var CallUrlResult = React.createClass({displayName: 'CallUrlResult',
|
||||
propTypes: {
|
||||
callUrl: React.PropTypes.string.isRequired,
|
||||
retry: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
handleButtonClick: function() {
|
||||
this.props.retry();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// XXX setting elem value from a state (in the callUrl input)
|
||||
// makes it immutable ie read only but that is fine in our case.
|
||||
// readOnly attr will suppress a warning regarding this issue
|
||||
// from the react lib.
|
||||
return (
|
||||
PanelLayout( {summary:__("share_link_url")},
|
||||
React.DOM.div( {className:"invite"},
|
||||
React.DOM.input( {type:"url", value:this.props.callUrl, readOnly:"true"} ),
|
||||
React.DOM.button( {onClick:this.handleButtonClick,
|
||||
className:"btn btn-success"}, __("new_url"))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var CallUrlForm = React.createClass({displayName: 'CallUrlForm',
|
||||
propTypes: {
|
||||
client: React.PropTypes.object.isRequired,
|
||||
notifier: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
pending: false,
|
||||
disabled: true,
|
||||
callUrl: false
|
||||
};
|
||||
},
|
||||
|
||||
retry: function() {
|
||||
this.setState(this.getInitialState());
|
||||
},
|
||||
|
||||
handleTextChange: function(event) {
|
||||
this.setState({disabled: !event.currentTarget.value});
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({pending: true});
|
||||
|
||||
this.props.client.requestCallUrl(
|
||||
this.refs.caller.getDOMNode().value, this._onCallUrlReceived);
|
||||
},
|
||||
|
||||
_onCallUrlReceived: function(err, callUrlData) {
|
||||
var callUrl = false;
|
||||
|
||||
this.props.notifier.clear();
|
||||
|
||||
if (err) {
|
||||
this.props.notifier.errorL10n("unable_retrieve_url");
|
||||
} else {
|
||||
callUrl = callUrlData.callUrl || callUrlData.call_url;
|
||||
}
|
||||
|
||||
this.setState({pending: false, callUrl: callUrl});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// If we have a call url, render result
|
||||
if (this.state.callUrl) {
|
||||
return (
|
||||
CallUrlResult( {callUrl:this.state.callUrl, retry:this.retry})
|
||||
);
|
||||
}
|
||||
|
||||
// If we don't display the form
|
||||
var cx = React.addons.classSet;
|
||||
return (
|
||||
PanelLayout( {summary:__("get_link_to_share")},
|
||||
React.DOM.form( {className:"invite", onSubmit:this.handleFormSubmit},
|
||||
|
||||
React.DOM.input( {type:"text", name:"caller", ref:"caller", required:"required",
|
||||
className:cx({'pending': this.state.pending}),
|
||||
onChange:this.handleTextChange,
|
||||
placeholder:__("call_identifier_textinput_placeholder")} ),
|
||||
|
||||
React.DOM.button( {type:"submit", className:"get-url btn btn-success",
|
||||
disabled:this.state.disabled},
|
||||
__("get_a_call_url")
|
||||
)
|
||||
),
|
||||
ToSView(null )
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Panel view.
|
||||
*/
|
||||
var PanelView = sharedViews.BaseView.extend({
|
||||
template: _.template([
|
||||
'<div class="description">',
|
||||
' <p data-l10n-id="get_link_to_share"></p>',
|
||||
'</div>',
|
||||
'<div class="action">',
|
||||
' <form class="invite">',
|
||||
' <input type="text" name="caller" data-l10n-id="caller" required>',
|
||||
' <button type="submit" class="get-url btn btn-success"',
|
||||
' data-l10n-id="get_a_call_url"></button>',
|
||||
' </form>',
|
||||
' <p class="tos"></p>',
|
||||
' <p class="result hide">',
|
||||
' <input id="call-url" type="url" readonly>',
|
||||
' <a class="go-back btn btn-info" href="" data-l10n-id="new_url"></a>',
|
||||
' </p>',
|
||||
' <p class="dnd"></p>',
|
||||
'</div>',
|
||||
].join("")),
|
||||
|
||||
className: "share generate-url",
|
||||
|
||||
/**
|
||||
* Do not disturb view.
|
||||
* @type {DoNotDisturbView|undefined}
|
||||
*/
|
||||
dndView: undefined,
|
||||
|
||||
events: {
|
||||
"keyup input[name=caller]": "changeButtonState",
|
||||
"submit form.invite": "getCallUrl",
|
||||
"click a.go-back": "goBack"
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
if (!options.notifier) {
|
||||
throw new Error("missing required notifier");
|
||||
}
|
||||
this.notifier = options.notifier;
|
||||
this.client = new loop.Client();
|
||||
},
|
||||
|
||||
getNickname: function() {
|
||||
return this.$("input[name=caller]").val();
|
||||
},
|
||||
|
||||
getCallUrl: function(event) {
|
||||
this.notifier.clear();
|
||||
event.preventDefault();
|
||||
var callback = function(err, callUrlData) {
|
||||
this.clearPending();
|
||||
if (err) {
|
||||
this.notifier.errorL10n("unable_retrieve_url");
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
this.onCallUrlReceived(callUrlData);
|
||||
}.bind(this);
|
||||
|
||||
this.setPending();
|
||||
this.client.requestCallUrl(this.getNickname(), callback);
|
||||
},
|
||||
|
||||
goBack: function(event) {
|
||||
event.preventDefault();
|
||||
this.$(".action .result").hide();
|
||||
this.$(".action .invite").show();
|
||||
this.$(".description p").text(__("get_link_to_share"));
|
||||
this.changeButtonState();
|
||||
},
|
||||
|
||||
onCallUrlReceived: function(callUrlData) {
|
||||
this.notifier.clear();
|
||||
this.$(".action .invite").hide();
|
||||
this.$(".action .invite input").val("");
|
||||
this.$(".action .result input").val(callUrlData.callUrl);
|
||||
this.$(".action .result").show();
|
||||
this.$(".description p").text(__("share_link_url"));
|
||||
},
|
||||
|
||||
setPending: function() {
|
||||
this.$("[name=caller]").addClass("pending");
|
||||
this.$(".get-url").addClass("disabled").attr("disabled", "disabled");
|
||||
},
|
||||
|
||||
clearPending: function() {
|
||||
this.$("[name=caller]").removeClass("pending");
|
||||
this.changeButtonState();
|
||||
},
|
||||
|
||||
changeButtonState: function() {
|
||||
var enabled = !!this.$("input[name=caller]").val();
|
||||
if (enabled) {
|
||||
this.$(".get-url").removeClass("disabled")
|
||||
.removeAttr("disabled", "disabled");
|
||||
} else {
|
||||
this.$(".get-url").addClass("disabled").attr("disabled", "disabled");
|
||||
}
|
||||
var PanelView = React.createClass({displayName: 'PanelView',
|
||||
propTypes: {
|
||||
notifier: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.template());
|
||||
// Do not Disturb sub view
|
||||
this.dndView = new DoNotDisturbView({el: this.$(".dnd")}).render();
|
||||
this.tosView = new ToSView({el: this.$(".tos")}).render();
|
||||
return this;
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
CallUrlForm( {client:this.props.client,
|
||||
notifier:this.props.notifier} ),
|
||||
DoNotDisturb(null )
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -230,10 +259,12 @@ loop.panel = (function(_, mozL10n) {
|
||||
* Resets this router to its initial state.
|
||||
*/
|
||||
reset: function() {
|
||||
// purge pending notifications
|
||||
this._notifier.clear();
|
||||
// reset home view
|
||||
this.loadView(new PanelView({notifier: this._notifier}));
|
||||
var client = new loop.Client({
|
||||
baseServerUrl: navigator.mozLoop.serverUrl
|
||||
});
|
||||
this.loadReactComponent(PanelView( {client:client,
|
||||
notifier:this._notifier} ));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -259,8 +290,9 @@ loop.panel = (function(_, mozL10n) {
|
||||
|
||||
return {
|
||||
init: init,
|
||||
DoNotDisturb: DoNotDisturb,
|
||||
CallUrlForm: CallUrlForm,
|
||||
PanelView: PanelView,
|
||||
DoNotDisturbView: DoNotDisturbView,
|
||||
PanelRouter: PanelRouter,
|
||||
ToSView: ToSView
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user