Merge "Put horizontal scrolling on screen, take 3"
This commit is contained in:
@@ -149,6 +149,7 @@ limitations under the License.
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
|
||||
display: block;
|
||||
margin: .25em 0 1em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.patchSetSelect {
|
||||
max-width: 8em;
|
||||
|
||||
@@ -14,18 +14,19 @@ 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="../../../behaviors/base-url-behavior/base-url-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
|
||||
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
|
||||
<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../shared/gr-button/gr-button.html">
|
||||
<link rel="import" href="../../shared/gr-header/gr-header.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">
|
||||
<link rel="import" href="../gr-diff/gr-diff.html">
|
||||
<link rel="import" href="../gr-diff-cursor/gr-diff-cursor.html">
|
||||
<link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
|
||||
<link rel="import" href="../gr-diff/gr-diff.html">
|
||||
<link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
|
||||
|
||||
<dom-module id="gr-diff-view">
|
||||
@@ -33,7 +34,14 @@ limitations under the License.
|
||||
<style>
|
||||
:host {
|
||||
background-color: var(--view-background-color);
|
||||
display: block;
|
||||
}
|
||||
gr-diff {
|
||||
border: none;
|
||||
}
|
||||
gr-header {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px #eee solid;
|
||||
z-index: 1;
|
||||
}
|
||||
header,
|
||||
.subHeader {
|
||||
@@ -177,66 +185,67 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<header>
|
||||
<h3>
|
||||
<a href$="[[_computeChangePath(_changeNum, _patchRange.*, _change.revisions)]]">
|
||||
[[_changeNum]]</a><span>:</span>
|
||||
<span>[[_change.subject]]</span>
|
||||
<span class="dash">—</span>
|
||||
<input id="reviewed"
|
||||
class="reviewed"
|
||||
type="checkbox"
|
||||
on-change="_handleReviewedChange"
|
||||
hidden$="[[!_loggedIn]]" hidden>
|
||||
<div class="jumpToFileContainer desktop">
|
||||
<gr-button link class="dropdown-trigger" id="trigger" on-tap="_showDropdownTapHandler">
|
||||
<span>[[_computeFileDisplayName(_path)]]</span>
|
||||
<span class="downArrow">▼</span>
|
||||
</gr-button>
|
||||
<!-- *-align="" to disable iron-dropdown's element positioning. -->
|
||||
<iron-dropdown id="dropdown"
|
||||
allow-outside-scroll
|
||||
vertical-align=""
|
||||
horizontal-align="">
|
||||
<div class="dropdown-content">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_fileList]]"
|
||||
as="path"
|
||||
initial-count="75">
|
||||
<a href$="[[_computeDiffURL(_changeNum, _patchRange.*, path)]]"
|
||||
selected$="[[_computeFileSelected(path, _path)]]"
|
||||
data-key-nav$="[[_computeKeyNav(path, _path, _fileList)]]"
|
||||
on-tap="_handleFileTap">[[_computeFileDisplayName(path)]]</a>
|
||||
<gr-header
|
||||
keep-on-scroll
|
||||
ready-for-measure="[[!_loading]]">
|
||||
<header>
|
||||
<h3>
|
||||
<a href$="[[_computeChangePath(_changeNum, _patchRange.*, _change.revisions)]]">
|
||||
[[_changeNum]]</a><span>:</span>
|
||||
<span>[[_change.subject]]</span>
|
||||
<span class="dash">—</span>
|
||||
<input id="reviewed"
|
||||
class="reviewed"
|
||||
type="checkbox"
|
||||
on-change="_handleReviewedChange"
|
||||
hidden$="[[!_loggedIn]]" hidden>
|
||||
<div class="jumpToFileContainer desktop">
|
||||
<gr-button link class="dropdown-trigger" id="trigger" on-tap="_showDropdownTapHandler">
|
||||
<span>[[_computeFileDisplayName(_path)]]</span>
|
||||
<span class="downArrow">▼</span>
|
||||
</gr-button>
|
||||
<!-- *-align="" to disable iron-dropdown's element positioning. -->
|
||||
<iron-dropdown id="dropdown"
|
||||
allow-outside-scroll
|
||||
vertical-align=""
|
||||
horizontal-align="">
|
||||
<div class="dropdown-content">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_fileList]]"
|
||||
as="path"
|
||||
initial-count="75">
|
||||
<a href$="[[_computeDiffURL(_changeNum, _patchRange.*, path)]]"
|
||||
selected$="[[_computeFileSelected(path, _path)]]"
|
||||
data-key-nav$="[[_computeKeyNav(path, _path, _fileList)]]"
|
||||
on-tap="_handleFileTap">[[_computeFileDisplayName(path)]]</a>
|
||||
</template>
|
||||
</div>
|
||||
</iron-dropdown>
|
||||
</div>
|
||||
<div class="mobileJumpToFileContainer mobile">
|
||||
<select on-change="_handleMobileSelectChange">
|
||||
<template is="dom-repeat" items="[[_fileList]]" as="path">
|
||||
<option
|
||||
value$="[[path]]"
|
||||
selected$="[[_computeFileSelected(path, _path)]]">
|
||||
[[_computeTruncatedFileDisplayName(path)]]
|
||||
</option>
|
||||
</template>
|
||||
</div>
|
||||
</iron-dropdown>
|
||||
</select>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="navLinks desktop">
|
||||
<a class="navLink"
|
||||
href$="[[_computeNavLinkURL(_path, _fileList, -1, 1)]]">Prev</a>
|
||||
/
|
||||
<a class="navLink"
|
||||
href$="[[_computeUpURL(_changeNum, _patchRange, _change, _change.revisions)]]">Up</a>
|
||||
/
|
||||
<a class="navLink"
|
||||
href$="[[_computeNavLinkURL(_path, _fileList, 1, 1)]]">Next</a>
|
||||
</div>
|
||||
<div class="mobileJumpToFileContainer mobile">
|
||||
<select on-change="_handleMobileSelectChange">
|
||||
<template is="dom-repeat" items="[[_fileList]]" as="path">
|
||||
<option
|
||||
value$="[[path]]"
|
||||
selected$="[[_computeFileSelected(path, _path)]]">
|
||||
[[_computeTruncatedFileDisplayName(path)]]
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="navLinks desktop">
|
||||
<a class="navLink"
|
||||
href$="[[_computeNavLinkURL(_path, _fileList, -1, 1)]]">Prev</a>
|
||||
/
|
||||
<a class="navLink"
|
||||
href$="[[_computeUpURL(_changeNum, _patchRange, _change, _change.revisions)]]">Up</a>
|
||||
/
|
||||
<a class="navLink"
|
||||
href$="[[_computeNavLinkURL(_path, _fileList, 1, 1)]]">Next</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
|
||||
<div hidden$="[[_loading]]" hidden>
|
||||
</header>
|
||||
<div class="subHeader">
|
||||
<div class="patchRangeLeft">
|
||||
<gr-patch-range-select
|
||||
@@ -276,6 +285,9 @@ limitations under the License.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</gr-header>
|
||||
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
|
||||
<div hidden hidden$="[[_loading]]">
|
||||
<gr-diff-preferences
|
||||
id="diffPreferences"
|
||||
prefs="{{_prefs}}"
|
||||
@@ -290,6 +302,8 @@ limitations under the License.
|
||||
</div>
|
||||
<gr-diff
|
||||
id="diff"
|
||||
hidden
|
||||
hidden$="[[_loading]]"
|
||||
is-image-diff="{{_isImageDiff}}"
|
||||
files-weblinks="{{_filesWeblinks}}"
|
||||
change-num="[[_changeNum]]"
|
||||
|
||||
@@ -45,7 +45,6 @@ limitations under the License.
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
font: 12px var(--monospace-font-family);
|
||||
overflow-x: auto;
|
||||
will-change: transform;
|
||||
}
|
||||
.diffContainer.hiddenscroll {
|
||||
|
||||
@@ -34,6 +34,7 @@ limitations under the License.
|
||||
<link rel="import" href="./settings/gr-cla-view/gr-cla-view.html">
|
||||
<link rel="import" href="./settings/gr-registration-dialog/gr-registration-dialog.html">
|
||||
<link rel="import" href="./settings/gr-settings-view/gr-settings-view.html">
|
||||
<link rel="import" href="./shared/gr-header/gr-header.html">
|
||||
<link rel="import" href="./shared/gr-overlay/gr-overlay.html">
|
||||
<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
|
||||
@@ -47,6 +48,13 @@ limitations under the License.
|
||||
min-height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
gr-header {
|
||||
/**
|
||||
* This one should be greater that the z-index in gr-diff-view
|
||||
* because gr-main-header contains overlay.
|
||||
*/
|
||||
z-index: 10;
|
||||
}
|
||||
gr-main-header,
|
||||
footer {
|
||||
color: var(--primary-text-color);
|
||||
@@ -93,8 +101,10 @@ limitations under the License.
|
||||
color: #b71c1c;
|
||||
}
|
||||
</style>
|
||||
<gr-main-header id="mainHeader" search-query="{{params.query}}">
|
||||
</gr-main-header>
|
||||
<gr-header>
|
||||
<gr-main-header id="mainHeader" search-query="{{params.query}}">
|
||||
</gr-main-header>
|
||||
</gr-header>
|
||||
<main>
|
||||
<template is="dom-if" if="[[_showChangeListView]]" restamp="true">
|
||||
<gr-change-list-view
|
||||
|
||||
46
polygerrit-ui/app/elements/shared/gr-header/gr-header.html
Normal file
46
polygerrit-ui/app/elements/shared/gr-header/gr-header.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!--
|
||||
Copyright (C) 2017 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">
|
||||
|
||||
<dom-module id="gr-header">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
height: var(--header-height);
|
||||
position: relative;
|
||||
}
|
||||
header {
|
||||
background: inherit;
|
||||
border: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
.floating {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
will-change: top;
|
||||
}
|
||||
</style>
|
||||
<header id="header" class$="[[_computeHeaderClass(_headerFloating)]]">
|
||||
<div class="contentWrapper">
|
||||
<content></content>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
<script src="gr-header.js"></script>
|
||||
</dom-module>
|
||||
150
polygerrit-ui/app/elements/shared/gr-header/gr-header.js
Normal file
150
polygerrit-ui/app/elements/shared/gr-header/gr-header.js
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (C) 2017 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';
|
||||
|
||||
Polymer({
|
||||
is: 'gr-header',
|
||||
|
||||
properties: {
|
||||
readyForMeasure: {
|
||||
type: Boolean,
|
||||
observer: '_maybeFloatHeader',
|
||||
},
|
||||
keepOnScroll: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_isMeasured: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
_topInitial: Number,
|
||||
_topLast: Number,
|
||||
_headerHeight: Number,
|
||||
_headerFloating: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
|
||||
attached() {
|
||||
// Enable content measure unless blocked by param.
|
||||
this.async(() => {
|
||||
if (this.readyForMeasure !== false) {
|
||||
this.readyForMeasure = true;
|
||||
}
|
||||
}, 1);
|
||||
this.listen(window, 'scroll', '_handleScroll');
|
||||
this.listen(window, 'resize', '_handleResize');
|
||||
},
|
||||
|
||||
detached() {
|
||||
this.unlisten(window, 'scroll', '_handleScroll');
|
||||
this.unlisten(window, 'resize', '_handleResize');
|
||||
},
|
||||
|
||||
_computeHeaderClass(headerFloating) {
|
||||
return headerFloating ? 'floating' : '';
|
||||
},
|
||||
|
||||
_getScrollY() {
|
||||
return window.scrollY;
|
||||
},
|
||||
|
||||
_handleResize() {
|
||||
if (this._headerFloating) {
|
||||
this._unfloatHeader();
|
||||
}
|
||||
this.debounce('resize', () => {
|
||||
this._maybeFloatHeader();
|
||||
this._handleScrollDebounced();
|
||||
}, 100);
|
||||
},
|
||||
|
||||
_handleScroll() {
|
||||
this._maybeFloatHeader();
|
||||
this.debounce('scroll', this._handleScrollDebounced);
|
||||
},
|
||||
|
||||
_handleScrollDebounced() {
|
||||
if (!this._headerFloating) {
|
||||
return;
|
||||
}
|
||||
const header = this.$.header;
|
||||
const scrollY = this._topInitial - this._getScrollY();
|
||||
let newTop;
|
||||
if (this.keepOnScroll) {
|
||||
if (scrollY > 0) {
|
||||
// Reposition to imitate natural scrolling.
|
||||
newTop = scrollY;
|
||||
} else {
|
||||
newTop = 0;
|
||||
}
|
||||
} else if (scrollY > -this._headerHeight ||
|
||||
this._topLast < -this._headerHeight) {
|
||||
// Allow to scroll away, but ignore when far behind the edge.
|
||||
newTop = scrollY;
|
||||
} else {
|
||||
newTop = -this._headerHeight;
|
||||
}
|
||||
if (this._topLast !== newTop) {
|
||||
if (newTop === undefined) {
|
||||
header.style.top = '';
|
||||
} else {
|
||||
header.style.top = newTop + 'px';
|
||||
}
|
||||
this._topLast = newTop;
|
||||
}
|
||||
},
|
||||
|
||||
_measure() {
|
||||
if (this._isMeasured) {
|
||||
return; // Already measured.
|
||||
}
|
||||
const rect = this.$.header.getBoundingClientRect();
|
||||
if (rect.height === 0 && rect.width === 0) {
|
||||
return; // Not ready for measurement yet.
|
||||
}
|
||||
const top = document.body.scrollTop + rect.top;
|
||||
this._topInitial = top;
|
||||
this._topLast = top;
|
||||
this._isMeasured = true;
|
||||
},
|
||||
|
||||
_maybeFloatHeader() {
|
||||
if (!this.readyForMeasure) {
|
||||
return;
|
||||
}
|
||||
this._measure();
|
||||
if (!this._headerFloating && this._isMeasured) {
|
||||
this._floatHeader();
|
||||
}
|
||||
},
|
||||
|
||||
_unfloatHeader() {
|
||||
this.customStyle['--header-height'] = '';
|
||||
this._headerFloating = false;
|
||||
this.updateStyles();
|
||||
},
|
||||
|
||||
_floatHeader() {
|
||||
const rect = this.$.header.getBoundingClientRect();
|
||||
this._headerHeight = rect.height;
|
||||
this.customStyle['--header-height'] = rect.height + 'px';
|
||||
this._headerFloating = true;
|
||||
this.updateStyles();
|
||||
},
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (C) 2017 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.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-header</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<script src="../../../bower_components/web-component-tester/browser.js"></script>
|
||||
|
||||
<link rel="import" href="gr-header.html">
|
||||
|
||||
<script>void(0);</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-header>
|
||||
<div style="height: 100px"></div>
|
||||
</gr-header>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-header', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
|
||||
setup(() => {
|
||||
element = fixture('basic');
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('header is the height of the content', () => {
|
||||
assert.equal(element.getBoundingClientRect().height, 100);
|
||||
});
|
||||
|
||||
test('scroll triggers _handleScroll', () => {
|
||||
sandbox.stub(element, '_handleScroll');
|
||||
window.dispatchEvent(new CustomEvent('scroll'));
|
||||
assert.isTrue(element._handleScroll.called);
|
||||
});
|
||||
|
||||
suite('_handleScroll', () => {
|
||||
const getHeaderTop = function() {
|
||||
return element.$.header.style.top;
|
||||
};
|
||||
|
||||
const emulateScrollY = function(distance) {
|
||||
element._getScrollY.returns(distance);
|
||||
element._handleScroll();
|
||||
element.flushDebouncer('scroll');
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
element._headerTopInitial = 10;
|
||||
sandbox.stub(element, '_getScrollY').returns(0);
|
||||
});
|
||||
|
||||
test('is debounced', () => {
|
||||
sandbox.stub(element, 'debounce');
|
||||
element._handleScroll();
|
||||
assert.isTrue(element.debounce.called);
|
||||
});
|
||||
|
||||
test('scrolls header along with document', () => {
|
||||
emulateScrollY(20);
|
||||
assert.equal(getHeaderTop(), '-10px');
|
||||
});
|
||||
|
||||
test('does not stick to the top by default', () => {
|
||||
emulateScrollY(150);
|
||||
assert.equal(getHeaderTop(), '-140px');
|
||||
});
|
||||
|
||||
test('sticks to the top if enabled', () => {
|
||||
element.keepOnScroll = true;
|
||||
emulateScrollY(120);
|
||||
assert.equal(getHeaderTop(), '0px');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user