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

Side by Side Diff: chrome/browser/resources/settings/device_page/night_light_slider.js

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

Powered by Google App Engine
This is Rietveld 408576698