@switchbot/homebridge-switchbot 5.0.0-beta.134 → 5.0.0-beta.135

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/config.schema.json +0 -11
  2. package/dist/deviceFactory.d.ts +3 -1
  3. package/dist/deviceFactory.d.ts.map +1 -1
  4. package/dist/deviceFactory.js +3 -18
  5. package/dist/deviceFactory.js.map +1 -1
  6. package/dist/devices/genericDevice.d.ts +1 -1
  7. package/dist/devices/genericDevice.d.ts.map +1 -1
  8. package/dist/devices/genericDevice.js +79 -159
  9. package/dist/devices/genericDevice.js.map +1 -1
  10. package/dist/homebridge-ui/public/index.html +107 -124
  11. package/dist/homebridge-ui/public/js/api.d.ts +1 -4
  12. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  13. package/dist/homebridge-ui/public/js/api.js +1 -7
  14. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  15. package/dist/homebridge-ui/public/js/api.ts +1 -8
  16. package/dist/homebridge-ui/public/js/app.js +201 -90
  17. package/dist/homebridge-ui/public/js/app.js.map +2 -2
  18. package/dist/homebridge-ui/public/js/app.ts +5 -6
  19. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
  20. package/dist/homebridge-ui/public/js/credentials.js +1 -1
  21. package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
  22. package/dist/homebridge-ui/public/js/credentials.ts +7 -3
  23. package/dist/homebridge-ui/public/js/devices-delete.js +1 -1
  24. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -1
  25. package/dist/homebridge-ui/public/js/devices-delete.ts +1 -1
  26. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  27. package/dist/homebridge-ui/public/js/devices.js +0 -1
  28. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  29. package/dist/homebridge-ui/public/js/devices.ts +0 -2
  30. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
  31. package/dist/homebridge-ui/public/js/discovery.js +6 -3
  32. package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
  33. package/dist/homebridge-ui/public/js/discovery.ts +9 -3
  34. package/dist/homebridge-ui/public/js/logger.d.ts +1 -0
  35. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -1
  36. package/dist/homebridge-ui/public/js/logger.js +8 -3
  37. package/dist/homebridge-ui/public/js/logger.js.map +1 -1
  38. package/dist/homebridge-ui/public/js/logger.ts +12 -6
  39. package/dist/homebridge-ui/public/js/modals.d.ts +4 -0
  40. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  41. package/dist/homebridge-ui/public/js/modals.js +152 -59
  42. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  43. package/dist/homebridge-ui/public/js/modals.ts +187 -60
  44. package/dist/homebridge-ui/public/js/render.d.ts +3 -0
  45. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  46. package/dist/homebridge-ui/public/js/render.js +63 -24
  47. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  48. package/dist/homebridge-ui/public/js/render.ts +102 -51
  49. package/dist/homebridge-ui/utils/logger.js +1 -1
  50. package/dist/homebridge-ui/utils/logger.js.map +1 -1
  51. package/dist/platform.d.ts.map +1 -1
  52. package/dist/platform.js +38 -30
  53. package/dist/platform.js.map +1 -1
  54. package/dist/switchbotClient.d.ts +5 -5
  55. package/dist/switchbotClient.d.ts.map +1 -1
  56. package/dist/switchbotClient.js +21 -12
  57. package/dist/switchbotClient.js.map +1 -1
  58. package/docs/variables/default.html +1 -1
  59. package/package.json +1 -1
  60. package/scripts/build-ui.js +4 -3
  61. package/scripts/generate-matter-maps.js +34 -17
  62. package/scripts/sync-device-types.mjs +4 -1
  63. package/src/deviceFactory.ts +3 -18
  64. package/src/devices/genericDevice.ts +77 -164
  65. package/src/homebridge-ui/public/index.html +107 -124
  66. package/src/homebridge-ui/public/js/api.ts +1 -8
  67. package/src/homebridge-ui/public/js/app.ts +5 -6
  68. package/src/homebridge-ui/public/js/credentials.ts +7 -3
  69. package/src/homebridge-ui/public/js/devices-delete.ts +1 -1
  70. package/src/homebridge-ui/public/js/devices.ts +0 -2
  71. package/src/homebridge-ui/public/js/discovery.ts +9 -3
  72. package/src/homebridge-ui/public/js/logger.ts +12 -6
  73. package/src/homebridge-ui/public/js/modals.ts +187 -60
  74. package/src/homebridge-ui/public/js/render.ts +102 -51
  75. package/src/homebridge-ui/utils/logger.ts +1 -1
  76. package/src/platform.ts +38 -29
  77. package/src/switchbotClient.ts +42 -27
  78. package/test/accessory-restore.spec.ts +9 -8
  79. package/test/device-mapping.spec.ts +4 -3
  80. package/test/deviceFactory.spec.ts +4 -3
  81. package/test/e2e/run-e2e.spec.ts +6 -10
  82. package/test/fan-swing.spec.ts +7 -6
  83. package/test/helpers/matter-harness.ts +18 -7
  84. package/test/lock-users.spec.ts +5 -5
  85. package/test/matter-childbridge.spec.ts +10 -7
  86. package/test/matter-descriptors.spec.ts +8 -8
  87. package/test/matter-device-state.spec.ts +52 -36
  88. package/test/matter-integration.spec.ts +53 -6
  89. package/test/platform.integration.spec.ts +7 -5
  90. package/test/switchbot-client-debounce.spec.ts +36 -40
  91. package/test/switchbot-client-openapi.spec.ts +22 -15
  92. package/test/switchbotClient.spec.ts +4 -3
  93. package/test/utils.spec.ts +2 -1
@@ -1,7 +1,15 @@
1
1
  import { DEVICE_TYPES } from './constants.js'
2
2
  import { uiLog } from './logger.js'
3
3
 
4
- export async function importDiscoveredDevice(device: any): Promise<{ configDeviceName: string, configDeviceType: string, address?: string } | null> {
4
+ export async function importDiscoveredDevice(device: any): Promise<{
5
+ configDeviceName: string
6
+ configDeviceType: string
7
+ address?: string
8
+ connectionPreference?: string
9
+ room?: string
10
+ encryptionKey?: string
11
+ keyId?: string
12
+ } | null> {
5
13
  return new Promise((resolve) => {
6
14
  const div = document.createElement('div')
7
15
  div.style.position = 'fixed'
@@ -91,6 +99,51 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
91
99
  typeSelect.appendChild(optgroup)
92
100
  })
93
101
 
102
+ // --- Connection Preference ---
103
+ const connectionPrefLabel = document.createElement('label')
104
+ connectionPrefLabel.textContent = 'Connection Preference'
105
+ connectionPrefLabel.style.display = 'block'
106
+ connectionPrefLabel.style.marginBottom = '6px'
107
+ connectionPrefLabel.style.fontWeight = '500'
108
+ connectionPrefLabel.style.fontSize = '12px'
109
+ connectionPrefLabel.style.color = '#6b7280'
110
+
111
+ const connectionPrefSelect = document.createElement('select')
112
+ connectionPrefSelect.style.width = '100%'
113
+ connectionPrefSelect.style.marginBottom = '12px'
114
+ connectionPrefSelect.style.padding = '8px 10px'
115
+ connectionPrefSelect.style.borderRadius = '6px'
116
+ connectionPrefSelect.style.fontSize = '14px'
117
+ connectionPrefSelect.style.boxSizing = 'border-box';
118
+ ['auto', 'ble', 'openapi'].forEach((val) => {
119
+ const opt = document.createElement('option')
120
+ opt.value = val
121
+ opt.text = val.charAt(0).toUpperCase() + val.slice(1)
122
+ opt.selected = (device.connectionPreference || 'auto') === val
123
+ connectionPrefSelect.appendChild(opt)
124
+ })
125
+
126
+ // --- Room ---
127
+ const roomLabel = document.createElement('label')
128
+ roomLabel.textContent = 'Room'
129
+ roomLabel.style.display = 'block'
130
+ roomLabel.style.marginBottom = '6px'
131
+ roomLabel.style.fontWeight = '500'
132
+ roomLabel.style.fontSize = '12px'
133
+ roomLabel.style.color = '#6b7280'
134
+
135
+ const roomInput = document.createElement('input')
136
+ roomInput.type = 'text'
137
+ roomInput.value = device.room || ''
138
+ roomInput.placeholder = 'Optional room/location metadata'
139
+ roomInput.style.width = '100%'
140
+ roomInput.style.marginBottom = '12px'
141
+ roomInput.style.padding = '8px 10px'
142
+ roomInput.style.borderRadius = '6px'
143
+ roomInput.style.fontSize = '14px'
144
+ roomInput.style.boxSizing = 'border-box'
145
+
146
+ // --- BLE MAC Address ---
94
147
  const macLabel = document.createElement('label')
95
148
  macLabel.textContent = 'BLE MAC Address (optional)'
96
149
  macLabel.style.display = 'block'
@@ -110,7 +163,7 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
110
163
  macInput.style.fontSize = '14px'
111
164
  macInput.style.boxSizing = 'border-box'
112
165
 
113
- // BLE Encryption Key
166
+ // --- Encryption Key ---
114
167
  const encryptionKeyLabel = document.createElement('label')
115
168
  encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)'
116
169
  encryptionKeyLabel.style.display = 'block'
@@ -130,7 +183,7 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
130
183
  encryptionKeyInput.style.fontSize = '14px'
131
184
  encryptionKeyInput.style.boxSizing = 'border-box'
132
185
 
133
- // BLE Key ID
186
+ // --- Key ID ---
134
187
  const keyIdLabel = document.createElement('label')
135
188
  keyIdLabel.textContent = 'BLE Key ID (optional)'
136
189
  keyIdLabel.style.display = 'block'
@@ -171,7 +224,15 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
171
224
  importBtn.style.padding = '8px 20px'
172
225
  importBtn.style.fontSize = '13px'
173
226
 
174
- const cleanup = (result: { configDeviceName: string, configDeviceType: string, address?: string, encryptionKey?: string, keyId?: string } | null) => {
227
+ const cleanup = (result: {
228
+ configDeviceName: string
229
+ configDeviceType: string
230
+ address?: string
231
+ connectionPreference?: string
232
+ room?: string
233
+ encryptionKey?: string
234
+ keyId?: string
235
+ } | null) => {
175
236
  div.remove()
176
237
  resolve(result)
177
238
  }
@@ -181,6 +242,8 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
181
242
  configDeviceName: nameInput.value || device.name || device.id,
182
243
  configDeviceType: typeSelect.value || device.type,
183
244
  address: macInput.value || undefined,
245
+ connectionPreference: connectionPrefSelect.value || undefined,
246
+ room: roomInput.value || undefined,
184
247
  encryptionKey: encryptionKeyInput.value || undefined,
185
248
  keyId: keyIdInput.value || undefined,
186
249
  })
@@ -197,6 +260,10 @@ export async function importDiscoveredDevice(device: any): Promise<{ configDevic
197
260
  contentDiv.appendChild(nameInput)
198
261
  contentDiv.appendChild(typeLabel)
199
262
  contentDiv.appendChild(typeSelect)
263
+ contentDiv.appendChild(connectionPrefLabel)
264
+ contentDiv.appendChild(connectionPrefSelect)
265
+ contentDiv.appendChild(roomLabel)
266
+ contentDiv.appendChild(roomInput)
200
267
  contentDiv.appendChild(macLabel)
201
268
  contentDiv.appendChild(macInput)
202
269
  contentDiv.appendChild(encryptionKeyLabel)
@@ -250,58 +317,6 @@ export async function editDevice(device: any): Promise<void> {
250
317
  typeSelect.appendChild(optgroup)
251
318
  })
252
319
 
253
- // --- Matter Type Advanced Field (only for window covering types) ---
254
- const windowCoveringTypes = ['Roller Shade', 'Curtain', 'Curtain3', 'Blind Tilt']
255
- const showMatterType = windowCoveringTypes.includes(device.configDeviceType || device.deviceType || device.type || '')
256
- const matterTypeLabel = document.createElement('label')
257
- matterTypeLabel.textContent = 'Matter Window Covering Type (Advanced)'
258
- matterTypeLabel.style.display = showMatterType ? 'block' : 'none'
259
- matterTypeLabel.style.marginBottom = '6px'
260
- matterTypeLabel.style.fontWeight = '500'
261
- matterTypeLabel.style.fontSize = '12px'
262
- matterTypeLabel.style.color = '#6b7280'
263
- matterTypeLabel.style.marginTop = '2px'
264
- const matterTypes = [
265
- { value: '', label: 'Auto-detect (recommended)' },
266
- { value: 0, label: 'Roller Shade (0)' },
267
- { value: 1, label: 'Roman Shade (1)' },
268
- { value: 2, label: 'Balloon Shade (2)' },
269
- { value: 3, label: 'Woven Wood (3)' },
270
- { value: 4, label: 'Drapery (4)' },
271
- { value: 5, label: 'Awning (5)' },
272
- { value: 6, label: 'Shutter (6)' },
273
- { value: 7, label: 'Tilt Blind (7)' },
274
- { value: 8, label: 'Blind Tilt (8)' },
275
- { value: 9, label: 'Projector Screen (9)' },
276
- { value: 15, label: 'Other (15)' },
277
- ]
278
- const matterTypeSelect = document.createElement('select')
279
- matterTypeSelect.style.width = '100%'
280
- matterTypeSelect.style.marginBottom = '12px'
281
- matterTypeSelect.style.padding = '8px 10px'
282
- matterTypeSelect.style.borderRadius = '6px'
283
- matterTypeSelect.style.fontSize = '14px'
284
- matterTypeSelect.style.boxSizing = 'border-box'
285
- matterTypes.forEach((opt) => {
286
- const option = document.createElement('option')
287
- option.value = String(opt.value)
288
- option.textContent = opt.label
289
- if (device.matterType != null && String(opt.value) === String(device.matterType)) {
290
- option.selected = true
291
- }
292
- matterTypeSelect.appendChild(option)
293
- })
294
- function updateMatterTypeVisibility() {
295
- if (windowCoveringTypes.includes(typeSelect.value)) {
296
- matterTypeLabel.style.display = 'block'
297
- matterTypeSelect.style.display = 'block'
298
- } else {
299
- matterTypeLabel.style.display = 'none'
300
- matterTypeSelect.style.display = 'none'
301
- }
302
- }
303
- typeSelect.addEventListener('change', updateMatterTypeVisibility)
304
- updateMatterTypeVisibility()
305
320
  // Create modal dialog for editing
306
321
  const div = document.createElement('div')
307
322
  div.style.position = 'fixed'
@@ -402,6 +417,90 @@ export async function editDevice(device: any): Promise<void> {
402
417
  typeSelect.appendChild(optgroup)
403
418
  })
404
419
 
420
+ // --- Connection Preference ---
421
+ const connectionPrefLabel = document.createElement('label')
422
+ connectionPrefLabel.textContent = 'Connection Preference'
423
+ connectionPrefLabel.style.display = 'block'
424
+ connectionPrefLabel.style.marginBottom = '6px'
425
+ connectionPrefLabel.style.fontWeight = '500'
426
+ connectionPrefLabel.style.fontSize = '12px'
427
+ connectionPrefLabel.style.color = '#6b7280'
428
+
429
+ const connectionPrefSelect = document.createElement('select')
430
+ connectionPrefSelect.style.width = '100%'
431
+ connectionPrefSelect.style.marginBottom = '12px'
432
+ connectionPrefSelect.style.padding = '8px 10px'
433
+ connectionPrefSelect.style.borderRadius = '6px'
434
+ connectionPrefSelect.style.fontSize = '14px'
435
+ connectionPrefSelect.style.boxSizing = 'border-box'
436
+ ;['auto', 'ble', 'openapi'].forEach((val) => {
437
+ const opt = document.createElement('option')
438
+ opt.value = val
439
+ opt.text = val.charAt(0).toUpperCase() + val.slice(1)
440
+ opt.selected = (device.connectionPreference || 'auto') === val
441
+ connectionPrefSelect.appendChild(opt)
442
+ })
443
+
444
+ // --- Room ---
445
+ const roomLabel = document.createElement('label')
446
+ roomLabel.textContent = 'Room'
447
+ roomLabel.style.display = 'block'
448
+ roomLabel.style.marginBottom = '6px'
449
+ roomLabel.style.fontWeight = '500'
450
+ roomLabel.style.fontSize = '12px'
451
+ roomLabel.style.color = '#6b7280'
452
+
453
+ const roomInput = document.createElement('input')
454
+ roomInput.type = 'text'
455
+ roomInput.value = device.room || ''
456
+ roomInput.placeholder = 'Optional room/location metadata'
457
+ roomInput.style.width = '100%'
458
+ roomInput.style.marginBottom = '12px'
459
+ roomInput.style.padding = '8px 10px'
460
+ roomInput.style.borderRadius = '6px'
461
+ roomInput.style.fontSize = '14px'
462
+ roomInput.style.boxSizing = 'border-box'
463
+
464
+ // --- Encryption Key ---
465
+ const encryptionKeyLabel = document.createElement('label')
466
+ encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)'
467
+ encryptionKeyLabel.style.display = 'block'
468
+ encryptionKeyLabel.style.marginBottom = '6px'
469
+ encryptionKeyLabel.style.fontWeight = '500'
470
+ encryptionKeyLabel.style.fontSize = '12px'
471
+ encryptionKeyLabel.style.color = '#6b7280'
472
+
473
+ const encryptionKeyInput = document.createElement('input')
474
+ encryptionKeyInput.type = 'password'
475
+ encryptionKeyInput.value = device.encryptionKey || ''
476
+ encryptionKeyInput.placeholder = 'Paste device BLE encryption key'
477
+ encryptionKeyInput.style.width = '100%'
478
+ encryptionKeyInput.style.marginBottom = '12px'
479
+ encryptionKeyInput.style.padding = '8px 10px'
480
+ encryptionKeyInput.style.borderRadius = '6px'
481
+ encryptionKeyInput.style.fontSize = '14px'
482
+ encryptionKeyInput.style.boxSizing = 'border-box'
483
+
484
+ // --- Key ID ---
485
+ const keyIdLabel = document.createElement('label')
486
+ keyIdLabel.textContent = 'BLE Key ID (optional)'
487
+ keyIdLabel.style.display = 'block'
488
+ keyIdLabel.style.marginBottom = '6px'
489
+ keyIdLabel.style.fontWeight = '500'
490
+ keyIdLabel.style.fontSize = '12px'
491
+ keyIdLabel.style.color = '#6b7280'
492
+
493
+ const keyIdInput = document.createElement('input')
494
+ keyIdInput.type = 'text'
495
+ keyIdInput.value = device.keyId || ''
496
+ keyIdInput.placeholder = 'e.g. ff'
497
+ keyIdInput.style.width = '100%'
498
+ keyIdInput.style.marginBottom = '12px'
499
+ keyIdInput.style.padding = '8px 10px'
500
+ keyIdInput.style.borderRadius = '6px'
501
+ keyIdInput.style.fontSize = '14px'
502
+ keyIdInput.style.boxSizing = 'border-box'
503
+
405
504
  const errorMessage = document.createElement('div')
406
505
  errorMessage.style.color = 'var(--switchbot-red, #ef4444)'
407
506
  errorMessage.style.marginBottom = '12px'
@@ -442,10 +541,32 @@ export async function editDevice(device: any): Promise<void> {
442
541
  deviceId: device.id,
443
542
  configDeviceName: nameInput.value || undefined,
444
543
  configDeviceType: typeSelect.value,
445
- matterType: windowCoveringTypes.includes(typeSelect.value) && matterTypeSelect.value !== '' ? Number(matterTypeSelect.value) : undefined,
544
+ connectionPreference: connectionPrefSelect.value,
545
+ room: roomInput.value || undefined,
546
+ encryptionKey: encryptionKeyInput.value || undefined,
547
+ keyId: keyIdInput.value || undefined,
548
+ }
549
+
550
+ // Only include defined properties in the options object
551
+ const options: any = {}
552
+ if (params.connectionPreference !== undefined) {
553
+ options.connectionPreference = params.connectionPreference
554
+ }
555
+ if (params.room !== undefined) {
556
+ options.room = params.room
557
+ }
558
+ if (params.encryptionKey !== undefined) {
559
+ options.encryptionKey = params.encryptionKey
560
+ }
561
+ if (params.keyId !== undefined) {
562
+ options.keyId = params.keyId
446
563
  }
447
564
 
448
- await updateDevice(params.deviceId, params.configDeviceName, params.configDeviceType, { matterType: params.matterType })
565
+ await updateDevice(
566
+ params.deviceId,
567
+ params.configDeviceName,
568
+ params.configDeviceType,
569
+ )
449
570
  await syncParentPluginConfigFromDisk()
450
571
 
451
572
  // Refresh device list
@@ -469,8 +590,14 @@ export async function editDevice(device: any): Promise<void> {
469
590
  contentDiv.appendChild(apiTypeInput)
470
591
  contentDiv.appendChild(typeLabel)
471
592
  contentDiv.appendChild(typeSelect)
472
- contentDiv.appendChild(matterTypeLabel)
473
- contentDiv.appendChild(matterTypeSelect)
593
+ contentDiv.appendChild(connectionPrefLabel)
594
+ contentDiv.appendChild(connectionPrefSelect)
595
+ contentDiv.appendChild(roomLabel)
596
+ contentDiv.appendChild(roomInput)
597
+ contentDiv.appendChild(encryptionKeyLabel)
598
+ contentDiv.appendChild(encryptionKeyInput)
599
+ contentDiv.appendChild(keyIdLabel)
600
+ contentDiv.appendChild(keyIdInput)
474
601
  contentDiv.appendChild(errorMessage)
475
602
  contentDiv.appendChild(buttons)
476
603
 
@@ -58,6 +58,9 @@ export declare function getDiscoveryPreferences(): {
58
58
  /**
59
59
  * Save filter/sort preferences to localStorage
60
60
  * @param preferences Preferences object to save
61
+ * @param preferences.connectionType Connection type filter
62
+ * @param preferences.sortBy Sort criterion
63
+ * @param preferences.searchQuery Search query string
61
64
  */
62
65
  export declare function setDiscoveryPreferences(preferences: {
63
66
  connectionType: 'all' | 'ble' | 'api' | 'both' | 'ir';
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/render.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG;IAC9D,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,CA8CA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CA+BtE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAiB9E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAKpE;AAED,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAiBhF;AAED,wBAAgB,aAAa,IAAI,WAAW,CAK3C;AAgGD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,GAAG,GAAG,WAAW,CAoNjE;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,GAAG,EAAE,EACd,OAAO,GAAE;IACP,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;CACrD,GACL,OAAO,CAAC,WAAW,CAAC,CAgPtB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,GAAG,EAAE,EACd,cAAc,GAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAY,EAC7D,WAAW,SAAK,GACf,GAAG,EAAE,CAmCP;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,GAAG,EAAE,EACd,MAAM,GAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAqB,GACzD,GAAG,EAAE,CA8CP;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAA;IACrD,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAA;IACjD,WAAW,EAAE,MAAM,CAAA;CACpB,CAeA;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE;IACnD,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAA;IACrD,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAA;IACjD,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,IAAI,CAMP;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAsKlD"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/render.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG;IAC9D,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,CA8CA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CA+BtE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAiB9E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAKpE;AAED,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAmBhF;AAED,wBAAgB,aAAa,IAAI,WAAW,CAK3C;AAgGD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,GAAG,GAAG,WAAW,CA6OjE;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,GAAG,EAAE,EACd,OAAO,GAAE;IACP,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;CACrD,GACL,OAAO,CAAC,WAAW,CAAC,CAuPtB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,GAAG,EAAE,EACd,cAAc,GAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAY,EAC7D,WAAW,SAAK,GACf,GAAG,EAAE,CAmCP;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,GAAG,EAAE,EACd,MAAM,GAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAqB,GACzD,GAAG,EAAE,CA8CP;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAA;IACrD,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAA;IACjD,WAAW,EAAE,MAAM,CAAA;CACpB,CAeA;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE;IACnD,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAAA;IACrD,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAA;IACjD,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,IAAI,CAMP;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CA+KlD"}
@@ -1,3 +1,8 @@
1
+ // Move regexes to module scope to avoid re-compilation on every call
2
+ // import type { DEVICE_TYPES } from './constants.js' // Removed unused import
3
+ const SPACES_REGEX = /\s/g;
4
+ const CAMELCASE_REGEX = /([A-Z])/g;
5
+ const FIRST_CHAR_REGEX = /^./;
1
6
  /**
2
7
  * Get RSSI signal quality level and color based on dBm value
3
8
  * @param rssi Signal strength in dBm (typically -30 to -90)
@@ -234,23 +239,29 @@ export function renderDeviceDetailsPanel(device) {
234
239
  let batteryHistory = [];
235
240
  try {
236
241
  const raw = localStorage.getItem(batteryHistoryKey);
237
- if (raw)
242
+ if (raw) {
238
243
  batteryHistory = JSON.parse(raw);
244
+ }
245
+ }
246
+ catch (e) {
247
+ // Optionally log or handle error
239
248
  }
240
- catch { }
241
249
  const now = Date.now();
242
250
  if (typeof device?.battery === 'number') {
243
251
  // Only add if different from last or >1h since last
244
- const last = batteryHistory[batteryHistory.length - 1];
252
+ const last = batteryHistory.at(-1);
245
253
  if (!last || last.value !== device.battery || now - last.ts > 60 * 60 * 1000) {
246
254
  batteryHistory.push({ value: device.battery, ts: now });
247
255
  // Keep only last 30 entries (about a month if daily)
248
- if (batteryHistory.length > 30)
256
+ if (batteryHistory.length > 30) {
249
257
  batteryHistory = batteryHistory.slice(-30);
258
+ }
250
259
  try {
251
260
  localStorage.setItem(batteryHistoryKey, JSON.stringify(batteryHistory));
252
261
  }
253
- catch { }
262
+ catch (e) {
263
+ // Optionally log or handle error
264
+ }
254
265
  }
255
266
  }
256
267
  const rows = [
@@ -329,7 +340,9 @@ export function renderDeviceDetailsPanel(device) {
329
340
  chart.style.height = '28px';
330
341
  chart.style.display = 'flex';
331
342
  // SVG sparkline
332
- const w = 120, h = 24, pad = 2;
343
+ const w = 120;
344
+ const h = 24;
345
+ const pad = 2;
333
346
  const min = Math.min(...batteryHistory.map(b => b.value), 100);
334
347
  const max = Math.max(...batteryHistory.map(b => b.value), 0);
335
348
  const range = max - min || 1;
@@ -386,11 +399,26 @@ export function renderDeviceDetailsPanel(device) {
386
399
  }
387
400
  // --- Expose advanced/extra features dynamically ---
388
401
  const featureKeys = [
389
- 'airQuality', 'pm25', 'pm10', 'voc', 'co2', 'humidity', 'temperature',
390
- 'preset', 'mode', 'presetMode', 'direction', 'calibration', 'multiCommand',
391
- 'extendedInfo', 'segmentedControl', 'features', 'capabilities', 'state',
402
+ 'airQuality',
403
+ 'pm25',
404
+ 'pm10',
405
+ 'voc',
406
+ 'co2',
407
+ 'humidity',
408
+ 'temperature',
409
+ 'preset',
410
+ 'mode',
411
+ 'presetMode',
412
+ 'direction',
413
+ 'calibration',
414
+ 'multiCommand',
415
+ 'extendedInfo',
416
+ 'segmentedControl',
417
+ 'features',
418
+ 'capabilities',
419
+ 'state',
392
420
  ];
393
- const shown = new Set(rows.map(r => r.label.toLowerCase().replace(/\s/g, '')));
421
+ const shown = new Set(rows.map(r => r.label.toLowerCase().replace(SPACES_REGEX, '')));
394
422
  for (const key of featureKeys) {
395
423
  if (device && device[key] !== undefined && !shown.has(key.toLowerCase())) {
396
424
  const line = document.createElement('div');
@@ -402,7 +430,7 @@ export function renderDeviceDetailsPanel(device) {
402
430
  const label = document.createElement('span');
403
431
  label.style.fontWeight = '600';
404
432
  label.style.minWidth = '110px';
405
- label.textContent = key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase()) + ':';
433
+ label.textContent = `${key.replace(CAMELCASE_REGEX, ' $1').replace(FIRST_CHAR_REGEX, s => s.toUpperCase())}:`;
406
434
  const value = document.createElement('span');
407
435
  value.style.fontFamily = 'monospace';
408
436
  value.style.fontSize = '11px';
@@ -485,15 +513,17 @@ export async function renderDiscoveredDevices(devices, options = {}) {
485
513
  }
486
514
  // Show offline/unreachable indicator if device is offline
487
515
  let offline = false;
488
- let lastSeen = d.lastSeen || d.lastseen || d.updatedAt;
516
+ const lastSeen = d.lastSeen || d.lastseen || d.updatedAt;
489
517
  if (typeof d.offline === 'boolean') {
490
518
  offline = d.offline;
491
519
  }
492
520
  else if (lastSeen) {
493
521
  try {
494
522
  const last = new Date(lastSeen).getTime();
495
- if (!isNaN(last) && Date.now() - last > 1000 * 60 * 60) { // 1 hour
496
- offline = true;
523
+ if (!Number.isNaN(last)) {
524
+ if (Date.now() - last > 1000 * 60 * 60) { // 1 hour
525
+ offline = true;
526
+ }
497
527
  }
498
528
  }
499
529
  catch { }
@@ -549,8 +579,8 @@ export async function renderDiscoveredDevices(devices, options = {}) {
549
579
  if (typeof d.battery === 'number' && d.battery < 20) {
550
580
  const batteryWarn = document.createElement('span');
551
581
  batteryWarn.textContent = `⚠️ ${d.battery}%`;
552
- batteryWarn.style.cssText =
553
- d.battery < 10
582
+ batteryWarn.style.cssText
583
+ = d.battery < 10
554
584
  ? 'background: #dc2626; color: white; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 6px;'
555
585
  : 'background: #fbbf24; color: #111; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 6px;';
556
586
  batteryWarn.title = d.battery < 10 ? 'Battery critically low' : 'Battery low';
@@ -652,7 +682,7 @@ export function filterDevices(devices, connectionType = 'all', searchQuery = '')
652
682
  let filtered = [...devices];
653
683
  // Filter by connection type
654
684
  if (connectionType !== 'all') {
655
- filtered = filtered.filter(d => {
685
+ filtered = filtered.filter((d) => {
656
686
  if (connectionType === 'ir') {
657
687
  return d.isIR === true;
658
688
  }
@@ -671,7 +701,7 @@ export function filterDevices(devices, connectionType = 'all', searchQuery = '')
671
701
  // Filter by search query
672
702
  if (searchQuery.trim()) {
673
703
  const query = searchQuery.toLowerCase();
674
- filtered = filtered.filter(d => {
704
+ filtered = filtered.filter((d) => {
675
705
  const name = (d.name || '').toLowerCase();
676
706
  const id = (d.id || '').toLowerCase();
677
707
  const type = (d.type || '').toLowerCase();
@@ -710,11 +740,11 @@ export function sortDevices(devices, sortBy = 'name') {
710
740
  case 'connection': {
711
741
  // Sort by connection type: Both > BLE > OpenAPI > Others
712
742
  const connectionOrder = {
713
- 'Both': 0,
714
- 'BLE': 1,
715
- 'OpenAPI': 2,
716
- 'API': 2,
717
- 'Unknown': 3,
743
+ Both: 0,
744
+ BLE: 1,
745
+ OpenAPI: 2,
746
+ API: 2,
747
+ Unknown: 3,
718
748
  };
719
749
  sorted.sort((a, b) => {
720
750
  const aOrder = connectionOrder[a.connectionType || 'Unknown'] ?? 3;
@@ -755,6 +785,9 @@ export function getDiscoveryPreferences() {
755
785
  /**
756
786
  * Save filter/sort preferences to localStorage
757
787
  * @param preferences Preferences object to save
788
+ * @param preferences.connectionType Connection type filter
789
+ * @param preferences.sortBy Sort criterion
790
+ * @param preferences.searchQuery Search query string
758
791
  */
759
792
  export function setDiscoveryPreferences(preferences) {
760
793
  try {
@@ -890,7 +923,12 @@ export function renderDeviceList(list) {
890
923
  }, 1200);
891
924
  }
892
925
  catch (e) {
893
- alert('Failed to copy');
926
+ copyBtn.textContent = 'Failed';
927
+ copyBtn.classList.add('error');
928
+ setTimeout(() => {
929
+ copyBtn.textContent = 'Copy ID';
930
+ copyBtn.classList.remove('error');
931
+ }, 1200);
894
932
  }
895
933
  });
896
934
  const deleteBtn = document.createElement('button');
@@ -910,5 +948,6 @@ export function renderDeviceList(list) {
910
948
  li.appendChild(buttons);
911
949
  ul.appendChild(li);
912
950
  }
951
+ // No return value needed for void function
913
952
  }
914
953
  //# sourceMappingURL=render.js.map