Revert "Replace gr-fixed-panel with position:sticky"

This reverts commit 368641eb4f.

Reason for revert: This introduced a bug which causes horizontal
scrolling on pages with a vertical scroll bar, due to 100vw hot taking
into account the width of the scroll bar. This is not an easy fix, so
reverting this change, at least for now.

Change-Id: Iff02ec60e02c85593dafdb3f9c5f99cf6cfac2ab
This commit is contained in:
Becky Siegel
2018-03-15 19:34:49 +00:00
parent 3591eca459
commit 377e289120
9 changed files with 511 additions and 172 deletions

View File

@@ -110,11 +110,6 @@ limitations under the License.
.changeStatus {
text-transform: capitalize;
}
.container {
/* Needed to restrict the change view from allowing horizontal
scrolling */
width: 100vw;
}
/* Strong specificity here is needed due to
https://github.com/Polymer/polymer/issues/2531 */
.container section.changeInfo {

View File

@@ -25,6 +25,7 @@ limitations under the License.
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-count-string-formatter/gr-count-string-formatter.html">
<link rel="import" href="../../shared/gr-dropdown-list/gr-dropdown-list.html">
<link rel="import" href="../../shared/gr-fixed-panel/gr-fixed-panel.html">
<link rel="import" href="../../shared/gr-icons/gr-icons.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">
@@ -54,6 +55,11 @@ limitations under the License.
border-bottom: 1px solid #eee;
}
}
gr-fixed-panel {
background-color: #fff;
border-bottom: 1px #eee solid;
z-index: 1;
}
header,
.subHeader {
align-items: center;
@@ -141,15 +147,6 @@ limitations under the License.
.separator.hide {
display: none;
}
.sticky {
background: #fff;
border-bottom: 1px solid #eee;
left: 0;
position: sticky;
top: 0;
width: 100vw;
z-index: 1;
}
gr-dropdown-list {
--trigger-style: {
text-transform: none;
@@ -208,136 +205,138 @@ limitations under the License.
}
}
</style>
<div>
<div class="sticky">
<header class$="[[_computeContainerClass(_editMode)]]">
<h3>
<a href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]">
[[_changeNum]]</a><span>:</span>
<span>[[_change.subject]]</span>
<span class="dash"></span>
<input id="reviewed"
class="reviewed hideOnEdit"
type="checkbox"
on-change="_handleReviewedChange"
hidden$="[[!_loggedIn]]" hidden>
<div class="jumpToFileContainer">
<gr-dropdown-list
id="dropdown"
value="[[_path]]"
on-value-change="_handleFileChange"
items="[[_formattedFiles]]"
initial-count="75">
</gr-dropdown-list>
</div>
</h3>
<div class="navLinks desktop">
<span class$="fileNum [[_computeFileNumClass(_fileNum, _formattedFiles)]]">
File [[_fileNum]] of [[_formattedFiles.length]]
<span class="separator"></span>
</span>
<a class="navLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, -1, 1)]]">
Prev</a>
<span class="separator"></span>
<a class="navLink"
href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]">
Up</a>
<span class="separator"></span>
<a class="navLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, 1, 1)]]">
Next</a>
<gr-fixed-panel
class$="[[_computeContainerClass(_editMode)]]"
floating-disabled="[[_panelFloatingDisabled]]"
keep-on-scroll
ready-for-measure="[[!_loading]]">
<header>
<h3>
<a href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]">
[[_changeNum]]</a><span>:</span>
<span>[[_change.subject]]</span>
<span class="dash"></span>
<input id="reviewed"
class="reviewed hideOnEdit"
type="checkbox"
on-change="_handleReviewedChange"
hidden$="[[!_loggedIn]]" hidden>
<div class="jumpToFileContainer">
<gr-dropdown-list
id="dropdown"
value="[[_path]]"
on-value-change="_handleFileChange"
items="[[_formattedFiles]]"
initial-count="75">
</gr-dropdown-list>
</div>
</header>
<div class="subHeader">
<div class="patchRangeLeft">
<gr-patch-range-select
id="rangeSelect"
change-num="[[_changeNum]]"
change-comments="[[_changeComments]]"
patch-num="[[_patchRange.patchNum]]"
base-patch-num="[[_patchRange.basePatchNum]]"
files-weblinks="[[_filesWeblinks]]"
available-patches="[[_allPatchSets]]"
revisions="[[_change.revisions]]"
revision-info="[[_revisionInfo]]"
on-patch-range-change="_handlePatchChange">
</gr-patch-range-select>
<span class="download desktop">
<span class="separator"></span>
<a
class="downloadLink"
download
href$="[[_computeDownloadLink(_changeNum, _patchRange, _path)]]">
Download
</a>
</span>
</h3>
<div class="navLinks desktop">
<span class$="fileNum [[_computeFileNumClass(_fileNum, _formattedFiles)]]">
File [[_fileNum]] of [[_formattedFiles.length]]
<span class="separator"></span>
</span>
<a class="navLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, -1, 1)]]">
Prev</a>
<span class="separator"></span>
<a class="navLink"
href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]">
Up</a>
<span class="separator"></span>
<a class="navLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, 1, 1)]]">
Next</a>
</div>
</header>
<div class="subHeader">
<div class="patchRangeLeft">
<gr-patch-range-select
id="rangeSelect"
change-num="[[_changeNum]]"
change-comments="[[_changeComments]]"
patch-num="[[_patchRange.patchNum]]"
base-patch-num="[[_patchRange.basePatchNum]]"
files-weblinks="[[_filesWeblinks]]"
available-patches="[[_allPatchSets]]"
revisions="[[_change.revisions]]"
revision-info="[[_revisionInfo]]"
on-patch-range-change="_handlePatchChange">
</gr-patch-range-select>
<span class="download desktop">
<span class="separator"></span>
<a
class="downloadLink"
download
href$="[[_computeDownloadLink(_changeNum, _patchRange, _path)]]">
Download
</a>
</span>
</div>
<div class="rightControls">
<span class$="blameLoader [[_computeBlameLoaderClass(_isImageDiff, _isBlameSupported)]]">
<gr-button
link
disabled="[[_isBlameLoading]]"
on-tap="_toggleBlame">[[_computeBlameToggleLabel(_isBlameLoaded, _isBlameLoading)]]</gr-button>
<span class="separator"></span>
</span>
<div class$="diffModeSelector [[_computeModeSelectHideClass(_isImageDiff)]]">
<span>Diff view:</span>
<gr-diff-mode-selector
id="modeSelect"
save-on-change="[[_loggedIn]]"
mode="{{changeViewState.diffMode}}"></gr-diff-mode-selector>
</div>
<div class="rightControls">
<span class$="blameLoader [[_computeBlameLoaderClass(_isImageDiff, _isBlameSupported)]]">
<span id="diffPrefsContainer"
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]" hidden>
<span class="preferences desktop">
<gr-button
link
disabled="[[_isBlameLoading]]"
on-tap="_toggleBlame">[[_computeBlameToggleLabel(_isBlameLoaded, _isBlameLoading)]]</gr-button>
<span class="separator"></span>
class="prefsButton"
has-tooltip
title="Diff preferences"
on-tap="_handlePrefsTap"><iron-icon icon="gr-icons:settings"></iron-icon></gr-button>
</span>
<div class$="diffModeSelector [[_computeModeSelectHideClass(_isImageDiff)]]">
<span>Diff view:</span>
<gr-diff-mode-selector
id="modeSelect"
save-on-change="[[_loggedIn]]"
mode="{{changeViewState.diffMode}}"></gr-diff-mode-selector>
</div>
<span id="diffPrefsContainer"
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]" hidden>
<span class="preferences desktop">
<gr-button
link
class="prefsButton"
has-tooltip
title="Diff preferences"
on-tap="_handlePrefsTap"><iron-icon icon="gr-icons:settings"></iron-icon></gr-button>
</span>
</span>
<gr-endpoint-decorator name="annotation-toggler">
<span hidden id="annotation-span">
<label for="annotation-checkbox" id="annotation-label"></label>
<input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
</span>
<gr-endpoint-decorator name="annotation-toggler">
<span hidden id="annotation-span">
<label for="annotation-checkbox" id="annotation-label"></label>
<input is="iron-input" type="checkbox" id="annotation-checkbox" disabled>
</span>
</gr-endpoint-decorator>
</div>
</div>
<div class="fileNav mobile">
<a class="mobileNavLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, -1, 1)]]">
&lt;</a>
<div class="fullFileName mobile">[[computeDisplayPath(_path)]]
</div>
<a class="mobileNavLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, 1, 1)]]">
&gt;</a>
</gr-endpoint-decorator>
</div>
</div>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
<gr-diff
id="diff"
hidden
hidden$="[[_loading]]"
class$="[[_computeDiffClass(_panelFloatingDisabled)]]"
is-image-diff="{{_isImageDiff}}"
files-weblinks="{{_filesWeblinks}}"
change-num="[[_changeNum]]"
commit-range="[[_commitRange]]"
patch-range="[[_patchRange]]"
path="[[_path]]"
prefs="[[_prefs]]"
project-config="[[_projectConfig]]"
project-name="[[_change.project]]"
view-mode="[[_diffMode]]"
is-blame-loaded="{{_isBlameLoaded}}"
on-line-selected="_onLineSelected">
</gr-diff>
</div>
<div class="fileNav mobile">
<a class="mobileNavLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, -1, 1)]]">
&lt;</a>
<div class="fullFileName mobile">[[computeDisplayPath(_path)]]
</div>
<a class="mobileNavLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, 1, 1)]]">
&gt;</a>
</div>
</gr-fixed-panel>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
<gr-diff
id="diff"
hidden
hidden$="[[_loading]]"
class$="[[_computeDiffClass(_panelFloatingDisabled)]]"
is-image-diff="{{_isImageDiff}}"
files-weblinks="{{_filesWeblinks}}"
change-num="[[_changeNum]]"
commit-range="[[_commitRange]]"
patch-range="[[_patchRange]]"
path="[[_path]]"
prefs="[[_prefs]]"
project-config="[[_projectConfig]]"
project-name="[[_change.project]]"
view-mode="[[_diffMode]]"
is-blame-loaded="{{_isBlameLoaded}}"
on-line-selected="_onLineSelected">
</gr-diff>
<gr-diff-preferences
id="diffPreferences"
prefs="{{_prefs}}"

View File

@@ -48,7 +48,6 @@ limitations under the License.
display: none;
}
.diffContainer {
border-bottom: 1px solid #eee;
display: flex;
font: var(--font-size-small) var(--monospace-font-family);
@apply --diff-container-styles;

View File

@@ -24,6 +24,7 @@ limitations under the License.
<link rel="import" href="../../plugins/gr-endpoint-param/gr-endpoint-param.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
<link rel="import" href="../../shared/gr-fixed-panel/gr-fixed-panel.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-default-editor/gr-default-editor.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -34,13 +35,9 @@ limitations under the License.
:host {
background-color: var(--view-background-color);
}
.sticky {
gr-fixed-panel {
background-color: #ebf5fb;
border-bottom: 1px #ddd solid;
position: -webkit-sticky;
position: sticky;
top: 0;
width: 100vw;
z-index: 1;
}
header,
@@ -88,7 +85,7 @@ limitations under the License.
}
}
</style>
<div class="sticky">
<gr-fixed-panel keep-on-scroll>
<header>
<span class="controlGroup">
<span>Edit mode</span>
@@ -112,7 +109,7 @@ limitations under the License.
on-tap="_saveEdit">Save</gr-button>
</span>
</header>
</div>
</gr-fixed-panel>
<div class="textareaWrapper">
<gr-endpoint-decorator id="editorEndpoint" name="editor">
<gr-endpoint-param name="fileContent" value="[[_newContent]]"></gr-endpoint-param>

View File

@@ -54,6 +54,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-fixed-panel/gr-fixed-panel.html">
<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
<script src="../scripts/util.js"></script>
@@ -62,45 +63,35 @@ limitations under the License.
<template>
<style include="shared-styles">
:host {
/* This needs to be inline-flex so that the app container inherits the
width of its children. From here, can use position: sticky to display
items that we want to be 'floating' like the header and footer, with
a combination of: position: sticky, width=100vw, and left: 0. */
display: inline-flex;
display: flex;
min-height: 100%;
flex-direction: column;
}
gr-fixed-panel {
/**
* 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);
}
gr-main-header {
background-color: var(--header-background-color);
padding: 0 var(--default-horizontal-margin);
border-bottom: 1px solid #ddd;
}
gr-main-header.shadow {
/* Make it obvious for shadow dom testing */
border-bottom: 1px solid pink;
}
gr-main-header {
background-color: var(--header-background-color);
border-bottom: 1px solid #ddd;
/* The combination of left, width, position allow this to be
floating. */
left: 0;
padding: 0 var(--default-horizontal-margin);
position: sticky;
width: 100vw;
/* Needed for the menu dropdowns to display on top of the sticky
headers with z-index of 1.*/
z-index: 2;
}
footer {
/* The combination of left, width, position allow this to be
floating. */
background-color: var(--footer-background-color);
display: flex;
left: 0;
width: 100vw;
justify-content: space-between;
padding: .5rem var(--default-horizontal-margin);
position: sticky;
z-index: 100;
}
main {
@@ -139,11 +130,13 @@ limitations under the License.
color: #b71c1c;
}
</style>
<gr-main-header
id="mainHeader"
search-query="{{params.query}}"
class$="[[_computeShadowClass(_isShadowDom)]]">
</gr-main-header>
<gr-fixed-panel id="header">
<gr-main-header
id="mainHeader"
search-query="{{params.query}}"
class$="[[_computeShadowClass(_isShadowDom)]]">
</gr-main-header>
</gr-fixed-panel>
<main>
<template is="dom-if" if="[[_showChangeListView]]" restamp="true">
<gr-change-list-view

View File

@@ -182,6 +182,7 @@
if (this.params.justRegistered) {
this.$.registration.open();
}
this.$.header.unfloat();
},
_computeShowGwtUiLink(config) {

View File

@@ -0,0 +1,50 @@
<!--
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">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-fixed-panel">
<template>
<style include="shared-styles">
:host {
display: block;
min-height: var(--header-height);
position: relative;
}
header {
background: inherit;
border: inherit;
display: inline;
height: inherit;
}
.floating {
left: 0;
position: fixed;
width: 100%;
will-change: top;
}
.fixedAtTop {
border-bottom: 1px solid #a4a4a4;
box-shadow: 0 4px 4px rgba(0,0,0,0.1);
}
</style>
<header id="header" class$="[[_computeHeaderClass(_headerFloating, _topLast)]]">
<slot></slot>
</header>
</template>
<script src="gr-fixed-panel.js"></script>
</dom-module>

View File

@@ -0,0 +1,194 @@
// 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-fixed-panel',
properties: {
floatingDisabled: Boolean,
readyForMeasure: {
type: Boolean,
observer: '_readyForMeasureObserver',
},
keepOnScroll: {
type: Boolean,
value: false,
},
_isMeasured: {
type: Boolean,
value: false,
},
/**
* Initial offset from the top of the document, in pixels.
*/
_topInitial: Number,
/**
* Current offset from the top of the window, in pixels.
*/
_topLast: Number,
_headerHeight: Number,
_headerFloating: {
type: Boolean,
value: false,
},
_observer: {
type: Object,
value: null,
},
_webComponentsReady: Boolean,
},
attached() {
if (this.floatingDisabled) {
return;
}
// Enable content measure unless blocked by param.
if (this.readyForMeasure !== false) {
this.readyForMeasure = true;
}
this.listen(window, 'resize', 'update');
this.listen(window, 'scroll', '_updateOnScroll');
this._observer = new MutationObserver(this.update.bind(this));
this._observer.observe(this.$.header, {childList: true, subtree: true});
},
detached() {
this.unlisten(window, 'scroll', '_updateOnScroll');
this.unlisten(window, 'resize', 'update');
if (this._observer) {
this._observer.disconnect();
}
},
_readyForMeasureObserver(readyForMeasure) {
if (readyForMeasure) {
this.update();
}
},
_computeHeaderClass(headerFloating, topLast) {
const fixedAtTop = this.keepOnScroll && topLast === 0;
return [
headerFloating ? 'floating' : '',
fixedAtTop ? 'fixedAtTop' : '',
].join(' ');
},
_getScrollY() {
return window.scrollY;
},
unfloat() {
if (this.floatingDisabled) {
return;
}
this.$.header.style.top = '';
this._headerFloating = false;
this.updateStyles({'--header-height': ''});
},
update() {
this.debounce('update', () => {
this._updateDebounced();
}, 100);
},
_updateOnScroll() {
this.debounce('update', () => {
this._updateDebounced();
});
},
_updateDebounced() {
if (this.floatingDisabled) {
return;
}
this._isMeasured = false;
this._maybeFloatHeader();
this._reposition();
},
_reposition() {
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._topLast = top;
this._headerHeight = rect.height;
this._topInitial =
this.getBoundingClientRect().top + document.body.scrollTop;
this._isMeasured = true;
},
_isFloatingNeeded() {
return this.keepOnScroll ||
document.body.scrollWidth > document.body.clientWidth;
},
_maybeFloatHeader() {
if (!this._isFloatingNeeded()) {
return;
}
this._measure();
if (this._isMeasured) {
this._floatHeader();
}
},
_floatHeader() {
this.updateStyles({'--header-height': this._headerHeight + 'px'});
this._headerFloating = true;
},
});
})();

View File

@@ -0,0 +1,111 @@
<!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-fixed-panel</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="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-fixed-panel.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-fixed-panel>
<div style="height: 100px"></div>
</gr-fixed-panel>
</template>
</test-fixture>
<script>
suite('gr-fixed-panel', () => {
let element;
let sandbox;
setup(() => {
sandbox = sinon.sandbox.create();
element = fixture('basic');
element.readyForMeasure = true;
});
teardown(() => {
sandbox.restore();
});
test('can be disabled with floatingDisabled', () => {
element.floatingDisabled = true;
sandbox.stub(element, '_reposition');
window.dispatchEvent(new CustomEvent('resize'));
element.flushDebouncer('update');
assert.isFalse(element._reposition.called);
});
test('header is the height of the content', () => {
assert.equal(element.getBoundingClientRect().height, 100);
});
test('scroll triggers _reposition', () => {
sandbox.stub(element, '_reposition');
window.dispatchEvent(new CustomEvent('scroll'));
element.flushDebouncer('update');
assert.isTrue(element._reposition.called);
});
suite('_reposition', () => {
const getHeaderTop = function() {
return element.$.header.style.top;
};
const emulateScrollY = function(distance) {
element._getScrollY.returns(distance);
element._updateDebounced();
element.flushDebouncer('scroll');
};
setup(() => {
element._headerTopInitial = 10;
sandbox.stub(element, '_getScrollY').returns(0);
});
test('scrolls header along with document', () => {
emulateScrollY(20);
assert.equal(getHeaderTop(), '-12px');
});
test('does not stick to the top by default', () => {
emulateScrollY(150);
assert.equal(getHeaderTop(), '-100px');
});
test('sticks to the top if enabled', () => {
element.keepOnScroll = true;
emulateScrollY(120);
assert.equal(getHeaderTop(), '0px');
});
test('drops a shadow when fixed to the top', () => {
element.keepOnScroll = true;
emulateScrollY(5);
assert.isFalse(element.$.header.classList.contains('fixedAtTop'));
emulateScrollY(120);
assert.isTrue(element.$.header.classList.contains('fixedAtTop'));
});
});
});
</script>