Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview | 6 * @fileoverview |
| 7 * Class representing the host-list portion of the home screen UI. | 7 * Class representing the host-list portion of the home screen UI. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 'use strict'; | 10 'use strict'; |
| 11 | 11 |
| 12 /** @suppress {duplicate} */ | 12 /** @suppress {duplicate} */ |
| 13 var remoting = remoting || {}; | 13 var remoting = remoting || {}; |
| 14 | 14 |
| 15 /** | 15 /** |
| 16 * The deserialized form of the chromoting host as returned by Apiary. | |
| 17 * Note that the object has more fields than are detailed below--these | |
| 18 * are just the ones that we refer to directly. | |
| 16 * @constructor | 19 * @constructor |
| 17 */ | 20 */ |
| 18 remoting.Host = function() { | 21 remoting.Host = function() { |
| 19 /** @type {string} */ | 22 /** @type {string} */ |
| 20 this.hostName = ''; | 23 this.hostName = ''; |
| 21 /** @type {string} */ | 24 /** @type {string} */ |
| 22 this.hostId = ''; | 25 this.hostId = ''; |
| 23 /** @type {string} */ | 26 /** @type {string} */ |
| 24 this.status = ''; | 27 this.status = ''; |
| 25 /** @type {string} */ | 28 /** @type {string} */ |
| 26 this.jabberId = ''; | 29 this.jabberId = ''; |
| 27 /** @type {string} */ | 30 /** @type {string} */ |
| 28 this.publicKey = ''; | 31 this.publicKey = ''; |
| 29 } | 32 }; |
| 30 | 33 |
| 34 /** | |
| 35 * An entry in the host table. | |
| 36 * @constructor | |
| 37 */ | |
| 38 remoting.HostTableEntry = function() { | |
| 39 /** @type {remoting.Host} */ | |
| 40 this.host = null; | |
| 41 /** @type {Element} */ | |
| 42 this.tableRow = null; | |
| 43 /** @type {Element} */ | |
| 44 this.hostNameCell = null; | |
| 45 }; | |
|
Jamie
2011/11/17 21:42:09
I separated out the raw object (Host) we get back
simonmorris
2011/11/17 23:59:48
Maybe HostModel and HostView would be more familia
Jamie
2011/11/18 22:54:57
It's not really MVC pattern though. I'd prefer to
| |
| 31 | 46 |
| 32 /** | 47 /** |
| 33 * @constructor | 48 * @constructor |
| 34 * @param {Element} table The HTML <table> to contain host-list. | 49 * @param {Element} table The HTML <table> to contain host-list. |
| 35 * @param {Element} errorDiv The HTML <div> to display error messages. | 50 * @param {Element} errorDiv The HTML <div> to display error messages. |
| 36 */ | 51 */ |
| 37 remoting.HostList = function(table, errorDiv) { | 52 remoting.HostList = function(table, errorDiv) { |
| 38 this.table = table; | 53 this.table = table; |
| 39 this.errorDiv = errorDiv; | 54 this.errorDiv = errorDiv; |
| 40 /** @type {Array.<remoting.Host>} */ | 55 /** |
| 41 this.hosts = null; | 56 * @type {Array.<remoting.HostTableEntry>} |
| 42 } | 57 * @private |
| 58 */ | |
| 59 this.hostTableEntries = null; | |
| 60 }; | |
| 61 | |
| 62 /** | |
| 63 * Search the host list for a host with the specified id. | |
| 64 * @param {string} hostId The unique id of the host. | |
| 65 * @return {remoting.HostTableEntry?} The host table entry, if any. | |
| 66 */ | |
| 67 remoting.HostList.prototype.getHostForId = function(hostId) { | |
| 68 for (var i = 0; i < this.hostTableEntries.length; ++i) { | |
| 69 if (this.hostTableEntries[i].host.hostId == hostId) { | |
| 70 return this.hostTableEntries[i]; | |
| 71 } | |
| 72 } | |
| 73 return null; | |
| 74 }; | |
| 43 | 75 |
| 44 /** | 76 /** |
| 45 * Refresh the host list with up-to-date details. | 77 * Refresh the host list with up-to-date details. |
| 46 * @param {Array.<remoting.Host>} hosts The new host list. | 78 * @param {Array.<remoting.Host>} hosts The new host list. |
| 47 * @return {void} Nothing. | 79 * @return {void} Nothing. |
| 48 */ | 80 */ |
| 49 remoting.HostList.prototype.update = function(hosts) { | 81 remoting.HostList.prototype.update = function(hosts) { |
| 50 this.table.innerHTML = ''; | 82 this.table.innerHTML = ''; |
| 51 this.showError(null); | 83 this.showError(null); |
| 52 this.hosts = hosts; | 84 this.hostTableEntries = []; |
| 53 | 85 |
| 54 for (var i = 0; i < hosts.length; ++i) { | 86 for (var i = 0; i < hosts.length; ++i) { |
| 55 var host = hosts[i]; | 87 var host = hosts[i]; |
| 88 // Validate the entry to make sure it has all the fields we expect. | |
| 56 if (!host.hostName || !host.hostId || !host.status || !host.jabberId || | 89 if (!host.hostName || !host.hostId || !host.status || !host.jabberId || |
| 57 !host.publicKey) | 90 !host.publicKey) |
| 58 continue; | 91 continue; |
| 59 | 92 |
| 60 var hostEntry = document.createElement('tr'); | 93 var hostTableEntry = new remoting.HostTableEntry(); |
| 61 addClass(hostEntry, 'host-list-row'); | 94 hostTableEntry.host = host; |
| 95 | |
| 96 hostTableEntry.tableRow = document.createElement('tr'); | |
| 97 addClass(hostTableEntry.tableRow, 'host-list-row'); | |
| 62 | 98 |
| 63 var hostIcon = document.createElement('td'); | 99 var hostIcon = document.createElement('td'); |
| 100 addClass(hostIcon, 'host-list-row-start'); | |
| 64 var hostIconImage = document.createElement('img'); | 101 var hostIconImage = document.createElement('img'); |
| 65 hostIconImage.src = 'icon_host.png'; | 102 hostIconImage.src = 'icon_host.png'; |
| 66 hostIcon.className = 'host-list-row-start'; | 103 addClass(hostIconImage, 'host-list-main-icon'); |
| 67 hostIcon.appendChild(hostIconImage); | 104 hostIcon.appendChild(hostIconImage); |
| 68 hostEntry.appendChild(hostIcon); | 105 hostTableEntry.tableRow.appendChild(hostIcon); |
| 69 | 106 |
| 70 var hostName = document.createElement('td'); | 107 hostTableEntry.hostNameCell = document.createElement('td'); |
| 71 hostName.setAttribute('class', 'mode-select-label'); | 108 hostTableEntry.hostNameCell.setAttribute('class', 'mode-select-label'); |
| 72 hostName.appendChild(document.createTextNode(host.hostName)); | 109 hostTableEntry.hostNameCell.appendChild( |
| 73 hostEntry.appendChild(hostName); | 110 document.createTextNode(host.hostName)); |
| 111 hostTableEntry.hostNameCell.setAttribute( | |
| 112 'ondblclick', | |
| 113 'remoting.hostList.beginRenameHost("' + | |
| 114 host.hostId + '"); return false;'); | |
| 115 hostTableEntry.tableRow.appendChild(hostTableEntry.hostNameCell); | |
| 74 | 116 |
| 75 var hostStatus = document.createElement('td'); | 117 var hostStatus = document.createElement('td'); |
| 76 if (host.status == 'ONLINE') { | 118 if (host.status == 'ONLINE') { |
| 77 var connectButton = document.createElement('button'); | 119 var connectButton = document.createElement('button'); |
| 78 connectButton.setAttribute('class', 'mode-select-button'); | 120 connectButton.setAttribute('class', 'mode-select-button'); |
| 79 connectButton.setAttribute('type', 'button'); | 121 connectButton.setAttribute('type', 'button'); |
| 80 connectButton.setAttribute('onclick', | 122 connectButton.setAttribute('onclick', |
| 81 'remoting.connectHost("'+host.hostId+'")'); | 123 'remoting.connectHost("' + host.hostId + '")'); |
| 82 connectButton.innerHTML = | 124 connectButton.innerHTML = |
| 83 chrome.i18n.getMessage(/*i18n-content*/'CONNECT_BUTTON'); | 125 chrome.i18n.getMessage(/*i18n-content*/'CONNECT_BUTTON'); |
| 84 hostStatus.appendChild(connectButton); | 126 hostStatus.appendChild(connectButton); |
| 85 } else { | 127 } else { |
| 86 addClass(hostEntry, 'host-offline'); | 128 addClass(hostTableEntry.tableRow, 'host-offline'); |
| 87 hostStatus.innerHTML = chrome.i18n.getMessage(/*i18n-content*/'OFFLINE'); | 129 hostStatus.innerHTML = chrome.i18n.getMessage(/*i18n-content*/'OFFLINE'); |
| 88 } | 130 } |
| 89 hostStatus.className = 'host-list-row-end'; | 131 hostStatus.className = 'host-list-row-end'; |
| 90 hostEntry.appendChild(hostStatus); | 132 hostTableEntry.tableRow.appendChild(hostStatus); |
| 91 | 133 |
| 92 this.table.appendChild(hostEntry); | 134 var editButton = document.createElement('td'); |
| 135 editButton.setAttribute('onclick', 'remoting.hostList.beginRenameHost("' + | |
| 136 host.hostId + '")'); | |
| 137 addClass(editButton, 'clickable'); | |
| 138 addClass(editButton, 'host-list-edit'); | |
| 139 var penImage = document.createElement('img'); | |
| 140 penImage.src = 'icon_pencil.png'; | |
| 141 addClass(penImage, 'host-list-rename-icon'); | |
| 142 editButton.appendChild(penImage); | |
| 143 hostTableEntry.tableRow.appendChild(editButton); | |
| 144 | |
| 145 var removeButton = document.createElement('td'); | |
| 146 removeButton.setAttribute('onclick', 'remoting.hostList.removeHost("' + | |
| 147 host.hostId + '")'); | |
| 148 addClass(removeButton, 'clickable'); | |
| 149 addClass(removeButton, 'host-list-edit'); | |
| 150 var crossImage = document.createElement('img'); | |
| 151 crossImage.src = 'icon_cross.png'; | |
| 152 addClass(crossImage, 'host-list-remove-icon'); | |
| 153 removeButton.appendChild(crossImage); | |
| 154 hostTableEntry.tableRow.appendChild(removeButton); | |
|
simonmorris
2011/11/17 23:59:48
Clearer to make this new code a method of HostTabl
Jamie
2011/11/18 22:54:57
Good point, thanks for spotting this. I've refacto
| |
| 155 | |
| 156 this.hostTableEntries[i] = hostTableEntry; | |
| 157 this.table.appendChild(hostTableEntry.tableRow); | |
| 93 } | 158 } |
| 94 | 159 |
| 95 this.showOrHide_(this.hosts.length != 0); | 160 this.showOrHide_(this.hostTableEntries.length != 0); |
| 96 } | 161 }; |
| 97 | 162 |
| 98 /** | 163 /** |
| 99 * Display a localized error message. | 164 * Display a localized error message. |
| 100 * @param {remoting.Error?} errorTag The error to display, or NULL to clear any | 165 * @param {remoting.Error?} errorTag The error to display, or NULL to clear any |
| 101 * previous error. | 166 * previous error. |
| 102 * @return {void} Nothing. | 167 * @return {void} Nothing. |
| 103 */ | 168 */ |
| 104 remoting.HostList.prototype.showError = function(errorTag) { | 169 remoting.HostList.prototype.showError = function(errorTag) { |
| 105 this.table.innerHTML = ''; | 170 this.table.innerHTML = ''; |
| 106 if (errorTag) { | 171 if (errorTag) { |
| 107 l10n.localizeElementFromTag(this.errorDiv, | 172 l10n.localizeElementFromTag(this.errorDiv, |
| 108 /** @type {string} */ (errorTag)); | 173 /** @type {string} */ (errorTag)); |
| 109 this.showOrHide_(true); | 174 this.showOrHide_(true); |
| 110 } else { | 175 } else { |
| 111 this.errorDiv.innerText = ''; | 176 this.errorDiv.innerText = ''; |
| 112 } | 177 } |
| 113 } | 178 }; |
| 114 | 179 |
| 115 /** | 180 /** |
| 116 * Show or hide the host-list UI. | 181 * Show or hide the host-list UI. |
| 117 * @param {boolean} show True to show the UI, or false to hide it. | 182 * @param {boolean} show True to show the UI, or false to hide it. |
| 118 * @return {void} Nothing. | 183 * @return {void} Nothing. |
| 119 * @private | 184 * @private |
| 120 */ | 185 */ |
| 121 remoting.HostList.prototype.showOrHide_ = function(show) { | 186 remoting.HostList.prototype.showOrHide_ = function(show) { |
| 122 var parent = /** @type {Element} */ (this.table.parentNode); | 187 var parent = /** @type {Element} */ (this.table.parentNode); |
| 123 parent.hidden = !show; | 188 parent.hidden = !show; |
| 124 if (show) { | 189 if (show) { |
| 125 parent.style.height = parent.scrollHeight + 'px'; | 190 parent.style.height = parent.scrollHeight + 'px'; |
| 126 removeClass(parent, remoting.HostList.COLLAPSED_); | 191 removeClass(parent, remoting.HostList.COLLAPSED_); |
| 127 } else { | 192 } else { |
| 128 addClass(parent, remoting.HostList.COLLAPSED_); | 193 addClass(parent, remoting.HostList.COLLAPSED_); |
| 129 } | 194 } |
| 130 } | 195 }; |
| 196 | |
| 197 /** | |
| 198 * Remove a host from the list, and deregister it. | |
|
simonmorris
2011/11/17 23:59:48
Won't the user expect this to have some effect on
Jamie
2011/11/18 22:54:57
I think the long-term plan is to have hosts shut d
| |
| 199 * @param {string} hostId The id of the host to be removed. | |
| 200 * @return {void} Nothing. | |
| 201 */ | |
| 202 remoting.HostList.prototype.removeHost = function(hostId) { | |
| 203 /** @type {remoting.HostTableEntry} */ | |
| 204 var hostTableEntry = this.getHostForId(hostId); | |
| 205 if (!hostTableEntry) { | |
| 206 console.error('No host registered for id ' + hostId); | |
| 207 return; | |
| 208 } | |
| 209 hostTableEntry.tableRow.parentElement.removeChild(hostTableEntry.tableRow); | |
| 210 var index = this.hostTableEntries.indexOf(hostTableEntry); | |
| 211 if (index != -1) { // Since we've just found it, index must be >= 0 | |
| 212 this.hostTableEntries.splice(index, 1); | |
| 213 } | |
| 214 | |
| 215 /** @param {string} token */ | |
| 216 var deleteHost = function(token) { | |
| 217 var headers = { 'Authorization': 'OAuth ' + token }; | |
| 218 remoting.xhr.remove( | |
| 219 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, | |
| 220 function() {}, '', headers); | |
|
Wez
2011/11/17 22:58:58
Why not refresh the host list when the XHR complet
Jamie
2011/11/18 22:54:57
I'm not sure about that. It's only going to make a
| |
| 221 } | |
| 222 remoting.oauth2.callWithToken(deleteHost); | |
| 223 | |
| 224 this.showOrHide_(this.hostTableEntries.length != 0); | |
| 225 }; | |
| 226 | |
| 227 /** | |
| 228 * Prepare a host for renaming by replacing its name with an edit box. | |
| 229 * @param {string} hostId The id of the host to be renamed. | |
| 230 * @return {void} Nothing. | |
| 231 */ | |
| 232 remoting.HostList.prototype.beginRenameHost = function(hostId) { | |
| 233 /** @type {remoting.HostTableEntry} */ | |
| 234 var hostTableEntry = this.getHostForId(hostId); | |
| 235 if (!hostTableEntry) { | |
| 236 console.error('No host registered for id ' + hostId); | |
| 237 return; | |
| 238 } | |
| 239 var editBox = /** @type {HTMLInputElement} */ document.createElement('input'); | |
| 240 editBox.type = 'text'; | |
| 241 editBox.value = hostTableEntry.host.hostName; | |
| 242 editBox.setAttribute('onblur', | |
| 243 'remoting.hostList.commitRenameHost("' + hostId + '");'); | |
| 244 /** @type {remoting.HostList} */ | |
| 245 var that = this; | |
| 246 /** @param {Event} event */ | |
| 247 var onKeydown = function(event) { | |
| 248 that.onKeydown(event, hostId); | |
| 249 } | |
| 250 editBox.onkeydown = onKeydown; | |
| 251 hostTableEntry.hostNameCell.innerHTML = ''; | |
| 252 hostTableEntry.hostNameCell.appendChild(editBox); | |
| 253 editBox.select(); | |
|
simonmorris
2011/11/17 23:59:48
This could be a method of HostView.
Jamie
2011/11/18 22:54:57
My refactoring addresses this.
| |
| 254 }; | |
| 255 | |
| 256 /** | |
| 257 * Remove the edit box corresponding to the specified host, and reset its name. | |
| 258 * @param {remoting.HostTableEntry} hostTableEntry The host being renamed. | |
| 259 * @return {void} Nothing. | |
| 260 */ | |
| 261 remoting.HostList.prototype.removeEditBox = function(hostTableEntry) { | |
| 262 var editBox = hostTableEntry.hostNameCell.querySelector('input'); | |
| 263 if (editBox) { | |
| 264 // onblur will fire when the edit box is removed, so remove the hook. | |
| 265 editBox.onblur = null; | |
| 266 } | |
| 267 hostTableEntry.hostNameCell.innerHTML = ''; | |
| 268 hostTableEntry.hostNameCell.appendChild( | |
| 269 document.createTextNode(hostTableEntry.host.hostName)); | |
| 270 }; | |
| 271 | |
| 272 /** | |
| 273 * Accept the host name entered by the user. | |
| 274 * @param {string} hostId The id of the host to be renamed. | |
| 275 * @return {void} Nothing. | |
| 276 */ | |
| 277 remoting.HostList.prototype.commitRenameHost = function(hostId) { | |
| 278 /** @type {remoting.HostTableEntry} */ | |
| 279 var hostTableEntry = this.getHostForId(hostId); | |
| 280 if (hostTableEntry) { | |
| 281 var editBox = hostTableEntry.hostNameCell.querySelector('input'); | |
| 282 if (editBox) { | |
| 283 if (hostTableEntry.host.hostName != editBox.value) { | |
| 284 hostTableEntry.host.hostName = editBox.value; | |
| 285 hostTableEntry.host.status = 'OFFLINE'; | |
|
simonmorris
2011/11/17 23:59:48
Why is the status set here?
Jamie
2011/11/18 22:54:57
Left-over debugging code. Removed.
| |
| 286 /** @param {string} token */ | |
| 287 var renameHost = function(token) { | |
| 288 var headers = { | |
| 289 'Authorization': 'OAuth ' + token, | |
| 290 'content-type' : 'application/json; charset=UTF-8' | |
|
Wez
2011/11/17 22:58:58
nit: content-type capitalization.
Jamie
2011/11/18 22:54:57
Done.
| |
| 291 }; | |
| 292 var newHostDetails = { data: hostTableEntry.host }; | |
| 293 remoting.xhr.put( | |
| 294 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, | |
| 295 function(xhr) {}, | |
|
Wez
2011/11/17 22:58:58
Why not refresh when the XHR returns?
Jamie
2011/11/18 22:54:57
Same argument as above.
| |
| 296 JSON.stringify(newHostDetails), | |
| 297 headers); | |
| 298 } | |
| 299 remoting.oauth2.callWithToken(renameHost); | |
| 300 } | |
| 301 } | |
| 302 } | |
| 303 this.removeEditBox(hostTableEntry); | |
| 304 }; | |
| 305 | |
| 306 /** | |
| 307 * Revert the host name, ignoring changes made by the user. | |
| 308 * @param {string} hostId The id of the host to be renamed. | |
| 309 * @return {void} Nothing. | |
| 310 */ | |
| 311 remoting.HostList.prototype.cancelRenameHost = function(hostId) { | |
| 312 var host = this.getHostForId(hostId); | |
|
Wez
2011/11/17 22:58:58
How can the user have cancelled editing if the hos
Jamie
2011/11/18 22:54:57
It's just a sanity check. I'd make it a DCHECK if
| |
| 313 if (host) { | |
| 314 this.removeEditBox(host); | |
| 315 } | |
| 316 }; | |
| 317 | |
| 318 /** | |
| 319 * Handle a key event while the user is typing a host name | |
| 320 * @param {Event} event The keyboard event. | |
| 321 * @param {string} hostId The id of the host being renamed. | |
| 322 * @return {void} Nothing. | |
| 323 */ | |
| 324 remoting.HostList.prototype.onKeydown = function(event, hostId) { | |
| 325 if (event.which == 27) { // Escape | |
| 326 this.cancelRenameHost(hostId); | |
| 327 } else if (event.which == 13) { // Enter | |
| 328 this.commitRenameHost(hostId); | |
| 329 } | |
| 330 }; | |
| 131 | 331 |
| 132 /** | 332 /** |
| 133 * Class name for the host list when it is collapsed. | 333 * Class name for the host list when it is collapsed. |
| 134 * @private | 334 * @private |
| 135 */ | 335 */ |
| 136 remoting.HostList.COLLAPSED_ = 'collapsed'; | 336 remoting.HostList.COLLAPSED_ = 'collapsed'; |
| 137 | 337 |
| 138 /** @type {remoting.HostList} */ | 338 /** @type {remoting.HostList} */ |
| 139 remoting.hostList = null; | 339 remoting.hostList = null; |
|
Wez
2011/11/17 22:58:58
Newline change?
Jamie
2011/11/18 22:54:57
Yep. The original was missing a newline.
| |
| OLD | NEW |