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