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

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 (57) hide show
  1. package/.github/copilot-instructions.md +39 -0
  2. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -1
  3. package/dist/homebridge-ui/endpoints/devices.js +10 -1
  4. package/dist/homebridge-ui/endpoints/devices.js.map +1 -1
  5. package/dist/homebridge-ui/public/css/styles.css +111 -16
  6. package/dist/homebridge-ui/public/index.html +34 -60
  7. package/dist/homebridge-ui/public/js/api.d.ts +5 -1
  8. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  9. package/dist/homebridge-ui/public/js/api.js +13 -3
  10. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  11. package/dist/homebridge-ui/public/js/api.ts +13 -3
  12. package/dist/homebridge-ui/public/js/app.js +338 -42
  13. package/dist/homebridge-ui/public/js/app.js.map +3 -3
  14. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
  15. package/dist/homebridge-ui/public/js/devices.js +12 -3
  16. package/dist/homebridge-ui/public/js/devices.js.map +1 -1
  17. package/dist/homebridge-ui/public/js/devices.ts +13 -3
  18. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
  19. package/dist/homebridge-ui/public/js/discovery.js +37 -3
  20. package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
  21. package/dist/homebridge-ui/public/js/discovery.ts +39 -2
  22. package/dist/homebridge-ui/public/js/modals.d.ts +5 -0
  23. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
  24. package/dist/homebridge-ui/public/js/modals.js +146 -0
  25. package/dist/homebridge-ui/public/js/modals.js.map +1 -1
  26. package/dist/homebridge-ui/public/js/modals.ts +167 -0
  27. package/dist/homebridge-ui/public/js/render.d.ts +3 -1
  28. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  29. package/dist/homebridge-ui/public/js/render.js +135 -32
  30. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  31. package/dist/homebridge-ui/public/js/render.ts +145 -32
  32. package/dist/platform.d.ts.map +1 -1
  33. package/dist/platform.js +64 -109
  34. package/dist/platform.js.map +1 -1
  35. package/dist/utils.d.ts.map +1 -1
  36. package/dist/utils.js +2 -1
  37. package/dist/utils.js.map +1 -1
  38. package/docs/variables/default.html +1 -1
  39. package/package.json +9 -8
  40. package/src/homebridge-ui/endpoints/devices.ts +5 -2
  41. package/src/homebridge-ui/public/css/styles.css +111 -16
  42. package/src/homebridge-ui/public/index.html +34 -60
  43. package/src/homebridge-ui/public/js/api.ts +13 -3
  44. package/src/homebridge-ui/public/js/devices.ts +13 -3
  45. package/src/homebridge-ui/public/js/discovery.ts +39 -2
  46. package/src/homebridge-ui/public/js/modals.ts +167 -0
  47. package/src/homebridge-ui/public/js/render.ts +145 -32
  48. package/src/platform.ts +77 -109
  49. package/src/utils.ts +2 -1
  50. package/test/e2e/run-e2e.spec.ts +12 -10
  51. package/test/helpers/matter-harness.ts +2 -2
  52. package/test/matter-childbridge.spec.ts +5 -1
  53. package/test/matter-integration.spec.ts +41 -0
  54. package/test/utils.spec.ts +18 -2
  55. package/todo/COMPLETED.md +53 -0
  56. package/todo/TODO.md +13 -42
  57. package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
@@ -137,10 +137,20 @@ async function discoverDevices() {
137
137
  throw e;
138
138
  }
139
139
  }
140
- async function addDevice(deviceId, name, type) {
140
+ async function addDevice(deviceId, name, type, options) {
141
141
  try {
142
- uiLog.info("Adding device to config:", { deviceId, name, type });
143
- const resp = await homebridge.request("/add-device", { deviceId, name, type });
142
+ const payload = { deviceId, name, type };
143
+ if (options?.address) {
144
+ payload.address = options.address;
145
+ }
146
+ if (options?.model) {
147
+ payload.model = options.model;
148
+ }
149
+ if (options?.rssi !== void 0 && options?.rssi !== null && options?.rssi !== 0) {
150
+ payload.rssi = options.rssi;
151
+ }
152
+ uiLog.info("Adding device to config:", payload);
153
+ const resp = await homebridge.request("/add-device", payload);
144
154
  uiLog.info("Add device response:", resp);
145
155
  if (!resp || resp.success === false) {
146
156
  throw new Error(resp?.data?.message || "Failed to add device");
@@ -284,6 +294,9 @@ __export(discovery_exports, {
284
294
  addDeviceToConfig: () => addDeviceToConfig,
285
295
  discoverDevices: () => discoverDevices2
286
296
  });
297
+ function normalizeId(value) {
298
+ return String(value ?? "").trim().toLowerCase();
299
+ }
287
300
  async function discoverDevices2() {
288
301
  const btn = document.getElementById("discoverBtn");
289
302
  const status = document.getElementById("discoverStatus");
@@ -379,6 +392,7 @@ async function discoverDevices2() {
379
392
  btn2.style.cursor = "pointer";
380
393
  btn2.style.border = preferences.connectionType === option.value ? "2px solid #007AFF" : "1px solid #ccc";
381
394
  btn2.style.backgroundColor = preferences.connectionType === option.value ? "#f0f7ff" : "#fff";
395
+ btn2.style.color = preferences.connectionType === option.value ? "#1d4ed8" : "#374151";
382
396
  btn2.onclick = () => {
383
397
  preferences.connectionType = option.value;
384
398
  setDiscoveryPreferences(preferences);
@@ -387,9 +401,11 @@ async function discoverDevices2() {
387
401
  ;
388
402
  b.style.border = "1px solid #ccc";
389
403
  b.style.backgroundColor = "#fff";
404
+ b.style.color = "#374151";
390
405
  }
391
406
  btn2.style.border = "2px solid #007AFF";
392
407
  btn2.style.backgroundColor = "#f0f7ff";
408
+ btn2.style.color = "#1d4ed8";
393
409
  };
394
410
  filterGroup.appendChild(btn2);
395
411
  }
@@ -447,6 +463,17 @@ async function discoverDevices2() {
447
463
  controlsDiv.appendChild(searchInput);
448
464
  list.innerHTML = "";
449
465
  list.appendChild(controlsDiv);
466
+ let deviceListContainer = document.getElementById("discoveredDevices");
467
+ if (!deviceListContainer) {
468
+ deviceListContainer = document.createElement("ul");
469
+ deviceListContainer.id = "discoveredDevices";
470
+ deviceListContainer.style.maxHeight = "400px";
471
+ deviceListContainer.style.overflowY = "auto";
472
+ deviceListContainer.style.marginTop = "12px";
473
+ deviceListContainer.style.padding = "0";
474
+ deviceListContainer.style.listStyle = "none";
475
+ list.appendChild(deviceListContainer);
476
+ }
450
477
  list.style.display = "block";
451
478
  await updateDiscoveryView(devices, preferences);
452
479
  } catch (e) {
@@ -464,8 +491,20 @@ async function discoverDevices2() {
464
491
  async function updateDiscoveryView(allDevices, preferences) {
465
492
  const filtered = filterDevices(allDevices, preferences.connectionType, preferences.searchQuery);
466
493
  const sorted = sortDevices(filtered, preferences.sortBy);
467
- const ul = await renderDiscoveredDevices(sorted);
468
- document.getElementById("discoveredDevices")?.replaceWith(ul);
494
+ const configured = await fetchDevices();
495
+ const configuredIds = new Set((configured || []).map((d) => normalizeId(d?.id || d?.deviceId)));
496
+ const ul = await renderDiscoveredDevices(sorted, {
497
+ configuredIds
498
+ });
499
+ const existingList = document.getElementById("discoveredDevices");
500
+ if (existingList && existingList.parentNode) {
501
+ existingList.replaceWith(ul);
502
+ } else {
503
+ const listContainer = document.getElementById("discoveredList");
504
+ if (listContainer) {
505
+ listContainer.appendChild(ul);
506
+ }
507
+ }
469
508
  ul.id = "discoveredDevices";
470
509
  const status = document.getElementById("discoverStatus");
471
510
  if (status) {
@@ -604,8 +643,155 @@ var init_constants = __esm({
604
643
  // src/homebridge-ui/public/js/modals.ts
605
644
  var modals_exports = {};
606
645
  __export(modals_exports, {
607
- editDevice: () => editDevice
646
+ editDevice: () => editDevice,
647
+ importDiscoveredDevice: () => importDiscoveredDevice
608
648
  });
649
+ async function importDiscoveredDevice(device) {
650
+ return new Promise((resolve) => {
651
+ const div = document.createElement("div");
652
+ div.style.position = "fixed";
653
+ div.style.top = "0";
654
+ div.style.left = "0";
655
+ div.style.width = "100%";
656
+ div.style.height = "100%";
657
+ div.style.background = "rgba(0,0,0,0.7)";
658
+ div.style.display = "flex";
659
+ div.style.alignItems = "center";
660
+ div.style.justifyContent = "center";
661
+ div.style.zIndex = "9999";
662
+ const modal = document.createElement("div");
663
+ modal.style.background = getComputedStyle(document.body).backgroundColor;
664
+ modal.style.color = getComputedStyle(document.body).color;
665
+ modal.style.padding = "0";
666
+ modal.style.borderRadius = "10px";
667
+ modal.style.minWidth = "440px";
668
+ modal.style.maxWidth = "90vw";
669
+ modal.style.boxShadow = "0 8px 32px rgba(0,0,0,0.35)";
670
+ modal.style.overflow = "hidden";
671
+ modal.style.borderTop = "3px solid var(--switchbot-red, #ef4444)";
672
+ const title = document.createElement("h3");
673
+ title.textContent = "Import Discovered Device";
674
+ title.style.marginTop = "0";
675
+ title.style.marginBottom = "16px";
676
+ title.style.padding = "20px 20px 0";
677
+ title.style.fontSize = "18px";
678
+ title.style.fontWeight = "600";
679
+ title.style.color = "var(--switchbot-red, #ef4444)";
680
+ title.style.letterSpacing = "-0.02em";
681
+ const contentDiv = document.createElement("div");
682
+ contentDiv.style.padding = "0 20px 20px";
683
+ const nameLabel = document.createElement("label");
684
+ nameLabel.textContent = "Device Name";
685
+ nameLabel.style.display = "block";
686
+ nameLabel.style.marginBottom = "6px";
687
+ nameLabel.style.fontWeight = "500";
688
+ nameLabel.style.fontSize = "12px";
689
+ nameLabel.style.color = "#6b7280";
690
+ const nameInput = document.createElement("input");
691
+ nameInput.type = "text";
692
+ nameInput.value = device.name || device.id || "";
693
+ nameInput.style.width = "100%";
694
+ nameInput.style.marginBottom = "12px";
695
+ nameInput.style.padding = "8px 10px";
696
+ nameInput.style.borderRadius = "6px";
697
+ nameInput.style.fontSize = "14px";
698
+ nameInput.style.boxSizing = "border-box";
699
+ const typeLabel = document.createElement("label");
700
+ typeLabel.textContent = "Config Device Type";
701
+ typeLabel.style.display = "block";
702
+ typeLabel.style.marginBottom = "6px";
703
+ typeLabel.style.fontWeight = "500";
704
+ typeLabel.style.fontSize = "12px";
705
+ typeLabel.style.color = "#6b7280";
706
+ const typeSelect = document.createElement("select");
707
+ typeSelect.style.width = "100%";
708
+ typeSelect.style.padding = "8px 10px";
709
+ typeSelect.style.marginBottom = "12px";
710
+ typeSelect.style.borderRadius = "6px";
711
+ typeSelect.style.fontSize = "14px";
712
+ typeSelect.style.background = getComputedStyle(nameInput).background;
713
+ typeSelect.style.color = getComputedStyle(nameInput).color;
714
+ typeSelect.style.border = getComputedStyle(nameInput).border;
715
+ typeSelect.style.boxSizing = "border-box";
716
+ Object.keys(DEVICE_TYPES).forEach((categoryName) => {
717
+ const optgroup = document.createElement("optgroup");
718
+ optgroup.label = categoryName;
719
+ DEVICE_TYPES[categoryName].forEach((deviceType) => {
720
+ const opt = document.createElement("option");
721
+ opt.value = deviceType;
722
+ opt.text = deviceType;
723
+ const detectedType = (device.type || "").toLowerCase();
724
+ opt.selected = deviceType.toLowerCase() === detectedType;
725
+ optgroup.appendChild(opt);
726
+ });
727
+ typeSelect.appendChild(optgroup);
728
+ });
729
+ const macLabel = document.createElement("label");
730
+ macLabel.textContent = "BLE MAC Address (optional)";
731
+ macLabel.style.display = "block";
732
+ macLabel.style.marginBottom = "6px";
733
+ macLabel.style.fontWeight = "500";
734
+ macLabel.style.fontSize = "12px";
735
+ macLabel.style.color = "#6b7280";
736
+ const macInput = document.createElement("input");
737
+ macInput.type = "text";
738
+ macInput.value = device.address || "";
739
+ macInput.placeholder = "AA:BB:CC:DD:EE:FF";
740
+ macInput.style.width = "100%";
741
+ macInput.style.marginBottom = "12px";
742
+ macInput.style.padding = "8px 10px";
743
+ macInput.style.borderRadius = "6px";
744
+ macInput.style.fontSize = "14px";
745
+ macInput.style.boxSizing = "border-box";
746
+ const buttons = document.createElement("div");
747
+ buttons.style.display = "flex";
748
+ buttons.style.gap = "10px";
749
+ buttons.style.justifyContent = "flex-end";
750
+ buttons.style.marginTop = "18px";
751
+ buttons.style.paddingTop = "18px";
752
+ buttons.style.borderTop = "1px solid rgba(0, 0, 0, 0.08)";
753
+ const cancelBtn = document.createElement("button");
754
+ cancelBtn.textContent = "Cancel";
755
+ cancelBtn.className = "secondary";
756
+ cancelBtn.style.background = "#6b7280";
757
+ cancelBtn.style.padding = "8px 16px";
758
+ cancelBtn.style.fontSize = "13px";
759
+ const importBtn = document.createElement("button");
760
+ importBtn.textContent = "Add to Config";
761
+ importBtn.style.background = "var(--switchbot-red, #ef4444)";
762
+ importBtn.style.padding = "8px 20px";
763
+ importBtn.style.fontSize = "13px";
764
+ const cleanup = (result) => {
765
+ div.remove();
766
+ resolve(result);
767
+ };
768
+ cancelBtn.onclick = () => cleanup(null);
769
+ importBtn.onclick = () => cleanup({
770
+ configDeviceName: nameInput.value || device.name || device.id,
771
+ configDeviceType: typeSelect.value || device.type,
772
+ address: macInput.value || void 0
773
+ });
774
+ div.addEventListener("click", (event) => {
775
+ if (event.target === div) {
776
+ cleanup(null);
777
+ }
778
+ });
779
+ buttons.appendChild(cancelBtn);
780
+ buttons.appendChild(importBtn);
781
+ contentDiv.appendChild(nameLabel);
782
+ contentDiv.appendChild(nameInput);
783
+ contentDiv.appendChild(typeLabel);
784
+ contentDiv.appendChild(typeSelect);
785
+ contentDiv.appendChild(macLabel);
786
+ contentDiv.appendChild(macInput);
787
+ contentDiv.appendChild(buttons);
788
+ modal.appendChild(title);
789
+ modal.appendChild(contentDiv);
790
+ div.appendChild(modal);
791
+ document.body.appendChild(div);
792
+ nameInput.focus();
793
+ });
794
+ }
609
795
  async function editDevice(device) {
610
796
  const div = document.createElement("div");
611
797
  div.style.position = "fixed";
@@ -738,7 +924,7 @@ async function editDevice(device) {
738
924
  saveBtn.style.fontSize = "13px";
739
925
  saveBtn.onclick = async () => {
740
926
  try {
741
- const { updateDevice: updateDevice2, syncParentPluginConfigFromDisk: syncParentPluginConfigFromDisk2, fetchDevices: fetchDevices3 } = await Promise.resolve().then(() => (init_api(), api_exports));
927
+ const { updateDevice: updateDevice2, syncParentPluginConfigFromDisk: syncParentPluginConfigFromDisk2, fetchDevices: fetchDevices2 } = await Promise.resolve().then(() => (init_api(), api_exports));
742
928
  const { renderDeviceList: renderDeviceList2 } = await Promise.resolve().then(() => (init_render(), render_exports));
743
929
  const params = {
744
930
  deviceId: device.id,
@@ -748,7 +934,7 @@ async function editDevice(device) {
748
934
  await updateDevice2(params.deviceId, params.configDeviceName, params.configDeviceType);
749
935
  await syncParentPluginConfigFromDisk2();
750
936
  uiLog.info("[Edit Device] Refreshing device list after update");
751
- const list = await fetchDevices3();
937
+ const list = await fetchDevices2();
752
938
  renderDeviceList2(list);
753
939
  div.remove();
754
940
  } catch (e) {
@@ -1119,37 +1305,68 @@ function renderIRBadge() {
1119
1305
  "background: #ff6b35; color: white; padding: 2px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 8px;"
1120
1306
  );
1121
1307
  }
1122
- async function renderDiscoveredDevices(devices) {
1308
+ function normalizeId2(value) {
1309
+ return String(value ?? "").trim().toLowerCase();
1310
+ }
1311
+ function scrollToConfiguredDevice(deviceId) {
1312
+ const normalizedId = normalizeId2(deviceId);
1313
+ const target = document.querySelector(`[data-device-id="${normalizedId}"]`);
1314
+ if (!target) {
1315
+ return;
1316
+ }
1317
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
1318
+ const originalOutline = target.style.outline;
1319
+ const originalBackground = target.style.background;
1320
+ target.style.outline = "2px solid var(--switchbot-red, #ef4444)";
1321
+ target.style.background = "rgba(239, 68, 68, 0.08)";
1322
+ setTimeout(() => {
1323
+ target.style.outline = originalOutline;
1324
+ target.style.background = originalBackground;
1325
+ }, 1800);
1326
+ }
1327
+ async function renderDiscoveredDevices(devices, options = {}) {
1123
1328
  const ul = document.createElement("ul");
1329
+ ul.className = "device-grid";
1124
1330
  ul.style.maxHeight = "400px";
1125
1331
  ul.style.overflowY = "auto";
1126
1332
  ul.style.marginTop = "12px";
1127
1333
  ul.style.padding = "0";
1128
1334
  ul.style.listStyle = "none";
1129
1335
  const { addDeviceToConfig: addDeviceToConfig3 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
1336
+ const { loadConfiguredDevices: loadConfiguredDevices2 } = await Promise.resolve().then(() => (init_devices(), devices_exports));
1337
+ const configuredIds = options.configuredIds ?? /* @__PURE__ */ new Set();
1130
1338
  for (const d of devices) {
1339
+ const deviceId = normalizeId2(d.id);
1340
+ const alreadyAdded = configuredIds.has(deviceId);
1131
1341
  const li = document.createElement("li");
1132
1342
  li.className = "device-item";
1133
1343
  li.style.display = "flex";
1134
- li.style.alignItems = "center";
1135
- li.style.justifyContent = "space-between";
1136
- li.style.padding = "10px 12px";
1137
- li.style.marginBottom = "6px";
1138
- li.style.borderRadius = "6px";
1344
+ li.style.flexDirection = "column";
1345
+ li.style.alignItems = "stretch";
1346
+ li.style.justifyContent = "flex-start";
1347
+ li.style.padding = "5px 8px";
1348
+ li.style.marginBottom = "0";
1349
+ li.style.borderRadius = "5px";
1139
1350
  li.style.transition = "all 0.2s ease";
1140
1351
  const info = document.createElement("div");
1141
- info.style.flex = "1";
1352
+ info.style.flex = "1 1 auto";
1353
+ info.style.width = "100%";
1354
+ info.style.minWidth = "0";
1142
1355
  const nameContainer = document.createElement("div");
1143
1356
  nameContainer.style.display = "flex";
1144
1357
  nameContainer.style.alignItems = "center";
1145
- nameContainer.style.marginBottom = "4px";
1358
+ nameContainer.style.marginBottom = "0";
1146
1359
  nameContainer.style.flexWrap = "wrap";
1147
- nameContainer.style.gap = "6px";
1360
+ nameContainer.style.gap = "4px";
1148
1361
  const name = document.createElement("div");
1149
1362
  name.style.fontWeight = "500";
1150
- name.style.fontSize = "14px";
1363
+ name.style.fontSize = "13px";
1151
1364
  name.textContent = d.name || d.id;
1152
1365
  nameContainer.appendChild(name);
1366
+ const duplicateBadge = document.createElement("span");
1367
+ duplicateBadge.textContent = alreadyAdded ? "\u2713 Already Added" : "\u2795 New Device";
1368
+ duplicateBadge.style.cssText = alreadyAdded ? "background: #16a34a; color: white; padding: 1px 5px; border-radius: 3px; font-size: 9px; font-weight: 600;" : "background: #2563eb; color: white; padding: 1px 5px; border-radius: 3px; font-size: 9px; font-weight: 600;";
1369
+ nameContainer.appendChild(duplicateBadge);
1153
1370
  if (d.connectionType) {
1154
1371
  const badge = renderConnectionBadge(d.connectionType);
1155
1372
  if (badge) {
@@ -1164,10 +1381,13 @@ async function renderDiscoveredDevices(devices) {
1164
1381
  nameContainer.appendChild(renderSignalQualityBadge(d.rssi));
1165
1382
  }
1166
1383
  const details = document.createElement("div");
1167
- details.style.fontSize = "11px";
1384
+ details.style.fontSize = "10px";
1168
1385
  details.style.opacity = "0.7";
1169
- details.style.marginTop = "4px";
1386
+ details.style.marginTop = "0";
1170
1387
  details.style.fontFamily = "monospace";
1388
+ details.style.whiteSpace = "normal";
1389
+ details.style.overflowWrap = "anywhere";
1390
+ details.style.wordBreak = "break-word";
1171
1391
  let detailsText = `ID: ${d.id} | Type: ${d.type} | Model: ${d.model || "N/A"}`;
1172
1392
  if (d.hubDeviceId) {
1173
1393
  detailsText += ` | Hub: ${d.hubDeviceId}`;
@@ -1179,17 +1399,65 @@ async function renderDiscoveredDevices(devices) {
1179
1399
  info.appendChild(nameContainer);
1180
1400
  info.appendChild(details);
1181
1401
  const addBtn = document.createElement("button");
1182
- addBtn.textContent = "+ Add";
1183
- addBtn.style.marginLeft = "8px";
1184
- addBtn.style.padding = "6px 14px";
1185
- addBtn.style.fontSize = "12px";
1402
+ addBtn.textContent = alreadyAdded ? "Already Added" : "Add to Config";
1403
+ addBtn.style.marginLeft = "0";
1404
+ addBtn.style.marginTop = "2px";
1405
+ addBtn.style.padding = "4px 9px";
1406
+ addBtn.style.fontSize = "11px";
1186
1407
  addBtn.style.whiteSpace = "nowrap";
1187
1408
  addBtn.style.flexShrink = "0";
1409
+ addBtn.disabled = alreadyAdded;
1410
+ if (alreadyAdded) {
1411
+ addBtn.style.opacity = "0.65";
1412
+ addBtn.style.cursor = "not-allowed";
1413
+ addBtn.style.background = "#6b7280";
1414
+ }
1188
1415
  addBtn.onclick = async () => {
1416
+ if (alreadyAdded) {
1417
+ return;
1418
+ }
1189
1419
  await addDeviceToConfig3(d);
1190
1420
  };
1421
+ if (alreadyAdded) {
1422
+ const viewBtn = document.createElement("button");
1423
+ viewBtn.textContent = "View in Config";
1424
+ viewBtn.className = "secondary";
1425
+ viewBtn.style.marginLeft = "0";
1426
+ viewBtn.style.padding = "4px 9px";
1427
+ viewBtn.style.fontSize = "11px";
1428
+ viewBtn.onclick = async () => {
1429
+ await loadConfiguredDevices2();
1430
+ scrollToConfiguredDevice(d.id);
1431
+ };
1432
+ li.appendChild(info);
1433
+ const actions2 = document.createElement("div");
1434
+ actions2.className = "device-actions";
1435
+ actions2.style.display = "flex";
1436
+ actions2.style.alignItems = "center";
1437
+ actions2.style.flexWrap = "wrap";
1438
+ actions2.style.justifyContent = "flex-start";
1439
+ actions2.style.marginLeft = "0";
1440
+ actions2.style.width = "100%";
1441
+ actions2.style.marginTop = "2px";
1442
+ actions2.style.gap = "5px";
1443
+ actions2.appendChild(viewBtn);
1444
+ actions2.appendChild(addBtn);
1445
+ li.appendChild(actions2);
1446
+ ul.appendChild(li);
1447
+ continue;
1448
+ }
1449
+ const actions = document.createElement("div");
1450
+ actions.className = "device-actions";
1451
+ actions.style.display = "flex";
1452
+ actions.style.flexWrap = "wrap";
1453
+ actions.style.justifyContent = "flex-start";
1454
+ actions.style.marginLeft = "0";
1455
+ actions.style.width = "100%";
1456
+ actions.style.marginTop = "2px";
1457
+ actions.style.gap = "5px";
1458
+ actions.appendChild(addBtn);
1191
1459
  li.appendChild(info);
1192
- li.appendChild(addBtn);
1460
+ li.appendChild(actions);
1193
1461
  ul.appendChild(li);
1194
1462
  }
1195
1463
  return ul;
@@ -1302,6 +1570,7 @@ function renderDeviceList(list) {
1302
1570
  return;
1303
1571
  }
1304
1572
  status.textContent = `Found ${list.length} device(s)`;
1573
+ ul.classList.add("device-grid");
1305
1574
  ul.innerHTML = "";
1306
1575
  if (removeAllContainer) {
1307
1576
  removeAllContainer.style.display = "block";
@@ -1309,21 +1578,33 @@ function renderDeviceList(list) {
1309
1578
  for (const d of list) {
1310
1579
  const li = document.createElement("li");
1311
1580
  li.className = "device-item";
1581
+ li.setAttribute("data-device-id", normalizeId2(d.id));
1582
+ li.style.display = "flex";
1583
+ li.style.flexDirection = "column";
1584
+ li.style.alignItems = "stretch";
1585
+ li.style.marginBottom = "0";
1312
1586
  const info = document.createElement("div");
1313
- info.style.flex = "1";
1587
+ info.style.flex = "1 1 auto";
1588
+ info.style.width = "100%";
1589
+ info.style.minWidth = "0";
1314
1590
  const nameContainer = document.createElement("div");
1315
1591
  nameContainer.style.display = "flex";
1316
- nameContainer.style.alignItems = "center";
1317
- nameContainer.style.flexWrap = "wrap";
1318
- nameContainer.style.gap = "6px";
1592
+ nameContainer.style.flexDirection = "column";
1593
+ nameContainer.style.alignItems = "flex-start";
1594
+ nameContainer.style.gap = "0";
1319
1595
  const name = document.createElement("div");
1320
1596
  name.style.fontWeight = "500";
1597
+ name.style.fontSize = "13px";
1321
1598
  name.textContent = d.name || d.id;
1322
1599
  const code = document.createElement("code");
1323
1600
  code.textContent = d.id;
1324
- code.style.fontSize = "12px";
1601
+ code.style.fontSize = "10px";
1325
1602
  code.style.opacity = "0.75";
1326
- code.style.marginLeft = "8px";
1603
+ code.style.marginLeft = "0";
1604
+ code.style.whiteSpace = "normal";
1605
+ code.style.overflowWrap = "anywhere";
1606
+ code.style.wordBreak = "break-word";
1607
+ code.style.maxWidth = "100%";
1327
1608
  nameContainer.appendChild(name);
1328
1609
  nameContainer.appendChild(code);
1329
1610
  if (d.rssi !== void 0 && d.rssi !== null && d.rssi !== 0) {
@@ -1332,8 +1613,8 @@ function renderDeviceList(list) {
1332
1613
  }
1333
1614
  const meta = document.createElement("div");
1334
1615
  meta.style.opacity = "0.75";
1335
- meta.style.marginTop = "4px";
1336
- meta.style.fontSize = "12px";
1616
+ meta.style.marginTop = "0";
1617
+ meta.style.fontSize = "11px";
1337
1618
  const typeText = d.type ? `type: ${d.type}` : "";
1338
1619
  const connText = d.connectionPreference ? `conn: ${d.connectionPreference}` : "";
1339
1620
  const roomText = d.room ? `room: ${d.room}` : "";
@@ -1341,20 +1622,26 @@ function renderDeviceList(list) {
1341
1622
  info.appendChild(nameContainer);
1342
1623
  info.appendChild(meta);
1343
1624
  const buttons = document.createElement("div");
1625
+ buttons.className = "device-actions";
1344
1626
  buttons.style.display = "flex";
1345
- buttons.style.gap = "8px";
1627
+ buttons.style.flexWrap = "wrap";
1628
+ buttons.style.justifyContent = "flex-start";
1629
+ buttons.style.marginLeft = "0";
1630
+ buttons.style.width = "100%";
1631
+ buttons.style.marginTop = "2px";
1632
+ buttons.style.gap = "5px";
1346
1633
  const editBtn = document.createElement("button");
1347
1634
  editBtn.textContent = "\u270F\uFE0F Edit";
1348
- editBtn.style.padding = "6px 12px";
1349
- editBtn.style.fontSize = "12px";
1635
+ editBtn.style.padding = "4px 9px";
1636
+ editBtn.style.fontSize = "11px";
1350
1637
  editBtn.onclick = async () => {
1351
1638
  const { editDevice: editDevice2 } = await Promise.resolve().then(() => (init_modals(), modals_exports));
1352
1639
  await editDevice2(d);
1353
1640
  };
1354
1641
  const copyBtn = document.createElement("button");
1355
1642
  copyBtn.textContent = "Copy ID";
1356
- copyBtn.style.padding = "6px 12px";
1357
- copyBtn.style.fontSize = "12px";
1643
+ copyBtn.style.padding = "4px 9px";
1644
+ copyBtn.style.fontSize = "11px";
1358
1645
  copyBtn.addEventListener("click", async () => {
1359
1646
  try {
1360
1647
  await navigator.clipboard.writeText(d.id);
@@ -1370,8 +1657,8 @@ function renderDeviceList(list) {
1370
1657
  });
1371
1658
  const deleteBtn = document.createElement("button");
1372
1659
  deleteBtn.textContent = "\u{1F5D1}\uFE0F Delete";
1373
- deleteBtn.style.padding = "6px 12px";
1374
- deleteBtn.style.fontSize = "12px";
1660
+ deleteBtn.style.padding = "4px 9px";
1661
+ deleteBtn.style.fontSize = "11px";
1375
1662
  deleteBtn.style.background = "#ef4444";
1376
1663
  deleteBtn.onclick = async () => {
1377
1664
  const { deleteDeviceFromConfig: deleteDeviceFromConfig2 } = await Promise.resolve().then(() => (init_devices_delete(), devices_delete_exports));
@@ -1401,15 +1688,24 @@ __export(devices_exports, {
1401
1688
  async function addDeviceToConfig2(device, options = {}) {
1402
1689
  const { refresh = true, showStatus = true } = options;
1403
1690
  try {
1691
+ const { importDiscoveredDevice: importDiscoveredDevice2 } = await Promise.resolve().then(() => (init_modals(), modals_exports));
1692
+ const importValues = await importDiscoveredDevice2(device);
1693
+ if (!importValues) {
1694
+ return { added: false };
1695
+ }
1404
1696
  showBusyUi();
1405
1697
  uiLog.info("Adding device to config:", device);
1406
- const resp = await addDevice(device.id, device.name, device.type);
1698
+ const resp = await addDevice(device.id, importValues.configDeviceName, importValues.configDeviceType, {
1699
+ address: importValues.address,
1700
+ model: device.model,
1701
+ rssi: device.rssi
1702
+ });
1407
1703
  uiLog.info("Add device response:", resp);
1408
1704
  if (!resp || resp.success === false) {
1409
1705
  throw new Error(resp?.data?.message || "Failed to add device");
1410
1706
  }
1411
1707
  const alreadyExists = !!resp?.alreadyExists;
1412
- const message = resp?.message || (alreadyExists ? `Device "${device.name}" already in config` : `Device "${device.name}" added successfully!`);
1708
+ const message = resp?.message || (alreadyExists ? `Device "${importValues.configDeviceName}" already in config` : `Device "${importValues.configDeviceName}" added successfully!`);
1413
1709
  if (alreadyExists) {
1414
1710
  toastInfo(message);
1415
1711
  } else {