Files
LEDMatrix/web_interface/static/v3/js/widgets/slider.js
T
ChuckandClaude Opus 5.5 76f5d8a336 fix(web): seven web UI bugs, and remove dead plugins_manager.js helpers (#647)
- Operation History: the plugin filter lists the installed plugin ids
  instead of one option, "plugins" (Object.keys of {plugins: [...]}).
- Ctrl/Cmd+S submits the active tab's first visible form with
  requestSubmit() (validation and onsubmit guards run) instead of a bare
  Event on the first form in the document; skipped inside a modal dialog
  and on tabs without a form.
- Overview "Check Updates" confirms like "Update Code", takes its button
  explicitly (no implicit global event) and shows the server's message.
  Both, and the Tools tab git pull, raise the restart-pending banner on
  restart_required.
- Tools: toolsAction and diagnostics show the server's error message;
  only a non-JSON body falls back to HTTP <status>.
- Installed list after uninstall: PluginAPI writes clear the throttler's
  GET cache, a forced loadInstalledPlugins clears it too, and the
  post-uninstall reload goes through refreshInstalledPlugins().
- Plugin widgets load from /static/plugin-widgets/ only (the other two
  paths have no route).
- Raw JSON editor escapes the parse error; slider escapes value/min/max/step.
- Removed the unreferenced array-of-objects and key-value helpers from
  plugins_manager.js, the textarea auto-resize and Ctrl+R handlers in
  app.js, and a redundant ?v= on the plugins_manager.js script tag.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:26:27 -04:00

169 lines
6.3 KiB
JavaScript

/**
* LEDMatrix Slider Widget
*
* Range slider with value display and optional tick marks.
*
* Schema example:
* {
* "volume": {
* "type": "number",
* "x-widget": "slider",
* "minimum": 0,
* "maximum": 100,
* "x-options": {
* "step": 5,
* "showValue": true,
* "showMinMax": true,
* "suffix": "%",
* "color": "blue" // "blue", "green", "red", "purple"
* }
* }
* }
*
* @module SliderWidget
*/
(function() {
'use strict';
const base = window.BaseWidget ? new window.BaseWidget('Slider', '1.0.0') : null;
function escapeHtml(text) { return window.LEDEscape.html(text); }
function escapeAttr(text) { return window.LEDEscape.attr(text); }
function sanitizeId(id) {
if (base) return base.sanitizeId(id);
return String(id).replace(/[^a-zA-Z0-9_-]/g, '_');
}
function triggerChange(fieldId, value) {
if (base) {
base.triggerChange(fieldId, value);
} else {
const event = new CustomEvent('widget-change', {
detail: { fieldId, value },
bubbles: true,
cancelable: true
});
document.dispatchEvent(event);
}
}
const COLOR_CLASSES = {
blue: 'accent-blue-600',
green: 'accent-green-600',
red: 'accent-red-600',
purple: 'accent-purple-600',
amber: 'accent-amber-500'
};
window.LEDMatrixWidgets.register('slider', {
name: 'Slider Widget',
version: '1.0.0',
render: function(container, config, value, options) {
const fieldId = sanitizeId(options.fieldId || container.id || 'slider');
const xOptions = config['x-options'] || config['x_options'] || {};
const min = config.minimum !== undefined ? config.minimum : (xOptions.min !== undefined ? xOptions.min : 0);
const max = config.maximum !== undefined ? config.maximum : (xOptions.max !== undefined ? xOptions.max : 100);
const step = xOptions.step || 1;
const showValue = xOptions.showValue !== false;
const showMinMax = xOptions.showMinMax !== false;
const suffix = xOptions.suffix || '';
const prefix = xOptions.prefix || '';
const color = xOptions.color || 'blue';
const disabled = xOptions.disabled === true;
const currentValue = value !== null && value !== undefined ? value : min;
const colorClass = COLOR_CLASSES[color] || COLOR_CLASSES.blue;
let html = `<div id="${fieldId}_widget" class="slider-widget" data-field-id="${fieldId}" data-prefix="${escapeAttr(prefix)}" data-suffix="${escapeAttr(suffix)}">`;
// Value display above slider
if (showValue) {
html += `
<div class="flex justify-center mb-2">
<span id="${fieldId}_value" class="text-lg font-semibold text-gray-700">
${escapeHtml(prefix)}${escapeHtml(currentValue)}${escapeHtml(suffix)}
</span>
</div>
`;
}
// Slider
html += `
<input type="range"
id="${fieldId}_input"
name="${escapeHtml(options.name || fieldId)}"
value="${escapeAttr(currentValue)}"
min="${escapeAttr(min)}"
max="${escapeAttr(max)}"
step="${escapeAttr(step)}"
${disabled ? 'disabled' : ''}
oninput="window.LEDMatrixWidgets.getHandlers('slider').onInput('${fieldId}')"
onchange="window.LEDMatrixWidgets.getHandlers('slider').onChange('${fieldId}')"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer ${colorClass} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}">
`;
// Min/Max labels
if (showMinMax) {
html += `
<div class="flex justify-between mt-1">
<span class="text-xs text-gray-400">${escapeHtml(prefix)}${escapeHtml(min)}${escapeHtml(suffix)}</span>
<span class="text-xs text-gray-400">${escapeHtml(prefix)}${escapeHtml(max)}${escapeHtml(suffix)}</span>
</div>
`;
}
html += '</div>';
container.innerHTML = html;
},
getValue: function(fieldId) {
const safeId = sanitizeId(fieldId);
const input = document.getElementById(`${safeId}_input`);
if (!input) return null;
const num = parseFloat(input.value);
return isNaN(num) ? null : num;
},
setValue: function(fieldId, value) {
const safeId = sanitizeId(fieldId);
const input = document.getElementById(`${safeId}_input`);
const valueEl = document.getElementById(`${safeId}_value`);
const widget = document.getElementById(`${safeId}_widget`);
if (input) {
input.value = value !== null && value !== undefined ? value : input.min;
}
if (valueEl && widget && input) {
const prefix = widget.dataset.prefix || '';
const suffix = widget.dataset.suffix || '';
valueEl.textContent = `${prefix}${input.value}${suffix}`;
}
},
handlers: {
onInput: function(fieldId) {
const safeId = sanitizeId(fieldId);
const input = document.getElementById(`${safeId}_input`);
const valueEl = document.getElementById(`${safeId}_value`);
const widget = document.getElementById(`${safeId}_widget`);
if (valueEl && input && widget) {
const prefix = widget.dataset.prefix || '';
const suffix = widget.dataset.suffix || '';
valueEl.textContent = `${prefix}${input.value}${suffix}`;
}
},
onChange: function(fieldId) {
const widget = window.LEDMatrixWidgets.get('slider');
triggerChange(fieldId, widget.getValue(fieldId));
}
}
});
})();