homebridge-ratgdo 2.1.3 → 2.2.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/dist/ratgdo-device.d.ts +0 -2
- package/dist/ratgdo-device.js +21 -47
- package/dist/ratgdo-device.js.map +1 -1
- package/dist/ratgdo-options.d.ts +4 -15
- package/dist/ratgdo-options.js +2 -93
- package/dist/ratgdo-options.js.map +1 -1
- package/dist/ratgdo-platform.d.ts +4 -4
- package/dist/ratgdo-platform.js +20 -31
- package/dist/ratgdo-platform.js.map +1 -1
- package/homebridge-ui/public/index.html +20 -1
- package/homebridge-ui/public/lib/featureoptions.js +366 -0
- package/homebridge-ui/public/lib/webUi.mjs +155 -0
- package/homebridge-ui/public/{ratgdo-featureoptions.mjs → lib/webui-featureoptions.mjs} +104 -142
- package/homebridge-ui/server.js +3 -39
- package/package.json +13 -13
- package/dist/ratgdo-mqtt.d.ts +0 -19
- package/dist/ratgdo-mqtt.js +0 -155
- package/dist/ratgdo-mqtt.js.map +0 -1
- package/homebridge-ui/public/lib/featureoptions.mjs +0 -200
- package/homebridge-ui/public/ui.mjs +0 -144
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
/* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
3
|
+
* webui-featureoptions.mjs: Device feature option webUI.
|
|
4
4
|
*/
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
|
-
import { FeatureOptions} from "./
|
|
7
|
+
import { FeatureOptions} from "./featureoptions.js";
|
|
8
8
|
|
|
9
|
-
export class
|
|
9
|
+
export class webUiFeatureOptions {
|
|
10
10
|
|
|
11
11
|
// The current plugin configuration.
|
|
12
12
|
currentConfig;
|
|
13
13
|
|
|
14
|
-
// Current configuration options selected in the webUI for a given device.
|
|
15
|
-
#configOptions;
|
|
16
|
-
|
|
17
14
|
// Table containing the currently displayed feature options.
|
|
18
15
|
#configTable;
|
|
19
16
|
|
|
20
|
-
//
|
|
21
|
-
#
|
|
17
|
+
// The current controller context.
|
|
18
|
+
#controller;
|
|
19
|
+
|
|
20
|
+
// Current list of devices from the Homebridge accessory cache.
|
|
21
|
+
#devices;
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
#
|
|
23
|
+
// Feature options instance.
|
|
24
|
+
#featureOptions;
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// Device sidebar category name.
|
|
27
|
+
#sidebar;
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
// Enable the use of controllers.
|
|
30
|
+
#useControllers;
|
|
31
|
+
|
|
32
|
+
// Current list of devices on a given controller, for webUI elements.
|
|
33
|
+
#webuiDeviceList;
|
|
34
|
+
|
|
35
|
+
constructor({ sidebar = "Devices", useControllers = true } = {}) {
|
|
29
36
|
|
|
30
|
-
this.configOptions = [];
|
|
31
37
|
this.configTable = document.getElementById("configTable");
|
|
38
|
+
this.controller = null;
|
|
32
39
|
this.currentConfig = [];
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
40
|
+
this.devices = [];
|
|
41
|
+
this.featureOptions = null;
|
|
42
|
+
this.sidebarName = sidebar;
|
|
43
|
+
this.useControllers = useControllers;
|
|
44
|
+
this.webuiDeviceList = [];
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
// Render the feature option webUI.
|
|
@@ -44,6 +54,12 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
44
54
|
// Make sure we have the refreshed configuration.
|
|
45
55
|
this.currentConfig = await homebridge.getPluginConfig();
|
|
46
56
|
|
|
57
|
+
// Retrieve the set of feature options available to us.
|
|
58
|
+
const features = (await homebridge.request("/getOptions")) ?? [];
|
|
59
|
+
|
|
60
|
+
// Initialize our feature option configuration.
|
|
61
|
+
this.featureOptions = new FeatureOptions(features.categories, features.options, this.currentConfig[0].options ?? []);
|
|
62
|
+
|
|
47
63
|
// Create our custom UI.
|
|
48
64
|
document.getElementById("menuHome").classList.remove("btn-elegant");
|
|
49
65
|
document.getElementById("menuHome").classList.add("btn-primary");
|
|
@@ -66,18 +82,18 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
66
82
|
controllersTable.innerHTML = "";
|
|
67
83
|
document.getElementById("devicesTable").innerHTML = "";
|
|
68
84
|
this.configTable.innerHTML = "";
|
|
69
|
-
this.
|
|
85
|
+
this.webuiDeviceList = [];
|
|
70
86
|
|
|
71
87
|
// Hide the UI until we're ready.
|
|
72
88
|
document.getElementById("sidebar").style.display = "none";
|
|
73
89
|
document.getElementById("headerInfo").style.display = "none";
|
|
74
90
|
document.getElementById("deviceStatsTable").style.display = "none";
|
|
75
91
|
|
|
76
|
-
// Get the list of devices the plugin knows about.
|
|
77
|
-
const ratgdoDevices = await homebridge.getCachedAccessories();
|
|
78
|
-
|
|
79
92
|
// Initialize our informational header.
|
|
80
|
-
document.getElementById("headerInfo").innerHTML = "Feature options are applied in prioritized order, from global to device-specific options
|
|
93
|
+
document.getElementById("headerInfo").innerHTML = "Feature options are applied in prioritized order, from global to device-specific options:" +
|
|
94
|
+
"<br><i class=\"text-warning\">Global options</i> (lowest priority) → " +
|
|
95
|
+
(this.useControllers ? "<i class=\"text-success\">Controller options</i> → " : "") +
|
|
96
|
+
"<i class=\"text-info\">Device options</i> (highest priority)";
|
|
81
97
|
|
|
82
98
|
// Enumerate our global options.
|
|
83
99
|
const trGlobal = document.createElement("tr");
|
|
@@ -94,7 +110,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
94
110
|
globalLabel.style.cursor = "pointer";
|
|
95
111
|
globalLabel.classList.add("mx-2", "my-0", "p-0", "w-100");
|
|
96
112
|
|
|
97
|
-
globalLabel.addEventListener("click",
|
|
113
|
+
globalLabel.addEventListener("click", () => this.#showDevices(true));
|
|
98
114
|
|
|
99
115
|
// Add the global options label.
|
|
100
116
|
tdGlobal.appendChild(globalLabel);
|
|
@@ -107,7 +123,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
107
123
|
controllersTable.appendChild(trGlobal);
|
|
108
124
|
|
|
109
125
|
// Add it as another device, for UI purposes.
|
|
110
|
-
this.
|
|
126
|
+
this.webuiDeviceList.push(globalLabel);
|
|
111
127
|
|
|
112
128
|
// All done. Let the user interact with us.
|
|
113
129
|
homebridge.hideSpinner();
|
|
@@ -123,18 +139,19 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
123
139
|
homebridge.showSpinner();
|
|
124
140
|
|
|
125
141
|
const devicesTable = document.getElementById("devicesTable");
|
|
126
|
-
this.
|
|
142
|
+
this.devices = [];
|
|
127
143
|
|
|
128
144
|
// If we're not accessing global options, pull the list of devices this plugin knows about from Homebridge.
|
|
129
|
-
this.
|
|
145
|
+
this.devices = (await homebridge.getCachedAccessories()).map(x => ({
|
|
130
146
|
firmwareVersion: (x.services.find(service => service.constructorName ===
|
|
131
147
|
"AccessoryInformation")?.characteristics.find(characteristic => characteristic.constructorName === "FirmwareRevision")?.value ?? ""),
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
name: x.displayName,
|
|
149
|
+
serial: (x.services.find(service => service.constructorName ===
|
|
150
|
+
"AccessoryInformation")?.characteristics.find(characteristic => characteristic.constructorName === "SerialNumber")?.value ?? "")
|
|
151
|
+
}));
|
|
135
152
|
|
|
136
153
|
// Sort it for posterity.
|
|
137
|
-
this.
|
|
154
|
+
this.devices?.sort((a, b) => {
|
|
138
155
|
|
|
139
156
|
const aCase = (a.name ?? "").toLowerCase();
|
|
140
157
|
const bCase = (b.name ?? "").toLowerCase();
|
|
@@ -147,16 +164,13 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
147
164
|
document.getElementById("headerInfo").style.display = "";
|
|
148
165
|
|
|
149
166
|
// Wipe out the device list, except for our global entry.
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
// We aren't using the concept of a controller category in Ratgdo, so we set this to something innocuous.
|
|
153
|
-
this.controller = "NA";
|
|
167
|
+
this.webuiDeviceList.splice(1, this.webuiDeviceList.length);
|
|
154
168
|
|
|
155
169
|
// Start with a clean slate.
|
|
156
170
|
devicesTable.innerHTML = "";
|
|
157
171
|
|
|
158
172
|
// Show the devices list only if we have actual devices to show.
|
|
159
|
-
if(this.
|
|
173
|
+
if(this.devices?.length) {
|
|
160
174
|
|
|
161
175
|
// Create a row for this device category.
|
|
162
176
|
const trCategory = document.createElement("tr");
|
|
@@ -166,7 +180,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
166
180
|
tdCategory.classList.add("m-0", "p-0");
|
|
167
181
|
|
|
168
182
|
// Add the category name, with appropriate casing.
|
|
169
|
-
tdCategory.appendChild(document.createTextNode(
|
|
183
|
+
tdCategory.appendChild(document.createTextNode(this.sidebarName));
|
|
170
184
|
tdCategory.style.fontWeight = "bold";
|
|
171
185
|
|
|
172
186
|
// Add the cell to the table row.
|
|
@@ -175,7 +189,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
175
189
|
// Add the table row to the table.
|
|
176
190
|
devicesTable.appendChild(trCategory);
|
|
177
191
|
|
|
178
|
-
for(const device of this.
|
|
192
|
+
for(const device of this.devices) {
|
|
179
193
|
|
|
180
194
|
// Create a row for this device.
|
|
181
195
|
const trDevice = document.createElement("tr");
|
|
@@ -187,12 +201,12 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
187
201
|
|
|
188
202
|
const label = document.createElement("label");
|
|
189
203
|
|
|
190
|
-
label.name = device.
|
|
191
|
-
label.appendChild(document.createTextNode(device.name ?? "
|
|
204
|
+
label.name = device.serial;
|
|
205
|
+
label.appendChild(document.createTextNode(device.name ?? "Unknown"));
|
|
192
206
|
label.style.cursor = "pointer";
|
|
193
207
|
label.classList.add("mx-2", "my-0", "p-0", "w-100");
|
|
194
208
|
|
|
195
|
-
label.addEventListener("click",
|
|
209
|
+
label.addEventListener("click", () => this.#showDeviceInfo(device.serial));
|
|
196
210
|
|
|
197
211
|
// Add the device label to our cell.
|
|
198
212
|
tdDevice.appendChild(label);
|
|
@@ -203,92 +217,47 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
203
217
|
// Add the table row to the table.
|
|
204
218
|
devicesTable.appendChild(trDevice);
|
|
205
219
|
|
|
206
|
-
this.
|
|
220
|
+
this.webuiDeviceList.push(label);
|
|
207
221
|
}
|
|
208
222
|
}
|
|
209
223
|
|
|
210
|
-
this.configOptions = [];
|
|
211
|
-
|
|
212
|
-
// Initialize our feature option configuration.
|
|
213
|
-
this.#updateConfigOptions(this.currentConfig[0].options ?? []);
|
|
214
|
-
|
|
215
224
|
// Display the feature options to the user.
|
|
216
|
-
this.#showDeviceInfo(isGlobal ? "Global Options" : this.
|
|
225
|
+
this.#showDeviceInfo(isGlobal ? "Global Options" : this.devices[0].serial);
|
|
217
226
|
|
|
218
227
|
// All done. Let the user interact with us.
|
|
219
228
|
homebridge.hideSpinner();
|
|
220
229
|
}
|
|
221
230
|
|
|
222
|
-
// Return the serial number of a given Ratgdo device.
|
|
223
|
-
getSerial(device) {
|
|
224
|
-
|
|
225
|
-
return !device ? undefined :
|
|
226
|
-
device.services.find(x => x.constructorName === "AccessoryInformation")?.characteristics.find(x => x.constructorName === "SerialNumber")?.value ?? "";
|
|
227
|
-
}
|
|
228
|
-
|
|
229
231
|
// Show feature option information for a specific device, controller, or globally.
|
|
230
232
|
async #showDeviceInfo(deviceId) {
|
|
231
233
|
|
|
232
234
|
homebridge.showSpinner();
|
|
233
235
|
|
|
234
236
|
// Update the selected device for visibility.
|
|
235
|
-
this.
|
|
237
|
+
this.webuiDeviceList.map(x => (x.name === deviceId) ?
|
|
238
|
+
x.parentElement.classList.add("bg-info", "text-white") : x.parentElement.classList.remove("bg-info", "text-white"));
|
|
236
239
|
|
|
237
240
|
// Populate the device information info pane.
|
|
238
|
-
const
|
|
241
|
+
const currentDevice = this.devices.find(x => x.serial === deviceId);
|
|
242
|
+
this.controller = currentDevice?.serial;
|
|
239
243
|
|
|
240
244
|
// Ensure we have a controller or device. The only time this won't be the case is when we're looking at global options.
|
|
241
|
-
if(
|
|
245
|
+
if(currentDevice) {
|
|
242
246
|
|
|
243
|
-
document.getElementById("device_firmware").innerHTML =
|
|
244
|
-
document.getElementById("device_serial").innerHTML =
|
|
247
|
+
document.getElementById("device_firmware").innerHTML = currentDevice.firmwareVersion;
|
|
248
|
+
document.getElementById("device_serial").innerHTML = currentDevice.serial;
|
|
245
249
|
document.getElementById("deviceStatsTable").style.display = "";
|
|
246
250
|
} else {
|
|
247
251
|
|
|
248
252
|
document.getElementById("deviceStatsTable").style.display = "none";
|
|
249
|
-
document.getElementById("device_firmware").innerHTML = "N/A"
|
|
253
|
+
document.getElementById("device_firmware").innerHTML = "N/A";
|
|
250
254
|
document.getElementById("device_serial").innerHTML = "N/A";
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
// Populate the feature options selected for this device.
|
|
254
|
-
const ratgdoFeatures = (await homebridge.request("/getOptions", { configOptions: this.configOptions, ratgdoDevice: ratgdoDevice })) ?? [];
|
|
255
|
-
const optionsDevice = ratgdoFeatures.options;
|
|
256
|
-
|
|
257
257
|
// Start with a clean slate.
|
|
258
|
-
let newConfigTableHtml = "";
|
|
259
258
|
this.configTable.innerHTML = "";
|
|
260
259
|
|
|
261
|
-
|
|
262
|
-
this.featureOptionList = {};
|
|
263
|
-
this.featureOptionGroups = {};
|
|
264
|
-
|
|
265
|
-
for(const category of ratgdoFeatures.categories) {
|
|
266
|
-
|
|
267
|
-
// Now enumerate all the feature options for a given device and add then to the full list.
|
|
268
|
-
for(const option of optionsDevice[category.name]) {
|
|
269
|
-
|
|
270
|
-
const featureOption = category.name + (option.name.length ? ("." + option.name): "");
|
|
271
|
-
|
|
272
|
-
// Add it to our full list.
|
|
273
|
-
this.featureOptionList[featureOption] = option;
|
|
274
|
-
|
|
275
|
-
// Cross reference the feature option group it belongs to, if any.
|
|
276
|
-
if(option.group !== undefined) {
|
|
277
|
-
|
|
278
|
-
const expandedGroup = category.name + (option.group.length ? ("." + option.group): "");
|
|
279
|
-
|
|
280
|
-
// Initialize the group entry if needed.
|
|
281
|
-
if(!this.featureOptionGroups[expandedGroup]) {
|
|
282
|
-
|
|
283
|
-
this.featureOptionGroups[expandedGroup] = [];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
this.featureOptionGroups[expandedGroup].push(featureOption);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
for(const category of ratgdoFeatures.categories) {
|
|
260
|
+
for(const category of this.featureOptions.categories) {
|
|
292
261
|
|
|
293
262
|
const optionTable = document.createElement("table");
|
|
294
263
|
const thead = document.createElement("thead");
|
|
@@ -304,7 +273,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
304
273
|
tbody.classList.add("table-bordered");
|
|
305
274
|
|
|
306
275
|
// Add the feature option category description.
|
|
307
|
-
th.appendChild(document.createTextNode(category.description + (!
|
|
276
|
+
th.appendChild(document.createTextNode(category.description + (!currentDevice ? " (Global)" : " (Device-specific)")));
|
|
308
277
|
|
|
309
278
|
// Add the table header to the row.
|
|
310
279
|
trFirst.appendChild(th);
|
|
@@ -319,10 +288,10 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
319
288
|
let optionsVisibleCount = 0;
|
|
320
289
|
|
|
321
290
|
// Now enumerate all the feature options for a given device.
|
|
322
|
-
for(const option of
|
|
291
|
+
for(const option of this.featureOptions.options[category.name]) {
|
|
323
292
|
|
|
324
293
|
// Expand the full feature option.
|
|
325
|
-
const featureOption =
|
|
294
|
+
const featureOption = this.featureOptions.expandOption(category, option);
|
|
326
295
|
|
|
327
296
|
// Create the next table row.
|
|
328
297
|
const trX = document.createElement("tr");
|
|
@@ -339,27 +308,27 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
339
308
|
checkbox.readOnly = false;
|
|
340
309
|
checkbox.id = featureOption;
|
|
341
310
|
checkbox.name = featureOption;
|
|
342
|
-
checkbox.value = featureOption + (!
|
|
311
|
+
checkbox.value = featureOption + (!currentDevice ? "" : ("." + currentDevice.serial));
|
|
343
312
|
|
|
344
313
|
let initialValue = undefined;
|
|
345
314
|
let initialScope;
|
|
346
315
|
|
|
347
316
|
// Determine our initial option scope to show the user what's been set.
|
|
348
|
-
switch(initialScope = this.
|
|
317
|
+
switch(initialScope = this.featureOptions.scope(featureOption, currentDevice?.serial)) {
|
|
349
318
|
|
|
350
319
|
case "global":
|
|
351
320
|
case "controller":
|
|
352
321
|
|
|
353
322
|
// 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.
|
|
354
|
-
if(!
|
|
323
|
+
if(!currentDevice) {
|
|
355
324
|
|
|
356
|
-
if(
|
|
325
|
+
if(this.featureOptions.isValue(featureOption)) {
|
|
357
326
|
|
|
358
|
-
checkbox.checked = this.
|
|
359
|
-
initialValue = this.
|
|
327
|
+
checkbox.checked = this.featureOptions.exists(featureOption);
|
|
328
|
+
initialValue = this.featureOptions.value(checkbox.id);
|
|
360
329
|
} else {
|
|
361
330
|
|
|
362
|
-
checkbox.checked = this.
|
|
331
|
+
checkbox.checked = this.featureOptions.test(featureOption);
|
|
363
332
|
}
|
|
364
333
|
|
|
365
334
|
if(checkbox.checked) {
|
|
@@ -369,9 +338,9 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
369
338
|
|
|
370
339
|
} else {
|
|
371
340
|
|
|
372
|
-
if(
|
|
341
|
+
if(this.featureOptions.isValue(featureOption)) {
|
|
373
342
|
|
|
374
|
-
initialValue = this.
|
|
343
|
+
initialValue = this.featureOptions.value(checkbox.id, (initialScope === "controller") ? this.controller : undefined);
|
|
375
344
|
}
|
|
376
345
|
|
|
377
346
|
checkbox.readOnly = checkbox.indeterminate = true;
|
|
@@ -383,13 +352,13 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
383
352
|
case "none":
|
|
384
353
|
default:
|
|
385
354
|
|
|
386
|
-
if(
|
|
355
|
+
if(this.featureOptions.isValue(featureOption)) {
|
|
387
356
|
|
|
388
|
-
checkbox.checked = this.
|
|
389
|
-
initialValue = this.
|
|
357
|
+
checkbox.checked = this.featureOptions.exists(featureOption, currentDevice?.serial);
|
|
358
|
+
initialValue = this.featureOptions.value(checkbox.id, currentDevice?.serial);
|
|
390
359
|
} else {
|
|
391
360
|
|
|
392
|
-
checkbox.checked = this.
|
|
361
|
+
checkbox.checked = this.featureOptions.test(featureOption, currentDevice?.serial);
|
|
393
362
|
}
|
|
394
363
|
|
|
395
364
|
break;
|
|
@@ -411,7 +380,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
411
380
|
let inputValue = null;
|
|
412
381
|
|
|
413
382
|
// Add an input field if we have a value-centric feature option.
|
|
414
|
-
if((
|
|
383
|
+
if(this.featureOptions.isValue(featureOption)) {
|
|
415
384
|
|
|
416
385
|
const tdInput = document.createElement("td");
|
|
417
386
|
tdInput.classList.add("mr-2");
|
|
@@ -427,8 +396,8 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
427
396
|
inputValue.addEventListener("change", async () => {
|
|
428
397
|
|
|
429
398
|
// Find the option in our list and delete it if it exists.
|
|
430
|
-
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!
|
|
431
|
-
const newOptions = this.
|
|
399
|
+
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!currentDevice ? "" : ("\\." + currentDevice.serial)) + "\\.[^\\.]+$", "gi");
|
|
400
|
+
const newOptions = this.featureOptions.configuredOptions.filter(x => !optionRegex.test(x));
|
|
432
401
|
|
|
433
402
|
if(checkbox.checked) {
|
|
434
403
|
|
|
@@ -436,7 +405,9 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
436
405
|
} else if(checkbox.indeterminate) {
|
|
437
406
|
|
|
438
407
|
// If we're in an indeterminate state, we need to traverse the tree to get the upstream value we're inheriting.
|
|
439
|
-
inputValue.value = (
|
|
408
|
+
inputValue.value = (currentDevice?.serial !== this.controller) ?
|
|
409
|
+
(this.featureOptions.value(checkbox.id, this.controller) ?? this.featureOptions.value(checkbox.id)) :
|
|
410
|
+
(this.featureOptions.value(checkbox.id) ?? option.defaultValue);
|
|
440
411
|
} else {
|
|
441
412
|
|
|
442
413
|
inputValue.value = option.defaultValue;
|
|
@@ -444,7 +415,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
444
415
|
|
|
445
416
|
// Update our configuration in Homebridge.
|
|
446
417
|
this.currentConfig[0].options = newOptions;
|
|
447
|
-
this
|
|
418
|
+
this.featureOptions.configuredOptions = newOptions;
|
|
448
419
|
await homebridge.updatePluginConfig(this.currentConfig);
|
|
449
420
|
});
|
|
450
421
|
|
|
@@ -459,7 +430,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
459
430
|
labelDescription.classList.add("user-select-none", "my-0", "py-0");
|
|
460
431
|
|
|
461
432
|
// Highlight options for the user that are different than our defaults.
|
|
462
|
-
const scopeColor = this.
|
|
433
|
+
const scopeColor = this.featureOptions.color(featureOption, currentDevice?.serial);
|
|
463
434
|
|
|
464
435
|
if(scopeColor) {
|
|
465
436
|
|
|
@@ -470,19 +441,19 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
470
441
|
checkbox.addEventListener("change", async () => {
|
|
471
442
|
|
|
472
443
|
// Find the option in our list and delete it if it exists.
|
|
473
|
-
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!
|
|
474
|
-
const newOptions = this.
|
|
444
|
+
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!currentDevice ? "" : ("\\." + currentDevice.serial)) + "$", "gi");
|
|
445
|
+
const newOptions = this.featureOptions.configuredOptions.filter(x => !optionRegex.test(x));
|
|
475
446
|
|
|
476
447
|
// Figure out if we've got the option set upstream.
|
|
477
448
|
let upstreamOption = false;
|
|
478
449
|
|
|
479
450
|
// 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.
|
|
480
|
-
switch(this.
|
|
451
|
+
switch(this.featureOptions.scope(checkbox.id, (currentDevice && (currentDevice.serial !== this.controller)) ? this.controller : undefined)) {
|
|
481
452
|
|
|
482
453
|
case "device":
|
|
483
454
|
case "controller":
|
|
484
455
|
|
|
485
|
-
if(
|
|
456
|
+
if(currentDevice.serial !== this.controller) {
|
|
486
457
|
|
|
487
458
|
upstreamOption = true;
|
|
488
459
|
}
|
|
@@ -491,7 +462,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
491
462
|
|
|
492
463
|
case "global":
|
|
493
464
|
|
|
494
|
-
if(
|
|
465
|
+
if(currentDevice) {
|
|
495
466
|
|
|
496
467
|
upstreamOption = true;
|
|
497
468
|
}
|
|
@@ -503,8 +474,9 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
503
474
|
break;
|
|
504
475
|
}
|
|
505
476
|
|
|
506
|
-
// For value-centric feature options, if there's an upstream value assigned above us, we don't allow for an unchecked state as it
|
|
507
|
-
|
|
477
|
+
// 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
|
|
478
|
+
// context.
|
|
479
|
+
if(checkbox.readOnly && (!this.featureOptions.isValue(featureOption) || (this.featureOptions.isValue(featureOption) && inputValue && !upstreamOption))) {
|
|
508
480
|
|
|
509
481
|
// We're truly unchecked. We need this because a checkbox can be in both an unchecked and indeterminate simultaneously,
|
|
510
482
|
// so we use the readOnly property to let us know that we've just cycled from an indeterminate state.
|
|
@@ -519,7 +491,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
519
491
|
checkbox.readOnly = checkbox.indeterminate = true;
|
|
520
492
|
}
|
|
521
493
|
|
|
522
|
-
if((
|
|
494
|
+
if(this.featureOptions.isValue(featureOption) && inputValue) {
|
|
523
495
|
|
|
524
496
|
inputValue.readOnly = true;
|
|
525
497
|
}
|
|
@@ -528,7 +500,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
528
500
|
// We've explicitly checked this option.
|
|
529
501
|
checkbox.readOnly = checkbox.indeterminate = false;
|
|
530
502
|
|
|
531
|
-
if((
|
|
503
|
+
if(this.featureOptions.isValue(featureOption) && inputValue) {
|
|
532
504
|
|
|
533
505
|
inputValue.readOnly = false;
|
|
534
506
|
}
|
|
@@ -546,7 +518,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
546
518
|
}
|
|
547
519
|
|
|
548
520
|
// Update our Homebridge configuration.
|
|
549
|
-
if((
|
|
521
|
+
if(this.featureOptions.isValue(featureOption) && inputValue) {
|
|
550
522
|
|
|
551
523
|
// Inform our value-centric feature option to update Homebridge.
|
|
552
524
|
const changeEvent = new Event("change");
|
|
@@ -556,14 +528,14 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
556
528
|
|
|
557
529
|
// Update our configuration in Homebridge.
|
|
558
530
|
this.currentConfig[0].options = newOptions;
|
|
559
|
-
this
|
|
531
|
+
this.featureOptions.configuredOptions = newOptions;
|
|
560
532
|
await homebridge.updatePluginConfig(this.currentConfig);
|
|
561
533
|
}
|
|
562
534
|
|
|
563
535
|
// If we've reset to defaults, make sure our color coding for scope is reflected.
|
|
564
536
|
if((checkbox.checked === option.default) || checkbox.indeterminate) {
|
|
565
537
|
|
|
566
|
-
const scopeColor = this.
|
|
538
|
+
const scopeColor = this.featureOptions.color(featureOption, currentDevice?.serial);
|
|
567
539
|
|
|
568
540
|
if(scopeColor) {
|
|
569
541
|
|
|
@@ -572,12 +544,12 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
572
544
|
}
|
|
573
545
|
|
|
574
546
|
// Adjust visibility of other feature options that depend on us.
|
|
575
|
-
if(this.
|
|
547
|
+
if(this.featureOptions.groups[checkbox.id]) {
|
|
576
548
|
|
|
577
|
-
const entryVisibility = this.
|
|
549
|
+
const entryVisibility = this.featureOptions.test(featureOption, currentDevice?.serial) ? "" : "none";
|
|
578
550
|
|
|
579
551
|
// Lookup each feature option setting and set the visibility accordingly.
|
|
580
|
-
for(const entry of this.
|
|
552
|
+
for(const entry of this.featureOptions.groups[checkbox.id]) {
|
|
581
553
|
|
|
582
554
|
document.getElementById("row-" + entry).style.display = entryVisibility;
|
|
583
555
|
}
|
|
@@ -597,7 +569,7 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
597
569
|
trX.appendChild(tdLabel);
|
|
598
570
|
|
|
599
571
|
// Adjust the visibility of the feature option, if it's logically grouped.
|
|
600
|
-
if((option.group !== undefined) && !this.
|
|
572
|
+
if((option.group !== undefined) && !this.featureOptions.test(category.name + (option.group.length ? ("." + option.group) : ""), currentDevice?.serial)) {
|
|
601
573
|
|
|
602
574
|
trX.style.display = "none";
|
|
603
575
|
} else {
|
|
@@ -625,14 +597,4 @@ export class ratgdoFeatureOptions extends FeatureOptions {
|
|
|
625
597
|
|
|
626
598
|
homebridge.hideSpinner();
|
|
627
599
|
}
|
|
628
|
-
|
|
629
|
-
// Update our configuration options.
|
|
630
|
-
#updateConfigOptions(newConfig) {
|
|
631
|
-
|
|
632
|
-
// Update our configuration.
|
|
633
|
-
this.configOptions = newConfig;
|
|
634
|
-
|
|
635
|
-
// Show all the valid options configured by the user.
|
|
636
|
-
this.optionsList = this.configOptions.filter(x => x.match(/^(Enable|Disable)\.*/gi)).map(x => x.toUpperCase());
|
|
637
|
-
}
|
|
638
600
|
}
|
package/homebridge-ui/server.js
CHANGED
|
@@ -1,58 +1,22 @@
|
|
|
1
1
|
/* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
2
|
*
|
|
3
3
|
* server.js: homebridge-ratgdo webUI server API.
|
|
4
|
-
*
|
|
5
|
-
* This module is heavily inspired by the homebridge-config-ui-x source code and borrows from both.
|
|
6
|
-
* Thank you oznu for your contributions to the HomeKit world.
|
|
7
4
|
*/
|
|
8
5
|
"use strict";
|
|
9
6
|
|
|
10
|
-
import { featureOptionCategories, featureOptions
|
|
7
|
+
import { featureOptionCategories, featureOptions } from "../dist/ratgdo-options.js";
|
|
11
8
|
import { HomebridgePluginUiServer } from "@homebridge/plugin-ui-utils";
|
|
12
|
-
import util from "node:util";
|
|
13
9
|
|
|
14
10
|
class PluginUiServer extends HomebridgePluginUiServer {
|
|
15
11
|
|
|
16
|
-
constructor
|
|
12
|
+
constructor() {
|
|
17
13
|
super();
|
|
18
14
|
|
|
19
15
|
// Register getOptions() with the Homebridge server API.
|
|
20
|
-
this
|
|
16
|
+
this.onRequest("/getOptions", () => ({ categories: featureOptionCategories, options: featureOptions }));
|
|
21
17
|
|
|
22
18
|
this.ready();
|
|
23
19
|
}
|
|
24
|
-
|
|
25
|
-
// Register the getOptions() webUI server API endpoint.
|
|
26
|
-
#registerGetOptions() {
|
|
27
|
-
|
|
28
|
-
// Return the list of options configured for a given Ratgdo device.
|
|
29
|
-
this.onRequest("/getOptions", async(request) => {
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
|
|
33
|
-
const optionSet = {};
|
|
34
|
-
|
|
35
|
-
// Loop through all the feature option categories.
|
|
36
|
-
for(const category of featureOptionCategories) {
|
|
37
|
-
|
|
38
|
-
optionSet[category.name] = [];
|
|
39
|
-
|
|
40
|
-
for(const options of featureOptions[category.name]) {
|
|
41
|
-
|
|
42
|
-
options.value = isOptionEnabled(request.configOptions, request.ratgdoDevice, category.name + "." + options.name, options.default);
|
|
43
|
-
optionSet[category.name].push(options);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return { categories: featureOptionCategories, options: optionSet };
|
|
48
|
-
|
|
49
|
-
} catch(err) {
|
|
50
|
-
|
|
51
|
-
// Return nothing if we error out for some reason.
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
20
|
}
|
|
57
21
|
|
|
58
22
|
(() => new PluginUiServer())();
|