Support loading for button and allow event listener to set it

currently gr-button supports `loading` but did not use it, this change will:

1. use disabled status when loading
2. add a spinner to show when loading is true
3. in repo-access component, set loading to true while promise ongoing
4. also fix the issue with non-zero initial tabindex for gr-button

screenshot: https://imgur.com/a/NCLwb45

Bug: 12421
Change-Id: Ie69c447455d23a118dbe1823c576144e0bca7c2d
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
index fab730d..54006b5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
@@ -105,12 +105,12 @@
             primary
             class$="[[_computeSaveBtnClass(_ownerOf)]]"
             on-click="_handleSave"
-            disabled$="[[!_modified]]">Save</gr-button>
+            disabled="[[!_modified]]">Save</gr-button>
         <gr-button id="saveReviewBtn"
             primary
             class$="[[_computeSaveReviewBtnClass(_canUpload)]]"
             on-click="_handleSaveForReview"
-            disabled$="[[!_modified]]">Save for review</gr-button>
+            disabled="[[!_modified]]">Save for review</gr-button>
         <template
             is="dom-repeat"
             items="{{_sections}}"
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index b350702..02b62e0 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -442,21 +442,42 @@
       return obj;
     }
 
-    _handleSave() {
+    _handleSave(e) {
       const obj = this._getObjforSave();
       if (!obj) { return; }
+      const button = e && e.target;
+      if (button) {
+        button.loading = true;
+      }
       return this.$.restAPI.setRepoAccessRights(this.repo, obj)
           .then(() => {
             this._reload(this.repo);
+          })
+          .finally(() => {
+            this._modified = false;
+            if (button) {
+              button.loading = false;
+            }
           });
     }
 
-    _handleSaveForReview() {
+    _handleSaveForReview(e) {
       const obj = this._getObjforSave();
       if (!obj) { return; }
-      return this.$.restAPI.setRepoAccessRightsForReview(this.repo, obj)
+      const button = e && e.target;
+      if (button) {
+        button.loading = true;
+      }
+      return this.$.restAPI
+          .setRepoAccessRightsForReview(this.repo, obj)
           .then(change => {
             Gerrit.Nav.navigateToChange(change);
+          })
+          .finally(() => {
+            this._modified = false;
+            if (button) {
+              button.loading = false;
+            }
           });
     }
 
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index 2fc31f7..d89b5df 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -1138,6 +1138,53 @@
         assert.equal(Object.keys(element._local).length, 1);
       });
 
+      test('_handleSave', done => {
+        const repoAccessInput = {
+          add: {
+            'refs/*': {
+              permissions: {
+                owner: {
+                  rules: {
+                    123: {action: 'DENY', modified: true},
+                  },
+                },
+              },
+            },
+          },
+          remove: {
+            'refs/*': {
+              permissions: {
+                owner: {
+                  rules: {
+                    123: {},
+                  },
+                },
+              },
+            },
+          },
+        };
+        sandbox.stub(element.$.restAPI, 'getRepoAccessRights').returns(
+            Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
+        sandbox.stub(Gerrit.Nav, 'navigateToChange');
+        let resolver;
+        const saveStub = sandbox.stub(element.$.restAPI,
+            'setRepoAccessRights')
+            .returns(new Promise(r => resolver = r));
+
+        element.repo = 'test-repo';
+        sandbox.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
+
+        element._modified = true;
+        MockInteractions.tap(element.$.saveBtn);
+        assert.equal(element.$.saveBtn.hasAttribute('loading'), true);
+        resolver({_number: 1});
+        flush(() => {
+          assert.isTrue(saveStub.called);
+          assert.isTrue(Gerrit.Nav.navigateToChange.notCalled);
+          done();
+        });
+      });
+
       test('_handleSaveForReview', done => {
         const repoAccessInput = {
           add: {
@@ -1166,14 +1213,19 @@
         sandbox.stub(element.$.restAPI, 'getRepoAccessRights').returns(
             Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
         sandbox.stub(Gerrit.Nav, 'navigateToChange');
+        let resolver;
         const saveForReviewStub = sandbox.stub(element.$.restAPI,
             'setRepoAccessRightsForReview')
-            .returns(Promise.resolve({_number: 1}));
+            .returns(new Promise(r => resolver = r));
 
         element.repo = 'test-repo';
         sandbox.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
 
-        element._handleSaveForReview().then(() => {
+        element._modified = true;
+        MockInteractions.tap(element.$.saveReviewBtn);
+        assert.equal(element.$.saveReviewBtn.hasAttribute('loading'), true);
+        resolver({_number: 1});
+        flush(() => {
           assert.isTrue(saveForReviewStub.called);
           assert.isTrue(Gerrit.Nav.navigateToChange
               .lastCall.calledWithExactly({_number: 1}));