homebridge-plugin-utils 1.6.1 → 1.7.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.
- package/build/eslint-rules.mjs +1 -1
- package/dist/ui/webUi-featureoptions.mjs +70 -70
- package/dist/ui/webUi.mjs +7 -7
- package/package.json +9 -9
package/build/eslint-rules.mjs
CHANGED
|
@@ -142,7 +142,7 @@ const tsRules = {
|
|
|
142
142
|
"@typescript-eslint/no-floating-promises": ["warn", { "ignoreIIFE": true }],
|
|
143
143
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
|
144
144
|
"@typescript-eslint/no-unused-expressions": "warn",
|
|
145
|
-
"@typescript-eslint/no-unused-vars": "warn",
|
|
145
|
+
"@typescript-eslint/no-unused-vars": ["warn", { "caughtErrors": "none" }],
|
|
146
146
|
"@typescript-eslint/promise-function-async": "warn",
|
|
147
147
|
"no-dupe-class-members": "off",
|
|
148
148
|
"no-undef": "off",
|
|
@@ -68,7 +68,7 @@ export class webUiFeatureOptions {
|
|
|
68
68
|
constructor(options = {}) {
|
|
69
69
|
|
|
70
70
|
// Defaults for the feature option webUI sidebar.
|
|
71
|
-
this
|
|
71
|
+
this.#ui = {
|
|
72
72
|
|
|
73
73
|
isController: () => false,
|
|
74
74
|
validOption: () => true,
|
|
@@ -76,7 +76,7 @@ export class webUiFeatureOptions {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
// Defaults for the feature option webUI sidebar.
|
|
79
|
-
this
|
|
79
|
+
this.#sidebar = {
|
|
80
80
|
|
|
81
81
|
controllerLabel: "Controllers",
|
|
82
82
|
deviceLabel: "Devices",
|
|
@@ -93,18 +93,18 @@ export class webUiFeatureOptions {
|
|
|
93
93
|
ui = {}
|
|
94
94
|
} = options;
|
|
95
95
|
|
|
96
|
-
this
|
|
97
|
-
this
|
|
96
|
+
this.#configTable = document.getElementById("configTable");
|
|
97
|
+
this.#controller = null;
|
|
98
98
|
this.currentConfig = [];
|
|
99
99
|
this.deviceStatsTable = document.getElementById("deviceStatsTable");
|
|
100
|
-
this
|
|
100
|
+
this.#devices = [];
|
|
101
101
|
this.devicesTable = document.getElementById("devicesTable");
|
|
102
|
-
this
|
|
103
|
-
this
|
|
104
|
-
this
|
|
105
|
-
this
|
|
106
|
-
this
|
|
107
|
-
this
|
|
102
|
+
this.#featureOptions = null;
|
|
103
|
+
this.#getDevices = getDevices;
|
|
104
|
+
this.#hasControllers = hasControllers;
|
|
105
|
+
this.#infoPanel = infoPanel;
|
|
106
|
+
this.#sidebar = Object.assign({}, this.#sidebar, sidebar);
|
|
107
|
+
this.#ui = Object.assign({}, this.#ui, ui);
|
|
108
108
|
this.webUiControllerList = [];
|
|
109
109
|
this.webUiDeviceList = [];
|
|
110
110
|
}
|
|
@@ -137,7 +137,7 @@ export class webUiFeatureOptions {
|
|
|
137
137
|
const features = (await homebridge.request("/getOptions")) ?? [];
|
|
138
138
|
|
|
139
139
|
// Initialize our feature option configuration.
|
|
140
|
-
this
|
|
140
|
+
this.#featureOptions = new FeatureOptions(features.categories, features.options, this.currentConfig[0].options ?? []);
|
|
141
141
|
|
|
142
142
|
// We render our global options, followed by either a list of controllers (if so configured) or by a list of devices from the Homebridge accessory cache.
|
|
143
143
|
|
|
@@ -147,7 +147,7 @@ export class webUiFeatureOptions {
|
|
|
147
147
|
// Start with a clean slate.
|
|
148
148
|
controllersTable.innerHTML = "";
|
|
149
149
|
this.devicesTable.innerHTML = "";
|
|
150
|
-
this
|
|
150
|
+
this.#configTable.innerHTML = "";
|
|
151
151
|
this.webUiDeviceList = [];
|
|
152
152
|
|
|
153
153
|
// Create our hover style for our sidebar.
|
|
@@ -169,7 +169,7 @@ export class webUiFeatureOptions {
|
|
|
169
169
|
document.getElementById("deviceStatsTable").style.display = "none";
|
|
170
170
|
|
|
171
171
|
// If we haven't configured any controllers, we're done.
|
|
172
|
-
if(this
|
|
172
|
+
if(this.#hasControllers && !this.currentConfig[0]?.controllers?.length) {
|
|
173
173
|
|
|
174
174
|
document.getElementById("headerInfo").innerHTML = "Please configure a controller to access in the main settings tab before configuring feature options.";
|
|
175
175
|
document.getElementById("headerInfo").style.display = "";
|
|
@@ -181,7 +181,7 @@ export class webUiFeatureOptions {
|
|
|
181
181
|
// Initialize our informational header.
|
|
182
182
|
document.getElementById("headerInfo").innerHTML = "Feature options are applied in prioritized order, from global to device-specific options:" +
|
|
183
183
|
"<br><i class=\"text-warning\">Global options</i> (lowest priority) → " +
|
|
184
|
-
(this
|
|
184
|
+
(this.#hasControllers ? "<i class=\"text-success\">Controller options</i> → " : "") +
|
|
185
185
|
"<i class=\"text-info\">Device options</i> (highest priority)";
|
|
186
186
|
|
|
187
187
|
// Enumerate our global options.
|
|
@@ -213,9 +213,9 @@ export class webUiFeatureOptions {
|
|
|
213
213
|
controllersTable.appendChild(trGlobal);
|
|
214
214
|
|
|
215
215
|
// Add it as another controller of device, for UI purposes.
|
|
216
|
-
(this
|
|
216
|
+
(this.#hasControllers ? this.webUiControllerList : this.webUiDeviceList).push(globalLabel);
|
|
217
217
|
|
|
218
|
-
if(this
|
|
218
|
+
if(this.#hasControllers) {
|
|
219
219
|
|
|
220
220
|
// Create a row for our controllers.
|
|
221
221
|
const trController = document.createElement("tr");
|
|
@@ -229,7 +229,7 @@ export class webUiFeatureOptions {
|
|
|
229
229
|
tdController.classList.add("m-0", "p-0", "pl-1", "w-100");
|
|
230
230
|
|
|
231
231
|
// Add the category name, with appropriate casing.
|
|
232
|
-
tdController.appendChild(document.createTextNode(this
|
|
232
|
+
tdController.appendChild(document.createTextNode(this.#sidebar.controllerLabel));
|
|
233
233
|
tdController.style.fontWeight = "bold";
|
|
234
234
|
|
|
235
235
|
// Add the cell to the table row.
|
|
@@ -276,7 +276,7 @@ export class webUiFeatureOptions {
|
|
|
276
276
|
homebridge.hideSpinner();
|
|
277
277
|
|
|
278
278
|
// Default the user on our global settings if we have no controller.
|
|
279
|
-
this.#showSidebar(this
|
|
279
|
+
this.#showSidebar(this.#hasControllers ? this.currentConfig[0].controllers[0] : null);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
// Show the device list taking the controller context into account.
|
|
@@ -286,19 +286,19 @@ export class webUiFeatureOptions {
|
|
|
286
286
|
homebridge.showSpinner();
|
|
287
287
|
|
|
288
288
|
// Grab the list of devices we're displaying.
|
|
289
|
-
this
|
|
289
|
+
this.#devices = await this.#getDevices(controller);
|
|
290
290
|
|
|
291
|
-
if(this
|
|
291
|
+
if(this.#hasControllers) {
|
|
292
292
|
|
|
293
293
|
// Make sure we highlight the selected controller so the user knows where we are.
|
|
294
294
|
this.webUiControllerList.map(webUiEntry => (webUiEntry.name === (controller ? controller.address : "Global Options")) ?
|
|
295
295
|
webUiEntry.parentElement.classList.add("bg-info", "text-white") : webUiEntry.parentElement.classList.remove("bg-info", "text-white"));
|
|
296
296
|
|
|
297
297
|
// Unable to connect to the controller for some reason.
|
|
298
|
-
if(controller && !this
|
|
298
|
+
if(controller && !this.#devices?.length) {
|
|
299
299
|
|
|
300
300
|
this.devicesTable.innerHTML = "";
|
|
301
|
-
this
|
|
301
|
+
this.#configTable.innerHTML = "";
|
|
302
302
|
|
|
303
303
|
document.getElementById("headerInfo").innerHTML = ["Unable to connect to the controller.",
|
|
304
304
|
"Check the Settings tab to verify the controller details are correct.",
|
|
@@ -312,7 +312,7 @@ export class webUiFeatureOptions {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
// The first entry returned by getDevices() must always be the controller.
|
|
315
|
-
this
|
|
315
|
+
this.#controller = this.#devices[0]?.serial ?? null;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// Make the UI visible.
|
|
@@ -326,10 +326,10 @@ export class webUiFeatureOptions {
|
|
|
326
326
|
this.devicesTable.innerHTML = "";
|
|
327
327
|
|
|
328
328
|
// Populate our devices sidebar.
|
|
329
|
-
this
|
|
329
|
+
this.#sidebar.showDevices(controller, this.#devices);
|
|
330
330
|
|
|
331
331
|
// Display the feature options to the user.
|
|
332
|
-
this.showDeviceOptions(controller ? this
|
|
332
|
+
this.showDeviceOptions(controller ? this.#devices[0].serial : "Global Options");
|
|
333
333
|
|
|
334
334
|
// All done. Let the user interact with us.
|
|
335
335
|
homebridge.hideSpinner();
|
|
@@ -345,7 +345,7 @@ export class webUiFeatureOptions {
|
|
|
345
345
|
webUiEntry.parentElement.classList.add("bg-info", "text-white") : webUiEntry.parentElement.classList.remove("bg-info", "text-white"));
|
|
346
346
|
|
|
347
347
|
// Populate the device information info pane.
|
|
348
|
-
const currentDevice = this
|
|
348
|
+
const currentDevice = this.#devices.find(device => device.serial === deviceId);
|
|
349
349
|
|
|
350
350
|
// Populate the details view. If there's no device specified, the context is considered global and we hide the device details view.
|
|
351
351
|
if(!currentDevice) {
|
|
@@ -353,7 +353,7 @@ export class webUiFeatureOptions {
|
|
|
353
353
|
this.deviceStatsTable.style.display = "none";
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
this
|
|
356
|
+
this.#infoPanel(currentDevice);
|
|
357
357
|
|
|
358
358
|
if(currentDevice) {
|
|
359
359
|
|
|
@@ -361,12 +361,12 @@ export class webUiFeatureOptions {
|
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
// Start with a clean slate.
|
|
364
|
-
this
|
|
364
|
+
this.#configTable.innerHTML = "";
|
|
365
365
|
|
|
366
|
-
for(const category of this
|
|
366
|
+
for(const category of this.#featureOptions.categories) {
|
|
367
367
|
|
|
368
368
|
// Validate that we should display this feature option category. This is useful when you want to only display feature option categories for certain device types.
|
|
369
|
-
if(!this
|
|
369
|
+
if(!this.#ui.validOptionCategory(currentDevice, category)) {
|
|
370
370
|
|
|
371
371
|
continue;
|
|
372
372
|
}
|
|
@@ -386,7 +386,7 @@ export class webUiFeatureOptions {
|
|
|
386
386
|
|
|
387
387
|
// Add the feature option category description.
|
|
388
388
|
th.appendChild(document.createTextNode(category.description + (!currentDevice ? " (Global)" :
|
|
389
|
-
(this
|
|
389
|
+
(this.#ui.isController(currentDevice) ? " (Controller-specific)" : " (Device-specific)"))));
|
|
390
390
|
|
|
391
391
|
// Add the table header to the row.
|
|
392
392
|
trFirst.appendChild(th);
|
|
@@ -401,16 +401,16 @@ export class webUiFeatureOptions {
|
|
|
401
401
|
let optionsVisibleCount = 0;
|
|
402
402
|
|
|
403
403
|
// Now enumerate all the feature options for a given device.
|
|
404
|
-
for(const option of this
|
|
404
|
+
for(const option of this.#featureOptions.options[category.name]) {
|
|
405
405
|
|
|
406
406
|
// Only show feature options that are valid for this device.
|
|
407
|
-
if(!this
|
|
407
|
+
if(!this.#ui.validOption(currentDevice, option)) {
|
|
408
408
|
|
|
409
409
|
continue;
|
|
410
410
|
}
|
|
411
411
|
|
|
412
412
|
// Expand the full feature option.
|
|
413
|
-
const featureOption = this
|
|
413
|
+
const featureOption = this.#featureOptions.expandOption(category, option);
|
|
414
414
|
|
|
415
415
|
// Create the next table row.
|
|
416
416
|
const trX = document.createElement("tr");
|
|
@@ -434,7 +434,7 @@ export class webUiFeatureOptions {
|
|
|
434
434
|
let initialScope;
|
|
435
435
|
|
|
436
436
|
// Determine our initial option scope to show the user what's been set.
|
|
437
|
-
switch(initialScope = this
|
|
437
|
+
switch(initialScope = this.#featureOptions.scope(featureOption, currentDevice?.serial, this.#controller)) {
|
|
438
438
|
|
|
439
439
|
case "global":
|
|
440
440
|
case "controller":
|
|
@@ -442,13 +442,13 @@ export class webUiFeatureOptions {
|
|
|
442
442
|
// If we're looking at the global scope, show the option value. Otherwise, we show that we're inheriting a value from the scope above.
|
|
443
443
|
if(!currentDevice) {
|
|
444
444
|
|
|
445
|
-
if(this
|
|
445
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
446
446
|
|
|
447
|
-
checkbox.checked = this
|
|
448
|
-
initialValue = this
|
|
447
|
+
checkbox.checked = this.#featureOptions.exists(featureOption);
|
|
448
|
+
initialValue = this.#featureOptions.value(checkbox.id);
|
|
449
449
|
} else {
|
|
450
450
|
|
|
451
|
-
checkbox.checked = this
|
|
451
|
+
checkbox.checked = this.#featureOptions.test(featureOption);
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
if(checkbox.checked) {
|
|
@@ -458,9 +458,9 @@ export class webUiFeatureOptions {
|
|
|
458
458
|
|
|
459
459
|
} else {
|
|
460
460
|
|
|
461
|
-
if(this
|
|
461
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
462
462
|
|
|
463
|
-
initialValue = this
|
|
463
|
+
initialValue = this.#featureOptions.value(checkbox.id, (initialScope === "controller") ? this.#controller : undefined);
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
checkbox.readOnly = checkbox.indeterminate = true;
|
|
@@ -472,13 +472,13 @@ export class webUiFeatureOptions {
|
|
|
472
472
|
case "none":
|
|
473
473
|
default:
|
|
474
474
|
|
|
475
|
-
if(this
|
|
475
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
476
476
|
|
|
477
|
-
checkbox.checked = this
|
|
478
|
-
initialValue = this
|
|
477
|
+
checkbox.checked = this.#featureOptions.exists(featureOption, currentDevice?.serial);
|
|
478
|
+
initialValue = this.#featureOptions.value(checkbox.id, currentDevice?.serial);
|
|
479
479
|
} else {
|
|
480
480
|
|
|
481
|
-
checkbox.checked = this
|
|
481
|
+
checkbox.checked = this.#featureOptions.test(featureOption, currentDevice?.serial);
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
break;
|
|
@@ -501,7 +501,7 @@ export class webUiFeatureOptions {
|
|
|
501
501
|
let inputValue = null;
|
|
502
502
|
|
|
503
503
|
// Add an input field if we have a value-centric feature option.
|
|
504
|
-
if(this
|
|
504
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
505
505
|
|
|
506
506
|
const tdInput = document.createElement("td");
|
|
507
507
|
|
|
@@ -519,7 +519,7 @@ export class webUiFeatureOptions {
|
|
|
519
519
|
|
|
520
520
|
// Find the option in our list and delete it if it exists.
|
|
521
521
|
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!currentDevice ? "" : ("\\." + currentDevice.serial)) + "\\.[^\\.]+$", "gi");
|
|
522
|
-
const newOptions = this
|
|
522
|
+
const newOptions = this.#featureOptions.configuredOptions.filter(entry => !optionRegex.test(entry));
|
|
523
523
|
|
|
524
524
|
if(checkbox.checked) {
|
|
525
525
|
|
|
@@ -527,9 +527,9 @@ export class webUiFeatureOptions {
|
|
|
527
527
|
} else if(checkbox.indeterminate) {
|
|
528
528
|
|
|
529
529
|
// If we're in an indeterminate state, we need to traverse the tree to get the upstream value we're inheriting.
|
|
530
|
-
inputValue.value = (currentDevice?.serial !== this
|
|
531
|
-
(this
|
|
532
|
-
(this
|
|
530
|
+
inputValue.value = (currentDevice?.serial !== this.#controller) ?
|
|
531
|
+
(this.#featureOptions.value(checkbox.id, this.#controller) ?? this.#featureOptions.value(checkbox.id)) :
|
|
532
|
+
(this.#featureOptions.value(checkbox.id) ?? option.defaultValue);
|
|
533
533
|
} else {
|
|
534
534
|
|
|
535
535
|
inputValue.value = option.defaultValue;
|
|
@@ -537,7 +537,7 @@ export class webUiFeatureOptions {
|
|
|
537
537
|
|
|
538
538
|
// Update our configuration in Homebridge.
|
|
539
539
|
this.currentConfig[0].options = newOptions;
|
|
540
|
-
this
|
|
540
|
+
this.#featureOptions.configuredOptions = newOptions;
|
|
541
541
|
await homebridge.updatePluginConfig(this.currentConfig);
|
|
542
542
|
});
|
|
543
543
|
|
|
@@ -553,7 +553,7 @@ export class webUiFeatureOptions {
|
|
|
553
553
|
labelDescription.classList.add("user-select-none", "my-0", "py-0");
|
|
554
554
|
|
|
555
555
|
// Highlight options for the user that are different than our defaults.
|
|
556
|
-
const scopeColor = this
|
|
556
|
+
const scopeColor = this.#featureOptions.color(featureOption, currentDevice?.serial, this.#controller);
|
|
557
557
|
|
|
558
558
|
if(scopeColor) {
|
|
559
559
|
|
|
@@ -565,18 +565,18 @@ export class webUiFeatureOptions {
|
|
|
565
565
|
|
|
566
566
|
// Find the option in our list and delete it if it exists.
|
|
567
567
|
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!currentDevice ? "" : ("\\." + currentDevice.serial)) + "$", "gi");
|
|
568
|
-
const newOptions = this
|
|
568
|
+
const newOptions = this.#featureOptions.configuredOptions.filter(entry => !optionRegex.test(entry));
|
|
569
569
|
|
|
570
570
|
// Figure out if we've got the option set upstream.
|
|
571
571
|
let upstreamOption = false;
|
|
572
572
|
|
|
573
573
|
// We explicitly want to check for the scope of the feature option above where we are now, so we can appropriately determine what we should show.
|
|
574
|
-
switch(this
|
|
574
|
+
switch(this.#featureOptions.scope(checkbox.id, (currentDevice && (currentDevice.serial !== this.#controller)) ? this.#controller : undefined)) {
|
|
575
575
|
|
|
576
576
|
case "device":
|
|
577
577
|
case "controller":
|
|
578
578
|
|
|
579
|
-
if(currentDevice.serial !== this
|
|
579
|
+
if(currentDevice.serial !== this.#controller) {
|
|
580
580
|
|
|
581
581
|
upstreamOption = true;
|
|
582
582
|
}
|
|
@@ -599,7 +599,7 @@ export class webUiFeatureOptions {
|
|
|
599
599
|
|
|
600
600
|
// For value-centric feature options, if there's an upstream value assigned above us, we don't allow for an unchecked state as it doesn't make sense in this
|
|
601
601
|
// context.
|
|
602
|
-
if(checkbox.readOnly && (!this
|
|
602
|
+
if(checkbox.readOnly && (!this.#featureOptions.isValue(featureOption) || (this.#featureOptions.isValue(featureOption) && inputValue && !upstreamOption))) {
|
|
603
603
|
|
|
604
604
|
// We're truly unchecked. We need this because a checkbox can be in both an unchecked and indeterminate simultaneously,
|
|
605
605
|
// so we use the readOnly property to let us know that we've just cycled from an indeterminate state.
|
|
@@ -614,7 +614,7 @@ export class webUiFeatureOptions {
|
|
|
614
614
|
checkbox.readOnly = checkbox.indeterminate = true;
|
|
615
615
|
}
|
|
616
616
|
|
|
617
|
-
if(this
|
|
617
|
+
if(this.#featureOptions.isValue(featureOption) && inputValue) {
|
|
618
618
|
|
|
619
619
|
inputValue.readOnly = true;
|
|
620
620
|
}
|
|
@@ -623,7 +623,7 @@ export class webUiFeatureOptions {
|
|
|
623
623
|
// We've explicitly checked this option.
|
|
624
624
|
checkbox.readOnly = checkbox.indeterminate = false;
|
|
625
625
|
|
|
626
|
-
if(this
|
|
626
|
+
if(this.#featureOptions.isValue(featureOption) && inputValue) {
|
|
627
627
|
|
|
628
628
|
inputValue.readOnly = false;
|
|
629
629
|
}
|
|
@@ -641,7 +641,7 @@ export class webUiFeatureOptions {
|
|
|
641
641
|
}
|
|
642
642
|
|
|
643
643
|
// Update our Homebridge configuration.
|
|
644
|
-
if(this
|
|
644
|
+
if(this.#featureOptions.isValue(featureOption) && inputValue) {
|
|
645
645
|
|
|
646
646
|
// Inform our value-centric feature option to update Homebridge.
|
|
647
647
|
const changeEvent = new Event("change");
|
|
@@ -651,14 +651,14 @@ export class webUiFeatureOptions {
|
|
|
651
651
|
|
|
652
652
|
// Update our configuration in Homebridge.
|
|
653
653
|
this.currentConfig[0].options = newOptions;
|
|
654
|
-
this
|
|
654
|
+
this.#featureOptions.configuredOptions = newOptions;
|
|
655
655
|
await homebridge.updatePluginConfig(this.currentConfig);
|
|
656
656
|
}
|
|
657
657
|
|
|
658
658
|
// If we've reset to defaults, make sure our color coding for scope is reflected.
|
|
659
659
|
if((checkbox.checked === option.default) || checkbox.indeterminate) {
|
|
660
660
|
|
|
661
|
-
const scopeColor = this
|
|
661
|
+
const scopeColor = this.#featureOptions.color(featureOption, currentDevice?.serial, this.#controller);
|
|
662
662
|
|
|
663
663
|
if(scopeColor) {
|
|
664
664
|
|
|
@@ -667,12 +667,12 @@ export class webUiFeatureOptions {
|
|
|
667
667
|
}
|
|
668
668
|
|
|
669
669
|
// Adjust visibility of other feature options that depend on us.
|
|
670
|
-
if(this
|
|
670
|
+
if(this.#featureOptions.groups[checkbox.id]) {
|
|
671
671
|
|
|
672
|
-
const entryVisibility = this
|
|
672
|
+
const entryVisibility = this.#featureOptions.test(featureOption, currentDevice?.serial) ? "" : "none";
|
|
673
673
|
|
|
674
674
|
// Lookup each feature option setting and set the visibility accordingly.
|
|
675
|
-
for(const entry of this
|
|
675
|
+
for(const entry of this.#featureOptions.groups[checkbox.id]) {
|
|
676
676
|
|
|
677
677
|
document.getElementById("row-" + entry).style.display = entryVisibility;
|
|
678
678
|
}
|
|
@@ -692,7 +692,7 @@ export class webUiFeatureOptions {
|
|
|
692
692
|
trX.appendChild(tdLabel);
|
|
693
693
|
|
|
694
694
|
// Adjust the visibility of the feature option, if it's logically grouped.
|
|
695
|
-
if((option.group !== undefined) && !this
|
|
695
|
+
if((option.group !== undefined) && !this.#featureOptions.test(category.name + (option.group.length ? ("." + option.group) : ""), currentDevice?.serial)) {
|
|
696
696
|
|
|
697
697
|
trX.style.display = "none";
|
|
698
698
|
} else {
|
|
@@ -715,7 +715,7 @@ export class webUiFeatureOptions {
|
|
|
715
715
|
}
|
|
716
716
|
|
|
717
717
|
// Add the table to the page.
|
|
718
|
-
this
|
|
718
|
+
this.#configTable.appendChild(optionTable);
|
|
719
719
|
}
|
|
720
720
|
|
|
721
721
|
homebridge.hideSpinner();
|
|
@@ -745,7 +745,7 @@ export class webUiFeatureOptions {
|
|
|
745
745
|
#showSidebarDevices() {
|
|
746
746
|
|
|
747
747
|
// Show the devices list only if we have actual devices to show.
|
|
748
|
-
if(!this
|
|
748
|
+
if(!this.#devices?.length) {
|
|
749
749
|
|
|
750
750
|
return;
|
|
751
751
|
}
|
|
@@ -762,7 +762,7 @@ export class webUiFeatureOptions {
|
|
|
762
762
|
tdCategory.classList.add("m-0", "p-0", "pl-1", "w-100");
|
|
763
763
|
|
|
764
764
|
// Add the category name, with appropriate casing.
|
|
765
|
-
tdCategory.appendChild(document.createTextNode(this
|
|
765
|
+
tdCategory.appendChild(document.createTextNode(this.#sidebar.deviceLabel));
|
|
766
766
|
tdCategory.style.fontWeight = "bold";
|
|
767
767
|
|
|
768
768
|
// Add the cell to the table row.
|
|
@@ -771,7 +771,7 @@ export class webUiFeatureOptions {
|
|
|
771
771
|
// Add the table row to the table.
|
|
772
772
|
this.devicesTable.appendChild(trCategory);
|
|
773
773
|
|
|
774
|
-
for(const device of this
|
|
774
|
+
for(const device of this.#devices) {
|
|
775
775
|
|
|
776
776
|
// Create a row for this device.
|
|
777
777
|
const trDevice = document.createElement("tr");
|
package/dist/ui/webUi.mjs
CHANGED
|
@@ -28,12 +28,12 @@ export class webUi {
|
|
|
28
28
|
constructor({ featureOptions, firstRun = {}, name } = {}) {
|
|
29
29
|
|
|
30
30
|
// Defaults for our first run handlers.
|
|
31
|
-
this
|
|
31
|
+
this.#firstRun = { isRequired: () => false, onStart: () => true, onSubmit: () => true };
|
|
32
32
|
|
|
33
33
|
// Figure out the options passed in to us.
|
|
34
34
|
this.featureOptions = new webUiFeatureOptions(featureOptions);
|
|
35
|
-
this
|
|
36
|
-
this
|
|
35
|
+
this.#firstRun = Object.assign({}, this.#firstRun, firstRun);
|
|
36
|
+
this.#name = name;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -63,7 +63,7 @@ export class webUi {
|
|
|
63
63
|
const buttonFirstRun = document.getElementById("firstRun");
|
|
64
64
|
|
|
65
65
|
// Run a custom initialization handler the user may have provided.
|
|
66
|
-
if(!(await this.#processHandler(this
|
|
66
|
+
if(!(await this.#processHandler(this.#firstRun.onStart))) {
|
|
67
67
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
@@ -75,7 +75,7 @@ export class webUi {
|
|
|
75
75
|
homebridge.showSpinner();
|
|
76
76
|
|
|
77
77
|
// Run a custom submit handler the user may have provided.
|
|
78
|
-
if(!(await this.#processHandler(this
|
|
78
|
+
if(!(await this.#processHandler(this.#firstRun.onSubmit))) {
|
|
79
79
|
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
@@ -146,7 +146,7 @@ export class webUi {
|
|
|
146
146
|
const devices = await homebridge.getCachedAccessories();
|
|
147
147
|
|
|
148
148
|
// If we've got devices detected, we launch our feature option UI. Otherwise, we launch our first run UI.
|
|
149
|
-
if(this.featureOptions.currentConfig.length && devices?.length && !(await this.#processHandler(this
|
|
149
|
+
if(this.featureOptions.currentConfig.length && devices?.length && !(await this.#processHandler(this.#firstRun.isRequired))) {
|
|
150
150
|
|
|
151
151
|
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
152
152
|
this.featureOptions.show();
|
|
@@ -155,7 +155,7 @@ export class webUi {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// If we have the name property set for the plugin configuration yet, let's do so now. If we don't have a configuration, let's initialize it as well.
|
|
158
|
-
(this.featureOptions.currentConfig[0] ??= { name: this
|
|
158
|
+
(this.featureOptions.currentConfig[0] ??= { name: this.#name }).name ??= this.#name;
|
|
159
159
|
|
|
160
160
|
// Update the plugin configuration and launch the first run UI.
|
|
161
161
|
await homebridge.updatePluginConfig(this.featureOptions.currentConfig);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-plugin-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"displayName": "Homebridge Plugin Utilities",
|
|
5
5
|
"description": "Opinionated utilities to provide common capabilities and create rich configuration webUI experiences for Homebridge plugins.",
|
|
6
6
|
"author": {
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
},
|
|
40
40
|
"main": "dist/index.js",
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@stylistic/eslint-plugin": "2.
|
|
43
|
-
"@types/node": "
|
|
44
|
-
"eslint": "8.
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"typescript": "
|
|
48
|
-
"typescript-eslint": "^7.16.1"
|
|
42
|
+
"@stylistic/eslint-plugin": "2.6.1",
|
|
43
|
+
"@types/node": "22.1.0",
|
|
44
|
+
"eslint": "^9.8.0",
|
|
45
|
+
"shx": "0.3.4",
|
|
46
|
+
"typescript": "5.5.4",
|
|
47
|
+
"typescript-eslint": "^8.0.0"
|
|
49
48
|
},
|
|
50
49
|
"dependencies": {
|
|
51
|
-
"
|
|
50
|
+
"homebridge": "1.8.4",
|
|
51
|
+
"mqtt": "5.9.1"
|
|
52
52
|
}
|
|
53
53
|
}
|