Bug 1038257 - Desktop client needs the ability to delete, block, and unblock contacts. r=mikedeboer
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Tue, 30 Sep 2014 13:10:24 +0100
changeset 218118 c47ec0044f8c4d978f61f7518c503a48b3d60f2d
parent 218117 05c0e2b99f47f05ce78ad1056d77127da80db8b7
child 218119 a29ef7c8a3e045467c9154c0e9ccbe24b769cb97
push id2
push usergszorc@mozilla.com
push dateWed, 12 Nov 2014 19:43:22 +0000
treeherderfig@7a5f4d72e05d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1038257
milestone34.0a2
Bug 1038257 - Desktop client needs the ability to delete, block, and unblock contacts. r=mikedeboer
browser/components/loop/content/js/contacts.js
browser/components/loop/content/js/contacts.jsx
browser/components/loop/content/shared/css/contacts.css
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -50,16 +50,20 @@ loop.contacts = (function(_, mozL10n) {
 
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
     render: function() {
       var cx = React.addons.classSet;
 
+      let blockAction = this.props.blocked ? "unblock" : "block";
+      let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
+                                          : "block_contact_menu_button";
+
       return (
         React.DOM.ul({className: cx({ "dropdown-menu": true,
                             "dropdown-menu-up": this.state.openDirUp })}, 
           React.DOM.li({className: cx({ "dropdown-menu-item": true,
                               "disabled": true }), 
               onClick: this.onItemClick, 'data-action': "video-call"}, 
             React.DOM.i({className: "icon icon-video-call"}), 
             mozL10n.get("video_call_menu_button")
@@ -72,24 +76,24 @@ loop.contacts = (function(_, mozL10n) {
           ), 
           React.DOM.li({className: cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit }), 
               onClick: this.onItemClick, 'data-action': "edit"}, 
             React.DOM.i({className: "icon icon-edit"}), 
             mozL10n.get("edit_contact_menu_button")
           ), 
           React.DOM.li({className: "dropdown-menu-item", 
-              onClick: this.onItemClick, 'data-action': "block"}, 
-            React.DOM.i({className: "icon icon-block"}), 
-            mozL10n.get("block_contact_menu_button")
+              onClick: this.onItemClick, 'data-action': blockAction}, 
+            React.DOM.i({className: "icon icon-" + blockAction}), 
+            mozL10n.get(blockLabel)
           ), 
           React.DOM.li({className: cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit }), 
-              onClick: this.onItemClick, 'data-action': "delete"}, 
-            React.DOM.i({className: "icon icon-delete"}), 
+              onClick: this.onItemClick, 'data-action': "remove"}, 
+            React.DOM.i({className: "icon icon-remove"}), 
             mozL10n.get("remove_contact_menu_button")
           )
         )
       );
     }
   });
 
   const ContactDetail = React.createClass({displayName: 'ContactDetail',
@@ -111,17 +115,21 @@ loop.contacts = (function(_, mozL10n) {
 
     showDropdownMenu: function() {
       document.body.addEventListener("click", this._onBodyClick);
       this.setState({showMenu: true});
     },
 
     hideDropdownMenu: function() {
       document.body.removeEventListener("click", this._onBodyClick);
-      this.setState({showMenu: false});
+      // Since this call may be deferred, we need to guard it, for example in
+      // case the contact was removed in the meantime.
+      if (this.isMounted()) {
+        this.setState({showMenu: false});
+      }
     },
 
     componentWillUnmount: function() {
       document.body.removeEventListener("click", this._onBodyClick);
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
@@ -186,17 +194,18 @@ loop.contacts = (function(_, mozL10n) {
           React.DOM.div({className: "icons"}, 
             React.DOM.i({className: "icon icon-video", 
                onClick: this.handleAction.bind(null, "video-call")}), 
             React.DOM.i({className: "icon icon-caret-down", 
                onClick: this.showDropdownMenu})
           ), 
           this.state.showMenu
             ? ContactDropdown({handleAction: this.handleAction, 
-                               canEdit: this.canEdit()})
+                               canEdit: this.canEdit(), 
+                               blocked: this.props.contact.blocked})
             : null
           
         )
       );
     }
   });
 
   const ContactsList = React.createClass({displayName: 'ContactsList',
@@ -271,16 +280,26 @@ loop.contacts = (function(_, mozL10n) {
       this.props.startForm("contacts_add");
     },
 
     handleContactAction: function(contact, actionName) {
       switch (actionName) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
+        case "remove":
+        case "block":
+        case "unblock":
+          // Invoke the API named like the action.
+          navigator.mozLoop.contacts[actionName](contact._guid, err => {
+            if (err) {
+              throw err;
+            }
+          });
+          break;
         default:
           console.error("Unrecognized action: " + actionName);
           break;
       }
     },
 
     sortContacts: function(contact1, contact2) {
       let comp = contact1.name[0].localeCompare(contact2.name[0]);
@@ -297,20 +316,19 @@ loop.contacts = (function(_, mozL10n) {
         return ContactDetail({key: item._guid, contact: item, 
                               handleContactAction: this.handleContactAction})
       };
 
       let shownContacts = _.groupBy(this.state.contacts, function(contact) {
         return contact.blocked ? "blocked" : "available";
       });
 
-      // Buttons are temporarily hidden using "style".
       return (
         React.DOM.div(null, 
-          React.DOM.div({className: "content-area", style: {display: "none"}}, 
+          React.DOM.div({className: "content-area"}, 
             ButtonGroup(null, 
               Button({caption: mozL10n.get("import_contacts_button"), 
                       disabled: true, 
                       onClick: this.handleImportButtonClick}), 
               Button({caption: mozL10n.get("new_contact_button"), 
                       onClick: this.handleAddContactButtonClick})
             )
           ), 
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -50,16 +50,20 @@ loop.contacts = (function(_, mozL10n) {
 
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
     render: function() {
       var cx = React.addons.classSet;
 
+      let blockAction = this.props.blocked ? "unblock" : "block";
+      let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
+                                          : "block_contact_menu_button";
+
       return (
         <ul className={cx({ "dropdown-menu": true,
                             "dropdown-menu-up": this.state.openDirUp })}>
           <li className={cx({ "dropdown-menu-item": true,
                               "disabled": true })}
               onClick={this.onItemClick} data-action="video-call">
             <i className="icon icon-video-call" />
             {mozL10n.get("video_call_menu_button")}
@@ -72,24 +76,24 @@ loop.contacts = (function(_, mozL10n) {
           </li>
           <li className={cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit })}
               onClick={this.onItemClick} data-action="edit">
             <i className="icon icon-edit" />
             {mozL10n.get("edit_contact_menu_button")}
           </li>
           <li className="dropdown-menu-item"
-              onClick={this.onItemClick} data-action="block">
-            <i className="icon icon-block" />
-            {mozL10n.get("block_contact_menu_button")}
+              onClick={this.onItemClick} data-action={blockAction}>
+            <i className={"icon icon-" + blockAction} />
+            {mozL10n.get(blockLabel)}
           </li>
           <li className={cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit })}
-              onClick={this.onItemClick} data-action="delete">
-            <i className="icon icon-delete" />
+              onClick={this.onItemClick} data-action="remove">
+            <i className="icon icon-remove" />
             {mozL10n.get("remove_contact_menu_button")}
           </li>
         </ul>
       );
     }
   });
 
   const ContactDetail = React.createClass({
@@ -111,17 +115,21 @@ loop.contacts = (function(_, mozL10n) {
 
     showDropdownMenu: function() {
       document.body.addEventListener("click", this._onBodyClick);
       this.setState({showMenu: true});
     },
 
     hideDropdownMenu: function() {
       document.body.removeEventListener("click", this._onBodyClick);
-      this.setState({showMenu: false});
+      // Since this call may be deferred, we need to guard it, for example in
+      // case the contact was removed in the meantime.
+      if (this.isMounted()) {
+        this.setState({showMenu: false});
+      }
     },
 
     componentWillUnmount: function() {
       document.body.removeEventListener("click", this._onBodyClick);
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
@@ -186,17 +194,18 @@ loop.contacts = (function(_, mozL10n) {
           <div className="icons">
             <i className="icon icon-video"
                onClick={this.handleAction.bind(null, "video-call")} />
             <i className="icon icon-caret-down"
                onClick={this.showDropdownMenu} />
           </div>
           {this.state.showMenu
             ? <ContactDropdown handleAction={this.handleAction}
-                               canEdit={this.canEdit()} />
+                               canEdit={this.canEdit()}
+                               blocked={this.props.contact.blocked} />
             : null
           }
         </li>
       );
     }
   });
 
   const ContactsList = React.createClass({
@@ -271,16 +280,26 @@ loop.contacts = (function(_, mozL10n) {
       this.props.startForm("contacts_add");
     },
 
     handleContactAction: function(contact, actionName) {
       switch (actionName) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
+        case "remove":
+        case "block":
+        case "unblock":
+          // Invoke the API named like the action.
+          navigator.mozLoop.contacts[actionName](contact._guid, err => {
+            if (err) {
+              throw err;
+            }
+          });
+          break;
         default:
           console.error("Unrecognized action: " + actionName);
           break;
       }
     },
 
     sortContacts: function(contact1, contact2) {
       let comp = contact1.name[0].localeCompare(contact2.name[0]);
@@ -297,20 +316,19 @@ loop.contacts = (function(_, mozL10n) {
         return <ContactDetail key={item._guid} contact={item}
                               handleContactAction={this.handleContactAction} />
       };
 
       let shownContacts = _.groupBy(this.state.contacts, function(contact) {
         return contact.blocked ? "blocked" : "available";
       });
 
-      // Buttons are temporarily hidden using "style".
       return (
         <div>
-          <div className="content-area" style={{display: "none"}}>
+          <div className="content-area">
             <ButtonGroup>
               <Button caption={mozL10n.get("import_contacts_button")}
                       disabled
                       onClick={this.handleImportButtonClick} />
               <Button caption={mozL10n.get("new_contact_button")}
                       onClick={this.handleAddContactButtonClick} />
             </ButtonGroup>
           </div>
--- a/browser/components/loop/content/shared/css/contacts.css
+++ b/browser/components/loop/content/shared/css/contacts.css
@@ -181,15 +181,19 @@
 .contact > .dropdown-menu > .dropdown-menu-item > .icon-edit {
   background-image: url("../img/icons-16x16.svg#contacts");
 }
 
 .contact > .dropdown-menu > .dropdown-menu-item > .icon-block {
   background-image: url("../img/icons-16x16.svg#block");
 }
 
-.contact > .dropdown-menu > .dropdown-menu-item > .icon-delete {
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-unblock {
+  background-image: url("../img/icons-16x16.svg#unblock");
+}
+
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-remove {
   background-image: url("../img/icons-16x16.svg#delete");
 }
 
 .contact-form > .button-group {
   margin-top: 14px;
 }