mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
Bug 1074674 - add button to copy room location to clipboard, r=NiKo
This commit is contained in:
parent
b149200aa7
commit
bd670479a9
@ -471,8 +471,13 @@ loop.panel = (function(_, mozL10n) {
|
||||
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { urlCopied: false };
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return nextProps.room.ctime > this.props.room.ctime;
|
||||
return (nextProps.room.ctime > this.props.room.ctime) ||
|
||||
(nextState.urlCopied !== this.state.urlCopied);
|
||||
},
|
||||
|
||||
handleClickRoom: function(event) {
|
||||
@ -480,6 +485,16 @@ loop.panel = (function(_, mozL10n) {
|
||||
this.props.openRoom(this.props.room);
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
navigator.mozLoop.copyString(this.props.room.roomUrl);
|
||||
this.setState({urlCopied: true});
|
||||
},
|
||||
|
||||
handleMouseLeave: function(event) {
|
||||
this.setState({urlCopied: false});
|
||||
},
|
||||
|
||||
_isActive: function() {
|
||||
// XXX bug 1074679 will implement this properly
|
||||
return this.props.room.currSize > 0;
|
||||
@ -491,12 +506,18 @@ loop.panel = (function(_, mozL10n) {
|
||||
"room-entry": true,
|
||||
"room-active": this._isActive()
|
||||
});
|
||||
var copyButtonClasses = React.addons.classSet({
|
||||
'copy-link': true,
|
||||
'checked': this.state.urlCopied
|
||||
});
|
||||
|
||||
return (
|
||||
React.DOM.div({className: roomClasses},
|
||||
React.DOM.div({className: roomClasses, onMouseLeave: this.handleMouseLeave},
|
||||
React.DOM.h2(null,
|
||||
React.DOM.span({className: "room-notification"}),
|
||||
room.roomName
|
||||
room.roomName,
|
||||
React.DOM.button({className: copyButtonClasses,
|
||||
onClick: this.handleCopyButtonClick})
|
||||
),
|
||||
React.DOM.p(null,
|
||||
React.DOM.a({ref: "room", href: "#", onClick: this.handleClickRoom},
|
||||
@ -762,6 +783,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
AvailabilityDropdown: AvailabilityDropdown,
|
||||
CallUrlResult: CallUrlResult,
|
||||
PanelView: PanelView,
|
||||
RoomEntry: RoomEntry,
|
||||
RoomList: RoomList,
|
||||
SettingsDropdown: SettingsDropdown,
|
||||
ToSView: ToSView
|
||||
|
@ -471,8 +471,13 @@ loop.panel = (function(_, mozL10n) {
|
||||
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { urlCopied: false };
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return nextProps.room.ctime > this.props.room.ctime;
|
||||
return (nextProps.room.ctime > this.props.room.ctime) ||
|
||||
(nextState.urlCopied !== this.state.urlCopied);
|
||||
},
|
||||
|
||||
handleClickRoom: function(event) {
|
||||
@ -480,6 +485,16 @@ loop.panel = (function(_, mozL10n) {
|
||||
this.props.openRoom(this.props.room);
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
navigator.mozLoop.copyString(this.props.room.roomUrl);
|
||||
this.setState({urlCopied: true});
|
||||
},
|
||||
|
||||
handleMouseLeave: function(event) {
|
||||
this.setState({urlCopied: false});
|
||||
},
|
||||
|
||||
_isActive: function() {
|
||||
// XXX bug 1074679 will implement this properly
|
||||
return this.props.room.currSize > 0;
|
||||
@ -491,12 +506,18 @@ loop.panel = (function(_, mozL10n) {
|
||||
"room-entry": true,
|
||||
"room-active": this._isActive()
|
||||
});
|
||||
var copyButtonClasses = React.addons.classSet({
|
||||
'copy-link': true,
|
||||
'checked': this.state.urlCopied
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={roomClasses}>
|
||||
<div className={roomClasses} onMouseLeave={this.handleMouseLeave}>
|
||||
<h2>
|
||||
<span className="room-notification" />
|
||||
{room.roomName}
|
||||
{room.roomName}
|
||||
<button className={copyButtonClasses}
|
||||
onClick={this.handleCopyButtonClick}/>
|
||||
</h2>
|
||||
<p>
|
||||
<a ref="room" href="#" onClick={this.handleClickRoom}>
|
||||
@ -762,6 +783,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
AvailabilityDropdown: AvailabilityDropdown,
|
||||
CallUrlResult: CallUrlResult,
|
||||
PanelView: PanelView,
|
||||
RoomEntry: RoomEntry,
|
||||
RoomList: RoomList,
|
||||
SettingsDropdown: SettingsDropdown,
|
||||
ToSView: ToSView
|
||||
|
@ -202,6 +202,49 @@ body {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > .copy-link {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
margin: .1em .5em; /* relative to _this_ line's font, not the document's */
|
||||
background-color: transparent; /* override browser default for button tags */
|
||||
}
|
||||
|
||||
@keyframes drop-and-fade-in {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 100; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.room-list > .room-entry:hover > h2 > .copy-link {
|
||||
background: transparent url(../img/svg/copy-16x16.svg);
|
||||
cursor: pointer;
|
||||
animation: drop-and-fade-in 0.4s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/* scale this up to 1.1x and then back to the original size */
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1.0); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > .copy-link.checked {
|
||||
background: transparent url(../img/svg/checkmark-16x16.svg);
|
||||
animation: pulse .250s;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* keep the various room-entry row pieces aligned with each other */
|
||||
.room-list > .room-entry > h2 > button,
|
||||
.room-list > .room-entry > h2 > span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
||||
.button-group {
|
||||
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<circle fill-rule="evenodd" clip-rule="evenodd" fill="#0096DD" cx="8"
|
||||
cy="8" r="8"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF"
|
||||
d="M7.236,12L12,5.007L10.956,4L7.224,9.465l-2.14-2.326L4,8.146L7.236,12z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 597 B |
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<circle fill-rule="evenodd" clip-rule="evenodd" fill="#0096DD" cx="8" cy="8"
|
||||
r="8"/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="none"
|
||||
stroke="#FFFFFF" stroke-width="0.75" stroke-miterlimit="10"
|
||||
d="M10.815,6.286H7.556c-0.164,0-0.296,0.128-0.296,0.286v5.143C7.259,11.872,7.392,12,7.556,12h4.148
|
||||
C11.867,12,12,11.872,12,11.714V7.429L10.815,6.286z
|
||||
M8.741,6.275V5.143L7.556,4H7.528C6.509,4,4.593,4,4.593,4H4.296
|
||||
C4.133,4,4,4.128,4,4.286v5.143c0,0.158,0.133,0.286,0.296,0.286H7.25V6.561c0-0.158,0.133-0.286,0.296-0.286H8.741z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd"
|
||||
fill="#FFFFFF" points="10.222,8 10.222,6.857 11.407,8"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF"
|
||||
points="6.963,5.714 6.963,4.571 8.148,5.714"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -48,6 +48,8 @@ browser.jar:
|
||||
content/browser/loop/shared/img/svg/glyph-account-16x16.svg (content/shared/img/svg/glyph-account-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-signin-16x16.svg (content/shared/img/svg/glyph-signin-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-signout-16x16.svg (content/shared/img/svg/glyph-signout-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/copy-16x16.svg (content/shared/img/svg/copy-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/checkmark-16x16.svg (content/shared/img/svg/checkmark-16x16.svg)
|
||||
content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
|
||||
content/browser/loop/shared/img/beta-ribbon.svg (content/shared/img/beta-ribbon.svg)
|
||||
content/browser/loop/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg)
|
||||
|
@ -637,6 +637,72 @@ describe("loop.panel", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("loop.panel.RoomEntry", function() {
|
||||
var buttonNode, roomData, roomEntry, roomStore, dispatcher;
|
||||
|
||||
beforeEach(function() {
|
||||
dispatcher = new loop.Dispatcher();
|
||||
roomData = {
|
||||
roomToken: "QzBbvGmIZWU",
|
||||
roomUrl: "http://sample/QzBbvGmIZWU",
|
||||
roomName: "Second Room Name",
|
||||
maxSize: 2,
|
||||
participants: [
|
||||
{ displayName: "Alexis", account: "alexis@example.com",
|
||||
roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" },
|
||||
{ displayName: "Adam",
|
||||
roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" }
|
||||
],
|
||||
ctime: 1405517418
|
||||
};
|
||||
roomStore = new loop.store.Room(roomData);
|
||||
roomEntry = mountRoomEntry();
|
||||
buttonNode = roomEntry.getDOMNode().querySelector("button.copy-link");
|
||||
});
|
||||
|
||||
function mountRoomEntry() {
|
||||
return TestUtils.renderIntoDocument(loop.panel.RoomEntry({
|
||||
openRoom: sandbox.stub(),
|
||||
room: roomStore
|
||||
}));
|
||||
}
|
||||
|
||||
it("should not display copy-link button by default", function() {
|
||||
expect(buttonNode).to.not.equal(null);
|
||||
});
|
||||
|
||||
it("should copy the URL when the click event fires", function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.copyString);
|
||||
sinon.assert.calledWithExactly(navigator.mozLoop.copyString,
|
||||
roomData.roomUrl);
|
||||
});
|
||||
|
||||
it("should set state.urlCopied when the click event fires", function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
expect(roomEntry.state.urlCopied).to.equal(true);
|
||||
});
|
||||
|
||||
it("should switch to displaying a check icon when the URL has been copied",
|
||||
function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
expect(buttonNode.classList.contains("checked")).eql(true);
|
||||
});
|
||||
|
||||
it("should not display a check icon after mouse leaves the entry",
|
||||
function() {
|
||||
var roomNode = roomEntry.getDOMNode();
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
TestUtils.SimulateNative.mouseOut(roomNode);
|
||||
|
||||
expect(buttonNode.classList.contains("checked")).eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loop.panel.RoomList", function() {
|
||||
var roomListStore, dispatcher;
|
||||
|
||||
|
@ -57,6 +57,7 @@ navigator.mozLoop = {
|
||||
}
|
||||
},
|
||||
releaseCallData: function() {},
|
||||
copyString: function() {},
|
||||
contacts: {
|
||||
getAll: function(callback) {
|
||||
callback(null, []);
|
||||
|
Loading…
Reference in New Issue
Block a user