| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 
|  | 2 // Use of this source code is governed by a BSD-style license that can be | 
|  | 3 // found in the LICENSE file. | 
|  | 4 | 
|  | 5 /** | 
|  | 6  * @fileoverview | 
|  | 7  * night-light-slider is used to set the custom automatic schedule of the | 
|  | 8  * Night Light feature, so that users can set their desired start and end | 
|  | 9  * times. | 
|  | 10  */ | 
|  | 11 | 
|  | 12 /** @const */ var HOURS_PER_DAY = 24; | 
|  | 13 /** @const */ var MIN_KNOBS_DISTANCE_MINUTES = 30; | 
|  | 14 /** @const */ var OFFSET_MINUTES_6PM = 18 * 60; | 
|  | 15 /** @const */ var TOTAL_MINUTES_PER_DAY = 24 * 60; | 
|  | 16 | 
|  | 17 Polymer({ | 
|  | 18   is: 'night-light-slider', | 
|  | 19 | 
|  | 20   behaviors: [ | 
|  | 21     I18nBehavior, | 
|  | 22     PrefsBehavior, | 
|  | 23     Polymer.IronA11yKeysBehavior, | 
|  | 24   ], | 
|  | 25 | 
|  | 26   properties: { | 
|  | 27     /** | 
|  | 28      * The object currently being dragged. Either the start or end knobs. | 
|  | 29      * @type {?Object} | 
|  | 30      * @private | 
|  | 31      */ | 
|  | 32     dragObject_: { | 
|  | 33       type: Object, | 
|  | 34       value: null, | 
|  | 35     }, | 
|  | 36 | 
|  | 37     /** | 
|  | 38      * The start knob time as a string to be shown on the start label bubble. | 
|  | 39      * @private | 
|  | 40      */ | 
|  | 41     startTime_: { | 
|  | 42       type: String, | 
|  | 43     }, | 
|  | 44 | 
|  | 45     /** | 
|  | 46      * The end knob time as a string to be shown on the end label bubble. | 
|  | 47      * @private | 
|  | 48      */ | 
|  | 49     endTime_: { | 
|  | 50       type: String, | 
|  | 51     }, | 
|  | 52   }, | 
|  | 53 | 
|  | 54   observers: [ | 
|  | 55     'customTimesChanged_(prefs.ash.night_light.custom_start_time.*, ' + | 
|  | 56                         'prefs.ash.night_light.custom_end_time.*)', | 
|  | 57   ], | 
|  | 58 | 
|  | 59   keyBindings: { | 
|  | 60     'left': 'onLeftKey_', | 
|  | 61     'right': 'onRightKey_', | 
|  | 62   }, | 
|  | 63 | 
|  | 64   ready: function() { | 
|  | 65     // Build the legend markers. | 
|  | 66     var markersContainer = this.$.markersContainer; | 
|  | 67     var width = markersContainer.offsetWidth; | 
|  | 68     for (var i = 0; i <= HOURS_PER_DAY; ++i) { | 
|  | 69       var marker = document.createElement('div'); | 
|  | 70       marker.className = 'markers'; | 
|  | 71       markersContainer.appendChild(marker); | 
|  | 72       marker.style.left = (i * 100 / HOURS_PER_DAY) + '%'; | 
|  | 73     } | 
|  | 74     this.async(function() { | 
|  | 75       // Read the initial prefs values and refresh the slider. | 
|  | 76       this.customTimesChanged_(); | 
|  | 77     }); | 
|  | 78   }, | 
|  | 79 | 
|  | 80   /** | 
|  | 81    * Expands or un-expands the knob being dragged along with its corresponding | 
|  | 82    * label bubble. | 
|  | 83    * @param {boolean} expand True to expand, and false to un-expand. | 
|  | 84    * @private | 
|  | 85    */ | 
|  | 86   setExpanded_: function(expand) { | 
|  | 87     var knob = this.$.startKnob; | 
|  | 88     var label = this.$.startLabel; | 
|  | 89     if (this.dragObject_ == this.$.endKnob) { | 
|  | 90       knob = this.$.endKnob; | 
|  | 91       label = this.$.endLabel; | 
|  | 92     } | 
|  | 93 | 
|  | 94     knob.classList.toggle('expanded-knob', expand); | 
|  | 95     label.classList.toggle('expanded-knob', expand); | 
|  | 96   }, | 
|  | 97 | 
|  | 98   /** | 
|  | 99    * If one of the two knobs is focused, this function blurs it. | 
|  | 100    * @private | 
|  | 101    */ | 
|  | 102   blurAnyFocusedKnob_: function() { | 
|  | 103     var activeElement = this.shadowRoot.activeElement; | 
|  | 104     if (activeElement == this.$.startKnob || activeElement == this.$.endKnob) | 
|  | 105       activeElement.blur(); | 
|  | 106   }, | 
|  | 107 | 
|  | 108   /** | 
|  | 109    * Start dragging the target knob. | 
|  | 110    * @private | 
|  | 111    */ | 
|  | 112   startDrag_: function(event) { | 
|  | 113     event.preventDefault(); | 
|  | 114     this.dragObject_ = event.target; | 
|  | 115     this.setExpanded_(true); | 
|  | 116 | 
|  | 117     // Focus is only given to the knobs by means of keyboard tab navigations. | 
|  | 118     // When we start dragging, we don't want to see any focus halos around any | 
|  | 119     // knob. | 
|  | 120     this.blurAnyFocusedKnob_(); | 
|  | 121 | 
|  | 122     // However, our night-light-slider element must get the focus. | 
|  | 123     this.focus(); | 
|  | 124   }, | 
|  | 125 | 
|  | 126   /** | 
|  | 127    * Continues dragging the selected knob if any. | 
|  | 128    * @private | 
|  | 129    */ | 
|  | 130   continueDrag_: function(event) { | 
|  | 131     if (!this.dragObject_) | 
|  | 132       return; | 
|  | 133 | 
|  | 134     event.stopPropagation(); | 
|  | 135     switch (event.detail.state) { | 
|  | 136       case 'start': | 
|  | 137         this.startDrag_(event); | 
|  | 138         break; | 
|  | 139       case 'track': | 
|  | 140         this.doKnobTracking_(event); | 
|  | 141         break; | 
|  | 142       case 'end': | 
|  | 143         this.endDrag_(event); | 
|  | 144         break; | 
|  | 145     } | 
|  | 146   }, | 
|  | 147 | 
|  | 148   /** | 
|  | 149    * Updates the knob's corresponding pref value in response to dragging, which | 
|  | 150    * will in turn update the location of the knob and its corresponding label | 
|  | 151    * bubble and its text contents. | 
|  | 152    * @private | 
|  | 153    */ | 
|  | 154   doKnobTracking_: function(event) { | 
|  | 155     var deltaRatio = Math.abs(event.detail.ddx) / this.$.sliderBar.offsetWidth; | 
|  | 156     var deltaMinutes = Math.floor(deltaRatio * TOTAL_MINUTES_PER_DAY); | 
|  | 157     if (deltaMinutes <= 0) | 
|  | 158       return; | 
|  | 159 | 
|  | 160     var knobPref = this.dragObject_ == this.$.startKnob ? | 
|  | 161         'ash.night_light.custom_start_time' : 'ash.night_light.custom_end_time'; | 
|  | 162 | 
|  | 163     if (event.detail.ddx > 0) { | 
|  | 164       // Increment the knob's pref by the amount of deltaMinutes. | 
|  | 165       this.incrementPref_(knobPref, deltaMinutes); | 
|  | 166     } else { | 
|  | 167       // Decrement the knob's pref by the amount of deltaMinutes. | 
|  | 168       this.decrementPref_(knobPref, deltaMinutes); | 
|  | 169     } | 
|  | 170   }, | 
|  | 171 | 
|  | 172   /** | 
|  | 173    * Ends the dragging. | 
|  | 174    * @private | 
|  | 175    */ | 
|  | 176   endDrag_: function(event) { | 
|  | 177     event.preventDefault(); | 
|  | 178     this.setExpanded_(false); | 
|  | 179     this.dragObject_ = null; | 
|  | 180   }, | 
|  | 181 | 
|  | 182   /** | 
|  | 183    * Gets the given knob's offset ratio with respect to its parent element | 
|  | 184    * (which is the slider bar). | 
|  | 185    * @param {HTMLDivElement} knob Either one of the two knobs. | 
|  | 186    * @return {number} | 
|  | 187    * @private | 
|  | 188    */ | 
|  | 189   getKnobRatio_: function(knob) { | 
|  | 190     return parseFloat(knob.style.left) / this.$.sliderBar.offsetWidth; | 
|  | 191   }, | 
|  | 192 | 
|  | 193   /** | 
|  | 194    * Pads the given number |num| with leading zeros such that its length as a | 
|  | 195    * string is 2. | 
|  | 196    * @param {number} num | 
|  | 197    * @return {string} | 
|  | 198    * @private | 
|  | 199    */ | 
|  | 200   pad2_: function(num) { | 
|  | 201     var s = String(num); | 
|  | 202     if (s.length == 2) | 
|  | 203       return s; | 
|  | 204 | 
|  | 205     return '0' + s; | 
|  | 206   }, | 
|  | 207 | 
|  | 208   /** | 
|  | 209    * Converts the |offsetMinutes| value (which the number of minutes since | 
|  | 210    * 00:00) to its string representation in the format 6:30 PM. | 
|  | 211    * @param {number} offsetMinutes The time of day represented as the number of | 
|  | 212    * minutes from 00:00. | 
|  | 213    * @return {string} | 
|  | 214    * @private | 
|  | 215    */ | 
|  | 216   offsetMinutesToTimeString_: function(offsetMinutes) { | 
|  | 217     // TODO(afakhry): Check if these values need to be localized. | 
|  | 218     var hour = Math.floor(offsetMinutes / 60); | 
|  | 219     var amPm = hour >= 12 ? ' PM' : ' AM'; | 
|  | 220     hour %= 12; | 
|  | 221     hour = hour == 0 ? 12 : hour; | 
|  | 222     var minute = Math.floor(offsetMinutes % 60); | 
|  | 223     return hour + ':' + this.pad2_(minute) + amPm; | 
|  | 224   }, | 
|  | 225 | 
|  | 226   /** | 
|  | 227    * Handles changes in the start and end times prefs. | 
|  | 228    * @private | 
|  | 229    */ | 
|  | 230   customTimesChanged_: function() { | 
|  | 231     var startOffsetMinutes = /** @type {number} */( | 
|  | 232         this.getPref('ash.night_light.custom_start_time').value); | 
|  | 233     this.startTime_ = this.offsetMinutesToTimeString_(startOffsetMinutes); | 
|  | 234     this.updateKnobLeft_(this.$.startKnob, startOffsetMinutes); | 
|  | 235     var endOffsetMinutes = /** @type {number} */( | 
|  | 236         this.getPref('ash.night_light.custom_end_time').value); | 
|  | 237     this.endTime_ = this.offsetMinutesToTimeString_(endOffsetMinutes); | 
|  | 238     this.updateKnobLeft_(this.$.endKnob, endOffsetMinutes); | 
|  | 239     this.refresh_(); | 
|  | 240   }, | 
|  | 241 | 
|  | 242   /** | 
|  | 243    * Updates the absolute left coordinate of the given |knob| based on the time | 
|  | 244    * it represents given as an |offsetMinutes| value. | 
|  | 245    * @param {HTMLDivElement} knob | 
|  | 246    * @param {number} offsetMinutes | 
|  | 247    * @private | 
|  | 248    */ | 
|  | 249   updateKnobLeft_: function(knob, offsetMinutes) { | 
|  | 250     var offsetAfter6pm = | 
|  | 251         (offsetMinutes + TOTAL_MINUTES_PER_DAY - OFFSET_MINUTES_6PM) % | 
|  | 252             TOTAL_MINUTES_PER_DAY; | 
|  | 253     var ratio = offsetAfter6pm / TOTAL_MINUTES_PER_DAY; | 
|  | 254 | 
|  | 255     if (ratio == 0) { | 
|  | 256       // If the ratio is 0, then there are two possibilities: | 
|  | 257       // - The knob time is 6:00 PM on the left side of the slider. | 
|  | 258       // - The knob time is 6:00 PM on the right side of the slider. | 
|  | 259       // We need to check the current knob offset ratio to determine which case | 
|  | 260       // it is. | 
|  | 261       var currentKnobRatio = this.getKnobRatio_(knob); | 
|  | 262       ratio = currentKnobRatio > 0.5 ? 1.0 : 0.0; | 
|  | 263     } | 
|  | 264 | 
|  | 265     knob.style.left = (ratio * this.$.sliderBar.offsetWidth) + 'px'; | 
|  | 266   }, | 
|  | 267 | 
|  | 268   /** | 
|  | 269    * Refreshes elements of the slider other than the knobs (the label bubbles, | 
|  | 270    * and the progress bar). | 
|  | 271    * @private | 
|  | 272    */ | 
|  | 273   refresh_: function() { | 
|  | 274     var endKnob = this.$.endKnob; | 
|  | 275     var startKnob = this.$.startKnob; | 
|  | 276     var startProgress = this.$.startProgress; | 
|  | 277     var endProgress = this.$.endProgress; | 
|  | 278     var startLabel = this.$.startLabel; | 
|  | 279     var endLabel = this.$.endLabel; | 
|  | 280 | 
|  | 281     // The label bubbles have the same left coordinates as their corresponding | 
|  | 282     // knobs. | 
|  | 283     startLabel.style.left = startKnob.style.left; | 
|  | 284     endLabel.style.left = endKnob.style.left; | 
|  | 285 | 
|  | 286     // The end progress bar starts from either the start knob or the start of | 
|  | 287     // the slider (whichever is to its left) and ends at the end knob. | 
|  | 288     var endProgressLeft = startKnob.offsetLeft >= endKnob.offsetLeft | 
|  | 289                               ? '0px' : startKnob.style.left; | 
|  | 290     endProgress.style.left = endProgressLeft; | 
|  | 291     endProgress.style.width = | 
|  | 292         (parseFloat(endKnob.style.left) - parseFloat(endProgressLeft)) + 'px'; | 
|  | 293 | 
|  | 294     // The start progress bar starts at the start knob, and ends at either the | 
|  | 295     // end knob or the end of the slider (whichever is to its right). | 
|  | 296     var startProgressRight = endKnob.offsetLeft < startKnob.offsetLeft | 
|  | 297                                 ? this.$.sliderBar.offsetWidth | 
|  | 298                                 : endKnob.style.left; | 
|  | 299     startProgress.style.left = startKnob.style.left; | 
|  | 300     startProgress.style.width = | 
|  | 301         (parseFloat(startProgressRight) - parseFloat(startKnob.style.left)) + | 
|  | 302         'px'; | 
|  | 303 | 
|  | 304     this.fixLabelsOverlapIfAny_(); | 
|  | 305   }, | 
|  | 306 | 
|  | 307   /** | 
|  | 308    * If the label bubbles overlap, this function fixes them by moving the end | 
|  | 309    * label up a little. | 
|  | 310    * @private | 
|  | 311    */ | 
|  | 312   fixLabelsOverlapIfAny_: function() { | 
|  | 313     var startLabel = this.$.startLabel; | 
|  | 314     var endLabel = this.$.endLabel; | 
|  | 315     var distance = Math.abs(parseFloat(startLabel.style.left) - | 
|  | 316         parseFloat(endLabel.style.left)); | 
|  | 317     if (distance <= startLabel.offsetWidth) { | 
|  | 318       // Shift the end label up so that it doesn't overlap with the start label. | 
|  | 319       endLabel.classList.add('end-label-overlap'); | 
|  | 320     } else { | 
|  | 321       endLabel.classList.remove('end-label-overlap'); | 
|  | 322     } | 
|  | 323   }, | 
|  | 324 | 
|  | 325   /** | 
|  | 326    * Given the |prefPath| that corresponds to one knob time, it gets the value | 
|  | 327    * of the pref that corresponds to the other knob. | 
|  | 328    * @param {string} prefPath | 
|  | 329    * @return {number} | 
|  | 330    * @private | 
|  | 331    */ | 
|  | 332   getOtherKnobPrefValue_: function(prefPath) { | 
|  | 333     if (prefPath == 'ash.night_light.custom_start_time') { | 
|  | 334       return /** @type {number} */ ( | 
|  | 335           this.getPref('ash.night_light.custom_end_time').value); | 
|  | 336     } | 
|  | 337 | 
|  | 338     return /** @type {number} */ ( | 
|  | 339         this.getPref('ash.night_light.custom_start_time').value); | 
|  | 340   }, | 
|  | 341 | 
|  | 342   /** | 
|  | 343    * Increments the value of the pref whose path is given by |prefPath| by the | 
|  | 344    * amount given in |increment|. | 
|  | 345    * @param {string} prefPath | 
|  | 346    * @param {number} increment | 
|  | 347    * @private | 
|  | 348    */ | 
|  | 349   incrementPref_: function(prefPath, increment) { | 
|  | 350     var value = this.getPref(prefPath).value + increment; | 
|  | 351 | 
|  | 352     var otherValue = this.getOtherKnobPrefValue_(prefPath); | 
|  | 353     if (otherValue > value && | 
|  | 354         ((otherValue - value) < MIN_KNOBS_DISTANCE_MINUTES)) { | 
|  | 355       // We are incrementing the minutes offset moving towards the other knob. | 
|  | 356       // We have a minimum 30 minutes overlap threshold. Move this knob to the | 
|  | 357       // other side of the other knob. | 
|  | 358       // | 
|  | 359       // Was: | 
|  | 360       // ------ (+) --- 29 MIN --- + ------->> | 
|  | 361       // | 
|  | 362       // Now: | 
|  | 363       // ------ + --- 30 MIN --- (+) ------->> | 
|  | 364       // | 
|  | 365       // (+) ==> Knob being moved. | 
|  | 366       value = otherValue + MIN_KNOBS_DISTANCE_MINUTES; | 
|  | 367     } | 
|  | 368 | 
|  | 369     // The knobs are allowed to wrap around. | 
|  | 370     this.setPrefValue(prefPath, (value % TOTAL_MINUTES_PER_DAY)); | 
|  | 371   }, | 
|  | 372 | 
|  | 373   /** | 
|  | 374    * Decrements the value of the pref whose path is given by |prefPath| by the | 
|  | 375    * amount given in |decrement|. | 
|  | 376    * @param {string} prefPath | 
|  | 377    * @param {number} decrement | 
|  | 378    * @private | 
|  | 379    */ | 
|  | 380   decrementPref_: function(prefPath, decrement) { | 
|  | 381     var value = /** @type {number} */(this.getPref(prefPath).value) - decrement; | 
|  | 382 | 
|  | 383     var otherValue = this.getOtherKnobPrefValue_(prefPath); | 
|  | 384     if (value > otherValue && | 
|  | 385         ((value - otherValue) < MIN_KNOBS_DISTANCE_MINUTES)) { | 
|  | 386       // We are decrementing the minutes offset moving towards the other knob. | 
|  | 387       // We have a minimum 30 minutes overlap threshold. Move this knob to the | 
|  | 388       // other side of the other knob. | 
|  | 389       // | 
|  | 390       // Was: | 
|  | 391       // <<------ + --- 29 MIN --- (+) ------- | 
|  | 392       // | 
|  | 393       // Now: | 
|  | 394       // <<------ (+) --- 30 MIN --- + ------ | 
|  | 395       // | 
|  | 396       // (+) ==> Knob being moved. | 
|  | 397       value = otherValue - MIN_KNOBS_DISTANCE_MINUTES; | 
|  | 398     } | 
|  | 399 | 
|  | 400     // The knobs are allowed to wrap around. | 
|  | 401     if (value < 0) | 
|  | 402       value += TOTAL_MINUTES_PER_DAY; | 
|  | 403 | 
|  | 404     this.setPrefValue(prefPath, Math.abs(value) % TOTAL_MINUTES_PER_DAY); | 
|  | 405   }, | 
|  | 406 | 
|  | 407   /** | 
|  | 408    * Gets the pref path of the currently focused knob. Returns null if no knob | 
|  | 409    * is currently focused. | 
|  | 410    * @return {?string} | 
|  | 411    * @private | 
|  | 412    */ | 
|  | 413   getFocusedKnobPrefPathIfAny_: function() { | 
|  | 414     var focusedElement = this.shadowRoot.activeElement; | 
|  | 415     if (focusedElement == this.$.startKnob) | 
|  | 416       return 'ash.night_light.custom_start_time'; | 
|  | 417 | 
|  | 418     if (focusedElement == this.$.endKnob) | 
|  | 419       return 'ash.night_light.custom_end_time'; | 
|  | 420 | 
|  | 421     return null; | 
|  | 422   }, | 
|  | 423 | 
|  | 424   /** | 
|  | 425    * Handles the 'left' key event. | 
|  | 426    * @private | 
|  | 427    */ | 
|  | 428   onLeftKey_: function(e) { | 
|  | 429     e.preventDefault(); | 
|  | 430     var knobPref = this.getFocusedKnobPrefPathIfAny_(); | 
|  | 431     if (!knobPref) | 
|  | 432       return; | 
|  | 433 | 
|  | 434     this.decrementPref_(knobPref, 1); | 
|  | 435   }, | 
|  | 436 | 
|  | 437   /** | 
|  | 438    * Handles the 'right' key event. | 
|  | 439    * @private | 
|  | 440    */ | 
|  | 441   onRightKey_: function(e) { | 
|  | 442     e.preventDefault(); | 
|  | 443     var knobPref = this.getFocusedKnobPrefPathIfAny_(); | 
|  | 444     if (!knobPref) | 
|  | 445       return; | 
|  | 446 | 
|  | 447     this.incrementPref_(knobPref, 1); | 
|  | 448   }, | 
|  | 449 }); | 
| OLD | NEW | 
|---|