Refactor directory structure of components

There is no change in functionality. Only moving things around.

+ Separate html from the js.
+ Place the unit test for a component within the same folder.
+ Organize the components in subfolders.

Change-Id: I51fdc510db75fc1b33f040ca63decbbdfd4d5513
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
new file mode 100644
index 0000000..847a641
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -0,0 +1,315 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+  'use strict';
+
+  var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
+
+  Polymer({
+    is: 'gr-diff-view',
+
+    /**
+     * Fired when the title of the page should change.
+     *
+     * @event title-change
+     */
+
+    properties: {
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+      /**
+       * URL params passed from the router.
+       */
+      params: {
+        type: Object,
+        observer: '_paramsChanged',
+      },
+      keyEventTarget: {
+        type: Object,
+        value: function() { return document.body; },
+      },
+      changeViewState: {
+        type: Object,
+        notify: true,
+        value: function() { return {}; },
+      },
+
+      _patchRange: Object,
+      _change: Object,
+      _changeNum: String,
+      _diff: Object,
+      _fileList: {
+        type: Array,
+        value: function() { return []; },
+      },
+      _path: {
+        type: String,
+        observer: '_pathChanged',
+      },
+      _loggedIn: {
+        type: Boolean,
+        value: false,
+      },
+      _xhrPromise: Object,  // Used for testing.
+    },
+
+    behaviors: [
+      Gerrit.KeyboardShortcutBehavior,
+      Gerrit.RESTClientBehavior,
+    ],
+
+    ready: function() {
+      app.accountReady.then(function() {
+        this._loggedIn = app.loggedIn;
+        if (this._loggedIn) {
+          this._setReviewed(true);
+        }
+      }.bind(this));
+    },
+
+    attached: function() {
+      if (this._path) {
+        this.fire('title-change',
+            {title: this._computeFileDisplayName(this._path)});
+      }
+      window.addEventListener('resize', this._boundWindowResizeHandler);
+    },
+
+    detached: function() {
+      window.removeEventListener('resize', this._boundWindowResizeHandler);
+    },
+
+    _handleReviewedChange: function(e) {
+      this._setReviewed(Polymer.dom(e).rootTarget.checked);
+    },
+
+    _setReviewed: function(reviewed) {
+      this.$.reviewed.checked = reviewed;
+      var method = reviewed ? 'PUT' : 'DELETE';
+      var url = this.changeBaseURL(this._changeNum,
+          this._patchRange.patchNum) + '/files/' +
+          encodeURIComponent(this._path) + '/reviewed';
+      this._send(method, url).catch(function(err) {
+        alert('Couldn’t change file review status. Check the console ' +
+            'and contact the PolyGerrit team for assistance.');
+        throw err;
+      }.bind(this));
+    },
+
+    _handleKey: function(e) {
+      if (this.shouldSupressKeyboardShortcut(e)) { return; }
+
+      switch (e.keyCode) {
+        case 219:  // '['
+          e.preventDefault();
+          this._navToFile(this._fileList, -1);
+          break;
+        case 221:  // ']'
+          e.preventDefault();
+          this._navToFile(this._fileList, 1);
+          break;
+        case 78:  // 'n'
+          if (e.shiftKey) {
+            this.$.diff.scrollToNextCommentThread();
+          } else {
+            this.$.diff.scrollToNextDiffChunk();
+          }
+          break;
+        case 80:  // 'p'
+          if (e.shiftKey) {
+            this.$.diff.scrollToPreviousCommentThread();
+          } else {
+            this.$.diff.scrollToPreviousDiffChunk();
+          }
+          break;
+        case 65:  // 'a'
+          if (!this._loggedIn) { return; }
+
+          this.set('changeViewState.showReplyDialog', true);
+          /* falls through */ // required by JSHint
+        case 85:  // 'u'
+          if (this._changeNum && this._patchRange.patchNum) {
+            e.preventDefault();
+            page.show(this._computeChangePath(
+                this._changeNum,
+                this._patchRange.patchNum,
+                this._change && this._change.revisions));
+          }
+          break;
+        case 188:  // ','
+          this.$.diff.showDiffPreferences();
+          break;
+      }
+    },
+
+    _handleDiffRender: function() {
+      if (window.location.hash.length > 0) {
+        this.$.diff.scrollToLine(
+            parseInt(window.location.hash.substring(1), 10));
+      }
+    },
+
+    _navToFile: function(fileList, direction) {
+      if (fileList.length == 0) { return; }
+
+      var idx = fileList.indexOf(this._path) + direction;
+      if (idx < 0 || idx > fileList.length - 1) {
+        page.show(this._computeChangePath(
+            this._changeNum,
+            this._patchRange.patchNum,
+            this._change && this._change.revisions));
+        return;
+      }
+      page.show(this._computeDiffURL(this._changeNum,
+                                     this._patchRange,
+                                     fileList[idx]));
+    },
+
+    _paramsChanged: function(value) {
+      if (value.view != this.tagName.toLowerCase()) { return; }
+
+      this._changeNum = value.changeNum;
+      this._patchRange = {
+        patchNum: value.patchNum,
+        basePatchNum: value.basePatchNum || 'PARENT',
+      };
+      this._path = value.path;
+
+      this.fire('title-change',
+          {title: this._computeFileDisplayName(this._path)});
+
+      // When navigating away from the page, there is a possibility that the
+      // patch number is no longer a part of the URL (say when navigating to
+      // the top-level change info view) and therefore undefined in `params`.
+      if (!this._patchRange.patchNum) {
+        return;
+      }
+
+      this.$.diff.reload();
+    },
+
+    _pathChanged: function(path) {
+      if (this._fileList.length == 0) { return; }
+
+      this.set('changeViewState.selectedFileIndex',
+          this._fileList.indexOf(path));
+
+      if (this._loggedIn) {
+        this._setReviewed(true);
+      }
+    },
+
+    _computeDiffURL: function(changeNum, patchRange, path) {
+      var patchStr = patchRange.patchNum;
+      if (patchRange.basePatchNum != null &&
+          patchRange.basePatchNum != 'PARENT') {
+        patchStr = patchRange.basePatchNum + '..' + patchRange.patchNum;
+      }
+      return '/c/' + changeNum + '/' + patchStr + '/' + path;
+    },
+
+    _computeAvailablePatches: function(revisions) {
+      var patchNums = [];
+      for (var rev in revisions) {
+        patchNums.push(revisions[rev]._number);
+      }
+      return patchNums.sort(function(a, b) { return a - b; });
+    },
+
+    _computeChangePath: function(changeNum, patchNum, revisions) {
+      var base = '/c/' + changeNum + '/';
+
+      // The change may not have loaded yet, making revisions unavailable.
+      if (!revisions) {
+        return base + patchNum;
+      }
+
+      var latestPatchNum = -1;
+      for (var rev in revisions) {
+        latestPatchNum = Math.max(latestPatchNum, revisions[rev]._number);
+      }
+      if (parseInt(patchNum, 10) != latestPatchNum) {
+        return base + patchNum;
+      }
+
+      return base;
+    },
+
+    _computeFileDisplayName: function(path) {
+      return path == COMMIT_MESSAGE_PATH ? 'Commit message' : path;
+    },
+
+    _computeChangeDetailPath: function(changeNum) {
+      return '/changes/' + changeNum + '/detail';
+    },
+
+    _computeChangeDetailQueryParams: function() {
+      return {O: this.listChangesOptionsToHex(
+          this.ListChangesOption.ALL_REVISIONS
+      )};
+    },
+
+    _computeFilesPath: function(changeNum, patchNum) {
+      return this.changeBaseURL(changeNum, patchNum) + '/files';
+    },
+
+    _computeProjectConfigPath: function(project) {
+      return '/projects/' + encodeURIComponent(project) + '/config';
+    },
+
+    _computeFileSelected: function(path, currentPath) {
+      return path == currentPath;
+    },
+
+    _computeKeyNav: function(path, selectedPath, fileList) {
+      var selectedIndex = fileList.indexOf(selectedPath);
+      if (fileList.indexOf(path) == selectedIndex - 1) {
+        return '[';
+      }
+      if (fileList.indexOf(path) == selectedIndex + 1) {
+        return ']';
+      }
+      return '';
+    },
+
+    _handleFileTap: function(e) {
+      this.$.dropdown.close();
+    },
+
+    _handleMobileSelectChange: function(e) {
+      var path = Polymer.dom(e).rootTarget.value;
+      page.show(
+          this._computeDiffURL(this._changeNum, this._patchRange, path));
+    },
+
+    _handleFilesResponse: function(e, req) {
+      this._fileList = Object.keys(e.detail.response).sort();
+    },
+
+    _showDropdownTapHandler: function(e) {
+      this.$.dropdown.open();
+    },
+
+    _send: function(method, url) {
+      var xhr = document.createElement('gr-request');
+      this._xhrPromise = xhr.send({
+        method: method,
+        url: url,
+      });
+      return this._xhrPromise;
+    },
+  });
+})();