mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
- 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>
169 lines
6.3 KiB
JavaScript
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));
|
|
}
|
|
}
|
|
});
|
|
})();
|