mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 06:45:09 +00:00
feat(web): honour x-display: hidden in plugin settings (#585)
* feat(web): honour x-display: hidden in plugin settings Plugins keep deprecated and internal keys declared so stored configs keep validating (weather api_key/radar_zoom, countdown's auto-generated row id), but the settings form drew them as live controls. A property marked "x-display": "hidden" -- or an object whose children are all hidden -- now gets no control at any depth: top level, nested sections, Advanced Settings (not counted either), array-table columns and the row editor. A hidden top-level key is not reported in __rendered_section. Saving never changes a hidden value. Plain and nested fields aren't posted, so the save's deep merge keeps them; _set_missing_booleans_to_false skips hidden booleans at every depth. A posted array row replaces the stored item, so hidden row properties are carried as JSON-encoded hidden inputs and decoded exactly on save (an id "1" stays a string). New rows get none. JSON API saves are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(web): read hidden row keys without dynamic property access Build the set of x-display: hidden item properties once and look values up through Object.entries, instead of indexing objects by a variable key on the lines this branch added (Codacy: object injection sink, 6 warnings). Behaviour is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,37 @@
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* "x-display": "hidden" — the property stays in the schema but gets no
|
||||
* control. An object whose every child is hidden counts as hidden too.
|
||||
* Mirrors prop_is_hidden in plugin_config.html and _is_hidden_prop on the
|
||||
* server.
|
||||
*/
|
||||
function isHiddenProp(schema) {
|
||||
if (!schema || typeof schema !== 'object') return false;
|
||||
if (schema['x-display'] === 'hidden') return true;
|
||||
const children = schema.properties;
|
||||
if (children && typeof children === 'object') {
|
||||
const values = Object.values(children);
|
||||
return values.length > 0 && values.every(isHiddenProp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A hidden input carrying a hidden property's stored value, JSON-encoded so
|
||||
* the save restores its exact type. A posted row replaces the stored item,
|
||||
* so a value that isn't posted is lost.
|
||||
*/
|
||||
function hiddenValueInput(name, dataKey, value) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = name;
|
||||
input.value = JSON.stringify(value);
|
||||
input.dataset.hiddenProp = dataKey;
|
||||
return input;
|
||||
}
|
||||
|
||||
function safeSetHTML(target, html) {
|
||||
target.textContent = '';
|
||||
// createContextualFragment parses html relative to the document context
|
||||
@@ -343,6 +374,16 @@
|
||||
if (propType === 'object' && propSchema.properties) {
|
||||
const nestedVal = (item && item[propName]) || {};
|
||||
Object.entries(propSchema.properties).forEach(([subName, subSchema]) => {
|
||||
if (isHiddenProp(subSchema)) {
|
||||
// No data-nested-prop, so the row editor never shows it.
|
||||
const stored = Object.entries(nestedVal).find(([name]) => name === subName);
|
||||
if (stored && stored[1] !== undefined && stored[1] !== null) {
|
||||
cell.appendChild(hiddenValueInput(
|
||||
`${fullKey}.${index}.${propName}.${subName}`,
|
||||
`${propName}.${subName}`, stored[1]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const subType = Array.isArray(subSchema.type)
|
||||
? subSchema.type.find(t => t !== 'null') || 'string'
|
||||
: (subSchema.type || 'string');
|
||||
@@ -386,7 +427,16 @@
|
||||
row.className = 'array-table-row';
|
||||
row.setAttribute('data-index', index);
|
||||
|
||||
// "x-display": "hidden" item properties: never a column (even if
|
||||
// x-columns names one), never in the advanced cell or row editor.
|
||||
const hiddenKeys = new Set(
|
||||
Object.entries(fullItemProperties)
|
||||
.filter(([, propSchema]) => isHiddenProp(propSchema))
|
||||
.map(([propName]) => propName)
|
||||
);
|
||||
|
||||
// Visible column cells
|
||||
displayColumns = displayColumns.filter(colName => !hiddenKeys.has(colName));
|
||||
displayColumns.forEach(colName => {
|
||||
const colDef = itemProperties[colName] || {};
|
||||
const colType = Array.isArray(colDef.type) ? colDef.type.find(t => t !== 'null') || 'string' : (colDef.type || 'string');
|
||||
@@ -396,10 +446,11 @@
|
||||
row.appendChild(createCell(fullKey, index, colName, colDef, colValue, pluginId));
|
||||
});
|
||||
|
||||
// Determine non-displayed properties (these go into the advanced cell + edit modal)
|
||||
// Determine non-displayed properties (these go into the advanced cell + edit modal).
|
||||
// Hidden ones ("x-display": "hidden") get no column and no editor field.
|
||||
const nonDisplayed = {};
|
||||
Object.keys(fullItemProperties).forEach(k => {
|
||||
if (!displayColumns.includes(k) && k !== 'id') {
|
||||
if (!displayColumns.includes(k) && k !== 'id' && !hiddenKeys.has(k)) {
|
||||
nonDisplayed[k] = fullItemProperties[k];
|
||||
}
|
||||
});
|
||||
@@ -430,6 +481,14 @@
|
||||
actionsCell.appendChild(editBtn);
|
||||
}
|
||||
|
||||
// Hidden properties: carry stored values only. A new row has none, so
|
||||
// nothing is invented (countdown fills in its own id).
|
||||
Object.entries(item).forEach(([propName, stored]) => {
|
||||
if (hiddenKeys.has(propName) && stored !== undefined && stored !== null) {
|
||||
actionsCell.appendChild(hiddenValueInput(`${fullKey}.${index}.${propName}`, propName, stored));
|
||||
}
|
||||
});
|
||||
|
||||
row.appendChild(actionsCell);
|
||||
|
||||
// Hidden advanced data cell
|
||||
@@ -494,7 +553,8 @@
|
||||
grid.className = 'grid grid-cols-2 gap-3';
|
||||
|
||||
Object.entries(propSchema.properties).forEach(([subName, subSchema]) => {
|
||||
const subType = Array.isArray(subSchema.type) ? subSchema.type.find(t => t !== 'null') || 'string' : (subSchema.type || 'string');
|
||||
if (isHiddenProp(subSchema)) return;
|
||||
const subType =Array.isArray(subSchema.type) ? subSchema.type.find(t => t !== 'null') || 'string' : (subSchema.type || 'string');
|
||||
const subLabel = subSchema.title || subName.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
||||
const subDesc = subSchema.description || '';
|
||||
const nestedPath = `${propName}.${subName}`;
|
||||
|
||||
@@ -2409,6 +2409,19 @@ function getSchemaProperty(schema, path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// "x-display": "hidden" (or an object of nothing but hidden children).
|
||||
// Mirrors prop_is_hidden in plugin_config.html.
|
||||
function isHiddenSchemaProp(schema) {
|
||||
if (!schema || typeof schema !== 'object') return false;
|
||||
if (schema['x-display'] === 'hidden') return true;
|
||||
const children = schema.properties;
|
||||
if (children && typeof children === 'object') {
|
||||
const values = Object.values(children);
|
||||
return values.length > 0 && values.every(isHiddenSchemaProp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper function to render a single item in an array of objects
|
||||
function renderArrayObjectItem(fieldId, fullKey, itemProperties, itemValue, index, itemsSchema) {
|
||||
const item = itemValue || {};
|
||||
@@ -2422,6 +2435,9 @@ function renderArrayObjectItem(fieldId, fullKey, itemProperties, itemValue, inde
|
||||
const propertyOrder = itemsSchema['x-propertyOrder'] || Object.keys(itemProperties);
|
||||
propertyOrder.forEach(propKey => {
|
||||
if (!itemProperties[propKey]) return;
|
||||
// "x-display": "hidden": no control. The stored value survives through
|
||||
// data-item-data, which updateArrayObjectData overlays edits onto.
|
||||
if (isHiddenSchemaProp(itemProperties[propKey])) return;
|
||||
|
||||
const propSchema = itemProperties[propKey];
|
||||
const propValue = item[propKey] !== undefined ? item[propKey] : propSchema.default;
|
||||
@@ -5478,6 +5494,7 @@ if (typeof window !== 'undefined') {
|
||||
itemHtml = `<div class="border border-gray-300 rounded-lg p-4 bg-gray-50 array-object-item" data-index="${newIndex}">`;
|
||||
Object.keys(itemsSchema.properties || {}).forEach(propKey => {
|
||||
const propSchema = itemsSchema.properties[propKey];
|
||||
if (propSchema && propSchema['x-display'] === 'hidden') return;
|
||||
const propValue = newItem[propKey] !== undefined ? newItem[propKey] : propSchema.default;
|
||||
const propLabel = propSchema.title || propKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||
itemHtml += `<div class="mb-3"><label class="block text-sm font-medium text-gray-700 mb-1">${escapeHtml(propLabel)}</label>`;
|
||||
|
||||
Reference in New Issue
Block a user