| <!-- |
| Copyright (C) 2015 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. |
| --> |
| |
| <link rel="import" href="../bower_components/polymer/polymer.html"> |
| <link rel="import" href="../bower_components/iron-dropdown/iron-dropdown.html"> |
| <link rel="import" href="../behaviors/keyboard-shortcut-behavior.html"> |
| <link rel="import" href="../behaviors/rest-client-behavior.html"> |
| <link rel="import" href="gr-ajax.html"> |
| <link rel="import" href="gr-diff.html"> |
| |
| <dom-module id="gr-diff-view"> |
| <template> |
| <style> |
| :host { |
| background-color: var(--view-background-color); |
| display: block; |
| } |
| h3 { |
| margin-top: 1em; |
| padding: .75em var(--default-horizontal-margin); |
| } |
| .jumpToFileContainer { |
| display: inline-block; |
| } |
| .downArrow { |
| display: inline-block; |
| font-size: .6em; |
| vertical-align: middle; |
| } |
| .dropdown-trigger { |
| color: #00e; |
| cursor: pointer; |
| padding: 0; |
| } |
| .dropdown-content { |
| background-color: #fff; |
| box-shadow: 0 1px 5px rgba(0, 0, 0, .3); |
| } |
| .dropdown-content a { |
| cursor: pointer; |
| display: block; |
| font-weight: normal; |
| padding: .3em .5em; |
| } |
| .dropdown-content a:before { |
| color: #ccc; |
| content: attr(data-key-nav); |
| display: inline-block; |
| margin-right: .5em; |
| width: .3em; |
| } |
| .dropdown-content a:hover { |
| background-color: #00e; |
| color: #fff; |
| } |
| .dropdown-content a[selected] { |
| color: #000; |
| font-weight: bold; |
| pointer-events: none; |
| text-decoration: none; |
| } |
| .dropdown-content a[selected]:hover { |
| background-color: #fff; |
| color: #000; |
| } |
| button { |
| background: none; |
| border: none; |
| font: inherit; |
| padding: .3em 0; |
| } |
| </style> |
| <gr-ajax id="changeDetailXHR" |
| auto |
| url="[[_computeChangeDetailPath(_changeNum)]]" |
| params="[[_computeChangeDetailQueryParams()]]" |
| last-response="{{_change}}"></gr-ajax> |
| <gr-ajax id="filesXHR" |
| auto |
| url="[[_computeFilesPath(_changeNum, _patchRange.patchNum)]]" |
| on-response="_handleFilesResponse"></gr-ajax> |
| <gr-ajax id="configXHR" |
| auto |
| url="[[_computeProjectConfigPath(_change.project)]]" |
| last-response="{{_projectConfig}}"></gr-ajax> |
| <h3> |
| <a href$="[[_computeChangePath(_changeNum)]]">[[_changeNum]]</a><span>:</span> |
| <span>[[_change.subject]]</span> — |
| <div class="jumpToFileContainer"> |
| <button class="dropdown-trigger" id="trigger" on-tap="_showDropdownTapHandler"> |
| <span>[[_path]]</span> |
| <span class="downArrow">▼</span> |
| </button> |
| <iron-dropdown id="dropdown" vertical-align="top" vertical-offset="25"> |
| <div class="dropdown-content"> |
| <template is="dom-repeat" items="[[_fileList]]" as="path"> |
| <a href$="[[_computeDiffURL(_changeNum, _patchRange, path)]]" |
| selected$="[[_computeFileSelected(path, _path)]]" |
| data-key-nav$="[[_computeKeyNav(path, _path, _fileList)]]" |
| on-tap="_handleFileTap">[[path]]</a> |
| </template> |
| </div> |
| </iron-dropdown> |
| </div> |
| </h3> |
| <gr-diff id="diff" |
| change-num="[[_changeNum]]" |
| prefs="{{prefs}}" |
| patch-range="[[_patchRange]]" |
| path="[[_path]]" |
| project-config="[[_projectConfig]]" |
| available-patches="[[_computeAvailablePatches(_change.revisions)]]" |
| on-render="_handleDiffRender"> |
| </gr-diff> |
| </template> |
| <script> |
| (function() { |
| 'use strict'; |
| |
| 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, |
| }, |
| }, |
| |
| behaviors: [ |
| Gerrit.KeyboardShortcutBehavior, |
| Gerrit.RESTClientBehavior, |
| ], |
| |
| ready: function() { |
| app.accountReady.then(function() { |
| this._loggedIn = app.loggedIn; |
| }.bind(this)); |
| }, |
| |
| attached: function() { |
| if (this._path) { |
| this.fire('title-change', {title: this._path}); |
| } |
| window.addEventListener('resize', this._boundWindowResizeHandler); |
| }, |
| |
| detached: function() { |
| window.removeEventListener('resize', this._boundWindowResizeHandler); |
| }, |
| |
| _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 65: // 'a' |
| if (!this._loggedIn) { return; } |
| |
| this.set('changeViewState.showReplyDropdown', true); |
| /* falls through */ // required by JSHint |
| case 85: // 'u' |
| if (this._changeNum) { |
| e.preventDefault(); |
| page.show(this._computeChangePath(this._changeNum)); |
| } |
| 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)); |
| 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._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)); |
| }, |
| |
| _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) { |
| return '/c/' + changeNum; |
| }, |
| |
| _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(); |
| }, |
| |
| _handleFilesResponse: function(e, req) { |
| this._fileList = Object.keys(e.detail.response).sort(); |
| }, |
| |
| _showDropdownTapHandler: function(e) { |
| this.$.dropdown.open(); |
| }, |
| }); |
| })(); |
| </script> |
| </dom-module> |