Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7163)

Unified Diff: chrome/browser/resources/settings/device_page/night_light_slider.js

Issue 2915753003: [Night Light] CL5: Schedule Settings (Closed)
Patch Set: Steven's comments Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/settings/device_page/night_light_slider.js
diff --git a/chrome/browser/resources/settings/device_page/night_light_slider.js b/chrome/browser/resources/settings/device_page/night_light_slider.js
new file mode 100644
index 0000000000000000000000000000000000000000..64e3aa5ac0648791c9254318c4054f488fe1015d
--- /dev/null
+++ b/chrome/browser/resources/settings/device_page/night_light_slider.js
@@ -0,0 +1,449 @@
+// 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 HOURS_PER_DAY = 24;
+/** @const */ var MIN_KNOBS_DISTANCE_MINUTES = 30;
+/** @const */ var OFFSET_MINUTES_6PM = 18 * 60;
+/** @const */ var TOTAL_MINUTES_PER_DAY = 24 * 60;
+
+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;
+ for (var i = 0; i <= HOURS_PER_DAY; ++i) {
+ var marker = document.createElement('div');
+ marker.className = 'markers';
+ markersContainer.appendChild(marker);
+ marker.style.left = (i * 100 / HOURS_PER_DAY) + '%';
+ }
+ 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;
+ }
+
+ knob.classList.toggle('expanded-knob', expand);
+ label.classList.toggle('expanded-knob', expand);
+ },
+
+ /**
+ * 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
+ */
+ startDrag_: 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.startDrag_(event);
+ break;
+ case 'track':
+ this.doKnobTracking_(event);
+ break;
+ case 'end':
+ this.endDrag_(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 = this.dragObject_ == this.$.startKnob ?
+ 'ash.night_light.custom_start_time' : 'ash.night_light.custom_end_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
+ */
+ endDrag_: 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) {
+ // TODO(afakhry): Check if these values need to be localized.
+ 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);
+ },
+});

Powered by Google App Engine
This is Rietveld 408576698