| Index: chrome/browser/resources/settings/controls/night_light_slider.js
|
| diff --git a/chrome/browser/resources/settings/controls/night_light_slider.js b/chrome/browser/resources/settings/controls/night_light_slider.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..24abd83f5ec3565601287f7edddf480109444ebf
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/settings/controls/night_light_slider.js
|
| @@ -0,0 +1,453 @@
|
| +// Copyright 2017 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.
|
| +
|
| +/**
|
| + * @fileoverview
|
| + * night-light-slider is used to set the custom automatic schedule of the
|
| + * Night Light feature, so that users can set their desired start and end
|
| + * times.
|
| + */
|
| +
|
| +/** @const */ var TOTAL_MINUTES_PER_DAY = 24 * 60;
|
| +/** @const */ var OFFSET_MINUTES_6PM = 18 * 60;
|
| +/** @const */ var MIN_KNOBS_DISTANCE_MINUTES = 30;
|
| +
|
| +Polymer({
|
| + is: 'night-light-slider',
|
| +
|
| + behaviors: [
|
| + I18nBehavior,
|
| + PrefsBehavior,
|
| + Polymer.IronA11yKeysBehavior,
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * The object currently being dragged. Either the start or end knobs.
|
| + * @type {?Object}
|
| + * @private
|
| + */
|
| + dragObject_: {
|
| + type: Object,
|
| + value: null,
|
| + },
|
| +
|
| + /**
|
| + * The start knob time as a string to be shown on the start label bubble.
|
| + * @private
|
| + */
|
| + startTime_: {
|
| + type: String,
|
| + },
|
| +
|
| + /**
|
| + * The end knob time as a string to be shown on the end label bubble.
|
| + * @private
|
| + */
|
| + endTime_: {
|
| + type: String,
|
| + },
|
| + },
|
| +
|
| + observers: [
|
| + 'customTimesChanged_(prefs.ash.night_light.custom_start_time.*, ' +
|
| + 'prefs.ash.night_light.custom_end_time.*)',
|
| + ],
|
| +
|
| + keyBindings: {
|
| + 'left': 'onLeftKey_',
|
| + 'right': 'onRightKey_',
|
| + },
|
| +
|
| + ready: function() {
|
| + // Build the legend markers.
|
| + var markersContainer = this.$.markersContainer;
|
| + var width = markersContainer.offsetWidth;
|
| + var num = 24;
|
| + for (var i = 0; i <= num; ++i) {
|
| + var marker = document.createElement('div');
|
| + marker.className = 'markers';
|
| + markersContainer.appendChild(marker);
|
| + marker.style.left = (i * 100 / num) + '%';
|
| + }
|
| + this.async(function() {
|
| + // Read the initial prefs values and refresh the slider.
|
| + this.customTimesChanged_();
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Expands or un-expands the knob being dragged along with its corresponding
|
| + * label bubble.
|
| + * @param {boolean} expand True to expand, and false to un-expand.
|
| + * @private
|
| + */
|
| + setExpanded_: function(expand) {
|
| + var knob = this.$.startKnob;
|
| + var label = this.$.startLabel;
|
| + if (this.dragObject_ == this.$.endKnob) {
|
| + knob = this.$.endKnob;
|
| + label = this.$.endLabel;
|
| + }
|
| +
|
| + if (expand) {
|
| + knob.classList.add('expanded-knob');
|
| + label.classList.add('expanded-knob');
|
| + } else {
|
| + knob.classList.remove('expanded-knob');
|
| + label.classList.remove('expanded-knob');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * If one of the two knobs is focused, this function blurs it.
|
| + * @private
|
| + */
|
| + blurAnyFocusedKnob_: function() {
|
| + var activeElement = this.shadowRoot.activeElement;
|
| + if (activeElement == this.$.startKnob || activeElement == this.$.endKnob)
|
| + activeElement.blur();
|
| + },
|
| +
|
| + /**
|
| + * Start dragging the target knob.
|
| + * @private
|
| + */
|
| + drag_: function(event) {
|
| + event.preventDefault();
|
| + this.dragObject_ = event.target;
|
| + this.setExpanded_(true);
|
| +
|
| + // Focus is only given to the knobs by means of keyboard tab navigations.
|
| + // When we start dragging, we don't want to see any focus halos around any
|
| + // knob.
|
| + this.blurAnyFocusedKnob_();
|
| +
|
| + // However, our night-light-slider element must get the focus.
|
| + this.focus();
|
| + },
|
| +
|
| + /**
|
| + * Continues dragging the selected knob if any.
|
| + * @private
|
| + */
|
| + continueDrag_: function(event) {
|
| + if (!this.dragObject_)
|
| + return;
|
| +
|
| + event.stopPropagation();
|
| + switch (event.detail.state) {
|
| + case 'start':
|
| + this.drag_(event);
|
| + break;
|
| + case 'track':
|
| + this.doKnobTracking_(event);
|
| + break;
|
| + case 'end':
|
| + this.drop_(event);
|
| + break;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Updates the knob's corresponding pref value in response to dragging, which
|
| + * will in turn update the location of the knob and its corresponding label
|
| + * bubble and its text contents.
|
| + * @private
|
| + */
|
| + doKnobTracking_: function(event) {
|
| + var deltaRatio = Math.abs(event.detail.ddx) / this.$.sliderBar.offsetWidth;
|
| + var deltaMinutes = Math.floor(deltaRatio * TOTAL_MINUTES_PER_DAY);
|
| + if (deltaMinutes <= 0)
|
| + return;
|
| +
|
| + var knobPref = 'ash.night_light.custom_end_time';
|
| + if (this.dragObject_ == this.$.startKnob)
|
| + knobPref = 'ash.night_light.custom_start_time';
|
| +
|
| + if (event.detail.ddx > 0) {
|
| + // Increment the knob's pref by the amount of deltaMinutes.
|
| + this.incrementPref_(knobPref, deltaMinutes);
|
| + } else {
|
| + // Decrement the knob's pref by the amount of deltaMinutes.
|
| + this.decrementPref_(knobPref, deltaMinutes);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Ends the dragging.
|
| + * @private
|
| + */
|
| + drop_: function(event) {
|
| + event.preventDefault();
|
| + this.setExpanded_(false);
|
| + this.dragObject_ = null;
|
| + },
|
| +
|
| + /**
|
| + * Gets the given knob's offset ratio with respect to its parent element
|
| + * (which is the slider bar).
|
| + * @param {HTMLDivElement} knob Either one of the two knobs.
|
| + * @return {number}
|
| + * @private
|
| + */
|
| + getKnobRatio_: function(knob) {
|
| + return parseFloat(knob.style.left) / this.$.sliderBar.offsetWidth;
|
| + },
|
| +
|
| + /**
|
| + * Pads the given number num with leading zeros such that its length as a
|
| + * string is 2.
|
| + * @param {number} num
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + pad2_: function(num) {
|
| + var s = String(num);
|
| + if (s.length == 2)
|
| + return s;
|
| +
|
| + return '0' + s;
|
| + },
|
| +
|
| + /**
|
| + * Converts the offsetMinutes value (which the number of minutes since 00:00)
|
| + * to its string representation in the format 6:30 PM.
|
| + * @param {number} offsetMinutes The time of day represented as the number of
|
| + * minutes from 00:00.
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + offsetMinutesToTimeString_: function(offsetMinutes) {
|
| + var hour = Math.floor(offsetMinutes / 60);
|
| + var amPm = hour >= 12 ? ' PM' : ' AM';
|
| + hour %= 12;
|
| + hour = hour == 0 ? 12 : hour;
|
| + var minute = Math.floor(offsetMinutes % 60);
|
| + return hour + ':' + this.pad2_(minute) + amPm;
|
| + },
|
| +
|
| + /**
|
| + * Handles changes in the start and end times prefs.
|
| + * @private
|
| + */
|
| + customTimesChanged_: function() {
|
| + var startOffsetMinutes = /** @type {number} */(
|
| + this.getPref('ash.night_light.custom_start_time').value);
|
| + this.startTime_ = this.offsetMinutesToTimeString_(startOffsetMinutes);
|
| + this.updateKnobLeft_(this.$.startKnob, startOffsetMinutes);
|
| + var endOffsetMinutes = /** @type {number} */(
|
| + this.getPref('ash.night_light.custom_end_time').value);
|
| + this.endTime_ = this.offsetMinutesToTimeString_(endOffsetMinutes);
|
| + this.updateKnobLeft_(this.$.endKnob, endOffsetMinutes);
|
| + this.refresh_();
|
| + },
|
| +
|
| + /**
|
| + * Updates the absolute left coordinate of the given knob based on the time it
|
| + * represents given as an offsetMinutes value.
|
| + * @param {HTMLDivElement} knob
|
| + * @param {number} offsetMinutes
|
| + * @private
|
| + */
|
| + updateKnobLeft_: function(knob, offsetMinutes) {
|
| + var offsetAfter6pm =
|
| + (offsetMinutes + TOTAL_MINUTES_PER_DAY - OFFSET_MINUTES_6PM) %
|
| + TOTAL_MINUTES_PER_DAY;
|
| + var ratio = offsetAfter6pm / TOTAL_MINUTES_PER_DAY;
|
| +
|
| + if (ratio == 0) {
|
| + // If the ratio is 0, then there are two possibilities:
|
| + // - The knob time is 6:00 PM on the left side of the slider.
|
| + // - The knob time is 6:00 PM on the right side of the slider.
|
| + // We need to check the current knob offset ratio to determine which case
|
| + // it is.
|
| + var currentKnobRatio = this.getKnobRatio_(knob);
|
| + ratio = currentKnobRatio > 0.5 ? 1.0 : 0.0;
|
| + }
|
| +
|
| + knob.style.left = (ratio * this.$.sliderBar.offsetWidth) + 'px';
|
| + },
|
| +
|
| + /**
|
| + * Refreshes elements of the slider other than the knobs (the label bubbles,
|
| + * and the progress bar).
|
| + * @private
|
| + */
|
| + refresh_: function() {
|
| + var endKnob = this.$.endKnob;
|
| + var startKnob = this.$.startKnob;
|
| + var startProgress = this.$.startProgress;
|
| + var endProgress = this.$.endProgress;
|
| + var startLabel = this.$.startLabel;
|
| + var endLabel = this.$.endLabel;
|
| +
|
| + // The label bubbles have the same left coordinates as their corresponding
|
| + // knobs.
|
| + startLabel.style.left = startKnob.style.left;
|
| + endLabel.style.left = endKnob.style.left;
|
| +
|
| + // The end progress bar starts from either the start knob or the start of
|
| + // the slider (whichever is to its left) and ends at the end knob.
|
| + var endProgressLeft = startKnob.offsetLeft >= endKnob.offsetLeft
|
| + ? '0px' : startKnob.style.left;
|
| + endProgress.style.left = endProgressLeft;
|
| + endProgress.style.width =
|
| + (parseFloat(endKnob.style.left) - parseFloat(endProgressLeft)) + 'px';
|
| +
|
| + // The start progress bar starts at the start knob, and ends at either the
|
| + // end knob or the end of the slider (whichever is to its right).
|
| + var startProgressRight = endKnob.offsetLeft < startKnob.offsetLeft
|
| + ? this.$.sliderBar.offsetWidth
|
| + : endKnob.style.left;
|
| + startProgress.style.left = startKnob.style.left;
|
| + startProgress.style.width =
|
| + (parseFloat(startProgressRight) - parseFloat(startKnob.style.left)) +
|
| + 'px';
|
| +
|
| + this.fixLabelsOverlapIfAny_();
|
| + },
|
| +
|
| + /**
|
| + * If the label bubbles overlap, this function fixes them by moving the end
|
| + * label up a little.
|
| + * @private
|
| + */
|
| + fixLabelsOverlapIfAny_: function() {
|
| + var startLabel = this.$.startLabel;
|
| + var endLabel = this.$.endLabel;
|
| + var distance = Math.abs(parseFloat(startLabel.style.left) -
|
| + parseFloat(endLabel.style.left));
|
| + if (distance <= startLabel.offsetWidth) {
|
| + // Shift the end label up so that it doesn't overlap with the start label.
|
| + endLabel.classList.add('end-label-overlap');
|
| + } else {
|
| + endLabel.classList.remove('end-label-overlap');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Given the prefPath that corresponds to one knob time, it gets the value of
|
| + * the pref that corresponds to the other knob.
|
| + * @param {string} prefPath
|
| + * @return {number}
|
| + * @private
|
| + */
|
| + getOtherKnobPrefValue_: function(prefPath) {
|
| + if (prefPath == 'ash.night_light.custom_start_time')
|
| + return /** @type {number} */ (
|
| + this.getPref('ash.night_light.custom_end_time').value);
|
| +
|
| + return /** @type {number} */ (
|
| + this.getPref('ash.night_light.custom_start_time').value);
|
| + },
|
| +
|
| + /**
|
| + * Increments the value of the pref whose path is given by prefPath by the
|
| + * amount given in increment.
|
| + * @param {string} prefPath
|
| + * @param {number} increment
|
| + * @private
|
| + */
|
| + incrementPref_: function(prefPath, increment) {
|
| + var value = this.getPref(prefPath).value + increment;
|
| +
|
| + var otherValue = this.getOtherKnobPrefValue_(prefPath);
|
| + if (otherValue > value &&
|
| + ((otherValue - value) < MIN_KNOBS_DISTANCE_MINUTES)) {
|
| + // We are incrementing the minutes offset moving towards the other knob.
|
| + // We have a minimum 30 minutes overlap threshold. Move this knob to the
|
| + // other side of the other knob.
|
| + //
|
| + // Was:
|
| + // ------ (+) --- 29 MIN --- + ------->>
|
| + //
|
| + // Now:
|
| + // ------ + --- 30 MIN --- (+) ------->>
|
| + //
|
| + // (+) ==> Knob being moved.
|
| + value = otherValue + MIN_KNOBS_DISTANCE_MINUTES;
|
| + }
|
| +
|
| + // The knobs are allowed to wrap around.
|
| + this.setPrefValue(prefPath, (value % TOTAL_MINUTES_PER_DAY));
|
| + },
|
| +
|
| + /**
|
| + * Decrements the value of the pref whose path is given by prefPath by the
|
| + * amount given in decrement.
|
| + * @param {string} prefPath
|
| + * @param {number} decrement
|
| + * @private
|
| + */
|
| + decrementPref_: function(prefPath, decrement) {
|
| + var value = /** @type {number} */(this.getPref(prefPath).value) - decrement;
|
| +
|
| + var otherValue = this.getOtherKnobPrefValue_(prefPath);
|
| + if (value > otherValue &&
|
| + ((value - otherValue) < MIN_KNOBS_DISTANCE_MINUTES)) {
|
| + // We are decrementing the minutes offset moving towards the other knob.
|
| + // We have a minimum 30 minutes overlap threshold. Move this knob to the
|
| + // other side of the other knob.
|
| + //
|
| + // Was:
|
| + // <<------ + --- 29 MIN --- (+) -------
|
| + //
|
| + // Now:
|
| + // <<------ (+) --- 30 MIN --- + ------
|
| + //
|
| + // (+) ==> Knob being moved.
|
| + value = otherValue - MIN_KNOBS_DISTANCE_MINUTES;
|
| + }
|
| +
|
| + // The knobs are allowed to wrap around.
|
| + if (value < 0)
|
| + value += TOTAL_MINUTES_PER_DAY;
|
| +
|
| + this.setPrefValue(prefPath, Math.abs(value) % TOTAL_MINUTES_PER_DAY);
|
| + },
|
| +
|
| + /**
|
| + * Gets the pref path of the currently focused knob. Returns null if no knob
|
| + * is currently focused.
|
| + * @return {?string}
|
| + * @private
|
| + */
|
| + getFocusedKnobPrefPathIfAny_: function() {
|
| + var focusedElement = this.shadowRoot.activeElement;
|
| + if (focusedElement == this.$.startKnob)
|
| + return 'ash.night_light.custom_start_time';
|
| +
|
| + if (focusedElement == this.$.endKnob)
|
| + return 'ash.night_light.custom_end_time';
|
| +
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * Handles the 'left' key event.
|
| + * @private
|
| + */
|
| + onLeftKey_: function(e) {
|
| + e.preventDefault();
|
| + var knobPref = this.getFocusedKnobPrefPathIfAny_();
|
| + if (!knobPref)
|
| + return;
|
| +
|
| + this.decrementPref_(knobPref, 1);
|
| + },
|
| +
|
| + /**
|
| + * Handles the 'right' key event.
|
| + * @private
|
| + */
|
| + onRightKey_: function(e) {
|
| + e.preventDefault();
|
| + var knobPref = this.getFocusedKnobPrefPathIfAny_();
|
| + if (!knobPref)
|
| + return;
|
| +
|
| + this.incrementPref_(knobPref, 1);
|
| + },
|
| +});
|
|
|