@switchbot/homebridge-switchbot 5.0.0-beta.99 → 5.1.0-beta.0

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 (281) hide show
  1. package/.changeset/config.json +14 -0
  2. package/.github/copilot-instructions.md +39 -0
  3. package/.github/workflows/ci.yml +4 -1
  4. package/.github/workflows/manual-e2e.yml +6 -3
  5. package/.github/workflows/release.yml +64 -15
  6. package/.github/workflows/stale.yml +2 -4
  7. package/.husky/pre-push +15 -0
  8. package/CHANGELOG.md +140 -135
  9. package/MIGRATION.md +21 -6
  10. package/README.md +109 -3
  11. package/TODO.md +263 -0
  12. package/config.schema.json +240 -36
  13. package/dist/SwitchBotHAPPlatform.d.ts +133 -0
  14. package/dist/SwitchBotHAPPlatform.d.ts.map +1 -0
  15. package/dist/SwitchBotHAPPlatform.js +584 -0
  16. package/dist/SwitchBotHAPPlatform.js.map +1 -0
  17. package/dist/SwitchBotMatterPlatform.d.ts +142 -0
  18. package/dist/SwitchBotMatterPlatform.d.ts.map +1 -0
  19. package/dist/SwitchBotMatterPlatform.js +569 -0
  20. package/dist/SwitchBotMatterPlatform.js.map +1 -0
  21. package/dist/device-types.d.ts +31 -0
  22. package/dist/device-types.d.ts.map +1 -0
  23. package/dist/device-types.js +246 -0
  24. package/dist/device-types.js.map +1 -0
  25. package/dist/deviceCommandMapper.d.ts +10 -0
  26. package/dist/deviceCommandMapper.d.ts.map +1 -0
  27. package/dist/deviceCommandMapper.js +319 -0
  28. package/dist/deviceCommandMapper.js.map +1 -0
  29. package/dist/deviceFactory.d.ts +3 -2
  30. package/dist/deviceFactory.d.ts.map +1 -1
  31. package/dist/deviceFactory.js +107 -29
  32. package/dist/deviceFactory.js.map +1 -1
  33. package/dist/devices/genericDevice.d.ts +59 -37
  34. package/dist/devices/genericDevice.d.ts.map +1 -1
  35. package/dist/devices/genericDevice.js +376 -78
  36. package/dist/devices/genericDevice.js.map +1 -1
  37. package/dist/errors.d.ts +38 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +32 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/homebridge-ui/device-types.js +246 -0
  42. package/dist/homebridge-ui/device-types.js.map +1 -0
  43. package/dist/homebridge-ui/deviceCommandMapper.js +319 -0
  44. package/dist/homebridge-ui/deviceCommandMapper.js.map +1 -0
  45. package/dist/homebridge-ui/endpoints/config.d.ts +3 -0
  46. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
  47. package/dist/homebridge-ui/endpoints/config.js +90 -0
  48. package/dist/homebridge-ui/endpoints/config.js.map +1 -0
  49. package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
  50. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
  51. package/dist/homebridge-ui/endpoints/devices.js +144 -0
  52. package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
  53. package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
  54. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
  55. package/dist/homebridge-ui/endpoints/discovery.js +219 -0
  56. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
  57. package/dist/homebridge-ui/errors.js +32 -0
  58. package/dist/homebridge-ui/errors.js.map +1 -0
  59. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js +90 -0
  60. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js.map +1 -0
  61. package/dist/homebridge-ui/homebridge-ui/endpoints/devices.js +144 -0
  62. package/dist/homebridge-ui/homebridge-ui/endpoints/devices.js.map +1 -0
  63. package/dist/homebridge-ui/homebridge-ui/endpoints/discovery.js +219 -0
  64. package/dist/homebridge-ui/homebridge-ui/endpoints/discovery.js.map +1 -0
  65. package/dist/homebridge-ui/homebridge-ui/server.js +11 -0
  66. package/dist/homebridge-ui/homebridge-ui/server.js.map +1 -0
  67. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js +108 -0
  68. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js.map +1 -0
  69. package/dist/homebridge-ui/homebridge-ui/utils/device-migration.js +111 -0
  70. package/dist/homebridge-ui/homebridge-ui/utils/device-migration.js.map +1 -0
  71. package/dist/homebridge-ui/homebridge-ui/utils/logger.js +17 -0
  72. package/dist/homebridge-ui/homebridge-ui/utils/logger.js.map +1 -0
  73. package/dist/homebridge-ui/public/css/styles.css +483 -0
  74. package/dist/homebridge-ui/public/index.html +197 -621
  75. package/dist/homebridge-ui/public/js/advanced-settings.d.ts +3 -0
  76. package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -0
  77. package/dist/homebridge-ui/public/js/advanced-settings.js +95 -0
  78. package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -0
  79. package/dist/homebridge-ui/public/js/advanced-settings.ts +94 -0
  80. package/dist/homebridge-ui/public/js/api.d.ts +72 -0
  81. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
  82. package/dist/homebridge-ui/public/js/api.js +313 -0
  83. package/dist/homebridge-ui/public/js/api.js.map +1 -0
  84. package/dist/homebridge-ui/public/js/api.ts +385 -0
  85. package/dist/homebridge-ui/public/js/app.d.ts +2 -0
  86. package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
  87. package/dist/homebridge-ui/public/js/app.js +3798 -0
  88. package/dist/homebridge-ui/public/js/app.js.map +7 -0
  89. package/dist/homebridge-ui/public/js/app.ts +22 -0
  90. package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
  91. package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
  92. package/dist/homebridge-ui/public/js/constants.js +2 -0
  93. package/dist/homebridge-ui/public/js/constants.js.map +1 -0
  94. package/dist/homebridge-ui/public/js/constants.ts +1 -0
  95. package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
  96. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
  97. package/dist/homebridge-ui/public/js/credentials.js +99 -0
  98. package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
  99. package/dist/homebridge-ui/public/js/credentials.ts +105 -0
  100. package/dist/homebridge-ui/public/js/devices-delete.d.ts +3 -0
  101. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
  102. package/dist/homebridge-ui/public/js/devices-delete.js +199 -0
  103. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
  104. package/dist/homebridge-ui/public/js/devices-delete.ts +227 -0
  105. package/dist/homebridge-ui/public/js/devices.d.ts +9 -0
  106. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
  107. package/dist/homebridge-ui/public/js/devices.js +104 -0
  108. package/dist/homebridge-ui/public/js/devices.js.map +1 -0
  109. package/dist/homebridge-ui/public/js/devices.ts +112 -0
  110. package/dist/homebridge-ui/public/js/discovery.d.ts +9 -0
  111. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
  112. package/dist/homebridge-ui/public/js/discovery.js +1201 -0
  113. package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
  114. package/dist/homebridge-ui/public/js/discovery.ts +1335 -0
  115. package/dist/homebridge-ui/public/js/logger.d.ts +7 -0
  116. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
  117. package/dist/homebridge-ui/public/js/logger.js +17 -0
  118. package/dist/homebridge-ui/public/js/logger.js.map +1 -0
  119. package/dist/homebridge-ui/public/js/logger.ts +17 -0
  120. package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
  121. package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
  122. package/dist/homebridge-ui/public/js/modal.js +35 -0
  123. package/dist/homebridge-ui/public/js/modal.js.map +1 -0
  124. package/dist/homebridge-ui/public/js/modal.ts +35 -0
  125. package/dist/homebridge-ui/public/js/modals.d.ts +16 -0
  126. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
  127. package/dist/homebridge-ui/public/js/modals.js +728 -0
  128. package/dist/homebridge-ui/public/js/modals.js.map +1 -0
  129. package/dist/homebridge-ui/public/js/modals.ts +823 -0
  130. package/dist/homebridge-ui/public/js/render.d.ts +71 -0
  131. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
  132. package/dist/homebridge-ui/public/js/render.js +961 -0
  133. package/dist/homebridge-ui/public/js/render.js.map +1 -0
  134. package/dist/homebridge-ui/public/js/render.ts +1085 -0
  135. package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
  136. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
  137. package/dist/homebridge-ui/public/js/toast.js +38 -0
  138. package/dist/homebridge-ui/public/js/toast.js.map +1 -0
  139. package/dist/homebridge-ui/public/js/toast.ts +44 -0
  140. package/dist/homebridge-ui/public/js/types.d.ts +23 -0
  141. package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
  142. package/dist/homebridge-ui/public/js/types.js +2 -0
  143. package/dist/homebridge-ui/public/js/types.js.map +1 -0
  144. package/dist/homebridge-ui/public/js/types.ts +26 -0
  145. package/dist/homebridge-ui/server.d.ts +1 -3
  146. package/dist/homebridge-ui/server.d.ts.map +1 -1
  147. package/dist/homebridge-ui/server.js +8 -471
  148. package/dist/homebridge-ui/server.js.map +1 -1
  149. package/dist/homebridge-ui/settings.js +8 -0
  150. package/dist/homebridge-ui/settings.js.map +1 -0
  151. package/dist/homebridge-ui/switchbotClient.js +247 -0
  152. package/dist/homebridge-ui/switchbotClient.js.map +1 -0
  153. package/dist/homebridge-ui/utils/config-parser.d.ts +39 -0
  154. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
  155. package/dist/homebridge-ui/utils/config-parser.js +108 -0
  156. package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
  157. package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
  158. package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
  159. package/dist/homebridge-ui/utils/device-migration.js +111 -0
  160. package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
  161. package/dist/homebridge-ui/utils/logger.d.ts +7 -0
  162. package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
  163. package/dist/homebridge-ui/utils/logger.js +17 -0
  164. package/dist/homebridge-ui/utils/logger.js.map +1 -0
  165. package/dist/index.d.ts +10 -0
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +12 -2
  168. package/dist/index.js.map +1 -1
  169. package/dist/settings.d.ts +12 -0
  170. package/dist/settings.d.ts.map +1 -1
  171. package/dist/settings.js +1 -0
  172. package/dist/settings.js.map +1 -1
  173. package/dist/switchbotClient.d.ts +12 -10
  174. package/dist/switchbotClient.d.ts.map +1 -1
  175. package/dist/switchbotClient.js +156 -103
  176. package/dist/switchbotClient.js.map +1 -1
  177. package/dist/utils.d.ts +81 -1
  178. package/dist/utils.d.ts.map +1 -1
  179. package/dist/utils.js +1148 -4
  180. package/dist/utils.js.map +1 -1
  181. package/docs/assets/highlight.css +29 -15
  182. package/docs/assets/main.js +1 -1
  183. package/docs/index.html +100 -6
  184. package/docs/variables/default.html +3 -1
  185. package/eslint.config.js +9 -5
  186. package/nodemon.json +2 -2
  187. package/package.json +34 -21
  188. package/scripts/build-ui.js +37 -0
  189. package/scripts/free-dev-ports.mjs +105 -0
  190. package/scripts/generate-matter-maps.js +34 -17
  191. package/scripts/sync-device-types.mjs +31 -0
  192. package/src/SwitchBotHAPPlatform.ts +583 -0
  193. package/src/SwitchBotMatterPlatform.ts +566 -0
  194. package/src/device-types.js +246 -0
  195. package/src/device-types.js.map +1 -0
  196. package/src/device-types.ts +261 -0
  197. package/src/deviceCommandMapper.js +319 -0
  198. package/src/deviceCommandMapper.js.map +1 -0
  199. package/src/deviceCommandMapper.ts +333 -0
  200. package/src/deviceFactory.ts +125 -45
  201. package/src/devices/genericDevice.ts +411 -69
  202. package/src/errors.js +32 -0
  203. package/src/errors.js.map +1 -0
  204. package/src/errors.ts +35 -0
  205. package/src/homebridge-ui/endpoints/config.ts +110 -0
  206. package/src/homebridge-ui/endpoints/devices.ts +153 -0
  207. package/src/homebridge-ui/endpoints/discovery.ts +240 -0
  208. package/src/homebridge-ui/public/css/styles.css +483 -0
  209. package/src/homebridge-ui/public/index.html +197 -621
  210. package/src/homebridge-ui/public/js/advanced-settings.ts +94 -0
  211. package/src/homebridge-ui/public/js/api.ts +385 -0
  212. package/src/homebridge-ui/public/js/app.ts +22 -0
  213. package/src/homebridge-ui/public/js/constants.ts +1 -0
  214. package/src/homebridge-ui/public/js/credentials.ts +105 -0
  215. package/src/homebridge-ui/public/js/devices-delete.ts +227 -0
  216. package/src/homebridge-ui/public/js/devices.ts +112 -0
  217. package/src/homebridge-ui/public/js/discovery.ts +1335 -0
  218. package/src/homebridge-ui/public/js/logger.ts +17 -0
  219. package/src/homebridge-ui/public/js/modal.ts +35 -0
  220. package/src/homebridge-ui/public/js/modals.ts +823 -0
  221. package/src/homebridge-ui/public/js/render.ts +1085 -0
  222. package/src/homebridge-ui/public/js/toast.ts +44 -0
  223. package/src/homebridge-ui/public/js/types.ts +26 -0
  224. package/src/homebridge-ui/server.ts +9 -554
  225. package/src/homebridge-ui/utils/config-parser.ts +125 -0
  226. package/src/homebridge-ui/utils/device-migration.ts +144 -0
  227. package/src/homebridge-ui/utils/logger.ts +17 -0
  228. package/src/index.ts +12 -2
  229. package/src/settings.js +8 -0
  230. package/src/settings.js.map +1 -0
  231. package/src/settings.ts +14 -0
  232. package/src/switchbotClient.js +247 -0
  233. package/src/switchbotClient.js.map +1 -0
  234. package/src/switchbotClient.ts +177 -114
  235. package/src/utils.ts +1164 -5
  236. package/test/client/switchbot-client-debounce.spec.ts +35 -0
  237. package/test/client/switchbot-client-openapi.spec.ts +19 -0
  238. package/test/client/switchbotClient.spec.ts +64 -0
  239. package/test/device/device-mapping.spec.ts +23 -0
  240. package/test/device/deviceBase.spec.ts +26 -0
  241. package/test/device/deviceFactory-edge.spec.ts +15 -0
  242. package/test/device/deviceFactory.spec.ts +33 -0
  243. package/test/device/fan-swing.spec.ts +34 -0
  244. package/test/device/genericDevice-blepoll.spec.ts +47 -0
  245. package/test/device/irdevice.spec.ts +9 -0
  246. package/test/device/lock-users.spec.ts +35 -0
  247. package/test/device/matter-descriptors.spec.ts +22 -0
  248. package/test/device/matter-device-state.spec.ts +37 -0
  249. package/test/e2e/run-e2e.spec.ts +18 -19
  250. package/test/errors/errors.spec.ts +10 -0
  251. package/test/helpers/matter-harness.ts +20 -9
  252. package/test/homebridge-ui/server.spec.ts +9 -0
  253. package/test/platform/accessory-restore.spec.ts +37 -0
  254. package/test/platform/matter-childbridge.spec.ts +34 -0
  255. package/test/platform/matter-integration.spec.ts +33 -0
  256. package/test/platform/platform-edge.spec.ts +73 -0
  257. package/test/platform/platform.integration.spec.ts +34 -0
  258. package/test/utils/utils-extra.spec.ts +10 -0
  259. package/test/utils/utils.spec.ts +68 -0
  260. package/todo/TODO.md +80 -0
  261. package/tsconfig.ui.json +11 -0
  262. package/.github/workflows/beta-release.yml +0 -52
  263. package/dist/platform.d.ts +0 -35
  264. package/dist/platform.d.ts.map +0 -1
  265. package/dist/platform.js +0 -945
  266. package/dist/platform.js.map +0 -1
  267. package/src/platform.ts +0 -963
  268. package/test/accessory-restore.spec.ts +0 -73
  269. package/test/device-mapping.spec.ts +0 -37
  270. package/test/deviceFactory.spec.ts +0 -18
  271. package/test/fan-swing.spec.ts +0 -29
  272. package/test/lock-users.spec.ts +0 -44
  273. package/test/matter-childbridge.spec.ts +0 -55
  274. package/test/matter-descriptors.spec.ts +0 -97
  275. package/test/matter-device-state.spec.ts +0 -101
  276. package/test/matter-integration.spec.ts +0 -70
  277. package/test/platform.integration.spec.ts +0 -55
  278. package/test/switchbot-client-debounce.spec.ts +0 -131
  279. package/test/switchbot-client-openapi.spec.ts +0 -56
  280. package/test/switchbotClient.spec.ts +0 -10
  281. package/test/utils.spec.ts +0 -20
@@ -0,0 +1,728 @@
1
+ import { DEVICE_TYPES } from './constants.js';
2
+ import { uiLog } from './logger.js';
3
+ export async function importDiscoveredDevice(device) {
4
+ // --- OpenAPI Polling Interval (refreshRate) ---
5
+ const openApiRefreshLabel = document.createElement('label');
6
+ openApiRefreshLabel.textContent = 'OpenAPI Polling Interval (seconds)';
7
+ openApiRefreshLabel.style.display = 'block';
8
+ openApiRefreshLabel.style.marginBottom = '6px';
9
+ openApiRefreshLabel.style.fontWeight = '500';
10
+ openApiRefreshLabel.style.fontSize = '12px';
11
+ openApiRefreshLabel.style.color = '#6b7280';
12
+ openApiRefreshLabel.title = 'How often to poll this device via OpenAPI for status (in seconds). Overrides platform value if set. Default: 300 (5 minutes). Minimum: 30.';
13
+ const openApiRefreshInput = document.createElement('input');
14
+ openApiRefreshInput.type = 'number';
15
+ openApiRefreshInput.value = device.refreshRate || 300;
16
+ openApiRefreshInput.min = '30';
17
+ openApiRefreshInput.step = '1';
18
+ openApiRefreshInput.style.width = '100%';
19
+ openApiRefreshInput.style.marginBottom = '12px';
20
+ openApiRefreshInput.style.padding = '8px 10px';
21
+ openApiRefreshInput.style.borderRadius = '6px';
22
+ openApiRefreshInput.style.fontSize = '14px';
23
+ openApiRefreshInput.style.boxSizing = 'border-box';
24
+ return new Promise((resolve) => {
25
+ const div = document.createElement('div');
26
+ div.style.position = 'fixed';
27
+ div.style.top = '0';
28
+ div.style.left = '0';
29
+ div.style.width = '100%';
30
+ div.style.height = '100%';
31
+ div.style.background = 'rgba(0,0,0,0.7)';
32
+ div.style.display = 'flex';
33
+ div.style.alignItems = 'center';
34
+ div.style.justifyContent = 'center';
35
+ div.style.zIndex = '9999';
36
+ const modal = document.createElement('div');
37
+ modal.style.background = getComputedStyle(document.body).backgroundColor;
38
+ modal.style.color = getComputedStyle(document.body).color;
39
+ modal.style.padding = '0';
40
+ modal.style.borderRadius = '10px';
41
+ modal.style.minWidth = '440px';
42
+ modal.style.maxWidth = '90vw';
43
+ modal.style.boxShadow = '0 8px 32px rgba(0,0,0,0.35)';
44
+ modal.style.overflow = 'hidden';
45
+ modal.style.borderTop = '3px solid var(--switchbot-red, #ef4444)';
46
+ const title = document.createElement('h3');
47
+ title.textContent = 'Import Discovered Device';
48
+ title.style.marginTop = '0';
49
+ title.style.marginBottom = '16px';
50
+ title.style.padding = '20px 20px 0';
51
+ title.style.fontSize = '18px';
52
+ title.style.fontWeight = '600';
53
+ title.style.color = 'var(--switchbot-red, #ef4444)';
54
+ title.style.letterSpacing = '-0.02em';
55
+ const contentDiv = document.createElement('div');
56
+ contentDiv.style.padding = '0 20px 20px';
57
+ const nameLabel = document.createElement('label');
58
+ nameLabel.textContent = 'Device Name';
59
+ nameLabel.style.display = 'block';
60
+ nameLabel.style.marginBottom = '6px';
61
+ nameLabel.style.fontWeight = '500';
62
+ nameLabel.style.fontSize = '12px';
63
+ nameLabel.style.color = '#6b7280';
64
+ const nameInput = document.createElement('input');
65
+ nameInput.type = 'text';
66
+ // Never allow 'undefined' as a name
67
+ let safeName = device.name;
68
+ if (!safeName || safeName === 'undefined') {
69
+ safeName = device.id || '';
70
+ }
71
+ nameInput.value = safeName;
72
+ nameInput.style.width = '100%';
73
+ nameInput.style.marginBottom = '12px';
74
+ nameInput.style.padding = '8px 10px';
75
+ nameInput.style.borderRadius = '6px';
76
+ nameInput.style.fontSize = '14px';
77
+ nameInput.style.boxSizing = 'border-box';
78
+ // --- Device Type Select ---
79
+ const typeLabel = document.createElement('label');
80
+ typeLabel.textContent = 'Config Device Type';
81
+ typeLabel.style.display = 'block';
82
+ typeLabel.style.marginBottom = '6px';
83
+ typeLabel.style.fontWeight = '500';
84
+ typeLabel.style.fontSize = '12px';
85
+ typeLabel.style.color = '#6b7280';
86
+ const typeSelect = document.createElement('select');
87
+ typeSelect.style.width = '100%';
88
+ typeSelect.style.padding = '8px 10px';
89
+ typeSelect.style.marginBottom = '12px';
90
+ typeSelect.style.borderRadius = '6px';
91
+ typeSelect.style.fontSize = '14px';
92
+ typeSelect.style.background = getComputedStyle(nameInput).background;
93
+ typeSelect.style.color = getComputedStyle(nameInput).color;
94
+ typeSelect.style.border = getComputedStyle(nameInput).border;
95
+ typeSelect.style.boxSizing = 'border-box';
96
+ Object.keys(DEVICE_TYPES).forEach((categoryName) => {
97
+ const optgroup = document.createElement('optgroup');
98
+ optgroup.label = categoryName;
99
+ DEVICE_TYPES[categoryName].forEach((deviceType) => {
100
+ const opt = document.createElement('option');
101
+ opt.value = deviceType;
102
+ opt.text = deviceType;
103
+ const detectedType = (device.type || '').toLowerCase();
104
+ opt.selected = deviceType.toLowerCase() === detectedType;
105
+ optgroup.appendChild(opt);
106
+ });
107
+ typeSelect.appendChild(optgroup);
108
+ });
109
+ // --- Connection Preference ---
110
+ const connectionPrefLabel = document.createElement('label');
111
+ connectionPrefLabel.textContent = 'Connection Preference';
112
+ connectionPrefLabel.style.display = 'block';
113
+ connectionPrefLabel.style.marginBottom = '6px';
114
+ connectionPrefLabel.style.fontWeight = '500';
115
+ connectionPrefLabel.style.fontSize = '12px';
116
+ connectionPrefLabel.style.color = '#6b7280';
117
+ const connectionPrefSelect = document.createElement('select');
118
+ connectionPrefSelect.style.width = '100%';
119
+ connectionPrefSelect.style.marginBottom = '12px';
120
+ connectionPrefSelect.style.padding = '8px 10px';
121
+ connectionPrefSelect.style.borderRadius = '6px';
122
+ connectionPrefSelect.style.fontSize = '14px';
123
+ connectionPrefSelect.style.boxSizing = 'border-box';
124
+ ['auto', 'ble', 'openapi'].forEach((val) => {
125
+ const opt = document.createElement('option');
126
+ opt.value = val;
127
+ opt.text = val.charAt(0).toUpperCase() + val.slice(1);
128
+ opt.selected = (device.connectionPreference || 'auto') === val;
129
+ connectionPrefSelect.appendChild(opt);
130
+ });
131
+ // --- External Publish Protocol ---
132
+ const externalPublishLabel = document.createElement('label');
133
+ externalPublishLabel.textContent = 'Publish as External Device';
134
+ externalPublishLabel.style.display = 'block';
135
+ externalPublishLabel.style.marginBottom = '6px';
136
+ externalPublishLabel.style.fontWeight = '500';
137
+ externalPublishLabel.style.fontSize = '12px';
138
+ externalPublishLabel.style.color = '#6b7280';
139
+ const externalPublishSelect = document.createElement('select');
140
+ externalPublishSelect.style.width = '100%';
141
+ externalPublishSelect.style.marginBottom = '12px';
142
+ externalPublishSelect.style.padding = '8px 10px';
143
+ externalPublishSelect.style.borderRadius = '6px';
144
+ externalPublishSelect.style.fontSize = '14px';
145
+ externalPublishSelect.style.boxSizing = 'border-box';
146
+ ['none', 'hap', 'matter'].forEach((val) => {
147
+ const opt = document.createElement('option');
148
+ opt.value = val;
149
+ opt.text = val === 'none' ? 'None' : val.toUpperCase();
150
+ opt.selected = (device.externalPublishProtocol || 'none') === val;
151
+ externalPublishSelect.appendChild(opt);
152
+ });
153
+ // --- Room ---
154
+ const roomLabel = document.createElement('label');
155
+ roomLabel.textContent = 'Room';
156
+ roomLabel.style.display = 'block';
157
+ roomLabel.style.marginBottom = '6px';
158
+ roomLabel.style.fontWeight = '500';
159
+ roomLabel.style.fontSize = '12px';
160
+ roomLabel.style.color = '#6b7280';
161
+ const roomInput = document.createElement('input');
162
+ roomInput.type = 'text';
163
+ roomInput.value = device.room || '';
164
+ roomInput.placeholder = 'Optional room/location metadata';
165
+ roomInput.style.width = '100%';
166
+ roomInput.style.marginBottom = '12px';
167
+ roomInput.style.padding = '8px 10px';
168
+ roomInput.style.borderRadius = '6px';
169
+ roomInput.style.fontSize = '14px';
170
+ roomInput.style.boxSizing = 'border-box';
171
+ // --- BLE MAC Address ---
172
+ const macLabel = document.createElement('label');
173
+ macLabel.textContent = 'BLE MAC Address (optional)';
174
+ macLabel.style.display = 'block';
175
+ macLabel.style.marginBottom = '6px';
176
+ macLabel.style.fontWeight = '500';
177
+ macLabel.style.fontSize = '12px';
178
+ macLabel.style.color = '#6b7280';
179
+ const macInput = document.createElement('input');
180
+ macInput.type = 'text';
181
+ macInput.value = device.address || '';
182
+ macInput.placeholder = 'AA:BB:CC:DD:EE:FF';
183
+ macInput.style.width = '100%';
184
+ macInput.style.marginBottom = '12px';
185
+ macInput.style.padding = '8px 10px';
186
+ macInput.style.borderRadius = '6px';
187
+ macInput.style.fontSize = '14px';
188
+ macInput.style.boxSizing = 'border-box';
189
+ // --- Encryption Key ---
190
+ const encryptionKeyLabel = document.createElement('label');
191
+ encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)';
192
+ encryptionKeyLabel.style.display = 'block';
193
+ encryptionKeyLabel.style.marginBottom = '6px';
194
+ encryptionKeyLabel.style.fontWeight = '500';
195
+ encryptionKeyLabel.style.fontSize = '12px';
196
+ encryptionKeyLabel.style.color = '#6b7280';
197
+ const encryptionKeyInput = document.createElement('input');
198
+ encryptionKeyInput.type = 'password';
199
+ encryptionKeyInput.value = device.encryptionKey || '';
200
+ encryptionKeyInput.placeholder = 'Paste device BLE encryption key';
201
+ encryptionKeyInput.style.width = '100%';
202
+ encryptionKeyInput.style.marginBottom = '12px';
203
+ encryptionKeyInput.style.padding = '8px 10px';
204
+ encryptionKeyInput.style.borderRadius = '6px';
205
+ encryptionKeyInput.style.fontSize = '14px';
206
+ encryptionKeyInput.style.boxSizing = 'border-box';
207
+ // --- Key ID ---
208
+ const keyIdLabel = document.createElement('label');
209
+ keyIdLabel.textContent = 'BLE Key ID (optional)';
210
+ keyIdLabel.style.display = 'block';
211
+ keyIdLabel.style.marginBottom = '6px';
212
+ keyIdLabel.style.fontWeight = '500';
213
+ keyIdLabel.style.fontSize = '12px';
214
+ keyIdLabel.style.color = '#6b7280';
215
+ const keyIdInput = document.createElement('input');
216
+ keyIdInput.type = 'text';
217
+ keyIdInput.value = device.keyId || '';
218
+ keyIdInput.placeholder = 'e.g. ff';
219
+ keyIdInput.style.width = '100%';
220
+ keyIdInput.style.marginBottom = '12px';
221
+ keyIdInput.style.padding = '8px 10px';
222
+ keyIdInput.style.borderRadius = '6px';
223
+ keyIdInput.style.fontSize = '14px';
224
+ keyIdInput.style.boxSizing = 'border-box';
225
+ // --- BLE Polling Enabled ---
226
+ const blePollingEnabledLabel = document.createElement('label');
227
+ blePollingEnabledLabel.textContent = 'Enable BLE Polling Fallback';
228
+ blePollingEnabledLabel.style.display = 'block';
229
+ blePollingEnabledLabel.style.marginBottom = '6px';
230
+ blePollingEnabledLabel.style.fontWeight = '500';
231
+ blePollingEnabledLabel.style.fontSize = '12px';
232
+ blePollingEnabledLabel.style.color = '#6b7280';
233
+ const blePollingEnabledInput = document.createElement('input');
234
+ blePollingEnabledInput.type = 'checkbox';
235
+ blePollingEnabledInput.checked = device.blePollingEnabled !== false; // default true
236
+ blePollingEnabledInput.style.marginRight = '8px';
237
+ blePollingEnabledInput.style.marginBottom = '12px';
238
+ // --- BLE Poll Interval ---
239
+ const blePollIntervalLabel = document.createElement('label');
240
+ blePollIntervalLabel.textContent = 'BLE Polling Interval (ms)';
241
+ blePollIntervalLabel.style.display = 'block';
242
+ blePollIntervalLabel.style.marginBottom = '6px';
243
+ blePollIntervalLabel.style.fontWeight = '500';
244
+ blePollIntervalLabel.style.fontSize = '12px';
245
+ blePollIntervalLabel.style.color = '#6b7280';
246
+ const blePollIntervalInput = document.createElement('input');
247
+ blePollIntervalInput.type = 'number';
248
+ blePollIntervalInput.value = device.blePollIntervalMs || 600000;
249
+ blePollIntervalInput.min = '60000';
250
+ blePollIntervalInput.step = '1000';
251
+ blePollIntervalInput.style.width = '100%';
252
+ blePollIntervalInput.style.marginBottom = '12px';
253
+ blePollIntervalInput.style.padding = '8px 10px';
254
+ blePollIntervalInput.style.borderRadius = '6px';
255
+ blePollIntervalInput.style.fontSize = '14px';
256
+ blePollIntervalInput.style.boxSizing = 'border-box';
257
+ const buttons = document.createElement('div');
258
+ buttons.style.display = 'flex';
259
+ buttons.style.gap = '10px';
260
+ buttons.style.justifyContent = 'flex-end';
261
+ buttons.style.marginTop = '18px';
262
+ buttons.style.paddingTop = '18px';
263
+ buttons.style.borderTop = '1px solid rgba(0, 0, 0, 0.08)';
264
+ const cancelBtn = document.createElement('button');
265
+ cancelBtn.textContent = 'Cancel';
266
+ cancelBtn.className = 'secondary';
267
+ cancelBtn.style.background = '#6b7280';
268
+ cancelBtn.style.padding = '8px 16px';
269
+ cancelBtn.style.fontSize = '13px';
270
+ const importBtn = document.createElement('button');
271
+ importBtn.textContent = 'Add to Config';
272
+ importBtn.style.background = 'var(--switchbot-red, #ef4444)';
273
+ importBtn.style.padding = '8px 20px';
274
+ importBtn.style.fontSize = '13px';
275
+ const cleanup = (result) => {
276
+ div.remove();
277
+ resolve(result);
278
+ };
279
+ cancelBtn.onclick = () => cleanup(null);
280
+ importBtn.onclick = () => {
281
+ // Never allow 'undefined' as a name
282
+ let finalName = nameInput.value;
283
+ if (!finalName || finalName === 'undefined') {
284
+ finalName = device.id || '';
285
+ }
286
+ cleanup({
287
+ configDeviceName: finalName,
288
+ configDeviceType: typeSelect.value || device.type,
289
+ address: macInput.value || undefined,
290
+ connectionPreference: connectionPrefSelect.value || undefined,
291
+ externalPublishProtocol: externalPublishSelect.value || 'none',
292
+ room: roomInput.value || undefined,
293
+ encryptionKey: encryptionKeyInput.value || undefined,
294
+ keyId: keyIdInput.value || undefined,
295
+ refreshRate: Number(openApiRefreshInput.value) || 300,
296
+ blePollingEnabled: blePollingEnabledInput.checked,
297
+ blePollIntervalMs: Number(blePollIntervalInput.value) || 600000,
298
+ });
299
+ };
300
+ div.addEventListener('click', (event) => {
301
+ if (event.target === div) {
302
+ cleanup(null);
303
+ }
304
+ });
305
+ buttons.appendChild(cancelBtn);
306
+ buttons.appendChild(importBtn);
307
+ contentDiv.appendChild(nameLabel);
308
+ contentDiv.appendChild(nameInput);
309
+ contentDiv.appendChild(typeLabel);
310
+ contentDiv.appendChild(typeSelect);
311
+ contentDiv.appendChild(connectionPrefLabel);
312
+ contentDiv.appendChild(connectionPrefSelect);
313
+ contentDiv.appendChild(externalPublishLabel);
314
+ contentDiv.appendChild(externalPublishSelect);
315
+ contentDiv.appendChild(roomLabel);
316
+ contentDiv.appendChild(roomInput);
317
+ contentDiv.appendChild(macLabel);
318
+ contentDiv.appendChild(macInput);
319
+ contentDiv.appendChild(encryptionKeyLabel);
320
+ contentDiv.appendChild(encryptionKeyInput);
321
+ contentDiv.appendChild(keyIdLabel);
322
+ contentDiv.appendChild(keyIdInput);
323
+ contentDiv.appendChild(openApiRefreshLabel);
324
+ contentDiv.appendChild(openApiRefreshInput);
325
+ contentDiv.appendChild(blePollingEnabledLabel);
326
+ contentDiv.appendChild(blePollingEnabledInput);
327
+ contentDiv.appendChild(blePollIntervalLabel);
328
+ contentDiv.appendChild(blePollIntervalInput);
329
+ contentDiv.appendChild(buttons);
330
+ modal.appendChild(title);
331
+ modal.appendChild(contentDiv);
332
+ div.appendChild(modal);
333
+ document.body.appendChild(div);
334
+ nameInput.focus();
335
+ });
336
+ }
337
+ export async function editDevice(device) {
338
+ // --- OpenAPI Polling Interval (refreshRate) ---
339
+ const openApiRefreshLabel = document.createElement('label');
340
+ openApiRefreshLabel.textContent = 'OpenAPI Polling Interval (seconds)';
341
+ openApiRefreshLabel.style.display = 'block';
342
+ openApiRefreshLabel.style.marginBottom = '6px';
343
+ openApiRefreshLabel.style.fontWeight = '500';
344
+ openApiRefreshLabel.style.fontSize = '12px';
345
+ openApiRefreshLabel.style.color = '#6b7280';
346
+ openApiRefreshLabel.title = 'How often to poll this device via OpenAPI for status (in seconds). Overrides platform value if set. Default: 300 (5 minutes). Minimum: 30.';
347
+ const openApiRefreshInput = document.createElement('input');
348
+ openApiRefreshInput.type = 'number';
349
+ openApiRefreshInput.value = device.refreshRate || 300;
350
+ openApiRefreshInput.min = '30';
351
+ openApiRefreshInput.step = '1';
352
+ openApiRefreshInput.style.width = '100%';
353
+ openApiRefreshInput.style.marginBottom = '12px';
354
+ openApiRefreshInput.style.padding = '8px 10px';
355
+ openApiRefreshInput.style.borderRadius = '6px';
356
+ openApiRefreshInput.style.fontSize = '14px';
357
+ openApiRefreshInput.style.boxSizing = 'border-box';
358
+ // --- Device Type Select ---
359
+ // --- BLE Polling Enabled ---
360
+ const blePollingEnabledLabel = document.createElement('label');
361
+ blePollingEnabledLabel.textContent = 'Enable BLE Polling Fallback';
362
+ blePollingEnabledLabel.style.display = 'block';
363
+ blePollingEnabledLabel.style.marginBottom = '6px';
364
+ blePollingEnabledLabel.style.fontWeight = '500';
365
+ blePollingEnabledLabel.style.fontSize = '12px';
366
+ blePollingEnabledLabel.style.color = '#6b7280';
367
+ const blePollingEnabledInput = document.createElement('input');
368
+ blePollingEnabledInput.type = 'checkbox';
369
+ blePollingEnabledInput.checked = device.blePollingEnabled !== false; // default true
370
+ blePollingEnabledInput.style.marginRight = '8px';
371
+ blePollingEnabledInput.style.marginBottom = '12px';
372
+ // --- BLE Poll Interval ---
373
+ const blePollIntervalLabel = document.createElement('label');
374
+ blePollIntervalLabel.textContent = 'BLE Polling Interval (ms)';
375
+ blePollIntervalLabel.style.display = 'block';
376
+ blePollIntervalLabel.style.marginBottom = '6px';
377
+ blePollIntervalLabel.style.fontWeight = '500';
378
+ blePollIntervalLabel.style.fontSize = '12px';
379
+ blePollIntervalLabel.style.color = '#6b7280';
380
+ const blePollIntervalInput = document.createElement('input');
381
+ blePollIntervalInput.type = 'number';
382
+ blePollIntervalInput.value = device.blePollIntervalMs || 600000;
383
+ blePollIntervalInput.min = '60000';
384
+ blePollIntervalInput.step = '1000';
385
+ blePollIntervalInput.style.width = '100%';
386
+ blePollIntervalInput.style.marginBottom = '12px';
387
+ blePollIntervalInput.style.padding = '8px 10px';
388
+ blePollIntervalInput.style.borderRadius = '6px';
389
+ blePollIntervalInput.style.fontSize = '14px';
390
+ blePollIntervalInput.style.boxSizing = 'border-box';
391
+ const typeLabel = document.createElement('label');
392
+ typeLabel.textContent = 'Config Device Type';
393
+ typeLabel.style.display = 'block';
394
+ typeLabel.style.marginBottom = '6px';
395
+ typeLabel.style.fontWeight = '500';
396
+ typeLabel.style.fontSize = '12px';
397
+ typeLabel.style.color = '#6b7280';
398
+ const typeSelect = document.createElement('select');
399
+ typeSelect.style.width = '100%';
400
+ typeSelect.style.padding = '8px 10px';
401
+ typeSelect.style.marginBottom = '12px';
402
+ typeSelect.style.borderRadius = '6px';
403
+ typeSelect.style.fontSize = '14px';
404
+ typeSelect.style.background = getComputedStyle(document.body).backgroundColor;
405
+ typeSelect.style.color = getComputedStyle(document.body).color;
406
+ typeSelect.style.border = '1px solid #ccc';
407
+ typeSelect.style.boxSizing = 'border-box';
408
+ // Add option groups with all API device types
409
+ Object.keys(DEVICE_TYPES).forEach((categoryName) => {
410
+ const optgroup = document.createElement('optgroup');
411
+ optgroup.label = categoryName;
412
+ DEVICE_TYPES[categoryName].forEach((deviceType) => {
413
+ const opt = document.createElement('option');
414
+ opt.value = deviceType;
415
+ opt.text = deviceType;
416
+ // Select current configDeviceType if set, otherwise match API deviceType
417
+ const currentType = device.configDeviceType || device.deviceType || device.type || '';
418
+ opt.selected = currentType === deviceType;
419
+ optgroup.appendChild(opt);
420
+ });
421
+ typeSelect.appendChild(optgroup);
422
+ });
423
+ // Create modal dialog for editing
424
+ const div = document.createElement('div');
425
+ div.style.position = 'fixed';
426
+ div.style.top = '0';
427
+ div.style.left = '0';
428
+ div.style.width = '100%';
429
+ div.style.height = '100%';
430
+ div.style.background = 'rgba(0,0,0,0.7)';
431
+ div.style.display = 'flex';
432
+ div.style.alignItems = 'center';
433
+ div.style.justifyContent = 'center';
434
+ div.style.zIndex = '9999';
435
+ const modal = document.createElement('div');
436
+ modal.style.background = getComputedStyle(document.body).backgroundColor;
437
+ modal.style.color = getComputedStyle(document.body).color;
438
+ modal.style.padding = '0';
439
+ modal.style.borderRadius = '10px';
440
+ modal.style.minWidth = '440px';
441
+ modal.style.maxWidth = '90vw';
442
+ modal.style.boxShadow = '0 8px 32px rgba(0,0,0,0.35)';
443
+ modal.style.overflow = 'hidden';
444
+ modal.style.borderTop = '3px solid var(--switchbot-red, #ef4444)';
445
+ const title = document.createElement('h3');
446
+ title.textContent = 'Edit Device';
447
+ title.style.marginTop = '0';
448
+ title.style.marginBottom = '16px';
449
+ title.style.padding = '20px 20px 0';
450
+ title.style.fontSize = '18px';
451
+ title.style.fontWeight = '600';
452
+ title.style.color = 'var(--switchbot-red, #ef4444)';
453
+ title.style.letterSpacing = '-0.02em';
454
+ const contentDiv = document.createElement('div');
455
+ contentDiv.style.padding = '0 20px 20px';
456
+ const nameLabel = document.createElement('label');
457
+ nameLabel.textContent = 'Device Name';
458
+ nameLabel.style.display = 'block';
459
+ nameLabel.style.marginBottom = '6px';
460
+ nameLabel.style.fontWeight = '500';
461
+ nameLabel.style.fontSize = '12px';
462
+ nameLabel.style.color = '#6b7280';
463
+ const nameInput = document.createElement('input');
464
+ nameInput.type = 'text';
465
+ nameInput.value = device.name || device.id;
466
+ nameInput.style.width = '100%';
467
+ nameInput.style.marginBottom = '12px';
468
+ nameInput.style.padding = '8px 10px';
469
+ nameInput.style.borderRadius = '6px';
470
+ nameInput.style.fontSize = '14px';
471
+ nameInput.style.boxSizing = 'border-box';
472
+ nameInput.style.transition = 'border-color 0.2s ease';
473
+ // Read-only API device type field
474
+ const apiTypeLabel = document.createElement('label');
475
+ apiTypeLabel.textContent = 'Device Type (API - Read Only)';
476
+ apiTypeLabel.style.display = 'block';
477
+ apiTypeLabel.style.marginBottom = '6px';
478
+ apiTypeLabel.style.fontWeight = '500';
479
+ apiTypeLabel.style.fontSize = '12px';
480
+ apiTypeLabel.style.color = '#6b7280';
481
+ const apiTypeInput = document.createElement('input');
482
+ apiTypeInput.type = 'text';
483
+ apiTypeInput.value = device.deviceType || device.type || 'Unknown';
484
+ apiTypeInput.readOnly = true;
485
+ apiTypeInput.style.width = '100%';
486
+ apiTypeInput.style.marginBottom = '12px';
487
+ apiTypeInput.style.padding = '8px 10px';
488
+ apiTypeInput.style.borderRadius = '6px';
489
+ apiTypeInput.style.fontSize = '13px';
490
+ apiTypeInput.style.opacity = '0.6';
491
+ apiTypeInput.style.cursor = 'not-allowed';
492
+ apiTypeInput.style.boxSizing = 'border-box';
493
+ apiTypeInput.style.backgroundColor = '#f9fafb';
494
+ // Editable config device type dropdown
495
+ // Add option groups with all API device types
496
+ Object.keys(DEVICE_TYPES).forEach((categoryName) => {
497
+ const optgroup = document.createElement('optgroup');
498
+ optgroup.label = categoryName;
499
+ DEVICE_TYPES[categoryName].forEach((deviceType) => {
500
+ const opt = document.createElement('option');
501
+ opt.value = deviceType;
502
+ opt.text = deviceType;
503
+ // Select current configDeviceType if set, otherwise match API deviceType
504
+ const currentType = device.configDeviceType || device.deviceType || device.type || '';
505
+ opt.selected = currentType === deviceType;
506
+ optgroup.appendChild(opt);
507
+ });
508
+ typeSelect.appendChild(optgroup);
509
+ });
510
+ // --- Connection Preference ---
511
+ const connectionPrefLabel = document.createElement('label');
512
+ connectionPrefLabel.textContent = 'Connection Preference';
513
+ connectionPrefLabel.style.display = 'block';
514
+ connectionPrefLabel.style.marginBottom = '6px';
515
+ connectionPrefLabel.style.fontWeight = '500';
516
+ connectionPrefLabel.style.fontSize = '12px';
517
+ connectionPrefLabel.style.color = '#6b7280';
518
+ const connectionPrefSelect = document.createElement('select');
519
+ connectionPrefSelect.style.width = '100%';
520
+ connectionPrefSelect.style.marginBottom = '12px';
521
+ connectionPrefSelect.style.padding = '8px 10px';
522
+ connectionPrefSelect.style.borderRadius = '6px';
523
+ connectionPrefSelect.style.fontSize = '14px';
524
+ connectionPrefSelect.style.boxSizing = 'border-box';
525
+ ['auto', 'ble', 'openapi'].forEach((val) => {
526
+ const opt = document.createElement('option');
527
+ opt.value = val;
528
+ opt.text = val.charAt(0).toUpperCase() + val.slice(1);
529
+ opt.selected = (device.connectionPreference || 'auto') === val;
530
+ connectionPrefSelect.appendChild(opt);
531
+ });
532
+ // --- External Publish Protocol ---
533
+ const externalPublishLabel = document.createElement('label');
534
+ externalPublishLabel.textContent = 'Publish as External Device';
535
+ externalPublishLabel.style.display = 'block';
536
+ externalPublishLabel.style.marginBottom = '6px';
537
+ externalPublishLabel.style.fontWeight = '500';
538
+ externalPublishLabel.style.fontSize = '12px';
539
+ externalPublishLabel.style.color = '#6b7280';
540
+ const externalPublishSelect = document.createElement('select');
541
+ externalPublishSelect.style.width = '100%';
542
+ externalPublishSelect.style.marginBottom = '12px';
543
+ externalPublishSelect.style.padding = '8px 10px';
544
+ externalPublishSelect.style.borderRadius = '6px';
545
+ externalPublishSelect.style.fontSize = '14px';
546
+ externalPublishSelect.style.boxSizing = 'border-box';
547
+ ['none', 'hap', 'matter'].forEach((val) => {
548
+ const opt = document.createElement('option');
549
+ opt.value = val;
550
+ opt.text = val === 'none' ? 'None' : val.toUpperCase();
551
+ opt.selected = (device.externalPublishProtocol || 'none') === val;
552
+ externalPublishSelect.appendChild(opt);
553
+ });
554
+ // --- Room ---
555
+ const roomLabel = document.createElement('label');
556
+ roomLabel.textContent = 'Room';
557
+ roomLabel.style.display = 'block';
558
+ roomLabel.style.marginBottom = '6px';
559
+ roomLabel.style.fontWeight = '500';
560
+ roomLabel.style.fontSize = '12px';
561
+ roomLabel.style.color = '#6b7280';
562
+ const roomInput = document.createElement('input');
563
+ roomInput.type = 'text';
564
+ roomInput.value = device.room || '';
565
+ roomInput.placeholder = 'Optional room/location metadata';
566
+ roomInput.style.width = '100%';
567
+ roomInput.style.marginBottom = '12px';
568
+ roomInput.style.padding = '8px 10px';
569
+ roomInput.style.borderRadius = '6px';
570
+ roomInput.style.fontSize = '14px';
571
+ roomInput.style.boxSizing = 'border-box';
572
+ // --- Encryption Key ---
573
+ const encryptionKeyLabel = document.createElement('label');
574
+ encryptionKeyLabel.textContent = 'BLE Encryption Key (optional)';
575
+ encryptionKeyLabel.style.display = 'block';
576
+ encryptionKeyLabel.style.marginBottom = '6px';
577
+ encryptionKeyLabel.style.fontWeight = '500';
578
+ encryptionKeyLabel.style.fontSize = '12px';
579
+ encryptionKeyLabel.style.color = '#6b7280';
580
+ const encryptionKeyInput = document.createElement('input');
581
+ encryptionKeyInput.type = 'password';
582
+ encryptionKeyInput.value = device.encryptionKey || '';
583
+ encryptionKeyInput.placeholder = 'Paste device BLE encryption key';
584
+ encryptionKeyInput.style.width = '100%';
585
+ encryptionKeyInput.style.marginBottom = '12px';
586
+ encryptionKeyInput.style.padding = '8px 10px';
587
+ encryptionKeyInput.style.borderRadius = '6px';
588
+ encryptionKeyInput.style.fontSize = '14px';
589
+ encryptionKeyInput.style.boxSizing = 'border-box';
590
+ // --- Key ID ---
591
+ const keyIdLabel = document.createElement('label');
592
+ keyIdLabel.textContent = 'BLE Key ID (optional)';
593
+ keyIdLabel.style.display = 'block';
594
+ keyIdLabel.style.marginBottom = '6px';
595
+ keyIdLabel.style.fontWeight = '500';
596
+ keyIdLabel.style.fontSize = '12px';
597
+ keyIdLabel.style.color = '#6b7280';
598
+ const keyIdInput = document.createElement('input');
599
+ keyIdInput.type = 'text';
600
+ keyIdInput.value = device.keyId || '';
601
+ keyIdInput.placeholder = 'e.g. ff';
602
+ keyIdInput.style.width = '100%';
603
+ keyIdInput.style.marginBottom = '12px';
604
+ keyIdInput.style.padding = '8px 10px';
605
+ keyIdInput.style.borderRadius = '6px';
606
+ keyIdInput.style.fontSize = '14px';
607
+ keyIdInput.style.boxSizing = 'border-box';
608
+ const errorMessage = document.createElement('div');
609
+ errorMessage.style.color = 'var(--switchbot-red, #ef4444)';
610
+ errorMessage.style.marginBottom = '12px';
611
+ errorMessage.style.fontSize = '12px';
612
+ errorMessage.style.display = 'none';
613
+ errorMessage.style.padding = '8px 10px';
614
+ errorMessage.style.background = 'var(--switchbot-red-light, #fee2e2)';
615
+ errorMessage.style.borderRadius = '6px';
616
+ errorMessage.style.fontWeight = '500';
617
+ const buttons = document.createElement('div');
618
+ buttons.style.display = 'flex';
619
+ buttons.style.gap = '10px';
620
+ buttons.style.justifyContent = 'flex-end';
621
+ buttons.style.marginTop = '18px';
622
+ buttons.style.paddingTop = '18px';
623
+ buttons.style.borderTop = '1px solid rgba(0, 0, 0, 0.08)';
624
+ const cancelBtn = document.createElement('button');
625
+ cancelBtn.textContent = 'Cancel';
626
+ cancelBtn.className = 'secondary';
627
+ cancelBtn.style.background = '#6b7280';
628
+ cancelBtn.style.padding = '8px 16px';
629
+ cancelBtn.style.fontSize = '13px';
630
+ cancelBtn.onclick = () => div.remove();
631
+ const saveBtn = document.createElement('button');
632
+ saveBtn.textContent = 'Save';
633
+ saveBtn.style.background = 'var(--switchbot-red, #ef4444)';
634
+ saveBtn.style.padding = '8px 20px';
635
+ saveBtn.style.fontSize = '13px';
636
+ saveBtn.onclick = async () => {
637
+ try {
638
+ const { updateDevice, syncParentPluginConfigFromDisk, fetchDevices } = await import('./api.js');
639
+ const { renderDeviceList } = await import('./render.js');
640
+ const params = {
641
+ deviceId: device.id,
642
+ configDeviceName: nameInput.value || undefined,
643
+ configDeviceType: typeSelect.value,
644
+ connectionPreference: connectionPrefSelect.value,
645
+ externalPublishProtocol: externalPublishSelect.value,
646
+ room: roomInput.value || undefined,
647
+ encryptionKey: encryptionKeyInput.value || undefined,
648
+ keyId: keyIdInput.value || undefined,
649
+ refreshRate: Number(openApiRefreshInput.value) || 300,
650
+ blePollingEnabled: blePollingEnabledInput.checked,
651
+ blePollIntervalMs: Number(blePollIntervalInput.value) || 600000,
652
+ };
653
+ // Only include defined properties in the options object
654
+ const options = {};
655
+ if (params.connectionPreference !== undefined) {
656
+ options.connectionPreference = params.connectionPreference;
657
+ }
658
+ if (params.room !== undefined) {
659
+ options.room = params.room;
660
+ }
661
+ if (params.externalPublishProtocol !== undefined) {
662
+ options.externalPublishProtocol = params.externalPublishProtocol;
663
+ }
664
+ if (params.encryptionKey !== undefined) {
665
+ options.encryptionKey = params.encryptionKey;
666
+ }
667
+ if (params.keyId !== undefined) {
668
+ options.keyId = params.keyId;
669
+ }
670
+ if (params.refreshRate !== undefined) {
671
+ options.refreshRate = params.refreshRate;
672
+ }
673
+ if (params.blePollingEnabled !== undefined) {
674
+ options.blePollingEnabled = params.blePollingEnabled;
675
+ }
676
+ if (params.blePollIntervalMs !== undefined) {
677
+ options.blePollIntervalMs = params.blePollIntervalMs;
678
+ }
679
+ await updateDevice(params.deviceId, params.configDeviceName, params.configDeviceType, options);
680
+ await syncParentPluginConfigFromDisk();
681
+ contentDiv.appendChild(openApiRefreshLabel);
682
+ contentDiv.appendChild(openApiRefreshInput);
683
+ // Refresh device list
684
+ uiLog.info('[Edit Device] Refreshing device list after update');
685
+ const list = await fetchDevices();
686
+ renderDeviceList(list);
687
+ div.remove();
688
+ }
689
+ catch (e) {
690
+ uiLog.error('Update error:', e);
691
+ errorMessage.textContent = `Error: ${e instanceof Error ? e.message : 'Failed to update device'}`;
692
+ errorMessage.style.display = 'block';
693
+ }
694
+ };
695
+ buttons.appendChild(cancelBtn);
696
+ buttons.appendChild(saveBtn);
697
+ contentDiv.appendChild(nameLabel);
698
+ contentDiv.appendChild(nameInput);
699
+ contentDiv.appendChild(apiTypeLabel);
700
+ contentDiv.appendChild(apiTypeInput);
701
+ contentDiv.appendChild(typeLabel);
702
+ contentDiv.appendChild(typeSelect);
703
+ contentDiv.appendChild(connectionPrefLabel);
704
+ contentDiv.appendChild(connectionPrefSelect);
705
+ contentDiv.appendChild(externalPublishLabel);
706
+ contentDiv.appendChild(externalPublishSelect);
707
+ contentDiv.appendChild(roomLabel);
708
+ contentDiv.appendChild(roomInput);
709
+ contentDiv.appendChild(encryptionKeyLabel);
710
+ contentDiv.appendChild(encryptionKeyInput);
711
+ contentDiv.appendChild(keyIdLabel);
712
+ contentDiv.appendChild(keyIdInput);
713
+ // Insert BLE polling fields before error/buttons
714
+ contentDiv.appendChild(openApiRefreshLabel);
715
+ contentDiv.appendChild(openApiRefreshInput);
716
+ contentDiv.appendChild(blePollingEnabledLabel);
717
+ contentDiv.appendChild(blePollingEnabledInput);
718
+ contentDiv.appendChild(blePollIntervalLabel);
719
+ contentDiv.appendChild(blePollIntervalInput);
720
+ contentDiv.appendChild(errorMessage);
721
+ contentDiv.appendChild(buttons);
722
+ modal.appendChild(title);
723
+ modal.appendChild(contentDiv);
724
+ div.appendChild(modal);
725
+ document.body.appendChild(div);
726
+ nameInput.focus();
727
+ }
728
+ //# sourceMappingURL=modals.js.map