Commit 74520997 authored by afakhry's avatar afakhry Committed by Commit Bot

[Night Light] CL5: Schedule Settings

Adds the Night Light automatic schedule settings.

Demo: https://bugs.chromium.org/p/chromium/issues/detail?id=705816#c15

BUG=705816
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2915753003
Cr-Commit-Position: refs/heads/master@{#478767}
parent ea696982
......@@ -2989,6 +2989,12 @@
<message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_SCHEDULE_SUNSET_TO_SUNRISE" desc="In Device Settings > Displays, the label of the option to set the automatic schedule of the Night Light feature to turn on at sunset and off at sunrise.">
Sunset to Sunrise
</message>
<message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_START_TIME" desc="In Device Settings > Displays, the label of the start time bubble.">
Start time
</message>
<message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_STOP_TIME" desc="In Device Settings > Displays, the label of the end time bubble.">
End time
</message>
<message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_TEXT" desc="In Device Settings > Displays, text describing the Night Light feature.">
Makes it easier to look at your screen or read in dim light
</message>
......
......@@ -304,6 +304,12 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetWhitelistedKeys() {
settings_private::PrefType::PREF_TYPE_BOOLEAN;
(*s_whitelist)[ash::prefs::kNightLightTemperature] =
settings_private::PrefType::PREF_TYPE_NUMBER;
(*s_whitelist)[ash::prefs::kNightLightScheduleType] =
settings_private::PrefType::PREF_TYPE_NUMBER;
(*s_whitelist)[ash::prefs::kNightLightCustomStartTime] =
settings_private::PrefType::PREF_TYPE_NUMBER;
(*s_whitelist)[ash::prefs::kNightLightCustomEndTime] =
settings_private::PrefType::PREF_TYPE_NUMBER;
// Input method settings.
(*s_whitelist)[::prefs::kLanguagePreloadEngines] =
......
......@@ -104,6 +104,15 @@
],
'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
},
{
'target_name': 'night_light_slider',
'dependencies': [
'<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior/compiled_resources2.gyp:iron-a11y-keys-behavior-extracted',
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior',
'../prefs/compiled_resources2.gyp:prefs_behavior',
],
'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
},
{
'target_name': 'power',
'dependencies': [
......
......@@ -11,6 +11,8 @@
<link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="display_layout.html">
<link rel="import" href="display_overscan_dialog.html">
<link rel="import" href="night_light_slider.html">
<link rel="import" href="../controls/settings_dropdown_menu.html">
<link rel="import" href="../controls/settings_slider.html">
<link rel="import" href="../prefs/prefs_behavior.html">
<link rel="import" href="../settings_shared_css.html">
......@@ -55,6 +57,10 @@
#controlsDiv > .settings-box:first-of-type {
border-top: none;
}
#nightLightSlider {
margin-top: 20px;
}
</style>
<div class="settings-box first layout vertical self-stretch">
<h2 class="layout self-start">
......@@ -228,6 +234,29 @@
pref="{{prefs.ash.night_light.color_temperature}}">
</settings-slider>
</div>
<!-- Schedule settings -->
<div class="settings-box embedded">
<div id="nightLightScheduleLabel" class="start textarea">
$i18n{displayNightLightScheduleLabel}
</div>
<settings-dropdown-menu
id="nightLightScheduleTypeDropDown"
aria-labelledby="nightLightScheduleLabel"
pref="{{prefs.ash.night_light.schedule_type}}"
menu-options="[[scheduleTypesList_]]">
</settings-dropdown-menu>
</div>
<!-- Custom schedule slider -->
<div class="settings-box embedded continuation">
<iron-collapse id="nightLightCustomScheduleCollapse"
class="start textarea layout vertical"
opened="[[shouldOpenCustomScheduleCollapse_]]">
<div class="settings-box embedded continuation">
<night-light-slider id="nightLightSlider" prefs="{{prefs}}">
</night-light-slider>
</div>
</iron-collapse>
</div>
</div>
</template>
......
......@@ -7,6 +7,17 @@
* 'settings-display' is the settings subpage for display settings.
*/
/**
* The types of Night Light automatic schedule. The values of the enum values
* are synced with the pref "prefs.ash.night_light.schedule_type".
* @enum {number}
*/
var NightLightScheduleType = {
NEVER: 0,
SUNSET_TO_SUNRISE: 1,
CUSTOM: 2,
};
cr.define('settings.display', function() {
var systemDisplayApi = /** @type {!SystemDisplay} */ (chrome.system.display);
......@@ -93,8 +104,33 @@ Polymer({
type: Boolean,
value: false,
},
/** @private */
scheduleTypesList_: {
type: Array,
value: function() {
return [{
name: loadTimeData.getString('displayNightLightScheduleNever'),
value: NightLightScheduleType.NEVER }, {
name: loadTimeData.getString(
'displayNightLightScheduleSunsetToSunRise'),
value: NightLightScheduleType.SUNSET_TO_SUNRISE }, {
name: loadTimeData.getString('displayNightLightScheduleCustom'),
value: NightLightScheduleType.CUSTOM }];
},
},
/** @private */
shouldOpenCustomScheduleCollapse_: {
type: Boolean,
value: false,
},
},
observers: [
'onScheduleTypeChanged_(prefs.ash.night_light.schedule_type.*)',
],
/** @private {number} Selected mode index received from chrome. */
currentSelectedModeIndex_: -1,
......@@ -532,4 +568,11 @@ Polymer({
'setDisplayProperties Error: ' + chrome.runtime.lastError.message);
}
},
/** @private */
onScheduleTypeChanged_: function() {
this.shouldOpenCustomScheduleCollapse_ =
this.getPref('ash.night_light.schedule_type').value ==
NightLightScheduleType.CUSTOM;
},
});
<html><head>
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/html/i18n_behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<link rel="import" href="../prefs/prefs_behavior.html">
</head><body><dom-module id="night-light-slider">
<template>
<style>
:host {
cursor: default;
text-align: center;
user-select: none;
}
#sliderContainer {
display: inline-block;
position: relative;
user-select: none;
width: 570px;
}
#sliderBar {
background-color: #ccc;
background-size: 100%;
display: inline-block;
height: 2px;
position: relative;
width: inherit;
}
.knob {
background: rgb(51, 103, 214);
border-radius: 6px;
height: 12px;
left: 0;
margin-left: -6px;
margin-top: -5px;
position: absolute;
width: 12px;
z-index: 3;
}
.knob:focus {
box-shadow: 0 0 5pt 5pt rgb(204, 217, 245);
outline: none;
}
.expanded-knob {
transform: scale(1.5);
z-index: 3;
}
#progressContainer {
height: 100%;
overflow: hidden;
position: absolute;
width: 100%;
}
.progress {
background: rgb(51, 103, 214);
height: 100%;
position: absolute;
z-index: 1;
}
#labelContainer {
height: 2em;
}
.label {
background: rgb(51, 103, 214);
border-radius: 14px;
color: white;
font-family: Roboto-Bold;
font-size: 12px;
left: 0;
line-height: 1.5em;
margin-left: -2.5em;
position: absolute;
text-align: center;
vertical-align: middle;
width: 5em;
}
.end-label-overlap {
margin-top: -2em;
}
#markersContainer {
display: flex;
height: 100%;
left: 0;
position: absolute;
width: 100%;
}
.markers {
background-color: black;
border-radius: 50%;
display: block;
height: 100%;
margin-left: -1px;
padding: 0;
position: absolute;
width: 2PX;
z-index: 2;
}
#legendContainer {
height: 10px;
position: relative;
width: inherit;
}
#legendContainer > div {
color: rgb(100, 100, 100);
font-family: Roboto-Regular;
font-size: 12px;
margin-left: -2em;
position: absolute;
text-align: center;
top: 5px;
width: 4em;
}
</style>
<div id="sliderContainer">
<div id="labelContainer">
<div id="startLabel" class="label"
aria-label="$i18n{displayNightLightStartTime}">
[[startTime_]]
</div>
<div id="endLabel" class="label"
aria-label="$i18n{displayNightLightStopTime}">
[[endTime_]]
</div>
</div>
<div id="sliderBar">
<div id="progressContainer">
<div id="endProgress" class="progress"></div>
<div id="startProgress" class="progress"></div>
</div>
<div id="markersContainer">
</div>
<div id="startKnob" class="knob" tabindex="1" on-down="startDrag_"
on-up="endDrag_" on-track="continueDrag_"></div>
<div id="endKnob" class="knob" tabindex="2" on-down="startDrag_"
on-up="endDrag_" on-track="continueDrag_"></div>
</div>
<div id="legendContainer">
<!-- TODO(afakhry): Check if these values need to be localized. -->
<div style="left: 0%;">6 PM</div>
<div style="left: 25%;">12 AM</div>
<div style="left: 50%;">6 AM</div>
<div style="left: 75%;">12 PM</div>
<div style="left: 100%;">6 PM</div>
</div>
</div>
</template>
</dom-module>
<script src="night_light_slider.js"></script></body></html>
\ No newline at end of file
// 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);
},
});
\ No newline at end of file
......@@ -561,6 +561,13 @@
<structure name="IDR_SETTINGS_DEVICE_LAYOUT_BEHAVIOR_JS"
file="device_page/layout_behavior.js"
type="chrome_html" />
<structure name="IDR_SETTINGS_DEVICE_NIGHT_LIGHT_SLIDER_HTML"
file="device_page/night_light_slider.html"
type="chrome_html" />
<structure name="IDR_SETTINGS_DEVICE_NIGHT_LIGHT_SLIDER_JS"
file="device_page/night_light_slider.js"
type="chrome_html" />
</if>
<structure name="IDR_SETTINGS_DIRECTION_DELEGATE_HTML"
file="direction_delegate.html"
......
......@@ -662,6 +662,9 @@ void AddDeviceStrings(content::WebUIDataSource* html_source) {
IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_SCHEDULE_NEVER},
{"displayNightLightScheduleSunsetToSunRise",
IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_SCHEDULE_SUNSET_TO_SUNRISE},
{"displayNightLightStartTime",
IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_START_TIME},
{"displayNightLightStopTime", IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_STOP_TIME},
{"displayNightLightText", IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_TEXT},
{"displayNightLightTemperatureLabel",
IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_TEMPERATURE_LABEL},
......
......@@ -83,6 +83,15 @@ cr.define('device_page_tests', function() {
function getFakePrefs() {
return {
ash: {
night_light: {
schedule_type: {
key: 'ash.night_light.schedule_type',
type: chrome.settingsPrivate.PrefType.NUMBER,
value: 0,
},
},
},
settings: {
touchpad: {
enable_tap_to_click: {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment