Add copy to clipboard buttons in gr-downlod-dialog

Adds quick copy to clipboard buttons for all commands in the
gr-download-dialog. Also focuses on the first button if it exists.
Otherwise, focuses on download link as it did previously.

Bug: Issue 5603
Change-Id: Ie0968b0779d5382f33a374a1e22801fba2e5fce2
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
index 7c888da..bcd9053 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
@@ -49,8 +49,6 @@
       input {
         font-family: var(--monospace-font-family);
         font-size: inherit;
-        margin-bottom: .5em;
-        width: 60em;
       }
       li[selected] gr-button {
         color: #000;
@@ -71,6 +69,19 @@
         justify-content: space-between;
         padding-top: .75em;
       }
+      .command {
+        display: flex;
+        flex-wrap: wrap;
+        margin-bottom: .5em;
+        width: 60em;
+      }
+      .command label {
+        flex: 0 0 100%;
+      }
+      .copyCommand {
+        flex-grow: 1;
+        margin-right: .3em;
+      }
       .closeButtonContainer {
         display: flex;
         flex: 1;
@@ -112,10 +123,14 @@
         <div class="command">
           <label>[[command.title]]</label>
           <input is="iron-input"
+              class="copyCommand"
               type="text"
               bind-value="[[command.command]]"
               on-tap="_handleInputTap"
               readonly>
+          <gr-button class="copyToClipboard" on-tap="_copyToClipboard">
+            copy
+          </gr-button>
         </div>
       </template>
     </main>
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
index 72425a4..41f6792 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
@@ -51,7 +51,11 @@
     ],
 
     focus: function() {
-      this.$.download.focus();
+      if (this._schemes.length) {
+        this.$$('.copyToClipboard').focus();
+      } else {
+        this.$.download.focus();
+      }
     },
 
     getFocusStops: function() {
@@ -162,5 +166,13 @@
         this._selectedScheme = schemes.sort()[0];
       }
     },
+
+    _copyToClipboard: function(e) {
+      e.target.parentElement.querySelector('.copyCommand').select();
+      document.execCommand('copy');
+      getSelection().removeAllRanges();
+      e.target.textContent = 'done';
+      setTimeout(function() { e.target.textContent = 'copy'; }, 1000);
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index 7d80c09..ce28d4e 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -97,6 +97,45 @@
     };
   }
 
+  function getChangeObjectNoFetch() {
+    return {
+      current_revision: '34685798fe548b6d17d1e8e5edc43a26d055cc72',
+      revisions: {
+        '34685798fe548b6d17d1e8e5edc43a26d055cc72': {
+          _number: 1,
+          fetch: {},
+        }
+      }
+    };
+  }
+
+  suite('gr-download-dialog tests with no fetch options', function() {
+    var element;
+
+    setup(function() {
+      element = fixture('basic');
+      element.change = getChangeObjectNoFetch();
+      element.patchNum = 1;
+      element.config = {
+        schemes: {
+          'anonymous http': {},
+          http: {},
+          repo: {},
+          ssh: {},
+        },
+        archives: ['tgz', 'tar', 'tbz2', 'txz'],
+      };
+    });
+
+    test('focuses on first download link if no copy links', function() {
+      flushAsynchronousOperations();
+      var focusStub = sinon.stub(element.$.download, 'focus');
+      element.focus();
+      assert.isTrue(focusStub.called);
+      focusStub.restore();
+    });
+  });
+
   suite('gr-download-dialog tests', function() {
     var element;
 
@@ -115,14 +154,23 @@
       };
     });
 
-    test('focuses on first download link', function() {
-      var focusStub = sinon.stub(element.$.download, 'focus');
+    test('focuses on first copy link', function() {
+      flushAsynchronousOperations();
+      var focusStub = sinon.stub(element.$$('.copyToClipboard'), 'focus');
       element.focus();
       flushAsynchronousOperations();
       assert.isTrue(focusStub.called);
       focusStub.restore();
     });
 
+    test('copy to clipboard', function() {
+      flushAsynchronousOperations();
+      var clipboardSpy = sinon.spy(element, '_copyToClipboard');
+      var copyBtn = element.$$('.copyToClipboard');
+      MockInteractions.tap(copyBtn);
+      assert.isTrue(clipboardSpy.called);
+    });
+
     test('element visibility', function() {
       assert.isFalse(element.$$('ul').hasAttribute('hidden'));
       assert.isFalse(element.$$('main').hasAttribute('hidden'));