Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_table_entry.js
blob296af37e9154e8825a5d76e75a8f87310dc9ae5e
1 // Copyright (c) 2012 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.
5 /**
6 * @fileoverview
7 * Class representing an entry in the host-list portion of the home screen.
8 */
10 /** @suppress {duplicate} */
11 var remoting = remoting || {};
13 (function() {
15 'use strict';
17 /**
18 * An entry in the host table.
20 * @param {number} webappMajorVersion The major version nmber of the web-app,
21 * used to identify out-of-date hosts.
22 * @param {function(string):void} onConnect Callback for
23 * connect operations.
24 * @param {function(remoting.HostTableEntry):void} onRename Callback for
25 * rename operations.
26 * @param {function(remoting.HostTableEntry):void=} opt_onDelete Callback for
27 * delete operations.
29 * @constructor
30 * @implements {base.Disposable}
32 remoting.HostTableEntry = function(
33 webappMajorVersion, onConnect, onRename, opt_onDelete) {
34 /** @type {remoting.Host} */
35 this.host = null;
36 /** @private {number} */
37 this.webappMajorVersion_ = webappMajorVersion;
38 /** @private {function(remoting.HostTableEntry):void} */
39 this.onRename_ = onRename;
40 /** @private {undefined|function(remoting.HostTableEntry):void} */
41 this.onDelete_ = opt_onDelete;
42 /** @private {function(string):void} */
43 this.onConnect_ = onConnect;
45 /** @private {HTMLElement} */
46 this.rootElement_ = null;
47 /** @private {HTMLElement} */
48 this.hostNameLabel_ = null;
49 /** @private {HTMLInputElement} */
50 this.renameInputField_ = null;
51 /** @private {HTMLElement} */
52 this.warningOverlay_ = null;
54 /** @private {base.Disposables} */
55 this.renameInputEventHooks_ = null;
56 /** @private {base.Disposables} */
57 this.disposables_ = new base.Disposables();
59 // References to event handlers so that they can be removed.
60 /** @private {function():void} */
61 this.onConfirmDeleteReference_ = function() {};
62 /** @private {function():void} */
63 this.onCancelDeleteReference_ = function() {};
65 this.createDom_();
68 /** @param {remoting.Host} host */
69 remoting.HostTableEntry.prototype.setHost = function(host) {
70 this.host = host;
71 this.updateUI_();
74 /** @return {HTMLElement} */
75 remoting.HostTableEntry.prototype.element = function() {
76 return this.rootElement_;
79 remoting.HostTableEntry.prototype.dispose = function() {
80 base.dispose(this.disposables_);
81 this.disposables_ = null;
82 base.dispose(this.renameInputEventHooks_);
83 this.renameInputEventHooks_ = null;
86 /** @return {string} */
87 remoting.HostTableEntry.prototype.getHTML_ = function() {
88 var html =
89 '<div class="host-list-main-icon">' +
90 '<span class="warning-overlay"></span>' +
91 '<img src="icon_host.webp">' +
92 '</div>' +
93 '<div class="box-spacer">' +
94 '<a class="host-name-label" href="#""></a>' +
95 '<input class="host-rename-input" type="text" hidden/>' +
96 '</div>' +
97 '<span tabindex="0" class="clickable host-list-edit rename-button">' +
98 '<img src="icon_pencil.webp" class="host-list-rename-icon">' +
99 '</span>';
100 if (this.onDelete_) {
101 html +=
102 '<span tabindex="0" class="clickable host-list-edit delete-button">' +
103 '<img src="icon_cross.webp" class="host-list-remove-icon">' +
104 '</span>';
106 return '<div class="section-row host-offline">' + html + '</div>';
110 * Create the HTML elements for this entry and set up event handlers.
111 * @return {void} Nothing.
113 remoting.HostTableEntry.prototype.createDom_ = function() {
114 var container = /** @type {HTMLElement} */ (document.createElement('div'));
115 container.innerHTML = this.getHTML_();
117 // Setup DOM references.
118 this.rootElement_ = /** @type {HTMLElement} */ (container.firstElementChild);
119 this.warningOverlay_ = container.querySelector('.warning-overlay');
120 this.hostNameLabel_ = container.querySelector('.host-name-label');
121 this.renameInputField_ = /** @type {HTMLInputElement} */ (
122 container.querySelector('.host-rename-input'));
124 // Register event handlers and set tooltips.
125 var editButton = container.querySelector('.rename-button');
126 var deleteButton = container.querySelector('.delete-button');
127 editButton.title = chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_RENAME');
128 this.registerButton_(editButton, this.beginRename_.bind(this));
129 this.registerButton_(this.rootElement_, this.onConnectButton_.bind(this));
130 if (deleteButton) {
131 this.registerButton_(deleteButton, this.showDeleteConfirmation_.bind(this));
132 deleteButton.title =
133 chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_DELETE');
137 /** @return {base.Disposable} @private */
138 remoting.HostTableEntry.prototype.registerButton_ = function(
139 /** HTMLElement */ button, /** Function */ callback) {
140 var onKeyDown = function(/** Event */ e) {
141 if (e.which === KeyCodes.ENTER || e.which === KeyCodes.SPACEBAR) {
142 callback();
143 e.stopPropagation();
146 var onClick = function(/** Event */ e) {
147 callback();
148 e.stopPropagation();
150 var onFocusChanged = this.onFocusChange_.bind(this);
151 this.disposables_.add(
152 new base.DomEventHook(button, 'click', onClick, false),
153 new base.DomEventHook(button, 'keydown', onKeyDown, false),
154 // Register focus and blur handlers to cause the parent node to be
155 // highlighted whenever a child link has keyboard focus. Note that this is
156 // only necessary because Chrome does not yet support the draft CSS
157 // Selectors 4 specification (http://www.w3.org/TR/selectors4/#subject),
158 // which provides a more elegant solution to this problem.
159 new base.DomEventHook(button, 'focus', onFocusChanged, false),
160 new base.DomEventHook(button, 'blur', onFocusChanged, false));
164 /** @return {void} @private */
165 remoting.HostTableEntry.prototype.onConnectButton_ = function() {
166 // Don't connect during renaming as this event fires if the user hits Enter
167 // after typing a new name.
168 if (!this.isRenaming_() && this.isOnline_()) {
169 var encodedHostId = encodeURIComponent(this.host.hostId);
170 this.onConnect_(encodedHostId);
174 /** @return {boolean} @private */
175 remoting.HostTableEntry.prototype.isOnline_ = function() {
176 return Boolean(this.host) && this.host.status === 'ONLINE';
179 /** @return {string} @private */
180 remoting.HostTableEntry.prototype.getHostDisplayName_ = function() {
181 if (this.isOnline_()) {
182 if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) {
183 return chrome.i18n.getMessage(
184 /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName);
186 return this.host.hostName;
187 } else {
188 if (this.host.updatedTime) {
189 var formattedTime = formatUpdateTime(this.host.updatedTime);
190 return chrome.i18n.getMessage(/*i18n-content*/ 'LAST_ONLINE',
191 [this.host.hostName, formattedTime]);
193 return chrome.i18n.getMessage(/*i18n-content*/ 'OFFLINE',
194 this.host.hostName);
199 * Update the UI to reflect the current status of the host
201 * @return {void} Nothing.
202 * @private
204 remoting.HostTableEntry.prototype.updateUI_ = function() {
205 if (!this.host) {
206 return;
208 var clickToConnect = this.isOnline_() && !this.isRenaming_();
209 var showOffline = !this.isOnline_();
210 this.rootElement_.querySelector('.box-spacer').id =
211 'host_' + this.host.hostId;
212 var connectLabel = chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_CONNECT',
213 this.host.hostName);
214 this.rootElement_.classList.toggle('clickable', clickToConnect);
215 this.rootElement_.title = (clickToConnect) ? connectLabel : '';
216 this.rootElement_.classList.toggle('host-online', !showOffline);
217 this.rootElement_.classList.toggle('host-offline', showOffline);
219 var hostReportedError = this.host.hostOfflineReason !== '';
220 var hostNeedsUpdate = remoting.Host.needsUpdate(
221 this.host, this.webappMajorVersion_);
222 this.warningOverlay_.hidden = !hostNeedsUpdate && !hostReportedError;
223 this.hostNameLabel_.innerText = this.getHostDisplayName_();
224 this.hostNameLabel_.title =
225 formatHostOfflineReason(this.host.hostOfflineReason);
226 this.renameInputField_.hidden = !this.isRenaming_();
227 this.hostNameLabel_.hidden = this.isRenaming_();
231 * Prepares the host for renaming by showing an edit box.
232 * @return {void} Nothing.
233 * @private
235 remoting.HostTableEntry.prototype.beginRename_ = function() {
236 this.renameInputField_.value = this.host.hostName;
237 base.debug.assert(this.renameInputEventHooks_ === null);
238 base.dispose(this.renameInputEventHooks_);
239 this.renameInputEventHooks_ = new base.Disposables(
240 new base.DomEventHook(this.renameInputField_, 'blur',
241 this.commitRename_.bind(this), false),
242 new base.DomEventHook(this.renameInputField_, 'keydown',
243 this.onKeydown_.bind(this), false));
244 this.updateUI_();
245 this.renameInputField_.focus();
248 /** @return {boolean} @private */
249 remoting.HostTableEntry.prototype.isRenaming_ = function() {
250 return Boolean(this.renameInputEventHooks_);
253 /** @return {void} @private */
254 remoting.HostTableEntry.prototype.commitRename_ = function() {
255 if (this.host.hostName != this.renameInputField_.value) {
256 this.host.hostName = this.renameInputField_.value;
257 this.onRename_(this);
259 this.hideEditBox_();
262 /** @return {void} @private */
263 remoting.HostTableEntry.prototype.hideEditBox_ = function() {
264 // onblur will fire when the edit box is removed, so remove the hook.
265 base.dispose(this.renameInputEventHooks_);
266 this.renameInputEventHooks_ = null;
267 // Update the tool-tip and event handler.
268 this.updateUI_();
272 * Handle a key event while the user is typing a host name
273 * @param {Event} event The keyboard event.
274 * @return {void} Nothing.
275 * @private
277 remoting.HostTableEntry.prototype.onKeydown_ = function(event) {
278 if (event.which == KeyCodes.ESCAPE) {
279 this.hideEditBox_();
280 } else if (event.which == KeyCodes.ENTER) {
281 this.commitRename_();
282 event.stopPropagation();
287 * Prompt the user to confirm or cancel deletion of a host.
288 * @return {void} Nothing.
289 * @private
291 remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() {
292 var message = document.getElementById('confirm-host-delete-message');
293 l10n.localizeElement(message, this.host.hostName);
294 var confirm = document.getElementById('confirm-host-delete');
295 var cancel = document.getElementById('cancel-host-delete');
296 this.onConfirmDeleteReference_ = this.confirmDelete_.bind(this);
297 this.onCancelDeleteReference_ = this.cancelDelete_.bind(this);
298 confirm.addEventListener('click', this.onConfirmDeleteReference_, false);
299 cancel.addEventListener('click', this.onCancelDeleteReference_, false);
300 remoting.setMode(remoting.AppMode.CONFIRM_HOST_DELETE);
304 * Confirm deletion of a host.
305 * @return {void} Nothing.
306 * @private
308 remoting.HostTableEntry.prototype.confirmDelete_ = function() {
309 this.onDelete_(this);
310 this.cleanUpConfirmationEventListeners_();
311 remoting.setMode(remoting.AppMode.HOME);
315 * Cancel deletion of a host.
316 * @return {void} Nothing.
317 * @private
319 remoting.HostTableEntry.prototype.cancelDelete_ = function() {
320 this.cleanUpConfirmationEventListeners_();
321 remoting.setMode(remoting.AppMode.HOME);
325 * Remove the confirm and cancel event handlers, which refer to this object.
326 * @return {void} Nothing.
327 * @private
329 remoting.HostTableEntry.prototype.cleanUpConfirmationEventListeners_ =
330 function() {
331 var confirm = document.getElementById('confirm-host-delete');
332 var cancel = document.getElementById('cancel-host-delete');
333 confirm.removeEventListener('click', this.onConfirmDeleteReference_, false);
334 cancel.removeEventListener('click', this.onCancelDeleteReference_, false);
335 this.onCancelDeleteReference_ = function() {};
336 this.onConfirmDeleteReference_ = function() {};
340 * Handle a focus change event within this table row.
341 * @return {void} Nothing.
342 * @private
344 remoting.HostTableEntry.prototype.onFocusChange_ = function() {
345 var element = document.activeElement;
346 while (element) {
347 if (element == this.rootElement_) {
348 this.rootElement_.classList.add('child-focused');
349 return;
351 element = element.parentNode;
353 this.rootElement_.classList.remove('child-focused');
357 * Formats host's updateTime value relative to current time (i.e. only
358 * displaying hours and minutes if updateTime is less than a day in the past).
359 * @param {string} updateTime RFC 3339 formatted date-time value.
360 * @return {string} Formatted value (i.e. 11/11/2014)
362 function formatUpdateTime(updateTime) {
363 var lastOnline = new Date(updateTime);
364 var now = new Date();
365 var displayString = '';
366 if (now.getFullYear() == lastOnline.getFullYear() &&
367 now.getMonth() == lastOnline.getMonth() &&
368 now.getDate() == lastOnline.getDate()) {
369 return lastOnline.toLocaleTimeString();
370 } else {
371 return lastOnline.toLocaleDateString();
376 * Formats host's host-offline-reason value (i.e. 'INVALID_HOST_CONFIGURATION')
377 * to a human-readable description of the error.
378 * @param {string} hostOfflineReason
379 * @return {string}
381 function formatHostOfflineReason(hostOfflineReason) {
382 if (!hostOfflineReason) {
383 return '';
385 var knownReasonTags = [
386 /*i18n-content*/'OFFLINE_REASON_INITIALIZATION_FAILED',
387 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_CONFIGURATION',
388 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_DOMAIN',
389 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_ID',
390 /*i18n-content*/'OFFLINE_REASON_INVALID_OAUTH_CREDENTIALS',
391 /*i18n-content*/'OFFLINE_REASON_LOGIN_SCREEN_NOT_SUPPORTED',
392 /*i18n-content*/'OFFLINE_REASON_POLICY_CHANGE_REQUIRES_RESTART',
393 /*i18n-content*/'OFFLINE_REASON_POLICY_READ_ERROR',
394 /*i18n-content*/'OFFLINE_REASON_SUCCESS_EXIT',
395 /*i18n-content*/'OFFLINE_REASON_USERNAME_MISMATCH'
397 var offlineReasonTag = 'OFFLINE_REASON_' + hostOfflineReason;
398 if (knownReasonTags.indexOf(offlineReasonTag) != (-1)) {
399 return chrome.i18n.getMessage(offlineReasonTag);
400 } else {
401 return chrome.i18n.getMessage(
402 /*i18n-content*/'OFFLINE_REASON_UNKNOWN', hostOfflineReason);
406 /** @enum {number} */
407 var KeyCodes = {
408 ENTER: 13,
409 ESCAPE: 27,
410 SPACEBAR: 32
413 })();