mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
Add simple table interface for custom feeds
- Replace complex array-of-objects widget with clean table - Table columns: Name, URL, Logo (upload), Enabled checkbox, Delete - Use dot notation for form field names (feeds.custom_feeds.0.name) - Add JavaScript functions for add/remove rows and logo upload - Fix file-upload detection order to prevent breaking static-image plugin
This commit is contained in:
@@ -4839,7 +4839,7 @@
|
||||
newRow.innerHTML = `
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<input type="text"
|
||||
name="${fullKey}[${newIndex}].name"
|
||||
name="${fullKey}.${newIndex}.name"
|
||||
value=""
|
||||
class="block w-full px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
placeholder="Feed Name"
|
||||
@@ -4847,7 +4847,7 @@
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<input type="url"
|
||||
name="${fullKey}[${newIndex}].url"
|
||||
name="${fullKey}.${newIndex}.url"
|
||||
value=""
|
||||
class="block w-full px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
placeholder="https://example.com/feed"
|
||||
@@ -4859,7 +4859,7 @@
|
||||
id="${fieldId}_logo_${newIndex}"
|
||||
accept="image/png,image/jpeg,image/bmp"
|
||||
style="display: none;"
|
||||
onchange="handleCustomFeedLogoUpload(event, '${fieldId}', ${newIndex}, 'ledmatrix-news')">
|
||||
onchange="handleCustomFeedLogoUpload(event, '${fieldId}', ${newIndex}, 'ledmatrix-news', '${fullKey}')">
|
||||
<button type="button"
|
||||
onclick="document.getElementById('${fieldId}_logo_${newIndex}').click()"
|
||||
class="px-2 py-1 text-xs bg-gray-200 hover:bg-gray-300 rounded">
|
||||
@@ -4870,8 +4870,9 @@
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-center">
|
||||
<input type="checkbox"
|
||||
name="${fullKey}[${newIndex}].enabled"
|
||||
name="${fullKey}.${newIndex}.enabled"
|
||||
checked
|
||||
value="true"
|
||||
class="h-4 w-4 text-blue-600">
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-center">
|
||||
@@ -4898,14 +4899,20 @@
|
||||
r.querySelectorAll('input, button').forEach(input => {
|
||||
const name = input.getAttribute('name');
|
||||
if (name) {
|
||||
input.setAttribute('name', name.replace(/\[\d+\]/, `[${index}]`));
|
||||
// Replace pattern like "feeds.custom_feeds.0.name" with "feeds.custom_feeds.1.name"
|
||||
input.setAttribute('name', name.replace(/\.\d+\./, `.${index}.`));
|
||||
}
|
||||
const id = input.id;
|
||||
if (id && id.includes('_logo_')) {
|
||||
const newId = id.replace(/_logo_\d+/, `_logo_${index}`);
|
||||
input.id = newId;
|
||||
if (input.onclick) {
|
||||
input.setAttribute('onclick', input.getAttribute('onclick').replace(/\d+/, index));
|
||||
const onclick = input.getAttribute('onclick');
|
||||
if (onclick) {
|
||||
input.setAttribute('onclick', onclick.replace(/,\s*\d+\s*,/, `, ${index},`));
|
||||
}
|
||||
const onchange = input.getAttribute('onchange');
|
||||
if (onchange) {
|
||||
input.setAttribute('onchange', onchange.replace(/,\s*\d+\s*,/, `, ${index},`));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -4913,7 +4920,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleCustomFeedLogoUpload(event, fieldId, index, pluginId) {
|
||||
function handleCustomFeedLogoUpload(event, fieldId, index, pluginId, fullKey) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
@@ -4932,21 +4939,26 @@
|
||||
const row = document.querySelector(`#${fieldId}_tbody tr[data-index="${index}"]`);
|
||||
if (row) {
|
||||
const logoCell = row.querySelector('td:nth-child(3)');
|
||||
const existingPathInput = logoCell.querySelector('input[name*=".logo.path"]');
|
||||
const existingIdInput = logoCell.querySelector('input[name*=".logo.id"]');
|
||||
const pathName = existingPathInput ? existingPathInput.name : `${fullKey}.${index}.logo.path`;
|
||||
const idName = existingIdInput ? existingIdInput.name : `${fullKey}.${index}.logo.id`;
|
||||
|
||||
logoCell.innerHTML = `
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="file"
|
||||
id="${fieldId}_logo_${index}"
|
||||
accept="image/png,image/jpeg,image/bmp"
|
||||
style="display: none;"
|
||||
onchange="handleCustomFeedLogoUpload(event, '${fieldId}', ${index}, '${pluginId}')">
|
||||
onchange="handleCustomFeedLogoUpload(event, '${fieldId}', ${index}, '${pluginId}', '${fullKey}')">
|
||||
<button type="button"
|
||||
onclick="document.getElementById('${fieldId}_logo_${index}').click()"
|
||||
class="px-2 py-1 text-xs bg-gray-200 hover:bg-gray-300 rounded">
|
||||
<i class="fas fa-upload mr-1"></i> Upload
|
||||
</button>
|
||||
<img src="/${uploadedFile.path}" alt="Logo" class="w-8 h-8 object-cover rounded border" id="${fieldId}_logo_preview_${index}">
|
||||
<input type="hidden" name="${logoCell.querySelector('input[name*=".logo.path"]')?.name || `feeds.custom_feeds[${index}].logo.path`}" value="${uploadedFile.path}">
|
||||
<input type="hidden" name="${logoCell.querySelector('input[name*=".logo.id"]')?.name || `feeds.custom_feeds[${index}].logo.id`}" value="${uploadedFile.id}">
|
||||
<input type="hidden" name="${pathName}" value="${uploadedFile.path}">
|
||||
<input type="hidden" name="${idName}" value="${uploadedFile.id}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -153,15 +153,105 @@
|
||||
<input type="hidden" id="{{ field_id }}_images_data" name="{{ full_key }}" value='{{ array_value|tojson|safe }}'>
|
||||
</div>
|
||||
{% else %}
|
||||
{# Regular array input (comma-separated) #}
|
||||
{% set array_value = value if value is not none else (prop.default if prop.default is defined else []) %}
|
||||
<input type="text"
|
||||
id="{{ field_id }}"
|
||||
name="{{ full_key }}"
|
||||
value="{{ array_value|join(', ') if array_value is iterable and array_value is not string else '' }}"
|
||||
placeholder="Enter values separated by commas"
|
||||
class="form-input w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 bg-white text-black placeholder:text-gray-500">
|
||||
<p class="text-xs text-gray-400 mt-1">Separate multiple values with commas</p>
|
||||
{# Check if it's an array of objects (like custom_feeds) - use simple table interface #}
|
||||
{% set items_schema = prop.get('items') or {} %}
|
||||
{% set is_array_of_objects = items_schema.get('type') == 'object' and items_schema.get('properties') %}
|
||||
{% if is_array_of_objects %}
|
||||
{# Simple table-based interface for custom feeds #}
|
||||
{% set item_properties = items_schema.get('properties', {}) %}
|
||||
{% set max_items = prop.get('maxItems', 50) %}
|
||||
{% set array_value = value if value is not none and value is iterable and value is not string else (prop.default if prop.default is defined and prop.default is iterable and prop.default is not string else []) %}
|
||||
|
||||
<div class="custom-feeds-table-container mt-1">
|
||||
<table class="min-w-full divide-y divide-gray-200 border border-gray-300 rounded-lg">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">URL</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Logo</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Enabled</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="{{ field_id }}_tbody" class="bg-white divide-y divide-gray-200">
|
||||
{% for item in array_value %}
|
||||
{% set item_index = loop.index0 %}
|
||||
<tr class="custom-feed-row" data-index="{{ item_index }}">
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<input type="text"
|
||||
name="{{ full_key }}.{{ item_index }}.name"
|
||||
value="{{ item.get('name', '') }}"
|
||||
class="block w-full px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
placeholder="Feed Name"
|
||||
required>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<input type="url"
|
||||
name="{{ full_key }}.{{ item_index }}.url"
|
||||
value="{{ item.get('url', '') }}"
|
||||
class="block w-full px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
placeholder="https://example.com/feed"
|
||||
required>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
{% set logo_value = item.get('logo') or {} %}
|
||||
{% set logo_path = logo_value.get('path', '') %}
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="file"
|
||||
id="{{ field_id }}_logo_{{ item_index }}"
|
||||
accept="image/png,image/jpeg,image/bmp"
|
||||
style="display: none;"
|
||||
onchange="handleCustomFeedLogoUpload(event, '{{ field_id }}', {{ item_index }}, '{{ plugin_id }}', '{{ full_key }}')">
|
||||
<button type="button"
|
||||
onclick="document.getElementById('{{ field_id }}_logo_{{ item_index }}').click()"
|
||||
class="px-2 py-1 text-xs bg-gray-200 hover:bg-gray-300 rounded">
|
||||
<i class="fas fa-upload mr-1"></i> Upload
|
||||
</button>
|
||||
{% if logo_path %}
|
||||
<img src="/{{ logo_path }}" alt="Logo" class="w-8 h-8 object-cover rounded border" id="{{ field_id }}_logo_preview_{{ item_index }}">
|
||||
<input type="hidden" name="{{ full_key }}.{{ item_index }}.logo.path" value="{{ logo_path }}">
|
||||
<input type="hidden" name="{{ full_key }}.{{ item_index }}.logo.id" value="{{ logo_value.get('id', '') }}">
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-400">No logo</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-center">
|
||||
<input type="checkbox"
|
||||
name="{{ full_key }}.{{ item_index }}.enabled"
|
||||
{% if item.get('enabled', true) %}checked{% endif %}
|
||||
value="true"
|
||||
class="h-4 w-4 text-blue-600">
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-center">
|
||||
<button type="button"
|
||||
onclick="removeCustomFeedRow(this)"
|
||||
class="text-red-600 hover:text-red-800 px-2 py-1">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button"
|
||||
onclick="addCustomFeedRow('{{ field_id }}', '{{ full_key }}', {{ max_items }})"
|
||||
class="mt-3 px-4 py-2 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md"
|
||||
{% if array_value|length >= max_items %}disabled style="opacity: 0.5;"{% endif %}>
|
||||
<i class="fas fa-plus mr-1"></i> Add Feed
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
{# Regular array input (comma-separated) #}
|
||||
{% set array_value = value if value is not none else (prop.default if prop.default is defined else []) %}
|
||||
<input type="text"
|
||||
id="{{ field_id }}"
|
||||
name="{{ full_key }}"
|
||||
value="{{ array_value|join(', ') if array_value is iterable and array_value is not string else '' }}"
|
||||
placeholder="Enter values separated by commas"
|
||||
class="form-input w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 bg-white text-black placeholder:text-gray-500">
|
||||
<p class="text-xs text-gray-400 mt-1">Separate multiple values with commas</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{# Text input (default) #}
|
||||
|
||||
Reference in New Issue
Block a user