Properly bookend file-list hotkey scrolling
The addition of incremental file loading in the file-list introduced an
edge case in which the user could scroll off the file-list with hotkeys.
This change implements the gr-cursor-manager to control file-list
scrolling behavior.
In addition, some interesting edge cases with the cursor-manager were
uncovered during use -- these were also fixed.
Change-Id: I936d4368058212c98684bc2617be2d98bdf6e41d
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index f607481..3142733 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -21,6 +21,7 @@
<link rel="import" href="../../diff/gr-diff/gr-diff.html">
<link rel="import" href="../../diff/gr-diff-cursor/gr-diff-cursor.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
@@ -64,7 +65,7 @@
.row:not(.header):hover {
background-color: #f5fafd;
}
- .row[selected] {
+ .row.selected {
background-color: #ebf5fb;
}
.path {
@@ -145,7 +146,7 @@
max-width: 25em;
}
@media screen and (max-width: 50em) {
- .row[selected] {
+ .row.selected {
background-color: transparent;
}
.stats {
@@ -207,7 +208,7 @@
items="[[_shownFiles]]"
as="file"
initial-count="[[_fileListIncrement]]">
- <div class="row" selected$="[[_computeFileSelected(index, selectedIndex)]]">
+ <div class="file-row row" selected$="[[_computeFileSelected(index, selectedIndex)]]">
<div class="reviewed" hidden$="[[!_loggedIn]]" hidden>
<input type="checkbox" checked$="[[_computeReviewed(file, _reviewed)]]"
data-path$="[[file.__path]]" on-change="_handleReviewedChange"
@@ -302,7 +303,11 @@
</gr-button>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-storage id="storage"></gr-storage>
- <gr-diff-cursor id="cursor"></gr-diff-cursor>
+ <gr-diff-cursor id="diffCursor"></gr-diff-cursor>
+ <gr-cursor-manager
+ id="fileCursor"
+ scroll-behavior="keep-visible"
+ cursor-target-class="selected"></gr-cursor-manager>
</template>
<script src="gr-file-list.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 3f13494..b423f50 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -35,10 +35,6 @@
drafts: Object,
revisions: Object,
projectConfig: Object,
- selectedIndex: {
- type: Number,
- notify: true,
- },
keyEventTarget: {
type: Object,
value: function() { return document.body; },
@@ -253,7 +249,7 @@
this.set(['_shownFiles', i, '__expanded'], false);
this.set(['_files', i, '__expanded'], false);
}
- this.$.cursor.handleDiffUpdate();
+ this.$.diffCursor.handleDiffUpdate();
},
_computeCommentsString: function(comments, patchNum, path) {
@@ -326,7 +322,7 @@
if (!this._showInlineDiffs) { return; }
e.preventDefault();
- this.$.cursor.moveLeft();
+ this.$.diffCursor.moveLeft();
},
_handleShiftRightKey: function(e) {
@@ -334,20 +330,20 @@
if (!this._showInlineDiffs) { return; }
e.preventDefault();
- this.$.cursor.moveRight();
+ this.$.diffCursor.moveRight();
},
_handleIKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
- if (this.selectedIndex === undefined) { return; }
+ if (this.$.fileCursor.index === -1) { return; }
e.preventDefault();
- var expanded = this._files[this.selectedIndex].__expanded;
+ var expanded = this._files[this.$.fileCursor.index].__expanded;
// Until Polymer 2.0, manual management of reflection between _files
// and _shownFiles is necessary.
- this.set(['_shownFiles', this.selectedIndex, '__expanded'],
+ this.set(['_shownFiles', this.$.fileCursor.index, '__expanded'],
!expanded);
- this.set(['_files', this.selectedIndex, '__expanded'], !expanded);
+ this.set(['_files', this.$.fileCursor.index, '__expanded'], !expanded);
},
_handleCapitalIKey: function(e) {
@@ -359,14 +355,11 @@
_handleDownKey: function(e) {
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
-
e.preventDefault();
if (this._showInlineDiffs) {
- this.$.cursor.moveDown();
+ this.$.diffCursor.moveDown();
} else {
- this.selectedIndex =
- Math.min(this._numFilesShown, this.selectedIndex + 1);
- this._scrollToSelectedFile();
+ this.$.fileCursor.next();
}
},
@@ -375,10 +368,9 @@
e.preventDefault();
if (this._showInlineDiffs) {
- this.$.cursor.moveUp();
+ this.$.diffCursor.moveUp();
} else {
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
- this._scrollToSelectedFile();
+ this.$.fileCursor.previous();
}
},
@@ -425,9 +417,9 @@
e.preventDefault();
if (e.shiftKey) {
- this.$.cursor.moveToNextCommentThread();
+ this.$.diffCursor.moveToNextCommentThread();
} else {
- this.$.cursor.moveToNextChunk();
+ this.$.diffCursor.moveToNextChunk();
}
},
@@ -437,9 +429,9 @@
e.preventDefault();
if (e.shiftKey) {
- this.$.cursor.moveToPreviousCommentThread();
+ this.$.diffCursor.moveToPreviousCommentThread();
} else {
- this.$.cursor.moveToPreviousChunk();
+ this.$.diffCursor.moveToPreviousChunk();
}
},
@@ -461,43 +453,27 @@
},
_openCursorFile: function() {
- var diff = this.$.cursor.getTargetDiffElement();
+ var diff = this.$.diffCursor.getTargetDiffElement();
page.show(this._computeDiffURL(diff.changeNum, diff.patchRange,
diff.path));
},
_openSelectedFile: function(opt_index) {
if (opt_index != null) {
- this.selectedIndex = opt_index;
+ this.$.fileCursor.setCursorAtIndex(opt_index);
}
page.show(this._computeDiffURL(this.changeNum, this.patchRange,
- this._files[this.selectedIndex].__path));
+ this._files[this.$.fileCursor.index].__path));
},
_addDraftAtTarget: function() {
- var diff = this.$.cursor.getTargetDiffElement();
- var target = this.$.cursor.getTargetLineElement();
+ var diff = this.$.diffCursor.getTargetDiffElement();
+ var target = this.$.diffCursor.getTargetLineElement();
if (diff && target) {
diff.addDraftAtLine(target);
}
},
- _scrollToSelectedFile: function() {
- var el = this.$$('.row[selected]');
- var top = 0;
- for (var node = el; node; node = node.offsetParent) {
- top += node.offsetTop;
- }
-
- // Don't scroll if it's already in view.
- if (top > window.pageYOffset &&
- top < window.pageYOffset + window.innerHeight - el.clientHeight) {
- return;
- }
-
- window.scrollTo(0, top - document.body.clientHeight / 2);
- },
-
_shouldHideChangeTotals: function(_patchChange) {
return _patchChange.inserted === 0 && _patchChange.deleted === 0;
},
@@ -576,8 +552,12 @@
var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
// Overwrite the cursor's list of diffs:
- this.$.cursor.splice.apply(this.$.cursor,
- ['diffs', 0, this.$.cursor.diffs.length].concat(diffElements));
+ this.$.diffCursor.splice.apply(this.$.diffCursor,
+ ['diffs', 0, this.$.diffCursor.diffs.length].concat(diffElements));
+
+ var files = Polymer.dom(this.root).querySelectorAll('.file-row');
+ this.$.fileCursor.stops = files;
+ if (this.$.fileCursor.index === -1) { this.$.fileCursor.moveToStart(); }
}.bind(this), 1);
},
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index b2c69a7..bc7c0c7 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -279,7 +279,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
});
test('toggle left diff via shortcut', function() {
@@ -298,24 +298,26 @@
test('keyboard shortcuts', function() {
flushAsynchronousOperations();
- var elementItems = Polymer.dom(element.root).querySelectorAll(
- '.row:not(.header)');
- assert.equal(elementItems.length, 5);
- assert.isTrue(elementItems[0].hasAttribute('selected'));
- assert.isFalse(elementItems[1].hasAttribute('selected'));
- assert.isFalse(elementItems[2].hasAttribute('selected'));
+
+ var items = Polymer.dom(element.root).querySelectorAll('.file-row');
+ element.$.fileCursor.stops = items;
+ element.$.fileCursor.setCursorAtIndex(0);
+ assert.equal(items.length, 3);
+ assert.isTrue(items[0].classList.contains('selected'));
+ assert.isFalse(items[1].classList.contains('selected'));
+ assert.isFalse(items[2].classList.contains('selected'));
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
- assert.equal(element.selectedIndex, 1);
+ assert.equal(element.$.fileCursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
var showStub = sandbox.stub(page, 'show');
- assert.equal(element.selectedIndex, 2);
+ assert.equal(element.$.fileCursor.index, 2);
MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
'Should navigate to /c/42/2/myfile.txt');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
- assert.equal(element.selectedIndex, 1);
+ assert.equal(element.$.fileCursor.index, 1);
MockInteractions.pressAndReleaseKeyOn(element, 79, null, 'o');
assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'),
'Should navigate to /c/42/2/file_added_in_rev2.txt');
@@ -323,20 +325,20 @@
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
- assert.equal(element.selectedIndex, 0);
-
- showStub.restore();
+ assert.equal(element.$.fileCursor.index, 0);
});
test('i key shows/hides selected inline diff', function() {
- element.selectedIndex = 0;
+ flushAsynchronousOperations();
+ element.$.fileCursor.stops = element.diffs;
+ element.$.fileCursor.setCursorAtIndex(0);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isFalse(element.diffs[0].hasAttribute('hidden'));
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isTrue(element.diffs[0].hasAttribute('hidden'));
- element.selectedIndex = 1;
+ element.$.fileCursor.setCursorAtIndex(1);
MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.isFalse(element.diffs[1].hasAttribute('hidden'));
@@ -416,7 +418,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
var fileRows =
@@ -499,7 +501,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
var fileRows =
Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
@@ -537,7 +539,7 @@
basePatchNum: 'PARENT',
patchNum: '2',
};
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
flushAsynchronousOperations();
var diffDisplay = element.diffs[0];
element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
@@ -580,7 +582,7 @@
patchNum: '2',
};
var computeSpy = sandbox.spy(element, '_fileListActionsVisible');
- element.selectedIndex = 0;
+ element.$.fileCursor.setCursorAtIndex(0);
element._numFilesShown = 1;
flush(function() {
assert.isTrue(computeSpy.lastCall.returnValue);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
index c8eea3c..2d0786a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
@@ -21,7 +21,7 @@
<template>
<gr-cursor-manager
id="cursorManager"
- scroll="[[_scrollBehavior]]"
+ scroll-behavior="[[_scrollBehavior]]"
cursor-target-class="target-row"
target="{{diffRow}}"></gr-cursor-manager>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index 7b3bc23..81f5186 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -59,7 +59,7 @@
* 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
* the viewport.
*/
- scroll: {
+ scrollBehavior: {
type: String,
value: ScrollBehavior.NEVER,
},
@@ -108,6 +108,10 @@
}
},
+ setCursorAtIndex: function(index) {
+ this.setCursor(this.stops[index]);
+ },
+
/**
* Move the cursor forward or backward by delta. Noop if moving past either
* end of the stop list.
@@ -194,7 +198,9 @@
},
_scrollToTarget: function() {
- if (!this.target || this.scroll === ScrollBehavior.NEVER) { return; }
+ if (!this.target || this.scrollBehavior === ScrollBehavior.NEVER) {
+ return;
+ }
// Calculate where the element is relative to the window.
var top = this.target.offsetTop;
@@ -204,7 +210,7 @@
top += offsetParent.offsetTop;
}
- if (this.scroll === ScrollBehavior.KEEP_VISIBLE &&
+ if (this.scrollBehavior === ScrollBehavior.KEEP_VISIBLE &&
top > window.pageYOffset &&
top < window.pageYOffset + window.innerHeight) { return; }