@switchbot/homebridge-switchbot 5.0.0-beta.109 → 5.0.0-beta.110

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 (78) hide show
  1. package/config.schema.json +4 -0
  2. package/dist/device-types.d.ts +18 -2
  3. package/dist/device-types.d.ts.map +1 -1
  4. package/dist/device-types.js +71 -0
  5. package/dist/device-types.js.map +1 -1
  6. package/dist/deviceFactory.d.ts.map +1 -1
  7. package/dist/deviceFactory.js +1 -0
  8. package/dist/deviceFactory.js.map +1 -1
  9. package/dist/homebridge-ui/endpoints/config.d.ts +0 -3
  10. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -1
  11. package/dist/homebridge-ui/endpoints/config.js +40 -9
  12. package/dist/homebridge-ui/endpoints/config.js.map +1 -1
  13. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -1
  14. package/dist/homebridge-ui/endpoints/devices.js +59 -2
  15. package/dist/homebridge-ui/endpoints/devices.js.map +1 -1
  16. package/dist/homebridge-ui/public/css/styles.css +254 -0
  17. package/dist/homebridge-ui/public/index.html +23 -168
  18. package/dist/homebridge-ui/public/js/api.d.ts +4 -0
  19. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  20. package/dist/homebridge-ui/public/js/api.js +14 -0
  21. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  22. package/dist/homebridge-ui/public/js/api.ts +17 -1
  23. package/dist/homebridge-ui/public/js/app.js +556 -44
  24. package/dist/homebridge-ui/public/js/app.js.map +3 -3
  25. package/dist/homebridge-ui/public/js/app.ts +2 -1
  26. package/dist/homebridge-ui/public/js/devices-delete.d.ts +1 -0
  27. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -1
  28. package/dist/homebridge-ui/public/js/devices-delete.js +108 -1
  29. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -1
  30. package/dist/homebridge-ui/public/js/devices-delete.ts +124 -1
  31. package/dist/homebridge-ui/public/js/devices.d.ts +1 -0
  32. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  33. package/dist/homebridge-ui/public/js/devices.js +10 -0
  34. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  35. package/dist/homebridge-ui/public/js/devices.ts +10 -0
  36. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
  37. package/dist/homebridge-ui/public/js/discovery.js +130 -4
  38. package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
  39. package/dist/homebridge-ui/public/js/discovery.ts +158 -4
  40. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  41. package/dist/homebridge-ui/public/js/modals.js +71 -26
  42. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  43. package/dist/homebridge-ui/public/js/modals.ts +74 -27
  44. package/dist/homebridge-ui/public/js/render.d.ts +57 -0
  45. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  46. package/dist/homebridge-ui/public/js/render.js +286 -13
  47. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  48. package/dist/homebridge-ui/public/js/render.ts +331 -11
  49. package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
  50. package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
  51. package/dist/homebridge-ui/utils/device-migration.js +111 -0
  52. package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
  53. package/dist/platform.js +2 -2
  54. package/dist/platform.js.map +1 -1
  55. package/docs/variables/default.html +1 -1
  56. package/package.json +1 -1
  57. package/src/device-types.ts +85 -0
  58. package/src/deviceFactory.ts +1 -0
  59. package/src/homebridge-ui/endpoints/config.ts +54 -10
  60. package/src/homebridge-ui/endpoints/devices.ts +62 -3
  61. package/src/homebridge-ui/public/css/styles.css +254 -0
  62. package/src/homebridge-ui/public/index.html +23 -168
  63. package/src/homebridge-ui/public/js/api.ts +17 -1
  64. package/src/homebridge-ui/public/js/app.ts +2 -1
  65. package/src/homebridge-ui/public/js/devices-delete.ts +124 -1
  66. package/src/homebridge-ui/public/js/devices.ts +10 -0
  67. package/src/homebridge-ui/public/js/discovery.ts +158 -4
  68. package/src/homebridge-ui/public/js/modals.ts +74 -27
  69. package/src/homebridge-ui/public/js/render.ts +331 -11
  70. package/src/homebridge-ui/utils/device-migration.ts +144 -0
  71. package/src/platform.ts +2 -2
  72. package/todo/COMPLETED.md +189 -0
  73. package/todo/README.md +59 -0
  74. package/todo/TODO.md +205 -0
  75. package/todo/mockups/filtering-sorting.svg +112 -0
  76. package/todo/mockups/modal-switchbot-theme.svg +66 -0
  77. package/todo/mockups/signal-strength-visualization.svg +94 -0
  78. package/TODO.md +0 -564
@@ -1,4 +1,4 @@
1
- import { deleteDevice as apiDeleteDevice, syncParentPluginConfigFromDisk, fetchDevices } from './api.js'
1
+ import { deleteDevice as apiDeleteDevice, deleteAllDevices as apiDeleteAllDevices, syncParentPluginConfigFromDisk, fetchDevices } from './api.js'
2
2
  import { uiLog } from './logger.js'
3
3
  import { hideBusyUi, showBusyUi } from './modal.js'
4
4
  import { renderDeviceList } from './render.js'
@@ -111,3 +111,126 @@ export async function deleteDeviceFromConfig(deviceId: string, deviceName: strin
111
111
  hideBusyUi()
112
112
  }
113
113
  }
114
+
115
+ async function confirmDeleteAllDialog(deviceCount: number): Promise<boolean> {
116
+ return new Promise((resolve) => {
117
+ const overlay = document.createElement('div')
118
+ overlay.style.position = 'fixed'
119
+ overlay.style.top = '0'
120
+ overlay.style.left = '0'
121
+ overlay.style.width = '100%'
122
+ overlay.style.height = '100%'
123
+ overlay.style.background = 'rgba(0,0,0,0.7)'
124
+ overlay.style.display = 'flex'
125
+ overlay.style.alignItems = 'center'
126
+ overlay.style.justifyContent = 'center'
127
+ overlay.style.zIndex = '10000'
128
+
129
+ const modal = document.createElement('div')
130
+ modal.style.background = getComputedStyle(document.body).backgroundColor
131
+ modal.style.color = getComputedStyle(document.body).color
132
+ modal.style.padding = '20px'
133
+ modal.style.borderRadius = '10px'
134
+ modal.style.minWidth = '380px'
135
+ modal.style.maxWidth = '90vw'
136
+ modal.style.boxShadow = '0 12px 40px rgba(0,0,0,0.35)'
137
+ modal.style.borderTop = '3px solid #ef4444'
138
+
139
+ const title = document.createElement('h3')
140
+ title.textContent = '⚠️ Remove All Devices'
141
+ title.style.margin = '0 0 12px 0'
142
+ title.style.color = '#ef4444'
143
+
144
+ const message = document.createElement('p')
145
+ message.innerHTML = `Are you sure you want to remove <strong>all ${deviceCount} device(s)</strong> from your configuration?<br><br>This action cannot be undone.`
146
+ message.style.margin = '0 0 18px 0'
147
+ message.style.lineHeight = '1.5'
148
+
149
+ const actions = document.createElement('div')
150
+ actions.style.display = 'flex'
151
+ actions.style.justifyContent = 'flex-end'
152
+ actions.style.gap = '10px'
153
+
154
+ const cancelBtn = document.createElement('button')
155
+ cancelBtn.textContent = 'Cancel'
156
+ cancelBtn.style.background = '#6b7280'
157
+ cancelBtn.style.padding = '8px 16px'
158
+ cancelBtn.style.fontSize = '13px'
159
+ cancelBtn.className = 'secondary'
160
+
161
+ const deleteBtn = document.createElement('button')
162
+ deleteBtn.textContent = 'Remove All'
163
+ deleteBtn.style.background = '#ef4444'
164
+ deleteBtn.style.padding = '8px 20px'
165
+ deleteBtn.style.fontSize = '13px'
166
+
167
+ const cleanup = (result: boolean) => {
168
+ overlay.remove()
169
+ resolve(result)
170
+ }
171
+
172
+ cancelBtn.onclick = () => cleanup(false)
173
+ deleteBtn.onclick = () => cleanup(true)
174
+ overlay.addEventListener('click', (event) => {
175
+ if (event.target === overlay) {
176
+ cleanup(false)
177
+ }
178
+ })
179
+
180
+ actions.appendChild(cancelBtn)
181
+ actions.appendChild(deleteBtn)
182
+ modal.appendChild(title)
183
+ modal.appendChild(message)
184
+ modal.appendChild(actions)
185
+ overlay.appendChild(modal)
186
+ document.body.appendChild(overlay)
187
+ })
188
+ }
189
+
190
+ export async function deleteAllDevicesFromConfig(): Promise<void> {
191
+ uiLog.info('Remove All Devices button clicked')
192
+
193
+ try {
194
+ // Get current device count
195
+ const list = await fetchDevices()
196
+ if (!list || list.length === 0) {
197
+ toastWarning('No devices to remove')
198
+ return
199
+ }
200
+
201
+ const confirmed = await confirmDeleteAllDialog(list.length)
202
+ if (!confirmed) {
203
+ uiLog.info('Remove all cancelled by user')
204
+ return
205
+ }
206
+
207
+ showBusyUi()
208
+ uiLog.info('Sending delete all devices request')
209
+ const resp = await apiDeleteAllDevices()
210
+ uiLog.info('Delete all response:', resp)
211
+
212
+ if (!resp || resp.success === false) {
213
+ throw new Error(resp?.data?.message || 'Failed to delete all devices')
214
+ }
215
+
216
+ uiLog.info('Syncing parent config from disk...')
217
+ const synced = await syncParentPluginConfigFromDisk(true)
218
+ if (!synced) {
219
+ toastWarning('Devices deleted, but configuration sync failed')
220
+ }
221
+
222
+ uiLog.info('Refreshing device list...')
223
+ const updatedList = await fetchDevices()
224
+ uiLog.info('Rendering devices:', updatedList.length)
225
+ renderDeviceList(updatedList)
226
+
227
+ const deletedCount = resp?.deletedCount || list.length
228
+ uiLog.info(`✓ Removed ${deletedCount} device(s) successfully`)
229
+ toastSuccess(`Removed ${deletedCount} device(s) successfully`)
230
+ } catch (e) {
231
+ uiLog.error('Delete all error:', e)
232
+ toastError(e instanceof Error ? e.message : 'Failed to delete all devices')
233
+ } finally {
234
+ hideBusyUi()
235
+ }
236
+ }
@@ -4,5 +4,6 @@ export declare function addDeviceToConfig(device: any, options?: {
4
4
  }): Promise<{
5
5
  added: boolean;
6
6
  }>;
7
+ export declare function initRemoveAllButton(): Promise<void>;
7
8
  export declare function loadConfiguredDevices(): Promise<void>;
8
9
  //# sourceMappingURL=devices.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/devices.ts"],"names":[],"mappings":"AAMA,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAkE3I;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3D"}
1
+ {"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/devices.ts"],"names":[],"mappings":"AAMA,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAkE3I;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAQzD;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3D"}
@@ -64,6 +64,16 @@ export async function addDeviceToConfig(device, options = {}) {
64
64
  hideBusyUi();
65
65
  }
66
66
  }
67
+ export async function initRemoveAllButton() {
68
+ const removeAllBtn = document.getElementById('removeAllBtn');
69
+ if (!removeAllBtn) {
70
+ return;
71
+ }
72
+ removeAllBtn.addEventListener('click', async () => {
73
+ const { deleteAllDevicesFromConfig } = await import('./devices-delete.js');
74
+ await deleteAllDevicesFromConfig();
75
+ });
76
+ }
67
77
  export async function loadConfiguredDevices() {
68
78
  const list = await fetchDevices();
69
79
  renderDeviceList(list);
@@ -1 +1 @@
1
- {"version":3,"file":"devices.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/devices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,8BAA8B,EAAE,MAAM,UAAU,CAAA;AAClF,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9E,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAW,EAAE,UAAuD,EAAE;IAC5G,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,GAAG,OAAO,CAAA;IAErD,IAAI,CAAC;QACH,UAAU,EAAE,CAAA;QACZ,KAAK,CAAC,IAAI,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAA;QAC9C,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QACjE,KAAK,CAAC,IAAI,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAA;QAExC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,IAAI,sBAAsB,CAAC,CAAA;QAChE,CAAC;QAED,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,EAAE,aAAa,CAAA;QAC3C,MAAM,OAAO,GACT,IAAI,EAAE,OAAO;eACV,CAAC,aAAa;gBACf,CAAC,CAAC,WAAW,MAAM,CAAC,IAAI,qBAAqB;gBAC7C,CAAC,CAAC,WAAW,MAAM,CAAC,IAAI,uBAAuB,CAAC,CAAA;QAEtD,IAAI,aAAa,EAAE,CAAC;YAClB,SAAS,CAAC,OAAO,CAAC,CAAA;QACpB,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,OAAO,CAAC,CAAA;QACvB,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;YACxD,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,WAAW,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;gBAC5D,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;gBAChC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;gBAEnC,6EAA6E;gBAC7E,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,MAAM,MAAM,GAAG,MAAM,8BAA8B,CAAC,IAAI,CAAC,CAAA;oBACzD,MAAM,CAAC,WAAW,IAAI,MAAM;wBAC1B,CAAC,CAAC,gCAAgC;wBAClC,CAAC,CAAC,qEAAqE,CAAA;oBAEzE,IAAI,MAAM,EAAE,CAAC;wBACX,YAAY,CAAC,8CAA8C,CAAC,CAAA;oBAC9D,CAAC;yBAAM,CAAC;wBACN,YAAY,CAAC,kEAAkE,CAAC,CAAA;oBAClF,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,qBAAqB,EAAE,CAAA;QAC/B,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,CAAC,aAAa,EAAE,CAAA;IAClC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,KAAK,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAA;QACnC,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;QACxD,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,WAAW,GAAG,YAAY,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAA;YAC1F,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC/B,CAAC;QACD,UAAU,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAA;QACnE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC;YAAS,CAAC;QACT,UAAU,EAAE,CAAA;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,IAAI,GAAG,MAAM,YAAY,EAAE,CAAA;IACjC,gBAAgB,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC"}
1
+ {"version":3,"file":"devices.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/devices.ts"],"names":[],"mappings":"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"}
@@ -72,6 +72,16 @@ export async function addDeviceToConfig(device: any, options: { refresh?: boolea
72
72
  }
73
73
  }
74
74
 
75
+ export async function initRemoveAllButton(): Promise<void> {
76
+ const removeAllBtn = document.getElementById('removeAllBtn')
77
+ if (!removeAllBtn) { return }
78
+
79
+ removeAllBtn.addEventListener('click', async () => {
80
+ const { deleteAllDevicesFromConfig } = await import('./devices-delete.js')
81
+ await deleteAllDevicesFromConfig()
82
+ })
83
+ }
84
+
75
85
  export async function loadConfiguredDevices(): Promise<void> {
76
86
  const list = await fetchDevices()
77
87
  renderDeviceList(list)
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/discovery.ts"],"names":[],"mappings":"AAYA,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAyGrD;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlE"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/discovery.ts"],"names":[],"mappings":"AAYA,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAoOrD;AAiCD,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlE"}
@@ -2,7 +2,7 @@ import { addDevicesInBulk, discoverDevices as apiDiscoverDevices, syncParentPlug
2
2
  import { loadConfiguredDevices } from './devices.js';
3
3
  import { uiLog } from './logger.js';
4
4
  import { hideBusyUi, showBusyUi } from './modal.js';
5
- import { renderDiscoveredDevices } from './render.js';
5
+ import { renderDiscoveredDevices, filterDevices, sortDevices, getDiscoveryPreferences, setDiscoveryPreferences } from './render.js';
6
6
  import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js';
7
7
  export async function discoverDevices() {
8
8
  const btn = document.getElementById('discoverBtn');
@@ -36,6 +36,9 @@ export async function discoverDevices() {
36
36
  deviceId: d.id,
37
37
  name: d.name,
38
38
  type: d.type,
39
+ rssi: d.rssi,
40
+ address: d.address,
41
+ model: d.model,
39
42
  })));
40
43
  uiLog.info('Bulk add response:', bulkResult);
41
44
  if (!bulkResult || bulkResult.success === false) {
@@ -82,10 +85,112 @@ export async function discoverDevices() {
82
85
  return;
83
86
  }
84
87
  status.textContent = `Found ${devices.length} device(s)`;
85
- const ul = await renderDiscoveredDevices(devices);
86
- document.getElementById('discoveredDevices')?.replaceWith(ul);
87
- ul.id = 'discoveredDevices';
88
+ // Create filter/sort controls
89
+ const controlsDiv = document.createElement('div');
90
+ controlsDiv.style.cssText = 'margin-bottom: 12px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center;';
91
+ // Load saved preferences
92
+ const preferences = getDiscoveryPreferences();
93
+ // Filter buttons
94
+ const filterLabel = document.createElement('label');
95
+ filterLabel.style.fontSize = '12px';
96
+ filterLabel.style.fontWeight = '500';
97
+ filterLabel.textContent = 'Filter:';
98
+ const filterGroup = document.createElement('div');
99
+ filterGroup.style.display = 'flex';
100
+ filterGroup.style.gap = '4px';
101
+ const filterOptions = [
102
+ { label: 'All', value: 'all' },
103
+ { label: 'BLE', value: 'ble' },
104
+ { label: 'API', value: 'api' },
105
+ { label: 'Both', value: 'both' },
106
+ { label: 'IR', value: 'ir' },
107
+ ];
108
+ for (const option of filterOptions) {
109
+ const btn = document.createElement('button');
110
+ btn.textContent = option.label;
111
+ btn.style.padding = '4px 8px';
112
+ btn.style.fontSize = '11px';
113
+ btn.style.borderRadius = '3px';
114
+ btn.style.cursor = 'pointer';
115
+ btn.style.border = preferences.connectionType === option.value ? '2px solid #007AFF' : '1px solid #ccc';
116
+ btn.style.backgroundColor = preferences.connectionType === option.value ? '#f0f7ff' : '#fff';
117
+ btn.onclick = () => {
118
+ preferences.connectionType = option.value;
119
+ setDiscoveryPreferences(preferences);
120
+ updateDiscoveryView(devices, preferences);
121
+ // Update button styles
122
+ for (const b of Array.from(filterGroup.querySelectorAll('button'))) {
123
+ ;
124
+ b.style.border = '1px solid #ccc';
125
+ b.style.backgroundColor = '#fff';
126
+ }
127
+ btn.style.border = '2px solid #007AFF';
128
+ btn.style.backgroundColor = '#f0f7ff';
129
+ };
130
+ filterGroup.appendChild(btn);
131
+ }
132
+ // Sort dropdown
133
+ const sortLabel = document.createElement('label');
134
+ sortLabel.style.fontSize = '12px';
135
+ sortLabel.style.fontWeight = '500';
136
+ sortLabel.style.marginLeft = '8px';
137
+ sortLabel.textContent = 'Sort:';
138
+ const sortSelect = document.createElement('select');
139
+ sortSelect.style.fontSize = '11px';
140
+ sortSelect.style.padding = '4px 8px';
141
+ sortSelect.style.borderRadius = '3px';
142
+ sortSelect.value = preferences.sortBy;
143
+ const sortOptions = [
144
+ { label: 'Name', value: 'name' },
145
+ { label: 'Signal Strength', value: 'signal' },
146
+ { label: 'Type', value: 'type' },
147
+ { label: 'Connection', value: 'connection' },
148
+ ];
149
+ for (const opt of sortOptions) {
150
+ const option = document.createElement('option');
151
+ option.value = opt.value;
152
+ option.textContent = opt.label;
153
+ sortSelect.appendChild(option);
154
+ }
155
+ sortSelect.onchange = () => {
156
+ preferences.sortBy = sortSelect.value;
157
+ setDiscoveryPreferences(preferences);
158
+ updateDiscoveryView(devices, preferences);
159
+ };
160
+ // Search box
161
+ const searchInput = document.createElement('input');
162
+ searchInput.type = 'text';
163
+ searchInput.placeholder = 'Search by name, ID, or type...';
164
+ searchInput.style.fontSize = '11px';
165
+ searchInput.style.padding = '4px 8px';
166
+ searchInput.style.borderRadius = '3px';
167
+ searchInput.style.border = '1px solid #ccc';
168
+ searchInput.style.flex = '1';
169
+ searchInput.style.minWidth = '200px';
170
+ searchInput.style.maxWidth = '300px';
171
+ searchInput.value = preferences.searchQuery;
172
+ let searchTimeout;
173
+ searchInput.oninput = () => {
174
+ clearTimeout(searchTimeout);
175
+ searchTimeout = setTimeout(() => {
176
+ preferences.searchQuery = searchInput.value;
177
+ setDiscoveryPreferences(preferences);
178
+ updateDiscoveryView(devices, preferences);
179
+ }, 300); // Debounce search
180
+ };
181
+ // Assemble controls
182
+ controlsDiv.appendChild(filterLabel);
183
+ controlsDiv.appendChild(filterGroup);
184
+ controlsDiv.appendChild(sortLabel);
185
+ controlsDiv.appendChild(sortSelect);
186
+ controlsDiv.appendChild(searchInput);
187
+ // Clear list and add controls
188
+ list.innerHTML = '';
189
+ list.appendChild(controlsDiv);
190
+ // Display discovery list
88
191
  list.style.display = 'block';
192
+ // Initial render with saved preferences
193
+ await updateDiscoveryView(devices, preferences);
89
194
  }
90
195
  catch (e) {
91
196
  uiLog.error('Discovery error:', e);
@@ -100,6 +205,27 @@ export async function discoverDevices() {
100
205
  btn.textContent = '🔍 Discover Devices';
101
206
  }
102
207
  }
208
+ /**
209
+ * Update discovery list with filtered and sorted devices
210
+ */
211
+ async function updateDiscoveryView(allDevices, preferences) {
212
+ // Apply filters and sort
213
+ const filtered = filterDevices(allDevices, preferences.connectionType, preferences.searchQuery);
214
+ const sorted = sortDevices(filtered, preferences.sortBy);
215
+ // Render updated list
216
+ const ul = await renderDiscoveredDevices(sorted);
217
+ document.getElementById('discoveredDevices')?.replaceWith(ul);
218
+ ul.id = 'discoveredDevices';
219
+ // Update status with count
220
+ const status = document.getElementById('discoverStatus');
221
+ if (status) {
222
+ const totalCount = allDevices.length;
223
+ const filteredCount = sorted.length;
224
+ status.textContent = filteredCount === totalCount
225
+ ? `Found ${totalCount} device(s)`
226
+ : `Showing ${filteredCount} of ${totalCount} device(s)`;
227
+ }
228
+ }
103
229
  export async function addDeviceToConfig(device) {
104
230
  const { addDeviceToConfig: addDevice } = await import('./devices.js');
105
231
  await addDevice(device);
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,eAAe,IAAI,kBAAkB,EAErC,8BAA8B,GAC/B,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9E,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,CAAC,aAAa,CAAsB,CAAA;IACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;IACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;IACtD,MAAM,UAAU,GAAI,QAAQ,CAAC,cAAc,CAAC,oBAAoB,CAAsB,EAAE,OAAO,CAAA;IAE/F,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAAC,OAAM;IAAC,CAAC;IAExC,IAAI,CAAC;QACH,UAAU,EAAE,CAAA;QACZ,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAA;QACnB,GAAG,CAAC,WAAW,GAAG,mBAAmB,CAAA;QACrC,MAAM,CAAC,WAAW,GAAG,gCAAgC,CAAA;QACrD,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAEhC,MAAM,UAAU,GAAG,MAAM,kBAAkB,EAAE,CAAA;QAC7C,KAAK,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAA;QAE5C,2BAA2B;QAC3B,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,KAAK,CAAC,CAAA;QAE7G,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,CAAC,WAAW,GAAG,4CAA4C,CAAA;YACjE,SAAS,CAAC,4CAA4C,CAAC,CAAA;YACvD,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;YAC3B,OAAM;QACR,CAAC;QAED,0EAA0E;QAC1E,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,CAAC,WAAW,GAAG,eAAe,OAAO,CAAC,MAAM,eAAe,CAAA;YAEjE,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,MAAM,gBAAgB,CACvC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBAChB,QAAQ,EAAE,CAAC,CAAC,EAAE;oBACd,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;iBACb,CAAC,CAAC,CACJ,CAAA;gBACD,KAAK,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAA;gBAE5C,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;oBAChD,MAAM,IAAI,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,IAAI,iBAAiB,CAAC,CAAA;gBACjE,CAAC;gBAED,MAAM,UAAU,GACZ,UAAU,EAAE,UAAU;uBACnB,UAAU,EAAE,IAAI,EAAE,UAAU;uBAC5B,CAAC,CAAA;gBACR,MAAM,YAAY,GACd,UAAU,EAAE,YAAY;uBACrB,UAAU,EAAE,IAAI,EAAE,YAAY;uBAC9B,CAAC,CAAA;gBACR,MAAM,CAAC,WAAW,GAAG,WAAW,UAAU,aAAa,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;gBAC7G,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;oBACnB,YAAY,CAAC,SAAS,UAAU,aAAa,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;gBACtG,CAAC;qBAAM,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;oBAC5B,YAAY,CAAC,8BAA8B,YAAY,WAAW,CAAC,CAAA;gBACrE,CAAC;gBACD,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;gBAChC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;gBAE3B,6EAA6E;gBAC7E,oFAAoF;gBACpF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACvB,MAAM,MAAM,GAAG,MAAM,8BAA8B,CAAC,IAAI,CAAC,CAAA;oBACzD,MAAM,CAAC,WAAW,IAAI,MAAM;wBAC1B,CAAC,CAAC,gCAAgC;wBAClC,CAAC,CAAC,qEAAqE,CAAA;oBAEzE,IAAI,MAAM,EAAE,CAAC;wBACX,YAAY,CAAC,8CAA8C,CAAC,CAAA;oBAC9D,CAAC;yBAAM,CAAC;wBACN,YAAY,CAAC,kEAAkE,CAAC,CAAA;oBAClF,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,KAAK,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAA;gBACjC,MAAM,CAAC,WAAW,GAAG,YAAY,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAA;gBAC3F,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;gBAC7B,UAAU,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAA;YACtE,CAAC;YAED,sCAAsC;YACtC,MAAM,qBAAqB,EAAE,CAAA;YAC7B,OAAM;QACR,CAAC;QAED,MAAM,CAAC,WAAW,GAAG,SAAS,OAAO,CAAC,MAAM,YAAY,CAAA;QACxD,MAAM,EAAE,GAAG,MAAM,uBAAuB,CAAC,OAAO,CAAC,CAAA;QACjD,QAAQ,CAAC,cAAc,CAAC,mBAAmB,CAAC,EAAE,WAAW,CAAC,EAAE,CAAC,CAAA;QAC7D,EAAE,CAAC,EAAE,GAAG,mBAAmB,CAAA;QAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAA;IAC9B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAA;QAClC,MAAM,CAAC,WAAW,GAAG,UAAU,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAAA;QACpF,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC7B,UAAU,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAA;QAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAA;IAC7B,CAAC;YAAS,CAAC;QACT,UAAU,EAAE,CAAA;QACZ,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAA;QACpB,GAAG,CAAC,WAAW,GAAG,qBAAqB,CAAA;IACzC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAW;IACjD,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAA;IACrE,MAAM,SAAS,CAAC,MAAM,CAAC,CAAA;AACzB,CAAC"}
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/discovery.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ import {
7
7
  import { loadConfiguredDevices } from './devices.js'
8
8
  import { uiLog } from './logger.js'
9
9
  import { hideBusyUi, showBusyUi } from './modal.js'
10
- import { renderDiscoveredDevices } from './render.js'
10
+ import { renderDiscoveredDevices, filterDevices, sortDevices, getDiscoveryPreferences, setDiscoveryPreferences } from './render.js'
11
11
  import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js'
12
12
 
13
13
  export async function discoverDevices(): Promise<void> {
@@ -48,6 +48,9 @@ export async function discoverDevices(): Promise<void> {
48
48
  deviceId: d.id,
49
49
  name: d.name,
50
50
  type: d.type,
51
+ rssi: d.rssi,
52
+ address: d.address,
53
+ model: d.model,
51
54
  })),
52
55
  )
53
56
  uiLog.info('Bulk add response:', bulkResult)
@@ -100,10 +103,130 @@ export async function discoverDevices(): Promise<void> {
100
103
  }
101
104
 
102
105
  status.textContent = `Found ${devices.length} device(s)`
103
- const ul = await renderDiscoveredDevices(devices)
104
- document.getElementById('discoveredDevices')?.replaceWith(ul)
105
- ul.id = 'discoveredDevices'
106
+
107
+ // Create filter/sort controls
108
+ const controlsDiv = document.createElement('div')
109
+ controlsDiv.style.cssText = 'margin-bottom: 12px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center;'
110
+
111
+ // Load saved preferences
112
+ const preferences = getDiscoveryPreferences()
113
+
114
+ // Filter buttons
115
+ const filterLabel = document.createElement('label')
116
+ filterLabel.style.fontSize = '12px'
117
+ filterLabel.style.fontWeight = '500'
118
+ filterLabel.textContent = 'Filter:'
119
+
120
+ const filterGroup = document.createElement('div')
121
+ filterGroup.style.display = 'flex'
122
+ filterGroup.style.gap = '4px'
123
+
124
+ const filterOptions: Array<{ label: string, value: 'all' | 'ble' | 'api' | 'both' | 'ir' }> = [
125
+ { label: 'All', value: 'all' },
126
+ { label: 'BLE', value: 'ble' },
127
+ { label: 'API', value: 'api' },
128
+ { label: 'Both', value: 'both' },
129
+ { label: 'IR', value: 'ir' },
130
+ ]
131
+
132
+ for (const option of filterOptions) {
133
+ const btn = document.createElement('button')
134
+ btn.textContent = option.label
135
+ btn.style.padding = '4px 8px'
136
+ btn.style.fontSize = '11px'
137
+ btn.style.borderRadius = '3px'
138
+ btn.style.cursor = 'pointer'
139
+ btn.style.border = preferences.connectionType === option.value ? '2px solid #007AFF' : '1px solid #ccc'
140
+ btn.style.backgroundColor = preferences.connectionType === option.value ? '#f0f7ff' : '#fff'
141
+
142
+ btn.onclick = () => {
143
+ preferences.connectionType = option.value
144
+ setDiscoveryPreferences(preferences)
145
+ updateDiscoveryView(devices, preferences)
146
+ // Update button styles
147
+ for (const b of Array.from(filterGroup.querySelectorAll('button'))) {
148
+ ;(b as HTMLButtonElement).style.border = '1px solid #ccc'
149
+ ;(b as HTMLButtonElement).style.backgroundColor = '#fff'
150
+ }
151
+ btn.style.border = '2px solid #007AFF'
152
+ btn.style.backgroundColor = '#f0f7ff'
153
+ }
154
+
155
+ filterGroup.appendChild(btn)
156
+ }
157
+
158
+ // Sort dropdown
159
+ const sortLabel = document.createElement('label')
160
+ sortLabel.style.fontSize = '12px'
161
+ sortLabel.style.fontWeight = '500'
162
+ sortLabel.style.marginLeft = '8px'
163
+ sortLabel.textContent = 'Sort:'
164
+
165
+ const sortSelect = document.createElement('select')
166
+ sortSelect.style.fontSize = '11px'
167
+ sortSelect.style.padding = '4px 8px'
168
+ sortSelect.style.borderRadius = '3px'
169
+ sortSelect.value = preferences.sortBy
170
+
171
+ const sortOptions = [
172
+ { label: 'Name', value: 'name' },
173
+ { label: 'Signal Strength', value: 'signal' },
174
+ { label: 'Type', value: 'type' },
175
+ { label: 'Connection', value: 'connection' },
176
+ ]
177
+
178
+ for (const opt of sortOptions) {
179
+ const option = document.createElement('option')
180
+ option.value = opt.value
181
+ option.textContent = opt.label
182
+ sortSelect.appendChild(option)
183
+ }
184
+
185
+ sortSelect.onchange = () => {
186
+ preferences.sortBy = sortSelect.value as any
187
+ setDiscoveryPreferences(preferences)
188
+ updateDiscoveryView(devices, preferences)
189
+ }
190
+
191
+ // Search box
192
+ const searchInput = document.createElement('input')
193
+ searchInput.type = 'text'
194
+ searchInput.placeholder = 'Search by name, ID, or type...'
195
+ searchInput.style.fontSize = '11px'
196
+ searchInput.style.padding = '4px 8px'
197
+ searchInput.style.borderRadius = '3px'
198
+ searchInput.style.border = '1px solid #ccc'
199
+ searchInput.style.flex = '1'
200
+ searchInput.style.minWidth = '200px'
201
+ searchInput.style.maxWidth = '300px'
202
+ searchInput.value = preferences.searchQuery
203
+
204
+ let searchTimeout: NodeJS.Timeout
205
+ searchInput.oninput = () => {
206
+ clearTimeout(searchTimeout)
207
+ searchTimeout = setTimeout(() => {
208
+ preferences.searchQuery = searchInput.value
209
+ setDiscoveryPreferences(preferences)
210
+ updateDiscoveryView(devices, preferences)
211
+ }, 300) // Debounce search
212
+ }
213
+
214
+ // Assemble controls
215
+ controlsDiv.appendChild(filterLabel)
216
+ controlsDiv.appendChild(filterGroup)
217
+ controlsDiv.appendChild(sortLabel)
218
+ controlsDiv.appendChild(sortSelect)
219
+ controlsDiv.appendChild(searchInput)
220
+
221
+ // Clear list and add controls
222
+ list.innerHTML = ''
223
+ list.appendChild(controlsDiv)
224
+
225
+ // Display discovery list
106
226
  list.style.display = 'block'
227
+
228
+ // Initial render with saved preferences
229
+ await updateDiscoveryView(devices, preferences)
107
230
  } catch (e) {
108
231
  uiLog.error('Discovery error:', e)
109
232
  status.textContent = `Error: ${e instanceof Error ? e.message : 'Discovery failed'}`
@@ -117,6 +240,37 @@ export async function discoverDevices(): Promise<void> {
117
240
  }
118
241
  }
119
242
 
243
+ /**
244
+ * Update discovery list with filtered and sorted devices
245
+ */
246
+ async function updateDiscoveryView(
247
+ allDevices: any[],
248
+ preferences: {
249
+ connectionType: 'all' | 'ble' | 'api' | 'both' | 'ir'
250
+ sortBy: 'name' | 'signal' | 'type' | 'connection'
251
+ searchQuery: string
252
+ },
253
+ ): Promise<void> {
254
+ // Apply filters and sort
255
+ const filtered = filterDevices(allDevices, preferences.connectionType, preferences.searchQuery)
256
+ const sorted = sortDevices(filtered, preferences.sortBy)
257
+
258
+ // Render updated list
259
+ const ul = await renderDiscoveredDevices(sorted)
260
+ document.getElementById('discoveredDevices')?.replaceWith(ul)
261
+ ul.id = 'discoveredDevices'
262
+
263
+ // Update status with count
264
+ const status = document.getElementById('discoverStatus')
265
+ if (status) {
266
+ const totalCount = allDevices.length
267
+ const filteredCount = sorted.length
268
+ status.textContent = filteredCount === totalCount
269
+ ? `Found ${totalCount} device(s)`
270
+ : `Showing ${filteredCount} of ${totalCount} device(s)`
271
+ }
272
+ }
273
+
120
274
  export async function addDeviceToConfig(device: any): Promise<void> {
121
275
  const { addDeviceToConfig: addDevice } = await import('./devices.js')
122
276
  await addDevice(device)
@@ -1 +1 @@
1
- {"version":3,"file":"modals.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/modals.ts"],"names":[],"mappings":"AAGA,wBAAsB,UAAU,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAqJ3D"}
1
+ {"version":3,"file":"modals.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/modals.ts"],"names":[],"mappings":"AAGA,wBAAsB,UAAU,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAoM3D"}