mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1074676 - Allow deleting a Loop room. r=Standard8
This commit is contained in:
parent
3e14f64be4
commit
b5ed576548
@ -106,6 +106,11 @@ const checkForParticipantsUpdate = function(room, updatedRoom) {
|
||||
let LoopRoomsInternal = {
|
||||
rooms: new Map(),
|
||||
|
||||
get sessionType() {
|
||||
return MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a list of rooms that the currently registered user is a member of.
|
||||
*
|
||||
@ -131,10 +136,8 @@ let LoopRoomsInternal = {
|
||||
}
|
||||
|
||||
// Fetch the rooms from the server.
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
let url = "/rooms" + (version ? "?version=" + encodeURIComponent(version) : "");
|
||||
let response = yield MozLoopService.hawkRequest(sessionType, url, "GET");
|
||||
let response = yield MozLoopService.hawkRequest(this.sessionType, url, "GET");
|
||||
let roomsList = JSON.parse(response.body);
|
||||
if (!Array.isArray(roomsList)) {
|
||||
throw new Error("Missing array of rooms in response.");
|
||||
@ -188,9 +191,7 @@ let LoopRoomsInternal = {
|
||||
return;
|
||||
}
|
||||
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
MozLoopService.hawkRequest(sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
|
||||
MozLoopService.hawkRequest(this.sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
|
||||
.then(response => {
|
||||
let data = JSON.parse(response.body);
|
||||
|
||||
@ -227,10 +228,7 @@ let LoopRoomsInternal = {
|
||||
return;
|
||||
}
|
||||
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
|
||||
MozLoopService.hawkRequest(sessionType, "/rooms", "POST", room)
|
||||
MozLoopService.hawkRequest(this.sessionType, "/rooms", "POST", room)
|
||||
.then(response => {
|
||||
let data = JSON.parse(response.body);
|
||||
extend(room, data);
|
||||
@ -252,6 +250,28 @@ let LoopRoomsInternal = {
|
||||
MozLoopService.openChatWindow(windowData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a room.
|
||||
*
|
||||
* @param {String} roomToken The room token.
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`.
|
||||
*/
|
||||
delete: function(roomToken, callback) {
|
||||
// XXX bug 1092954: Before deleting a room, the client should check room
|
||||
// membership and forceDisconnect() all current participants.
|
||||
let room = this.rooms.get(roomToken);
|
||||
let url = "/rooms/" + encodeURIComponent(roomToken);
|
||||
MozLoopService.hawkRequest(this.sessionType, url, "DELETE")
|
||||
.then(response => {
|
||||
this.rooms.delete(roomToken);
|
||||
eventEmitter.emit("delete", room);
|
||||
callback(null, room);
|
||||
}, error => callback(error)).catch(error => callback(error));
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Callback used to indicate changes to rooms data on the LoopServer.
|
||||
*
|
||||
@ -273,7 +293,7 @@ Object.freeze(LoopRoomsInternal);
|
||||
* At this point the following events may be subscribed to:
|
||||
* - 'add[:{room-id}]': A new room object was successfully added to the data
|
||||
* store.
|
||||
* - 'remove[:{room-id}]': A room was successfully removed from the data store.
|
||||
* - 'delete[:{room-id}]': A room was successfully removed from the data store.
|
||||
* - 'update[:{room-id}]': A room object was successfully updated with changed
|
||||
* properties in the data store.
|
||||
* - 'joined[:{room-id}]': A participant joined a room.
|
||||
@ -298,6 +318,10 @@ this.LoopRooms = {
|
||||
return LoopRoomsInternal.open(roomToken);
|
||||
},
|
||||
|
||||
delete: function(roomToken, callback) {
|
||||
return LoopRoomsInternal.delete(roomToken, callback);
|
||||
},
|
||||
|
||||
promise: function(method, ...params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this[method](...params, (error, result) => {
|
||||
|
@ -467,7 +467,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
*/
|
||||
var RoomEntry = React.createClass({displayName: 'RoomEntry',
|
||||
propTypes: {
|
||||
openRoom: React.PropTypes.func.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||
},
|
||||
|
||||
@ -480,9 +480,11 @@ loop.panel = (function(_, mozL10n) {
|
||||
(nextState.urlCopied !== this.state.urlCopied);
|
||||
},
|
||||
|
||||
handleClickRoom: function(event) {
|
||||
handleClickRoomUrl: function(event) {
|
||||
event.preventDefault();
|
||||
this.props.openRoom(this.props.room);
|
||||
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
|
||||
roomToken: this.props.room.roomToken
|
||||
}));
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
@ -491,6 +493,14 @@ loop.panel = (function(_, mozL10n) {
|
||||
this.setState({urlCopied: true});
|
||||
},
|
||||
|
||||
handleDeleteButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
// XXX We should prompt end user for confirmation; see bug 1092953.
|
||||
this.props.dispatcher.dispatch(new sharedActions.DeleteRoom({
|
||||
roomToken: this.props.room.roomToken
|
||||
}));
|
||||
},
|
||||
|
||||
handleMouseLeave: function(event) {
|
||||
this.setState({urlCopied: false});
|
||||
},
|
||||
@ -507,8 +517,8 @@ loop.panel = (function(_, mozL10n) {
|
||||
"room-active": this._isActive()
|
||||
});
|
||||
var copyButtonClasses = React.addons.classSet({
|
||||
'copy-link': true,
|
||||
'checked': this.state.urlCopied
|
||||
"copy-link": true,
|
||||
"checked": this.state.urlCopied
|
||||
});
|
||||
|
||||
return (
|
||||
@ -517,10 +527,12 @@ loop.panel = (function(_, mozL10n) {
|
||||
React.DOM.span({className: "room-notification"}),
|
||||
room.roomName,
|
||||
React.DOM.button({className: copyButtonClasses,
|
||||
onClick: this.handleCopyButtonClick})
|
||||
onClick: this.handleCopyButtonClick}),
|
||||
React.DOM.button({className: "delete-link",
|
||||
onClick: this.handleDeleteButtonClick})
|
||||
),
|
||||
React.DOM.p(null,
|
||||
React.DOM.a({ref: "room", href: "#", onClick: this.handleClickRoom},
|
||||
React.DOM.a({href: "#", onClick: this.handleClickRoomUrl},
|
||||
room.roomUrl
|
||||
)
|
||||
)
|
||||
@ -581,12 +593,6 @@ loop.panel = (function(_, mozL10n) {
|
||||
}));
|
||||
},
|
||||
|
||||
openRoom: function(room) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
|
||||
roomToken: room.roomToken
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.error) {
|
||||
// XXX Better end user reporting of errors.
|
||||
@ -598,7 +604,11 @@ loop.panel = (function(_, mozL10n) {
|
||||
React.DOM.h1(null, this._getListHeading()),
|
||||
React.DOM.div({className: "room-list"},
|
||||
this.state.rooms.map(function(room, i) {
|
||||
return RoomEntry({key: i, room: room, openRoom: this.openRoom});
|
||||
return RoomEntry({
|
||||
key: room.roomToken,
|
||||
dispatcher: this.props.dispatcher,
|
||||
room: room}
|
||||
);
|
||||
}, this)
|
||||
),
|
||||
React.DOM.p(null,
|
||||
|
@ -467,7 +467,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
*/
|
||||
var RoomEntry = React.createClass({
|
||||
propTypes: {
|
||||
openRoom: React.PropTypes.func.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||
},
|
||||
|
||||
@ -480,9 +480,11 @@ loop.panel = (function(_, mozL10n) {
|
||||
(nextState.urlCopied !== this.state.urlCopied);
|
||||
},
|
||||
|
||||
handleClickRoom: function(event) {
|
||||
handleClickRoomUrl: function(event) {
|
||||
event.preventDefault();
|
||||
this.props.openRoom(this.props.room);
|
||||
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
|
||||
roomToken: this.props.room.roomToken
|
||||
}));
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
@ -491,6 +493,14 @@ loop.panel = (function(_, mozL10n) {
|
||||
this.setState({urlCopied: true});
|
||||
},
|
||||
|
||||
handleDeleteButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
// XXX We should prompt end user for confirmation; see bug 1092953.
|
||||
this.props.dispatcher.dispatch(new sharedActions.DeleteRoom({
|
||||
roomToken: this.props.room.roomToken
|
||||
}));
|
||||
},
|
||||
|
||||
handleMouseLeave: function(event) {
|
||||
this.setState({urlCopied: false});
|
||||
},
|
||||
@ -507,8 +517,8 @@ loop.panel = (function(_, mozL10n) {
|
||||
"room-active": this._isActive()
|
||||
});
|
||||
var copyButtonClasses = React.addons.classSet({
|
||||
'copy-link': true,
|
||||
'checked': this.state.urlCopied
|
||||
"copy-link": true,
|
||||
"checked": this.state.urlCopied
|
||||
});
|
||||
|
||||
return (
|
||||
@ -518,9 +528,11 @@ loop.panel = (function(_, mozL10n) {
|
||||
{room.roomName}
|
||||
<button className={copyButtonClasses}
|
||||
onClick={this.handleCopyButtonClick} />
|
||||
<button className="delete-link"
|
||||
onClick={this.handleDeleteButtonClick} />
|
||||
</h2>
|
||||
<p>
|
||||
<a ref="room" href="#" onClick={this.handleClickRoom}>
|
||||
<a href="#" onClick={this.handleClickRoomUrl}>
|
||||
{room.roomUrl}
|
||||
</a>
|
||||
</p>
|
||||
@ -581,12 +593,6 @@ loop.panel = (function(_, mozL10n) {
|
||||
}));
|
||||
},
|
||||
|
||||
openRoom: function(room) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
|
||||
roomToken: room.roomToken
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.error) {
|
||||
// XXX Better end user reporting of errors.
|
||||
@ -598,7 +604,11 @@ loop.panel = (function(_, mozL10n) {
|
||||
<h1>{this._getListHeading()}</h1>
|
||||
<div className="room-list">{
|
||||
this.state.rooms.map(function(room, i) {
|
||||
return <RoomEntry key={i} room={room} openRoom={this.openRoom} />;
|
||||
return <RoomEntry
|
||||
key={room.roomToken}
|
||||
dispatcher={this.props.dispatcher}
|
||||
room={room}
|
||||
/>;
|
||||
}, this)
|
||||
}</div>
|
||||
<p>
|
||||
|
@ -222,25 +222,35 @@ body {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > .copy-link {
|
||||
@keyframes drop-and-fade-in {
|
||||
0% {opacity: 0; top: -15px;}
|
||||
25% {opacity: 0; top: -15px;}
|
||||
100% {opacity: 1; top: 0px;}
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
margin: .1em .5em; /* relative to _this_ line's font, not the document's */
|
||||
margin: .1em .1em .1em .5em; /* relative to _this_ line's font, not the document's */
|
||||
background-color: transparent; /* override browser default for button tags */
|
||||
top: -15px;
|
||||
}
|
||||
|
||||
@keyframes drop-and-fade-in {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 100; transform: translateY(0); }
|
||||
.room-list > .room-entry:hover > h2 > button {
|
||||
animation: drop-and-fade-in 0.250s;
|
||||
animation-fill-mode: forwards;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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;
|
||||
background-image: url(../img/icons-16x16.svg#copy);
|
||||
}
|
||||
|
||||
.room-list > .room-entry:hover > h2 > .delete-link {
|
||||
background-image: url(../img/icons-16x16.svg#trash);
|
||||
}
|
||||
|
||||
/* scale this up to 1.1x and then back to the original size */
|
||||
@ -250,9 +260,10 @@ body {
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > .copy-link.checked {
|
||||
background: transparent url(../img/svg/checkmark-16x16.svg);
|
||||
animation: pulse .250s;
|
||||
background: transparent url(../img/icons-16x16.svg#checkmark);
|
||||
animation: pulse .150s;
|
||||
animation-timing-function: ease-in-out;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 {
|
||||
|
@ -87,6 +87,37 @@ use[id$="-red"] {
|
||||
<path id="video-shape" fill-rule="evenodd" clip-rule="evenodd" d="M14.9,3.129l-3.476,3.073V3.873c0-0.877-0.663-1.587-1.482-1.587
|
||||
H1.482C0.663,2.286,0,2.996,0,3.873v8.254c0,0.877,0.663,1.587,1.482,1.587h8.461c0.818,0,1.482-0.711,1.482-1.587V9.762
|
||||
l3.476,3.073c0.3,0.321,0.714,0.416,1.1,0.331V2.798C15.614,2.713,15.2,2.808,14.9,3.129z"/>
|
||||
<g id="copy-shape">
|
||||
<circle fill-rule="evenodd" clip-rule="evenodd" fill="#0096DD" cx="8" cy="8" r="8"/>
|
||||
<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"/>
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd"
|
||||
fill="#FFFFFF" points="10.222,8 10.222,6.857 11.407,8"/>
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF"
|
||||
points="6.963,5.714 6.963,4.571 8.148,5.714"/>
|
||||
</g>
|
||||
<g id="checkmark-shape">
|
||||
<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"/>
|
||||
</g>
|
||||
<g id="trash-shape">
|
||||
<circle fill-rule="evenodd" clip-rule="evenodd" fill="#D74345" cx="8" cy="8" r="8"/>
|
||||
<path fill="#FFFFFF" d="M12,5.79c0-0.742-0.537-1.344-1.2-1.344h-0.583V4.121c0-0.713-0.516-1.29-1.152-1.29h-2.13
|
||||
c-0.636,0-1.152,0.578-1.152,1.29v0.324H5.2C4.537,4.446,4,5.048,4,5.79v0.898h0.687l0.508,5.438
|
||||
c0.054,0.585,0.543,1.044,1.114,1.044h3.38c0.57,0,1.06-0.458,1.114-1.043l0.509-5.439H12V5.79z M6.407,4.264V4.165
|
||||
c0-0.375,0.271-0.678,0.606-0.678h1.974c0.334,0,0.606,0.304,0.606,0.678v0.099c0,0.063-0.01,0.123-0.025,0.181H6.432
|
||||
C6.417,4.387,6.407,4.328,6.407,4.264z M10.057,12.056c-0.019,0.197-0.188,0.363-0.368,0.363h-3.38
|
||||
c-0.182,0-0.35-0.166-0.368-0.363L5.44,6.687h5.12L10.057,12.056z"/>
|
||||
<rect x="7.75" y="7.542" fill="#FFFFFF" width="0.5" height="4"/>
|
||||
<polyline fill="#FFFFFF" points="9.25,7.542 8.75,7.542 8.75,11.542 9.25,11.542 "/>
|
||||
<rect x="6.75" y="7.542" fill="#FFFFFF" width="0.5" height="4"/>
|
||||
</g>
|
||||
</defs>
|
||||
<use id="audio" xlink:href="#audio-shape"/>
|
||||
<use id="audio-hover" xlink:href="#audio-shape"/>
|
||||
@ -98,6 +129,8 @@ use[id$="-red"] {
|
||||
<use id="contacts" xlink:href="#contacts-shape"/>
|
||||
<use id="contacts-hover" xlink:href="#contacts-shape"/>
|
||||
<use id="contacts-active" xlink:href="#contacts-shape"/>
|
||||
<use id="copy" xlink:href="#copy-shape"/>
|
||||
<use id="checkmark" xlink:href="#checkmark-shape"/>
|
||||
<use id="google" xlink:href="#google-shape"/>
|
||||
<use id="google-hover" xlink:href="#google-shape"/>
|
||||
<use id="google-active" xlink:href="#google-shape"/>
|
||||
@ -113,6 +146,7 @@ use[id$="-red"] {
|
||||
<use id="tag" xlink:href="#tag-shape"/>
|
||||
<use id="tag-hover" xlink:href="#tag-shape"/>
|
||||
<use id="tag-active" xlink:href="#tag-shape"/>
|
||||
<use id="trash" xlink:href="#trash-shape"/>
|
||||
<use id="unblock" xlink:href="#unblock-shape"/>
|
||||
<use id="unblock-hover" xlink:href="#unblock-shape"/>
|
||||
<use id="unblock-active" xlink:href="#unblock-shape"/>
|
||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 10 KiB |
@ -1,10 +0,0 @@
|
||||
<?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>
|
Before Width: | Height: | Size: 597 B |
@ -1,28 +0,0 @@
|
||||
<?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>
|
Before Width: | Height: | Size: 1.3 KiB |
@ -158,6 +158,22 @@ loop.shared.actions = (function() {
|
||||
error: Error
|
||||
}),
|
||||
|
||||
/**
|
||||
* Deletes a room.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*/
|
||||
DeleteRoom: Action.define("deleteRoom", {
|
||||
roomToken: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Room deletion error.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*/
|
||||
DeleteRoomError: Action.define("deleteRoomError", {
|
||||
error: Error
|
||||
}),
|
||||
|
||||
/**
|
||||
* Retrieves room list.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
|
@ -71,6 +71,8 @@ loop.store = loop.store || {};
|
||||
this._dispatcher.register(this, [
|
||||
"createRoom",
|
||||
"createRoomError",
|
||||
"deleteRoom",
|
||||
"deleteRoomError",
|
||||
"getAllRooms",
|
||||
"getAllRoomsError",
|
||||
"openRoom",
|
||||
@ -139,7 +141,7 @@ loop.store = loop.store || {};
|
||||
// Rooms event registration
|
||||
this._mozLoop.rooms.on("add", this._onRoomAdded.bind(this));
|
||||
this._mozLoop.rooms.on("update", this._onRoomUpdated.bind(this));
|
||||
this._mozLoop.rooms.on("remove", this._onRoomRemoved.bind(this));
|
||||
this._mozLoop.rooms.on("delete", this._onRoomRemoved.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -181,7 +183,7 @@ loop.store = loop.store || {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed when a room is removed.
|
||||
* Executed when a room is deleted.
|
||||
*
|
||||
* @param {String} eventName The event name (unused).
|
||||
* @param {Object} removedRoomData The removed room data.
|
||||
@ -194,7 +196,6 @@ loop.store = loop.store || {};
|
||||
}));
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Maps and sorts the raw room list received from the mozLoop API.
|
||||
*
|
||||
@ -284,6 +285,28 @@ loop.store = loop.store || {};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new room.
|
||||
*
|
||||
* @param {sharedActions.DeleteRoom} actionData The action data.
|
||||
*/
|
||||
deleteRoom: function(actionData) {
|
||||
this._mozLoop.rooms.delete(actionData.roomToken, function(err) {
|
||||
if (err) {
|
||||
this._dispatchAction(new sharedActions.DeleteRoomError({error: err}));
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed when a room deletion error occurs.
|
||||
*
|
||||
* @param {sharedActions.DeleteRoomError} actionData The action data.
|
||||
*/
|
||||
deleteRoomError: function(actionData) {
|
||||
this.setStoreState({error: actionData.error});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gather the list of all available rooms from the MozLoop API.
|
||||
*/
|
||||
|
@ -49,8 +49,6 @@ 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)
|
||||
|
@ -639,7 +639,7 @@ describe("loop.panel", function() {
|
||||
});
|
||||
|
||||
describe("loop.panel.RoomEntry", function() {
|
||||
var buttonNode, roomData, roomEntry, roomStore, dispatcher;
|
||||
var dispatcher, roomData;
|
||||
|
||||
beforeEach(function() {
|
||||
dispatcher = new loop.Dispatcher();
|
||||
@ -656,24 +656,30 @@ describe("loop.panel", function() {
|
||||
],
|
||||
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
|
||||
}));
|
||||
function mountRoomEntry(props) {
|
||||
return TestUtils.renderIntoDocument(loop.panel.RoomEntry(props));
|
||||
}
|
||||
|
||||
it("should not display copy-link button by default", function() {
|
||||
expect(buttonNode).to.not.equal(null);
|
||||
describe("Copy button", function() {
|
||||
var roomEntry, copyButton;
|
||||
|
||||
beforeEach(function() {
|
||||
roomEntry = mountRoomEntry({
|
||||
dispatcher: dispatcher,
|
||||
deleteRoom: sandbox.stub(),
|
||||
room: new loop.store.Room(roomData)
|
||||
});
|
||||
copyButton = roomEntry.getDOMNode().querySelector("button.copy-link");
|
||||
});
|
||||
|
||||
it("should not display a copy button by default", function() {
|
||||
expect(copyButton).to.not.equal(null);
|
||||
});
|
||||
|
||||
it("should copy the URL when the click event fires", function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
TestUtils.Simulate.click(copyButton);
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.copyString);
|
||||
sinon.assert.calledWithExactly(navigator.mozLoop.copyString,
|
||||
@ -681,26 +687,72 @@ describe("loop.panel", function() {
|
||||
});
|
||||
|
||||
it("should set state.urlCopied when the click event fires", function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
TestUtils.Simulate.click(copyButton);
|
||||
|
||||
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);
|
||||
TestUtils.Simulate.click(copyButton);
|
||||
|
||||
expect(buttonNode.classList.contains("checked")).eql(true);
|
||||
expect(copyButton.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.Simulate.click(copyButton);
|
||||
|
||||
TestUtils.SimulateNative.mouseOut(roomNode);
|
||||
|
||||
expect(buttonNode.classList.contains("checked")).eql(false);
|
||||
expect(copyButton.classList.contains("checked")).eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Delete button click", function() {
|
||||
var roomEntry, deleteButton;
|
||||
|
||||
beforeEach(function() {
|
||||
roomEntry = mountRoomEntry({
|
||||
dispatcher: dispatcher,
|
||||
room: new loop.store.Room(roomData)
|
||||
});
|
||||
deleteButton = roomEntry.getDOMNode().querySelector("button.delete-link");
|
||||
});
|
||||
|
||||
it("should not display a delete button by default", function() {
|
||||
expect(deleteButton).to.not.equal(null);
|
||||
});
|
||||
|
||||
it("should call the delete function when clicked", function() {
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
|
||||
TestUtils.Simulate.click(deleteButton);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.DeleteRoom({roomToken: roomData.roomToken}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Room URL click", function() {
|
||||
var roomEntry;
|
||||
|
||||
it("should dispatch an OpenRoom action", function() {
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
roomEntry = mountRoomEntry({
|
||||
dispatcher: dispatcher,
|
||||
room: new loop.store.Room(roomData)
|
||||
});
|
||||
var urlLink = roomEntry.getDOMNode().querySelector("p > a");
|
||||
|
||||
TestUtils.Simulate.click(urlLink);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.OpenRoom({roomToken: roomData.roomToken}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -775,20 +827,6 @@ describe("loop.panel", function() {
|
||||
var buttonNode = view.getDOMNode().querySelector("button[disabled]");
|
||||
expect(buttonNode).to.not.equal(null);
|
||||
});
|
||||
|
||||
describe("#openRoom", function() {
|
||||
it("should dispatch an OpenRoom action", function() {
|
||||
var view = createTestComponent();
|
||||
var dispatch = sandbox.stub(dispatcher, "dispatch");
|
||||
|
||||
view.openRoom({roomToken: "42cba"});
|
||||
|
||||
sinon.assert.calledOnce(dispatch);
|
||||
sinon.assert.calledWithExactly(dispatch, new sharedActions.OpenRoom({
|
||||
roomToken: "42cba"
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loop.panel.ToSView', function() {
|
||||
|
@ -135,9 +135,9 @@ describe("loop.store.RoomListStore", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", function() {
|
||||
it("should remove a room from the list", function() {
|
||||
fakeMozLoop.rooms.trigger("remove", "remove", {
|
||||
describe("delete", function() {
|
||||
it("should delete a room from the list", function() {
|
||||
fakeMozLoop.rooms.trigger("delete", "delete", {
|
||||
roomToken: "_nxD4V4FflQ"
|
||||
});
|
||||
|
||||
|
@ -262,6 +262,15 @@ add_task(function* test_createRoom() {
|
||||
compareRooms(room, kCreateRoomProps);
|
||||
});
|
||||
|
||||
// Test if deleting a room works as expected.
|
||||
add_task(function* test_deleteRoom() {
|
||||
let roomToken = "QzBbvGmIZWU";
|
||||
let deletedRoom = yield LoopRooms.promise("delete", roomToken);
|
||||
Assert.equal(deletedRoom.roomToken, roomToken);
|
||||
let rooms = yield LoopRooms.promise("getAll");
|
||||
Assert.ok(!rooms.some((room) => room.roomToken == roomToken));
|
||||
});
|
||||
|
||||
// Test if opening a new room window works correctly.
|
||||
add_task(function* test_openRoom() {
|
||||
let openedUrl;
|
||||
|
@ -162,3 +162,24 @@
|
||||
/* Removes the fake video image for ended conversations */
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* SVG icons showcase */
|
||||
|
||||
.svg-icon-entry {
|
||||
width: 180px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.svg-icon-entry > p {
|
||||
float: left;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
}
|
||||
|
@ -101,6 +101,41 @@
|
||||
detailsButtonLabel: "Retry",
|
||||
});
|
||||
|
||||
var SVGIcon = React.createClass({displayName: 'SVGIcon',
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.span({className: "svg-icon", style: {
|
||||
"background-image": "url(/content/shared/img/icons-16x16.svg#" + this.props.shapeId + ")"
|
||||
}})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SVGIcons = React.createClass({displayName: 'SVGIcons',
|
||||
shapes: [
|
||||
"audio", "audio-hover", "audio-active", "block",
|
||||
"block-red", "block-hover", "block-active", "contacts", "contacts-hover",
|
||||
"contacts-active", "copy", "checkmark", "google", "google-hover",
|
||||
"google-active", "history", "history-hover", "history-active",
|
||||
"precall", "precall-hover", "precall-active", "settings", "settings-hover",
|
||||
"settings-active", "tag", "tag-hover", "tag-active", "trash", "unblock",
|
||||
"unblock-hover", "unblock-active", "video", "video-hover", "video-active"
|
||||
],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div({className: "svg-icon-list"},
|
||||
this.shapes.map(function(shapeId, i) {
|
||||
return React.DOM.div({className: "svg-icon-entry"},
|
||||
React.DOM.p(null, SVGIcon({key: i, shapeId: shapeId})),
|
||||
React.DOM.p(null, shapeId)
|
||||
);
|
||||
}, this)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var Example = React.createClass({displayName: 'Example',
|
||||
makeId: function(prefix) {
|
||||
return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
|
||||
@ -490,6 +525,12 @@
|
||||
UnsupportedDeviceView(null)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
Section({name: "SVG icons preview"},
|
||||
Example({summary: "16x16"},
|
||||
SVGIcons(null)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
|
@ -101,6 +101,41 @@
|
||||
detailsButtonLabel: "Retry",
|
||||
});
|
||||
|
||||
var SVGIcon = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<span className="svg-icon" style={{
|
||||
"background-image": "url(/content/shared/img/icons-16x16.svg#" + this.props.shapeId + ")"
|
||||
}} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SVGIcons = React.createClass({
|
||||
shapes: [
|
||||
"audio", "audio-hover", "audio-active", "block",
|
||||
"block-red", "block-hover", "block-active", "contacts", "contacts-hover",
|
||||
"contacts-active", "copy", "checkmark", "google", "google-hover",
|
||||
"google-active", "history", "history-hover", "history-active",
|
||||
"precall", "precall-hover", "precall-active", "settings", "settings-hover",
|
||||
"settings-active", "tag", "tag-hover", "tag-active", "trash", "unblock",
|
||||
"unblock-hover", "unblock-active", "video", "video-hover", "video-active"
|
||||
],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="svg-icon-list">{
|
||||
this.shapes.map(function(shapeId, i) {
|
||||
return <div className="svg-icon-entry">
|
||||
<p><SVGIcon key={i} shapeId={shapeId} /></p>
|
||||
<p>{shapeId}</p>
|
||||
</div>;
|
||||
}, this)
|
||||
}</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var Example = React.createClass({
|
||||
makeId: function(prefix) {
|
||||
return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
|
||||
@ -492,6 +527,12 @@
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="SVG icons preview">
|
||||
<Example summary="16x16">
|
||||
<SVGIcons />
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
</ShowCase>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user