mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-11 13:23:00 +00:00
fix(array-objects): Fix schema lookup, reindexing, and disable file upload
Address PR review feedback for array-of-objects helpers: 1. Schema resolution: Use getSchemaProperty() instead of manual traversal - Fixes nested array-of-objects schema lookup (e.g., news.custom_feeds) - Now properly descends through .properties for nested objects 2. Reindexing: Replace brittle regex with targeted patterns - Only replace index in bracket notation [0], [1], etc. for names - Only replace _item_<digits> pattern for IDs (not arbitrary digits) - Use specific function parameter patterns for onclick handlers - Prevents corruption of fieldId, pluginId, or other numeric values 3. File upload: Disable widget until properly implemented - Hide/disable upload button with clear message - Show existing logos if present but disable upload functionality - Prevents silent failures when users attempt to upload files - Added TODO comments for future implementation Also fixes exit code handling in one-shot-install.sh to properly capture first_time_install.sh exit status before error trap fires.
This commit is contained in:
@@ -343,14 +343,16 @@ main() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Execute with proper error handling
|
# Execute with proper error handling
|
||||||
|
# Temporarily disable errexit to capture exit code instead of exiting immediately
|
||||||
|
set +e
|
||||||
# Use sudo if we're not root, otherwise run directly
|
# Use sudo if we're not root, otherwise run directly
|
||||||
if [ "$EUID" -eq 0 ]; then
|
if [ "$EUID" -eq 0 ]; then
|
||||||
bash ./first_time_install.sh
|
bash ./first_time_install.sh
|
||||||
else
|
else
|
||||||
sudo bash ./first_time_install.sh
|
sudo bash ./first_time_install.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INSTALL_EXIT_CODE=$?
|
INSTALL_EXIT_CODE=$?
|
||||||
|
set -e # Re-enable errexit
|
||||||
|
|
||||||
if [ $INSTALL_EXIT_CODE -eq 0 ]; then
|
if [ $INSTALL_EXIT_CODE -eq 0 ]; then
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -2485,6 +2485,10 @@ function renderArrayObjectItem(fieldId, fullKey, itemProperties, itemValue, inde
|
|||||||
html += `<div class="mb-3">`;
|
html += `<div class="mb-3">`;
|
||||||
|
|
||||||
// Handle file-upload widget (for logo field)
|
// Handle file-upload widget (for logo field)
|
||||||
|
// NOTE: File upload for array-of-objects items is not yet implemented.
|
||||||
|
// The widget is disabled to prevent silent failures when users try to upload files.
|
||||||
|
// TODO: Implement handleArrayObjectFileUpload and removeArrayObjectFile with proper
|
||||||
|
// endpoint support and [data-file-data] attribute updates before enabling this widget.
|
||||||
if (propSchema['x-widget'] === 'file-upload') {
|
if (propSchema['x-widget'] === 'file-upload') {
|
||||||
html += `<label class="block text-sm font-medium text-gray-700 mb-1">${escapeHtml(propLabel)}</label>`;
|
html += `<label class="block text-sm font-medium text-gray-700 mb-1">${escapeHtml(propLabel)}</label>`;
|
||||||
if (propDescription) {
|
if (propDescription) {
|
||||||
@@ -2494,29 +2498,26 @@ function renderArrayObjectItem(fieldId, fullKey, itemProperties, itemValue, inde
|
|||||||
const pluginId = uploadConfig.plugin_id || (typeof currentPluginConfig !== 'undefined' ? currentPluginConfig?.pluginId : null) || (typeof window.currentPluginConfig !== 'undefined' ? window.currentPluginConfig?.pluginId : null) || 'ledmatrix-news';
|
const pluginId = uploadConfig.plugin_id || (typeof currentPluginConfig !== 'undefined' ? currentPluginConfig?.pluginId : null) || (typeof window.currentPluginConfig !== 'undefined' ? window.currentPluginConfig?.pluginId : null) || 'ledmatrix-news';
|
||||||
const logoValue = propValue || {};
|
const logoValue = propValue || {};
|
||||||
|
|
||||||
html += `
|
// Display existing logo if present, but disable upload functionality
|
||||||
<div class="file-upload-widget-inline">
|
|
||||||
<input type="file"
|
|
||||||
id="${itemId}_logo_file"
|
|
||||||
accept="${(uploadConfig.allowed_types || ['image/png', 'image/jpeg', 'image/bmp']).join(',')}"
|
|
||||||
style="display: none;"
|
|
||||||
onchange="handleArrayObjectFileUpload(event, '${fieldId}', ${index}, '${propKey}', '${pluginId}')">
|
|
||||||
<button type="button"
|
|
||||||
onclick="document.getElementById('${itemId}_logo_file').click()"
|
|
||||||
class="px-3 py-2 text-sm bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-md transition-colors">
|
|
||||||
<i class="fas fa-upload mr-1"></i> Upload Logo
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (logoValue.path) {
|
if (logoValue.path) {
|
||||||
html += `
|
html += `
|
||||||
<div class="mt-2 flex items-center space-x-2">
|
<div class="file-upload-widget-inline">
|
||||||
<img src="/${logoValue.path}" alt="Logo" class="w-16 h-16 object-cover rounded border">
|
<div class="mt-2 flex items-center space-x-2">
|
||||||
|
<img src="/${logoValue.path}" alt="Logo" class="w-16 h-16 object-cover rounded border">
|
||||||
|
<span class="text-sm text-gray-500 italic">File upload not yet available for array items</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
html += `
|
||||||
|
<div class="file-upload-widget-inline">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onclick="removeArrayObjectFile('${fieldId}', ${index}, '${propKey}')"
|
disabled
|
||||||
class="text-red-600 hover:text-red-800">
|
class="px-3 py-2 text-sm bg-gray-200 text-gray-400 rounded-md cursor-not-allowed opacity-50"
|
||||||
<i class="fas fa-trash"></i> Remove
|
title="File upload for array items is not yet implemented">
|
||||||
|
<i class="fas fa-upload mr-1"></i> Upload Logo (Not Available)
|
||||||
</button>
|
</button>
|
||||||
|
<p class="text-xs text-gray-500 mt-1 italic">File upload functionality for array items is coming soon</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -6433,19 +6434,13 @@ if (typeof window !== 'undefined') {
|
|||||||
const schema = (typeof currentPluginConfig !== 'undefined' && currentPluginConfig?.schema) || (typeof window.currentPluginConfig !== 'undefined' && window.currentPluginConfig?.schema);
|
const schema = (typeof currentPluginConfig !== 'undefined' && currentPluginConfig?.schema) || (typeof window.currentPluginConfig !== 'undefined' && window.currentPluginConfig?.schema);
|
||||||
if (!schema) return;
|
if (!schema) return;
|
||||||
|
|
||||||
// Navigate to the items schema
|
// Use getSchemaProperty to properly handle nested schemas (e.g., news.custom_feeds)
|
||||||
const keys = fullKey.split('.');
|
const arraySchema = getSchemaProperty(schema, fullKey);
|
||||||
let itemsSchema = schema.properties;
|
if (!arraySchema || arraySchema.type !== 'array' || !arraySchema.items) {
|
||||||
for (const key of keys) {
|
return;
|
||||||
if (itemsSchema && itemsSchema[key]) {
|
|
||||||
itemsSchema = itemsSchema[key];
|
|
||||||
if (itemsSchema.type === 'array' && itemsSchema.items) {
|
|
||||||
itemsSchema = itemsSchema.items;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemsSchema = arraySchema.items;
|
||||||
if (!itemsSchema || !itemsSchema.properties) return;
|
if (!itemsSchema || !itemsSchema.properties) return;
|
||||||
|
|
||||||
const newIndex = currentItems.length;
|
const newIndex = currentItems.length;
|
||||||
@@ -6489,24 +6484,55 @@ if (typeof window !== 'undefined') {
|
|||||||
if (item) {
|
if (item) {
|
||||||
item.remove();
|
item.remove();
|
||||||
// Re-index remaining items
|
// Re-index remaining items
|
||||||
|
// Use data-index for index storage - no need to encode index in onclick strings or IDs
|
||||||
const remainingItems = itemsContainer.querySelectorAll('.array-object-item');
|
const remainingItems = itemsContainer.querySelectorAll('.array-object-item');
|
||||||
remainingItems.forEach((itemEl, newIndex) => {
|
remainingItems.forEach((itemEl, newIndex) => {
|
||||||
itemEl.setAttribute('data-index', newIndex);
|
itemEl.setAttribute('data-index', newIndex);
|
||||||
// Update all inputs within this item - need to update name/id attributes
|
// Update all inputs within this item - only update index in array bracket notation
|
||||||
itemEl.querySelectorAll('input, select, textarea').forEach(input => {
|
itemEl.querySelectorAll('input, select, textarea').forEach(input => {
|
||||||
const name = input.getAttribute('name') || input.id;
|
const name = input.getAttribute('name');
|
||||||
|
const id = input.id;
|
||||||
if (name) {
|
if (name) {
|
||||||
// Update name/id attribute with new index
|
// Only replace index in bracket notation like [0], [1], etc.
|
||||||
const newName = name.replace(/\[\d+\]/, `[${newIndex}]`);
|
// Match pattern: field_name[index] but not field_name123
|
||||||
if (input.getAttribute('name')) input.setAttribute('name', newName);
|
const newName = name.replace(/\[(\d+)\]/, `[${newIndex}]`);
|
||||||
if (input.id) input.id = input.id.replace(/\d+/, newIndex);
|
input.setAttribute('name', newName);
|
||||||
|
}
|
||||||
|
if (id) {
|
||||||
|
// Only update index in specific patterns like _item_0, _item_1
|
||||||
|
// Match pattern: _item_<digits> but be careful not to break other numeric IDs
|
||||||
|
const newId = id.replace(/_item_(\d+)/, `_item_${newIndex}`);
|
||||||
|
input.id = newId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Update button onclick attributes
|
// Update button onclick attributes - only update the index parameter
|
||||||
|
// Since we use data-index for tracking, we can compute index from closest('.array-object-item')
|
||||||
|
// For now, update onclick strings but be more careful with the regex
|
||||||
itemEl.querySelectorAll('button[onclick]').forEach(button => {
|
itemEl.querySelectorAll('button[onclick]').forEach(button => {
|
||||||
const onclick = button.getAttribute('onclick');
|
const onclick = button.getAttribute('onclick');
|
||||||
if (onclick) {
|
if (onclick) {
|
||||||
button.setAttribute('onclick', onclick.replace(/\d+/, newIndex));
|
// Match patterns like:
|
||||||
|
// removeArrayObjectItem('fieldId', 0)
|
||||||
|
// handleArrayObjectFileUpload(event, 'fieldId', 0, 'propKey', 'pluginId')
|
||||||
|
// removeArrayObjectFile('fieldId', 0, 'propKey')
|
||||||
|
// Only replace the numeric index parameter (second or third argument depending on function)
|
||||||
|
let newOnclick = onclick;
|
||||||
|
// For removeArrayObjectItem('fieldId', index) - second param
|
||||||
|
newOnclick = newOnclick.replace(
|
||||||
|
/removeArrayObjectItem\s*\(\s*['"]([^'"]+)['"]\s*,\s*\d+\s*\)/g,
|
||||||
|
`removeArrayObjectItem('$1', ${newIndex})`
|
||||||
|
);
|
||||||
|
// For handleArrayObjectFileUpload(event, 'fieldId', index, ...) - third param
|
||||||
|
newOnclick = newOnclick.replace(
|
||||||
|
/handleArrayObjectFileUpload\s*\(\s*event\s*,\s*['"]([^'"]+)['"]\s*,\s*\d+\s*,/g,
|
||||||
|
`handleArrayObjectFileUpload(event, '$1', ${newIndex},`
|
||||||
|
);
|
||||||
|
// For removeArrayObjectFile('fieldId', index, ...) - second param
|
||||||
|
newOnclick = newOnclick.replace(
|
||||||
|
/removeArrayObjectFile\s*\(\s*['"]([^'"]+)['"]\s*,\s*\d+\s*,/g,
|
||||||
|
`removeArrayObjectFile('$1', ${newIndex},`
|
||||||
|
);
|
||||||
|
button.setAttribute('onclick', newOnclick);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -6514,12 +6540,17 @@ if (typeof window !== 'undefined') {
|
|||||||
|
|
||||||
// Update add button state
|
// Update add button state
|
||||||
const addButton = itemsContainer.nextElementSibling;
|
const addButton = itemsContainer.nextElementSibling;
|
||||||
if (addButton) {
|
if (addButton && addButton.getAttribute('onclick')) {
|
||||||
const maxItems = parseInt(addButton.getAttribute('onclick').match(/\d+/)[0]);
|
// Extract maxItems from onclick attribute more safely
|
||||||
if (remainingItems.length < maxItems) {
|
// Pattern: addArrayObjectItem('fieldId', 'fullKey', maxItems)
|
||||||
addButton.disabled = false;
|
const onclickMatch = addButton.getAttribute('onclick').match(/addArrayObjectItem\s*\([^,]+,\s*[^,]+,\s*(\d+)\)/);
|
||||||
addButton.style.opacity = '1';
|
if (onclickMatch && onclickMatch[1]) {
|
||||||
addButton.style.cursor = 'pointer';
|
const maxItems = parseInt(onclickMatch[1]);
|
||||||
|
if (remainingItems.length < maxItems) {
|
||||||
|
addButton.disabled = false;
|
||||||
|
addButton.style.opacity = '1';
|
||||||
|
addButton.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user