Bug 1184924 - Implement the refreshed design for the invitation overlay [r=Standard8]

This commit is contained in:
Ed Lee 2015-09-18 10:45:47 -07:00
Родитель 9ce1f24f2c
Коммит 1e28b26a7f
9 изменённых файлов: 148 добавлений и 156 удалений

Просмотреть файл

@ -191,6 +191,10 @@ loop.roomViews = (function(mozL10n) {
* Desktop room invitation view (overlay).
*/
var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView",
statics: {
TRIGGERED_RESET_DELAY: 2000
},
mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
propTypes: {
@ -236,6 +240,16 @@ loop.roomViews = (function(mozL10n) {
}));
this.setState({copiedUrl: true});
setTimeout(this.resetTriggeredButtons, this.constructor.TRIGGERED_RESET_DELAY);
},
/**
* Reset state of triggered buttons if necessary
*/
resetTriggeredButtons: function() {
if (this.state.copiedUrl) {
this.setState({copiedUrl: false});
}
},
handleShareButtonClick: function(event) {
@ -252,14 +266,6 @@ loop.roomViews = (function(mozL10n) {
this.toggleDropdownMenu();
},
handleAddContextClick: function(event) {
event.preventDefault();
if (this.props.onAddContextClick) {
this.props.onAddContextClick();
}
},
handleEditContextClose: function() {
if (this.props.onEditContextClose) {
this.props.onEditContextClose();
@ -271,22 +277,12 @@ loop.roomViews = (function(mozL10n) {
return null;
}
var canAddContext = this.props.mozLoop.getLoopPref("contextInConversations.enabled") &&
// Don't show the link when we're showing the edit form already:
!this.props.showEditContext &&
// Don't show the link when there's already context data available:
!(this.props.roomData.roomContextUrls || this.props.roomData.roomDescription);
var cx = React.addons.classSet;
return (
React.createElement("div", {className: "room-invitation-overlay"},
React.createElement("div", {className: "room-invitation-content"},
React.createElement("p", {className: cx({hide: this.props.showEditContext})},
mozL10n.get("invite_header_text")
),
React.createElement("a", {className: cx({hide: !canAddContext, "room-invitation-addcontext": true}),
onClick: this.handleAddContextClick},
mozL10n.get("context_add_some_label")
mozL10n.get("invite_header_text2")
)
),
React.createElement("div", {className: cx({
@ -294,19 +290,21 @@ loop.roomViews = (function(mozL10n) {
"call-action-group": true,
hide: this.props.showEditContext
})},
React.createElement("button", {className: "btn btn-info btn-email",
onClick: this.handleEmailButtonClick},
mozL10n.get("email_link_button")
React.createElement("div", {className: cx({
"btn-copy": true,
"invite-button": true,
"triggered": this.state.copiedUrl
}),
onClick: this.handleCopyButtonClick},
React.createElement("img", {src: "loop/shared/img/svg/glyph-link-16x16.svg"}),
React.createElement("p", null, mozL10n.get("invite_copy_" +
(this.state.copiedUrl ? "triggered" : "button")))
),
React.createElement("button", {className: "btn btn-info btn-copy",
onClick: this.handleCopyButtonClick},
this.state.copiedUrl ? mozL10n.get("copied_url_button") :
mozL10n.get("copy_url_button2")
),
React.createElement("button", {className: "btn btn-info btn-share",
onClick: this.handleShareButtonClick,
ref: "anchor"},
mozL10n.get("share_button3")
React.createElement("div", {className: "btn-email invite-button",
onClick: this.handleEmailButtonClick,
onMouseOver: this.resetTriggeredButtons},
React.createElement("img", {src: "loop/shared/img/svg/glyph-email-16x16.svg"}),
React.createElement("p", null, mozL10n.get("invite_email_button"))
)
),
React.createElement(SocialShareDropdown, {

Просмотреть файл

@ -191,6 +191,10 @@ loop.roomViews = (function(mozL10n) {
* Desktop room invitation view (overlay).
*/
var DesktopRoomInvitationView = React.createClass({
statics: {
TRIGGERED_RESET_DELAY: 2000
},
mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
propTypes: {
@ -236,6 +240,16 @@ loop.roomViews = (function(mozL10n) {
}));
this.setState({copiedUrl: true});
setTimeout(this.resetTriggeredButtons, this.constructor.TRIGGERED_RESET_DELAY);
},
/**
* Reset state of triggered buttons if necessary
*/
resetTriggeredButtons: function() {
if (this.state.copiedUrl) {
this.setState({copiedUrl: false});
}
},
handleShareButtonClick: function(event) {
@ -252,14 +266,6 @@ loop.roomViews = (function(mozL10n) {
this.toggleDropdownMenu();
},
handleAddContextClick: function(event) {
event.preventDefault();
if (this.props.onAddContextClick) {
this.props.onAddContextClick();
}
},
handleEditContextClose: function() {
if (this.props.onEditContextClose) {
this.props.onEditContextClose();
@ -271,43 +277,35 @@ loop.roomViews = (function(mozL10n) {
return null;
}
var canAddContext = this.props.mozLoop.getLoopPref("contextInConversations.enabled") &&
// Don't show the link when we're showing the edit form already:
!this.props.showEditContext &&
// Don't show the link when there's already context data available:
!(this.props.roomData.roomContextUrls || this.props.roomData.roomDescription);
var cx = React.addons.classSet;
return (
<div className="room-invitation-overlay">
<div className="room-invitation-content">
<p className={cx({hide: this.props.showEditContext})}>
{mozL10n.get("invite_header_text")}
{mozL10n.get("invite_header_text2")}
</p>
<a className={cx({hide: !canAddContext, "room-invitation-addcontext": true})}
onClick={this.handleAddContextClick}>
{mozL10n.get("context_add_some_label")}
</a>
</div>
<div className={cx({
"btn-group": true,
"call-action-group": true,
hide: this.props.showEditContext
})}>
<button className="btn btn-info btn-email"
onClick={this.handleEmailButtonClick}>
{mozL10n.get("email_link_button")}
</button>
<button className="btn btn-info btn-copy"
onClick={this.handleCopyButtonClick}>
{this.state.copiedUrl ? mozL10n.get("copied_url_button") :
mozL10n.get("copy_url_button2")}
</button>
<button className="btn btn-info btn-share"
onClick={this.handleShareButtonClick}
ref="anchor">
{mozL10n.get("share_button3")}
</button>
<div className={cx({
"btn-copy": true,
"invite-button": true,
"triggered": this.state.copiedUrl
})}
onClick={this.handleCopyButtonClick}>
<img src="loop/shared/img/svg/glyph-link-16x16.svg" />
<p>{mozL10n.get("invite_copy_" +
(this.state.copiedUrl ? "triggered" : "button"))}</p>
</div>
<div className="btn-email invite-button"
onClick={this.handleEmailButtonClick}
onMouseOver={this.resetTriggeredButtons}>
<img src="loop/shared/img/svg/glyph-email-16x16.svg" />
<p>{mozL10n.get("invite_email_button")}</p>
</div>
</div>
<SocialShareDropdown
dispatcher={this.props.dispatcher}

Просмотреть файл

@ -268,6 +268,41 @@ html[dir="rtl"] .conversation-toolbar-btn-box.btn-edit-entry {
width: 100%;
}
.call-action-group > .invite-button {
margin: 0 4px;
position: relative;
}
.call-action-group > .invite-button > img {
background-color: #00a9dc;
border-radius: 100%;
height: 28px;
width: 28px;
}
.call-action-group > .invite-button:hover > img {
background-color: #5cccee;
}
.call-action-group > .invite-button.triggered > img {
background-color: #56b397;
}
.call-action-group > .invite-button > p {
display: none;
/* Position the text under the button while centering it without impacting the
* rest of the layout */
left: -10rem;
margin: .5rem 0 0;
position: absolute;
right: -10rem;
}
.call-action-group > .invite-button.triggered > p,
.call-action-group > .invite-button:hover > p {
display: block;
}
.direct-call-failure,
.room-failure {
/* This flex allows us to not calculate the height of the logo area
@ -663,13 +698,13 @@ html[dir="rtl"] .room-conversation-wrapper header a {
.room-invitation-overlay {
position: absolute;
background: rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.85);
top: 0;
height: 100%;
right: 0;
left: 0;
text-align: center;
color: #fff;
color: #000;
z-index: 1010;
display: flex;
flex-flow: column nowrap;
@ -686,30 +721,7 @@ html[dir="rtl"] .room-conversation-wrapper header a {
}
.room-invitation-overlay .btn-group {
padding: 0 0 5rem 0;
}
.room-invitation-addcontext {
color: #0095dd;
padding-left: 1.5em;
margin-bottom: 1em;
background-image: url("../img/icons-10x10.svg#edit-active");
background-size: 1em 1em;
background-repeat: no-repeat;
background-position: left top;
font-size: 1em;
cursor: pointer;
}
.room-invitation-addcontext:hover,
.room-invitation-addcontext:hover:active {
text-decoration: underline;
}
html[dir="rtl"] .room-invitation-addcontext {
padding-left: 0;
padding-right: 1.5em;
background-position: right top;
padding: 0 0 10rem;
}
.share-service-dropdown {
@ -804,6 +816,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
text-shadow: 1px 1px 0 rgba(0,0,0,.3);
}
.room-invitation-content,
.room-context-header {
color: #333;
font-size: 1.2rem;

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#FFF" d="M12 10.4c0 .2-.1.4-.2.5-.1.1-.3.2-.5.2H4.7c-.2 0-.4-.1-.5-.2-.1-.1-.2-.3-.2-.5V6.9c.1.1.3.3.5.4 1 .7 1.8 1.2 2.2 1.5.1.1.3.2.4.3.1.1.2.1.4.2s.3.1.5.1.3 0 .5-.1.3-.1.4-.2c.1-.1.3-.2.4-.3.5-.4 1.2-.9 2.2-1.5.2-.1.4-.3.5-.4v3.5zm-.2-4.2c-.1.2-.3.4-.5.5-1.1.8-1.8 1.3-2.1 1.5 0 0-.1.1-.2.1-.1.2-.2.2-.3.3-.1 0-.1.1-.2.1-.1.1-.2.1-.3.1h-.4c-.1 0-.2-.1-.3-.1-.1-.1-.2-.1-.2-.1-.1-.1-.2-.1-.3-.2-.1-.1-.1-.1-.1-.2-.3-.1-.7-.4-1.2-.8-.5-.3-.8-.5-.9-.6-.2-.1-.4-.3-.6-.5S4 5.9 4 5.7c0-.2.1-.4.2-.6.1-.2.3-.2.5-.2h6.6c.2 0 .4.1.5.2.1.1.2.3.2.5s-.1.4-.2.6z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 635 B

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#FFF" d="M12 11.6c0 .1 0 .2-.1.3s-.2.1-.3.1h-2V8.9h1l.2-1.2H9.5v-.8c0-.2 0-.3.1-.4.1-.1.2-.1.5-.1h.6V5.3h-.9c-.4-.1-.8 0-1.1.3-.3.3-.4.7-.4 1.2v.9h-1v1.2h1V12H4.4c-.1 0-.2 0-.3-.1-.1-.1-.1-.2-.1-.3V4.4c0-.1 0-.2.1-.3.1-.1.2-.1.3-.1h7.1c.1 0 .2 0 .3.1.2.1.2.2.2.3v7.2z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 348 B

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#FFF" d="M12 9.9c0 .4-.1.7-.4 1l-.7.7c-.3.3-.6.4-1 .4s-.7-.1-1-.4l-1-1c-.3-.3-.4-.6-.4-1s.1-.7.4-1l-.4-.5c-.3.3-.6.4-1 .4s-.7-.1-1-.4l-1-1c-.4-.3-.5-.6-.5-1s.1-.7.4-1l.7-.7c.3-.3.6-.4 1-.4s.7.1 1 .4l1 1c.3.3.4.6.4 1s-.1.7-.4 1l.4.4c.3-.3.6-.4 1-.4s.7.1 1 .4l1 1c.4.4.5.7.5 1.1zM7.6 6.4c0-.1 0-.2-.1-.3l-1-1c-.1-.1-.2-.2-.4-.2-.1 0-.2.1-.3.2l-.7.7c-.1.1-.2.2-.2.3 0 .1 0 .2.1.3l1 1c.1.1.2.1.3.1.1 0 .3-.1.4-.2l-.1-.1v.1s0-.1-.1-.1l-.1-.1V7c0-.1 0-.2.1-.3s.2-.1.3-.1h.1s.1 0 .1.1l.1.1.1.1.1.1c.3-.3.3-.4.3-.6zm3.5 3.5c0-.1 0-.2-.1-.3l-1-1c-.2-.2-.3-.2-.4-.2-.1 0-.3.1-.4.2l.1.1.1.1s0 .1.1.1l.1.1v.1c0 .1 0 .2-.1.3s-.3.2-.4.2H9s-.1 0-.1-.1l-.1-.1-.1-.1c-.1 0-.1-.1-.2-.1-.1.1-.1.2-.1.4 0 .1 0 .2.1.3l1 1c.1.1.2.1.3.1.1 0 .2 0 .3-.1l.7-.7c.2-.1.3-.2.3-.3z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 832 B

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#FFF" d="M6.5 4.3c.4-.5.9-.7 1.5-.7s1.1.2 1.5.6.6.9.6 1.5-.2 1.1-.6 1.5c-.4.6-.9.8-1.5.8s-1.1-.2-1.5-.6c-.5-.5-.7-1-.7-1.6 0-.6.2-1.1.7-1.5zm5.1 7.7c-.3.3-.6.4-1.1.4h-5c-.5 0-.8-.1-1.1-.4-.3-.3-.4-.7-.4-1.1v-.6s0-.4.1-.6c0-.2.1-.4.2-.6.1-.2.1-.4.2-.6.1-.2.2-.3.4-.5.1-.1.2-.2.4-.2s.4-.2.7-.2c0 0 .1 0 .2.1s.3.2.4.3l.6.3s.5.1.8.1c.3 0 .5 0 .8-.1l.6-.3c.2-.1.3-.2.4-.3.1 0 .2-.1.2-.1.2 0 .4 0 .6.1.2.1.4.2.5.3.1.1.2.3.4.5s.2.4.2.6c.1.2.1.4.2.6 0 .2.1.4.1.6v.6c0 .4-.1.8-.4 1.1z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 556 B

Просмотреть файл

@ -55,7 +55,11 @@ browser.jar:
content/browser/loop/shared/img/video-inverse-14x14@2x.png (content/shared/img/video-inverse-14x14@2x.png)
content/browser/loop/shared/img/dropdown-inverse.png (content/shared/img/dropdown-inverse.png)
content/browser/loop/shared/img/dropdown-inverse@2x.png (content/shared/img/dropdown-inverse@2x.png)
content/browser/loop/shared/img/svg/glyph-email-16x16.svg (content/shared/img/svg/glyph-email-16x16.svg)
content/browser/loop/shared/img/svg/glyph-facebook-16x16.svg (content/shared/img/svg/glyph-facebook-16x16.svg)
content/browser/loop/shared/img/svg/glyph-help-16x16.svg (content/shared/img/svg/glyph-help-16x16.svg)
content/browser/loop/shared/img/svg/glyph-link-16x16.svg (content/shared/img/svg/glyph-link-16x16.svg)
content/browser/loop/shared/img/svg/glyph-user-16x16.svg (content/shared/img/svg/glyph-user-16x16.svg)
content/browser/loop/shared/img/svg/exit.svg (content/shared/img/svg/exit.svg)
content/browser/loop/shared/img/svg/audio.svg (content/shared/img/svg/audio.svg)
content/browser/loop/shared/img/svg/audio-hover.svg (content/shared/img/svg/audio-hover.svg)

Просмотреть файл

@ -14,7 +14,7 @@ describe("loop.roomViews", function () {
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var sandbox, dispatcher, roomStore, activeRoomStore, view;
var fakeWindow, fakeMozLoop, fakeContextURL;
var clock, fakeWindow, fakeMozLoop, fakeContextURL;
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
beforeEach(function() {
@ -48,6 +48,8 @@ describe("loop.roomViews", function () {
setLoopPref: sandbox.stub()
};
clock = sandbox.useFakeTimers();
fakeWindow = {
close: sinon.stub(),
document: {},
@ -92,6 +94,7 @@ describe("loop.roomViews", function () {
afterEach(function() {
sandbox.restore();
clock.restore();
loop.shared.mixins.setRootObject(window);
view = null;
});
@ -250,82 +253,54 @@ describe("loop.roomViews", function () {
});
it("should dispatch a CopyRoomUrl action when the copy button is pressed", function() {
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch, new sharedActions.CopyRoomUrl({
roomUrl: "http://invalid",
from: "conversation"
}));
});
it("should change the text when the url has been copied", function() {
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
// copied_url_button is the l10n string.
expect(copyBtn.textContent).eql("copied_url_button");
});
});
describe("Share button", function() {
it("should dispatch a AddSocialShareProvider action when the share button is clicked", function() {
view = mountTestComponent();
var shareBtn = view.getDOMNode().querySelector(".btn-share");
React.addons.TestUtils.Simulate.click(shareBtn);
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWith(dispatcher.dispatch,
new sharedActions.AddSocialShareProvider());
sinon.assert.calledWith(dispatcher.dispatch, new sharedActions.CopyRoomUrl({
roomUrl: "http://invalid",
from: "conversation"
}));
});
it("should toggle the share dropdown when the share button is clicked", function() {
view = mountTestComponent({
socialShareProviders: [{
name: "foo",
origin: "https://foo",
iconURL: "http://example.com/foo.png"
}]
});
it("should change the text when the url has been copied", function() {
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
var shareBtn = view.getDOMNode().querySelector(".btn-share");
// invite_copy_triggered is the l10n string.
expect(copyBtn.textContent).eql("invite_copy_triggered");
});
React.addons.TestUtils.Simulate.click(shareBtn);
it("should keep the text for a while after the url has been copied", function() {
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
clock.tick(loop.roomViews.DesktopRoomInvitationView.TRIGGERED_RESET_DELAY / 2);
expect(view.state.showMenu).to.eql(true);
expect(view.refs.menu.props.show).to.eql(true);
// invite_copy_triggered is the l10n string.
expect(copyBtn.textContent).eql("invite_copy_triggered");
});
it("should reset the text a bit after the url has been copied", function() {
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
clock.tick(loop.roomViews.DesktopRoomInvitationView.TRIGGERED_RESET_DELAY);
// invite_copy_button is the l10n string.
expect(copyBtn.textContent).eql("invite_copy_button");
});
it("should reset the text after the url has been copied then mouse over another button", function() {
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
React.addons.TestUtils.Simulate.click(copyBtn);
var emailBtn = view.getDOMNode().querySelector(".btn-email");
React.addons.TestUtils.Simulate.mouseOver(emailBtn);
// invite_copy_button is the l10n string.
expect(copyBtn.textContent).eql("invite_copy_button");
});
});
describe("Edit Context", function() {
it("should show the 'Add some context' link", function() {
view = mountTestComponent();
expect(view.getDOMNode().querySelector(
".room-invitation-addcontext")).to.not.eql(null);
});
it("should call a callback when the link is clicked", function() {
var onAddContextClick = sinon.stub();
view = mountTestComponent({
onAddContextClick: onAddContextClick
});
var node = view.getDOMNode();
expect(node.querySelector(".room-context")).to.eql(null);
var addLink = node.querySelector(".room-invitation-addcontext");
React.addons.TestUtils.Simulate.click(addLink);
sinon.assert.calledOnce(onAddContextClick);
});
it("should show the edit context view", function() {
view = mountTestComponent({
showEditContext: true