Bug 1100595 - Add UI for indicating if renaming a room failed. r=NiKo`

--HG--
extra : rebase_source : a03dfe3f46d5ec8881ec7e34f9ec605467f01b8c
This commit is contained in:
Jared Wein 2014-12-16 13:23:16 -05:00
parent be34215a78
commit 75b048977a
6 changed files with 112 additions and 17 deletions

View File

@ -67,10 +67,20 @@ loop.roomViews = (function(mozL10n) {
getInitialState: function() {
return {
copiedUrl: false,
newRoomName: ""
newRoomName: "",
error: null,
};
},
componentWillMount: function() {
this.listenTo(this.props.roomStore, "change:error",
this.onRoomError);
},
componentWillUnmount: function() {
this.stopListening(this.props.roomStore);
},
handleFormSubmit: function(event) {
event.preventDefault();
@ -101,9 +111,23 @@ loop.roomViews = (function(mozL10n) {
this.setState({copiedUrl: true});
},
onRoomError: function() {
// Only update the state if we're mounted, to avoid the problem where
// stopListening doesn't nuke the active listeners during a event
// processing.
if (this.isMounted()) {
this.setState({error: this.props.roomStore.getStoreState("error")});
}
},
render: function() {
var cx = React.addons.classSet;
return (
React.DOM.div({className: "room-invitation-overlay"},
React.DOM.p({className: cx({"error": !!this.state.error,
"error-display-area": true})},
mozL10n.get("rooms_name_change_failed_label")
),
React.DOM.form({onSubmit: this.handleFormSubmit},
React.DOM.input({type: "text", className: "input-room-name",
valueLink: this.linkState("newRoomName"),

View File

@ -67,10 +67,20 @@ loop.roomViews = (function(mozL10n) {
getInitialState: function() {
return {
copiedUrl: false,
newRoomName: ""
newRoomName: "",
error: null,
};
},
componentWillMount: function() {
this.listenTo(this.props.roomStore, "change:error",
this.onRoomError);
},
componentWillUnmount: function() {
this.stopListening(this.props.roomStore);
},
handleFormSubmit: function(event) {
event.preventDefault();
@ -101,9 +111,23 @@ loop.roomViews = (function(mozL10n) {
this.setState({copiedUrl: true});
},
onRoomError: function() {
// Only update the state if we're mounted, to avoid the problem where
// stopListening doesn't nuke the active listeners during a event
// processing.
if (this.isMounted()) {
this.setState({error: this.props.roomStore.getStoreState("error")});
}
},
render: function() {
var cx = React.addons.classSet;
return (
<div className="room-invitation-overlay">
<p className={cx({"error": !!this.state.error,
"error-display-area": true})}>
{mozL10n.get("rooms_name_change_failed_label")}
</p>
<form onSubmit={this.handleFormSubmit}>
<input type="text" className="input-room-name"
valueLink={this.linkState("newRoomName")}

View File

@ -157,7 +157,7 @@
opacity: 1;
}
.conversation-toolbar .media-control:hover {
background-color: rgba(255, 255, 255, .35);
background-color: rgba(255,255,255,.35);
opacity: 1;
}
.conversation-toolbar .media-control.muted {
@ -208,7 +208,7 @@
.standalone .local-stream {
/* required to have it superimposed to the control toolbar */
z-index: 1001;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
box-shadow: 0 2px 4px rgba(0,0,0,.5);
}
/* Side by side video elements */
@ -320,7 +320,7 @@
position: absolute; /* element can be wider than the parent */
background: #fff;
margin: 0;
box-shadow: 0 4px 5px rgba(30, 30, 30, .3);
box-shadow: 0 4px 5px rgba(30,30,30,.3);
border-style: solid;
border-width: 1px 1px 1px 2px;
border-color: #aaa #111 #111 #aaa;
@ -473,7 +473,7 @@
width: 30%;
height: 28%;
max-height: 105px;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
box-shadow: 0px 2px 4px rgba(0,0,0,.5);
}
.fx-embedded .local-stream.room-preview {
@ -566,7 +566,7 @@
background-color: #4ba6e7;
background-size: contain;
overflow: hidden;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
box-shadow: inset 0 0 0 1px rgba(255,255,255,.3);
float: left;
-moz-margin-end: 1em;
}
@ -776,7 +776,7 @@ html, .fx-embedded, #main,
.room-invitation-overlay {
position: absolute;
background: rgba(0, 0, 0, .6);
background: rgba(0,0,0,.6);
/* This matches .fx-embedded .conversation toolbar height */
top: 26px;
right: 0;
@ -787,18 +787,36 @@ html, .fx-embedded, #main,
z-index: 1010;
}
.room-invitation-overlay .error-display-area.error,
.room-invitation-overlay input[type="text"] {
display: block;
background-color: rgba(0,0,0,.5);
border-radius: 3px;
padding: .5em;
}
.room-invitation-overlay .error-display-area {
display: none;
}
.room-invitation-overlay .error-display-area.error {
position: absolute;
top: 2em;
left: 1em;
right: 1em;
text-align: start;
width: calc(258px - 2em);
color: #d74345;
}
.room-invitation-overlay form {
padding: 8em 0 2.5em 0;
}
.room-invitation-overlay input[type="text"] {
display: block;
background: rgba(0, 0, 0, .5);
color: #fff;
font-size: 1.2em;
border: none;
border-radius: 3px;
padding: .5em;
width: 200px;
margin: 0 auto;
}

View File

@ -264,6 +264,14 @@ loop.shared.actions = (function() {
newRoomName: String
}),
/**
* Renaming a room error.
* XXX: should move to some roomActions module - refs bug 1079284
*/
RenameRoomError: Action.define("renameRoomError", {
error: [Error, Object]
}),
/**
* Copy a room url into the user's clipboard.
* XXX: should move to some roomActions module - refs bug 1079284

View File

@ -98,6 +98,7 @@ loop.store = loop.store || {};
"getAllRoomsError",
"openRoom",
"renameRoom",
"renameRoomError",
"updateRoomList"
],
@ -120,7 +121,7 @@ loop.store = loop.store || {};
error: null,
pendingCreation: false,
pendingInitialRetrieval: false,
rooms: []
rooms: [],
};
},
@ -378,13 +379,17 @@ loop.store = loop.store || {};
* @param {sharedActions.RenameRoom} actionData
*/
renameRoom: function(actionData) {
this.setStoreState({error: null});
this._mozLoop.rooms.rename(actionData.roomToken, actionData.newRoomName,
function(err) {
if (err) {
// XXX Give this a proper UI - bug 1100595.
console.error("Failed to rename the room", err);
this.dispatchAction(new sharedActions.RenameRoomError({error: err}));
}
});
}.bind(this));
},
renameRoomError: function(actionData) {
this.setStoreState({error: actionData.error});
}
});
})();

View File

@ -81,6 +81,7 @@ describe("loop.store.RoomStore", function () {
create: function() {},
getAll: function() {},
open: function() {},
rename: function() {},
on: sandbox.stub()
}
};
@ -433,13 +434,14 @@ describe("loop.store.RoomStore", function () {
beforeEach(function() {
fakeMozLoop = {
rooms: {
rename: sinon.spy()
rename: null
}
};
store = new loop.store.RoomStore(dispatcher, {mozLoop: fakeMozLoop});
});
it("should rename the room via mozLoop", function() {
fakeMozLoop.rooms.rename = sinon.spy();
dispatcher.dispatch(new sharedActions.RenameRoom({
roomToken: "42abc",
newRoomName: "silly name"
@ -449,5 +451,19 @@ describe("loop.store.RoomStore", function () {
sinon.assert.calledWith(fakeMozLoop.rooms.rename, "42abc",
"silly name");
});
it("should store any rename-encountered error", function() {
var err = new Error("fake");
sandbox.stub(fakeMozLoop.rooms, "rename", function(roomToken, roomName, cb) {
cb(err);
});
dispatcher.dispatch(new sharedActions.RenameRoom({
roomToken: "42abc",
newRoomName: "silly name"
}));
expect(store.getStoreState().error).eql(err);
});
});
});