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.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 |