fix(web): keep disabled plugins in the saved rotation order and Vegas exclusions

PluginOrderList draws one row per enabled plugin and, once drawn, rewrites
its hidden inputs (plugin_rotation_order, vegas_plugin_order,
vegas_excluded_plugins) from those rows. A disabled plugin has no row, so
merely opening the Display or Rotation & Durations tab took it out of the
inputs, and the next save of that form stored the lists without it. Exclude
Clock from Vegas, disable it, change the brightness, re-enable it: Clock was
scrolling in Vegas again and had moved to the end of the rotation.

syncInputs now keeps the saved ids that have no row. In the order, each one
keeps its saved slot and the rows fill the other slots in their current
order, with rows not in the saved order last, as before. In the exclusions
they follow the unchecked rows. Only string ids are carried over, once each:
/config/main refuses a list holding anything else, which would block every
later save of the tab.

Tests: test/js/unit/test_plugin_order_list.js runs the shipped widget in a vm
with a fake DOM (draw, reorder, include/exclude, the rotation list, junk ids)
and is in run_all.js and the README. The durations DOM suite now reads only
its own rows' ids from the input, since a rig's saved order can hold others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-03 20:50:05 -04:00
co-authored by Claude Opus 5.5
parent 4c5bba34ef
commit 2b34f2545c
6 changed files with 227 additions and 7 deletions
@@ -18,7 +18,8 @@
* });
*
* The container re-renders from /api/v3/plugins/installed each init; the
* hidden input(s) must already hold the saved order/exclusions (JSON).
* hidden input(s) must already hold the saved order/exclusions (JSON). Saved
* ids without a row (disabled plugins) stay in them, in their saved places.
*/
(function() {
'use strict';
@@ -39,17 +40,53 @@
const excludedInput = options.excludedInputId ? document.getElementById(options.excludedInputId) : null;
if (!container || !orderInput) return;
// The saved lists as the inputs held them when the rows were drawn.
// Only enabled plugins get a row, and the inputs are rewritten from
// the rows, so a disabled plugin's place and exclusion have to be
// carried over from these: dropped, the next Display or Durations
// save stored the lists without it, and once re-enabled it came back
// at the end of the rotation and scrolling in Vegas again.
let savedOrder = [];
let savedExcluded = [];
// Saved ids with no row, once each. Only strings: /config/main
// refuses a list holding anything else, which would block every save.
function unlisted(saved, rowIds) {
const seen = new Set(rowIds);
return saved.filter(id => {
if (typeof id !== 'string' || seen.has(id)) return false;
seen.add(id);
return true;
});
}
function syncInputs() {
const order = [];
const rowIds = [];
const excluded = [];
container.querySelectorAll('.plugin-order-item').forEach(item => {
const pluginId = item.dataset.pluginId;
order.push(pluginId);
rowIds.push(pluginId);
const checkbox = item.querySelector('.plugin-order-include');
if (checkbox && !checkbox.checked) excluded.push(pluginId);
});
orderInput.value = JSON.stringify(order);
if (excludedInput) excludedInput.value = JSON.stringify(excluded);
// An id without a row keeps its saved slot; the rows fill the
// other slots in their current order, and any rows left over
// (plugins not in the saved order) go last.
const kept = new Set(unlisted(savedOrder, rowIds));
const order = [];
let next = 0;
savedOrder.forEach(id => {
if (kept.has(id)) {
order.push(id);
kept.delete(id);
} else if (rowIds.includes(id) && next < rowIds.length) {
order.push(rowIds[next++]);
}
});
orderInput.value = JSON.stringify(order.concat(rowIds.slice(next)));
if (excludedInput) {
excludedInput.value = JSON.stringify(excluded.concat(unlisted(savedExcluded, rowIds)));
}
}
function setupDragAndDrop() {
@@ -125,6 +162,8 @@
// (e.g. a saved value of "null"); normalize to arrays.
if (!Array.isArray(currentOrder)) currentOrder = [];
if (!Array.isArray(excluded)) excluded = [];
savedOrder = currentOrder;
savedExcluded = excluded;
// Saved order first, then any newly enabled plugins.
const orderedPlugins = [];