| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 * Javascript for Device-related features, served from | 6 * Javascript for Device-related features, served from |
| 7 * chrome://bluetooth-internals/. | 7 * chrome://bluetooth-internals/. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 /** @typedef {function(device.Device): Promise} */ |
| 11 var ConnectionHandler; |
| 12 |
| 10 cr.define('device', function() { | 13 cr.define('device', function() { |
| 11 | 14 |
| 15 |
| 12 var REMOVED_CSS = 'removed'; | 16 var REMOVED_CSS = 'removed'; |
| 13 | 17 |
| 14 /** | 18 /** |
| 15 * A table that lists the devices and responds to changes in the given | 19 * A table that lists the devices and responds to changes in the given |
| 16 * DeviceCollection. | 20 * DeviceCollection. |
| 17 * @constructor | 21 * @constructor |
| 18 * @extends {HTMLTableElement} | 22 * @extends {HTMLTableElement} |
| 19 */ | 23 */ |
| 20 var DeviceTable = cr.ui.define(function() { | 24 var DeviceTable = cr.ui.define(function() { |
| 21 this.devices_ = null; | 25 this.devices_ = null; |
| 26 this.connectHandler_ = null; |
| 27 this.disconnectHandler_ = null; |
| 22 | 28 |
| 23 return document.importNode($('table-template').content.children[0], | 29 return document.importNode($('table-template').content.children[0], |
| 24 true /* deep */); | 30 true /* deep */); |
| 25 }); | 31 }); |
| 26 DeviceTable.prototype = { | 32 DeviceTable.prototype = { |
| 27 __proto__: HTMLTableElement.prototype, | 33 __proto__: HTMLTableElement.prototype, |
| 28 | 34 |
| 29 /** | 35 /** |
| 30 * Decorates an element as a UI element class. Caches references to the | 36 * Decorates an element as a UI element class. Caches references to the |
| 31 * table body and headers. | 37 * table body and headers. |
| 32 */ | 38 */ |
| 33 decorate: function() { | 39 decorate: function() { |
| 34 this.body_ = this.tBodies[0]; | 40 this.body_ = this.tBodies[0]; |
| 35 this.headers_ = this.tHead.rows[0].cells; | 41 this.headers_ = this.tHead.rows[0].cells; |
| 36 }, | 42 }, |
| 37 | 43 |
| 38 /** | 44 /** |
| 45 * Sets the connection handlers for this device table. Handlers are expected |
| 46 * to return a Promise which resolves or throws an Error. The message |
| 47 * given in the error is displayed in the last column of the table. |
| 48 * @param {ConnectionHandler} connectHandler Handler that is called |
| 49 * when a connection needs to be created. |
| 50 * @param {ConnectionHandler} disconnectHandler Handler that is |
| 51 * called when a connection needs to be closed. |
| 52 */ |
| 53 setConnectionHandlers: function(connectHandler, disconnectHandler) { |
| 54 this.connectHandler_ = connectHandler; |
| 55 this.disconnectHandler_ = disconnectHandler; |
| 56 }, |
| 57 |
| 58 /** |
| 39 * Sets the tables device collection. | 59 * Sets the tables device collection. |
| 40 * @param {!DeviceCollection} deviceCollection | 60 * @param {!DeviceCollection} deviceCollection |
| 41 */ | 61 */ |
| 42 setDevices: function(deviceCollection) { | 62 setDevices: function(deviceCollection) { |
| 43 if (this.devices_) { | 63 if (this.devices_) { |
| 44 this.devices_.removeEventListener('sorted', this.redraw_.bind(this)); | 64 this.devices_.removeEventListener('sorted', this.redraw_.bind(this)); |
| 45 this.devices_.removeEventListener('change', | 65 this.devices_.removeEventListener('change', |
| 46 this.handleChange_.bind(this)); | 66 this.handleChange_.bind(this)); |
| 47 this.devices_.removeEventListener('splice', | 67 this.devices_.removeEventListener('splice', |
| 48 this.handleSplice_.bind(this)); | 68 this.handleSplice_.bind(this)); |
| 49 } | 69 } |
| 50 | 70 |
| 51 this.devices_ = deviceCollection; | 71 this.devices_ = deviceCollection; |
| 52 this.devices_.addEventListener('sorted', this.redraw_.bind(this)); | 72 this.devices_.addEventListener('sorted', this.redraw_.bind(this)); |
| 53 this.devices_.addEventListener('change', this.handleChange_.bind(this)); | 73 this.devices_.addEventListener('change', this.handleChange_.bind(this)); |
| 54 this.devices_.addEventListener('splice', this.handleSplice_.bind(this)); | 74 this.devices_.addEventListener('splice', this.handleSplice_.bind(this)); |
| 55 | 75 |
| 56 this.redraw_(); | 76 this.redraw_(); |
| 57 }, | 77 }, |
| 58 | 78 |
| 59 /** | 79 /** |
| 60 * Updates table row on change event of the device collection. | 80 * Updates table row on change event of the device collection. |
| 61 * @param {!Event} event | 81 * @param {!Event} event |
| 62 */ | 82 */ |
| 63 handleChange_: function(event) { | 83 handleChange_: function(event) { |
| 64 this.updateRow_(this.devices_.item(event.index), event.index); | 84 this.updateRow_(this.devices_.item(event.index), event.index); |
| 65 }, | 85 }, |
| 66 | 86 |
| 67 /** | 87 /** |
| 88 * Generates a function to handle click events on the connect button for the |
| 89 * given |row|. |
| 90 * @param {HTMLTableRowElement} row The table row that was clicked. |
| 91 * @return {function(Event)} |
| 92 */ |
| 93 handleConnect_: function(row) { |
| 94 return function(event) { |
| 95 var cellCount = row.cells.length; |
| 96 var connectCell = row.cells[cellCount - 2]; |
| 97 var connectButton = connectCell.children[0]; |
| 98 var connectErrorCell = row.cells[cellCount - 1]; |
| 99 |
| 100 connectErrorCell.textContent = ''; |
| 101 |
| 102 var device = this.devices_.getByAddress(row.id); |
| 103 if (this.connectHandler_ && !device.proxy) { |
| 104 connectButton.textContent = 'Connecting...'; |
| 105 connectButton.disabled = true; |
| 106 |
| 107 this.connectHandler_(device).catch(function(error) { |
| 108 connectErrorCell.textContent = error.message; |
| 109 connectButton.textContent = 'Connect'; |
| 110 connectButton.disabled = false; |
| 111 }); |
| 112 } else if (this.disconnectHandler_ && device.proxy) { |
| 113 connectButton.textContent = 'Disconnecting...'; |
| 114 connectButton.disabled = true; |
| 115 |
| 116 this.disconnectHandler_(device); |
| 117 } |
| 118 }.bind(this); |
| 119 }, |
| 120 |
| 121 /** |
| 68 * Updates table row on splice event of the device collection. | 122 * Updates table row on splice event of the device collection. |
| 69 * @param {!Event} event | 123 * @param {!Event} event |
| 70 */ | 124 */ |
| 71 handleSplice_: function(event) { | 125 handleSplice_: function(event) { |
| 72 event.removed.forEach(function() { | 126 event.removed.forEach(function() { |
| 73 this.removeRow(event.index); | 127 this.removeRow(event.index); |
| 74 }, this); | 128 }, this); |
| 75 | 129 |
| 76 event.added.forEach(function(device, index) { | 130 event.added.forEach(function(device, index) { |
| 77 this.newRowForDevice_(device, event.index + index); | 131 this.newRowForDevice_(device, event.index + index); |
| 78 }, this); | 132 }, this); |
| 79 }, | 133 }, |
| 80 | 134 |
| 81 /** | 135 /** |
| 82 * Inserts a new row at |index| and updates it with info from |device|. | 136 * Inserts a new row at |index| and updates it with info from |device|. |
| 83 * @param {!Device} device | 137 * @param {!Device} device |
| 84 * @param {?number} index | 138 * @param {?number} index |
| 85 */ | 139 */ |
| 86 newRowForDevice_: function(device, index) { | 140 newRowForDevice_: function(device, index) { |
| 87 var row = this.body_.insertRow(index); | 141 var row = this.body_.insertRow(index); |
| 88 row.id = device.info.address; | 142 row.id = device.info.address; |
| 89 | 143 |
| 90 for (var i = 0; i < this.headers_.length; i++) { | 144 for (var i = 0; i < this.headers_.length; i++) { |
| 91 row.insertCell(); | 145 row.insertCell(); |
| 92 } | 146 } |
| 93 | 147 |
| 148 // Make two extra cells for the connect button and connect errors. |
| 149 var connectCell = row.insertCell(); |
| 150 var connectErrorCell = row.insertCell(); |
| 151 |
| 152 var connectButton = document.createElement('button'); |
| 153 connectCell.appendChild(connectButton); |
| 154 |
| 155 connectButton.textContent = 'Connect'; |
| 156 connectButton.addEventListener('click', |
| 157 this.handleConnect_(row).bind(this)); |
| 158 |
| 94 this.updateRow_(device, row.sectionRowIndex); | 159 this.updateRow_(device, row.sectionRowIndex); |
| 95 }, | 160 }, |
| 96 | 161 |
| 97 /** | 162 /** |
| 98 * Deletes and recreates the table using the cached |devices_|. | 163 * Deletes and recreates the table using the cached |devices_|. |
| 99 */ | 164 */ |
| 100 redraw_: function() { | 165 redraw_: function() { |
| 101 this.removeChild(this.body_); | 166 this.removeChild(this.body_); |
| 102 this.appendChild(document.createElement('tbody')); | 167 this.appendChild(document.createElement('tbody')); |
| 103 this.body_ = this.tBodies[0]; | 168 this.body_ = this.tBodies[0]; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 115 */ | 180 */ |
| 116 updateRow_: function(device, index) { | 181 updateRow_: function(device, index) { |
| 117 var row = this.body_.rows[index]; | 182 var row = this.body_.rows[index]; |
| 118 | 183 |
| 119 if (device.removed) { | 184 if (device.removed) { |
| 120 row.classList.add(REMOVED_CSS); | 185 row.classList.add(REMOVED_CSS); |
| 121 } else { | 186 } else { |
| 122 row.classList.remove(REMOVED_CSS); | 187 row.classList.remove(REMOVED_CSS); |
| 123 } | 188 } |
| 124 | 189 |
| 190 var cellCount = row.cells.length; |
| 191 var connectCell = row.cells[cellCount - 2]; |
| 192 var connectButton = connectCell.children[0]; |
| 193 var connectErrorCell = row.cells[cellCount - 1]; |
| 194 |
| 195 if (device.info.is_gatt_connected) { |
| 196 connectButton.textContent = 'Disconnect'; |
| 197 } else if (device.proxy) { |
| 198 connectButton.textContent = 'Connect'; |
| 199 connectErrorCell.textContent = 'Lost connection'; |
| 200 device.proxy = null; |
| 201 } else { |
| 202 connectButton.textContent = 'Connect'; |
| 203 } |
| 204 |
| 205 connectButton.disabled = false; |
| 206 |
| 125 // Update the properties based on the header field path. | 207 // Update the properties based on the header field path. |
| 126 for (var i = 0; i < this.headers_.length; i++) { | 208 for (var i = 0; i < this.headers_.length; i++) { |
| 127 var header = this.headers_[i]; | 209 var header = this.headers_[i]; |
| 128 var propName = header.dataset.field; | 210 var propName = header.dataset.field; |
| 129 | 211 |
| 130 var parts = propName.split('.'); | 212 var parts = propName.split('.'); |
| 131 var obj = device.info; | 213 var obj = device.info; |
| 132 while (obj != null && parts.length > 0) { | 214 while (obj != null && parts.length > 0) { |
| 133 var part = parts.shift(); | 215 var part = parts.shift(); |
| 134 obj = obj[part]; | 216 obj = obj[part]; |
| 135 } | 217 } |
| 136 | 218 |
| 137 var cell = row.cells[i]; | 219 var cell = row.cells[i]; |
| 138 cell.textContent = obj || 'Unknown'; | 220 cell.textContent = obj || 'Unknown'; |
| 139 cell.dataset.label = header.innerText; | 221 cell.dataset.label = header.textContent; |
| 140 } | 222 } |
| 141 }, | 223 }, |
| 142 }; | 224 }; |
| 143 | 225 |
| 144 /* | 226 /** |
| 145 * Collection of devices with helper functions for searching and updating by | 227 * Collection of devices with helper functions for searching and updating by |
| 146 * device address. | 228 * device address. |
| 147 * @constructor | 229 * @constructor |
| 148 * @param {!Array} array the starting collection of devices. | 230 * @param {!Array} array the starting collection of devices. |
| 149 * @extends {cr.ui.ArrayDataModel} | 231 * @extends {cr.ui.ArrayDataModel} |
| 150 */ | 232 */ |
| 151 var DeviceCollection = function(array) { | 233 var DeviceCollection = function(array) { |
| 152 cr.ui.ArrayDataModel.call(this, array); | 234 cr.ui.ArrayDataModel.call(this, array); |
| 153 }; | 235 }; |
| 154 DeviceCollection.prototype = { | 236 DeviceCollection.prototype = { |
| 155 __proto__: cr.ui.ArrayDataModel.prototype, | 237 __proto__: cr.ui.ArrayDataModel.prototype, |
| 156 getByAddress: function(address) { | 238 getByAddress: function(address) { |
| 157 for (var i = 0; i < this.length; i++) { | 239 for (var i = 0; i < this.length; i++) { |
| 158 var device = this.item(i); | 240 var device = this.item(i); |
| 159 if (address == device.info.address) | 241 if (address == device.info.address) |
| 160 return device; | 242 return device; |
| 161 } | 243 } |
| 162 return null; | 244 return null; |
| 163 }, | 245 }, |
| 164 update: function(device) { | 246 update: function(device) { |
| 165 var oldDevice = this.getByAddress(device.info.address); | 247 var oldDevice = this.getByAddress(device.info.address); |
| 166 if (oldDevice) { | 248 if (oldDevice) { |
| 167 this.replaceItem(oldDevice, device); | 249 this.replaceItem(oldDevice, device); |
| 168 } else { | 250 } else { |
| 169 this.push(device); | 251 this.push(device); |
| 170 } | 252 } |
| 171 } | 253 } |
| 172 }; | 254 }; |
| 173 | 255 |
| 174 /* | 256 /** |
| 175 * Data model for a cached device. | 257 * Data model for a cached device. |
| 176 * @constructor | 258 * @constructor |
| 177 * @param {!bluetoothDevice.DeviceInfo} info | 259 * @param {!bluetoothDevice.DeviceInfo} info |
| 178 */ | 260 */ |
| 179 var Device = function(info) { | 261 var Device = function(info) { |
| 180 this.info = info; | 262 this.info = info; |
| 263 this.proxy = null; |
| 181 this.removed = false; | 264 this.removed = false; |
| 182 }; | 265 }; |
| 183 | 266 |
| 184 return { | 267 return { |
| 185 Device: Device, | 268 Device: Device, |
| 186 DeviceCollection, DeviceCollection, | 269 DeviceCollection, DeviceCollection, |
| 187 DeviceTable: DeviceTable, | 270 DeviceTable: DeviceTable, |
| 188 }; | 271 }; |
| 189 }); | 272 }); |
| OLD | NEW |