@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.
- package/.github/copilot-instructions.md +39 -0
- package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/endpoints/devices.js +10 -1
- package/dist/homebridge-ui/endpoints/devices.js.map +1 -1
- package/dist/homebridge-ui/public/css/styles.css +111 -16
- package/dist/homebridge-ui/public/index.html +34 -60
- package/dist/homebridge-ui/public/js/api.d.ts +5 -1
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/api.js +13 -3
- package/dist/homebridge-ui/public/js/api.js.map +1 -1
- package/dist/homebridge-ui/public/js/api.ts +13 -3
- package/dist/homebridge-ui/public/js/app.js +338 -42
- package/dist/homebridge-ui/public/js/app.js.map +3 -3
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/devices.js +12 -3
- package/dist/homebridge-ui/public/js/devices.js.map +1 -1
- package/dist/homebridge-ui/public/js/devices.ts +13 -3
- package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.js +37 -3
- package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
- package/dist/homebridge-ui/public/js/discovery.ts +39 -2
- package/dist/homebridge-ui/public/js/modals.d.ts +5 -0
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/modals.js +146 -0
- package/dist/homebridge-ui/public/js/modals.js.map +1 -1
- package/dist/homebridge-ui/public/js/modals.ts +167 -0
- package/dist/homebridge-ui/public/js/render.d.ts +3 -1
- package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
- package/dist/homebridge-ui/public/js/render.js +135 -32
- package/dist/homebridge-ui/public/js/render.js.map +1 -1
- package/dist/homebridge-ui/public/js/render.ts +145 -32
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +64 -109
- package/dist/platform.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +9 -8
- package/src/homebridge-ui/endpoints/devices.ts +5 -2
- package/src/homebridge-ui/public/css/styles.css +111 -16
- package/src/homebridge-ui/public/index.html +34 -60
- package/src/homebridge-ui/public/js/api.ts +13 -3
- package/src/homebridge-ui/public/js/devices.ts +13 -3
- package/src/homebridge-ui/public/js/discovery.ts +39 -2
- package/src/homebridge-ui/public/js/modals.ts +167 -0
- package/src/homebridge-ui/public/js/render.ts +145 -32
- package/src/platform.ts +77 -109
- package/src/utils.ts +2 -1
- package/test/e2e/run-e2e.spec.ts +12 -10
- package/test/helpers/matter-harness.ts +2 -2
- package/test/matter-childbridge.spec.ts +5 -1
- package/test/matter-integration.spec.ts +41 -0
- package/test/utils.spec.ts +18 -2
- package/todo/COMPLETED.md +53 -0
- package/todo/TODO.md +13 -42
- 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
|
-
|
|
143
|
-
|
|
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
|
|
468
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
1135
|
-
li.style.
|
|
1136
|
-
li.style.
|
|
1137
|
-
li.style.
|
|
1138
|
-
li.style.
|
|
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 = "
|
|
1358
|
+
nameContainer.style.marginBottom = "0";
|
|
1146
1359
|
nameContainer.style.flexWrap = "wrap";
|
|
1147
|
-
nameContainer.style.gap = "
|
|
1360
|
+
nameContainer.style.gap = "4px";
|
|
1148
1361
|
const name = document.createElement("div");
|
|
1149
1362
|
name.style.fontWeight = "500";
|
|
1150
|
-
name.style.fontSize = "
|
|
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 = "
|
|
1384
|
+
details.style.fontSize = "10px";
|
|
1168
1385
|
details.style.opacity = "0.7";
|
|
1169
|
-
details.style.marginTop = "
|
|
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 = "
|
|
1183
|
-
addBtn.style.marginLeft = "
|
|
1184
|
-
addBtn.style.
|
|
1185
|
-
addBtn.style.
|
|
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(
|
|
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.
|
|
1317
|
-
nameContainer.style.
|
|
1318
|
-
nameContainer.style.gap = "
|
|
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 = "
|
|
1601
|
+
code.style.fontSize = "10px";
|
|
1325
1602
|
code.style.opacity = "0.75";
|
|
1326
|
-
code.style.marginLeft = "
|
|
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 = "
|
|
1336
|
-
meta.style.fontSize = "
|
|
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.
|
|
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 = "
|
|
1349
|
-
editBtn.style.fontSize = "
|
|
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 = "
|
|
1357
|
-
copyBtn.style.fontSize = "
|
|
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 = "
|
|
1374
|
-
deleteBtn.style.fontSize = "
|
|
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,
|
|
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 "${
|
|
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 {
|