blob: 20fd84404b5e775ccaef5e7270447b5719bd104e [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @typedef {{about: boolean, settings: boolean}}
*/
let MainPageVisibility;
/**
* @fileoverview
* 'settings-main' displays the selected settings page.
*/
Polymer({
is: 'settings-main',
behaviors: [settings.RouteObserverBehavior],
properties: {
/**
* Preferences state.
*/
prefs: {
type: Object,
notify: true,
},
advancedToggleExpanded: {
type: Boolean,
notify: true,
},
/** @private */
overscroll_: {
type: Number,
observer: 'overscrollChanged_',
},
/**
* Controls which main pages are displayed via dom-ifs, based on the current
* route.
* @private {!MainPageVisibility}
*/
showPages_: {
type: Object,
value: function() {
return {about: false, settings: false};
},
},
/**
* Whether a search operation is in progress or previous search results are
* being displayed.
* @private {boolean}
*/
inSearchMode_: {
type: Boolean,
value: false,
},
/** @private */
showNoResultsFound_: {
type: Boolean,
value: false,
},
toolbarSpinnerActive: {
type: Boolean,
value: false,
notify: true,
},
/**
* Dictionary defining page visibility.
* @type {!GuestModePageVisibility}
*/
pageVisibility: Object,
showAndroidApps: Boolean,
showMultidevice: Boolean,
havePlayStoreApp: Boolean,
},
/** @override */
attached: function() {
this.listen(this, 'freeze-scroll', 'onFreezeScroll_');
this.listen(this, 'lazy-loaded', 'onLazyLoaded_');
},
/** @private */
onLazyLoaded_: function() {
Polymer.dom.flush();
this.updateOverscrollForPage_();
},
/** @override */
detached: function() {
this.unlisten(this, 'freeze-scroll', 'onFreezeScroll_');
},
/** @private */
overscrollChanged_: function() {
if (!this.overscroll_ && this.boundScroll_) {
this.offsetParent.removeEventListener('scroll', this.boundScroll_);
window.removeEventListener('resize', this.boundScroll_);
this.boundScroll_ = null;
} else if (this.overscroll_ && !this.boundScroll_) {
this.boundScroll_ = () => {
if (!this.ignoreScroll_)
this.setOverscroll_(0);
};
this.offsetParent.addEventListener('scroll', this.boundScroll_);
window.addEventListener('resize', this.boundScroll_);
}
},
/**
* Sets the overscroll padding. Never forces a scroll, i.e., always leaves
* any currently visible overflow as-is.
* @param {number=} opt_minHeight The minimum overscroll height needed.
* @private
*/
setOverscroll_: function(opt_minHeight) {
const scroller = this.offsetParent;
if (!scroller)
return;
const overscroll = this.$.overscroll;
const visibleBottom = scroller.scrollTop + scroller.clientHeight;
const overscrollBottom = overscroll.offsetTop + overscroll.scrollHeight;
// How much of the overscroll is visible (may be negative).
const visibleOverscroll =
overscroll.scrollHeight - (overscrollBottom - visibleBottom);
this.overscroll_ =
Math.max(opt_minHeight || 0, Math.ceil(visibleOverscroll));
},
/**
* Enables or disables user scrolling, via overscroll: hidden. Room for the
* hidden scrollbar is added to prevent the page width from changing back and
* forth. Also freezes the overscroll height.
* @param {!Event} e |e.detail| is true to freeze, false to unfreeze.
* @private
*/
onFreezeScroll_: function(e) {
if (e.detail) {
// Update the overscroll and ignore scroll events.
this.setOverscroll_(this.overscrollHeight_());
this.ignoreScroll_ = true;
// Prevent scrolling the container.
const scrollerWidth = this.offsetParent.clientWidth;
this.offsetParent.style.overflow = 'hidden';
const scrollbarWidth = this.offsetParent.clientWidth - scrollerWidth;
this.offsetParent.style.width = 'calc(100% - ' + scrollbarWidth + 'px)';
} else {
this.ignoreScroll_ = false;
this.offsetParent.style.overflow = '';
this.offsetParent.style.width = '';
}
},
/** @param {!settings.Route} newRoute */
currentRouteChanged: function(newRoute) {
this.updatePagesShown_();
},
/** @private */
onSubpageExpand_: function() {
this.updatePagesShown_();
},
/**
* Updates the hidden state of the about and settings pages based on the
* current route.
* @private
*/
updatePagesShown_: function() {
const inAbout = settings.routes.ABOUT.contains(settings.getCurrentRoute());
this.showPages_ = {about: inAbout, settings: !inAbout};
// Calculate and set the overflow padding.
this.updateOverscrollForPage_();
// Wait for any other changes, then calculate the overflow padding again.
setTimeout(() => {
// Ensure any dom-if reflects the current properties.
Polymer.dom.flush();
this.updateOverscrollForPage_();
});
},
/**
* Calculates the necessary overscroll and sets the overscroll to that value
* (at minimum). For the About page, this just zeroes the overscroll.
* @private
*/
updateOverscrollForPage_: function() {
if (this.showPages_.about || this.inSearchMode_) {
// Set overscroll directly to remove any existing overscroll that
// setOverscroll_ would otherwise preserve.
this.overscroll_ = 0;
return;
}
this.setOverscroll_(this.overscrollHeight_());
},
/**
* Return the height that the overscroll padding should be set to.
* This is used to determine how much padding to apply to the end of the
* content so that the last element may align with the top of the content
* area.
* @return {number}
* @private
*/
overscrollHeight_: function() {
const route = settings.getCurrentRoute();
if (!route.section || route.isSubpage() || this.showPages_.about)
return 0;
const page = this.getPage_(route);
const section = page && page.getSection(route.section);
if (!section || !section.offsetParent)
return 0;
// Find the distance from the section's top to the overscroll.
const sectionTop = section.offsetParent.offsetTop + section.offsetTop;
const distance = this.$.overscroll.offsetTop - sectionTop;
return Math.max(0, this.offsetParent.clientHeight - distance);
},
/**
* Returns the root page (if it exists) for a route.
* @param {!settings.Route} route
* @return {(?SettingsAboutPageElement|?SettingsBasicPageElement)}
*/
getPage_: function(route) {
if (settings.routes.ABOUT.contains(route)) {
return /** @type {?SettingsAboutPageElement} */ (
this.$$('settings-about-page'));
}
if (settings.routes.BASIC.contains(route) ||
(settings.routes.ADVANCED &&
settings.routes.ADVANCED.contains(route))) {
return /** @type {?SettingsBasicPageElement} */ (
this.$$('settings-basic-page'));
}
assertNotReached();
},
/**
* @param {string} query
* @return {!Promise} A promise indicating that searching finished.
*/
searchContents: function(query) {
// Trigger rendering of the basic and advanced pages and search once ready.
this.inSearchMode_ = true;
this.toolbarSpinnerActive = true;
return new Promise((resolve, reject) => {
setTimeout(() => {
const whenSearchDone =
assert(this.getPage_(settings.routes.BASIC)).searchContents(query);
whenSearchDone.then(result => {
resolve();
if (result.canceled) {
// Nothing to do here. A previous search request was canceled
// because a new search request was issued with a different query
// before the previous completed.
return;
}
this.toolbarSpinnerActive = false;
this.inSearchMode_ = !result.wasClearSearch;
this.showNoResultsFound_ =
this.inSearchMode_ && !result.didFindMatches;
if (this.inSearchMode_) {
Polymer.IronA11yAnnouncer.requestAvailability();
this.fire('iron-announce', {
text: this.showNoResultsFound_ ?
loadTimeData.getString('searchNoResults') :
loadTimeData.getStringF('searchResults', query)
});
}
});
}, 0);
});
},
});