feat(admin-editor): add per-section UIComponent management with inline editing, deletion and re-ordering

This commit is contained in:
Daniel S
2026-08-08 23:50:07 +02:00
parent c5165547b4
commit 4c165d8107

View File

@@ -107,19 +107,32 @@ const { siteConfig, themeConfig } = loadWaasConfigs();
</div> </div>
</div> </div>
<!-- Dynamische UIKomponente Hinzufuegen --> <!-- Dynamische UIKomponente Hinzufuegen per Sektion -->
<div class="glass-panel rounded-xl p-4 space-y-4"> <div class="glass-panel rounded-xl p-4 space-y-4">
<h3 class="text-xs font-bold text-sky-400 uppercase tracking-wider"> <h3 class="text-xs font-bold text-sky-400 uppercase tracking-wider">
UIKomponente Hinzufuegen UI-Komponente Hinzufuegen &amp; Verwaltungsverzeichnis
</h3> </h3>
<div class="space-y-3"> <div class="space-y-3">
<div>
<label class="block text-[10px] font-bold text-slate-400 uppercase mb-1">Ziel-Sektion</label>
<select id="new-el-section" class="w-full glass-panel bg-slate-900 rounded-lg p-2 text-xs text-white focus:outline-none focus:border-sky-400 capitalize">
{siteConfig.sectionsOrder.map(sec => (
<option value={sec}>{sec} Sektion</option>
))}
</select>
</div>
<div> <div>
<label class="block text-[10px] font-bold text-slate-400 uppercase mb-1">Platz (Slot ID)</label> <label class="block text-[10px] font-bold text-slate-400 uppercase mb-1">Platz (Slot ID)</label>
<select id="new-el-slot" class="w-full glass-panel bg-slate-900 rounded-lg p-2 text-xs text-white focus:outline-none focus:border-sky-400"> <select id="new-el-slot" class="w-full glass-panel bg-slate-900 rounded-lg p-2 text-xs text-white focus:outline-none focus:border-sky-400">
<option value="hero-badge-slot">Hero Badge Slot</option> <option value="hero-badge-slot">Hero Badge Slot</option>
<option value="hero-actions-slot">Hero Buttons Slot</option> <option value="hero-actions-slot">Hero Buttons Slot</option>
<option value="hero-custom-slot">Hero Custom Elements Slot</option> <option value="hero-custom-slot">Hero Custom Elements Slot</option>
<option value="bento-header-slot">Bento Header Slot</option>
<option value="bento-grid-slot">Bento Grid Elements Slot</option>
<option value="contact-form-slot">Contact Form Slot</option>
<option value="section-custom-slot">Allgemeiner Sektions-Slot</option>
</select> </select>
</div> </div>
@@ -143,24 +156,47 @@ const { siteConfig, themeConfig } = loadWaasConfigs();
</div> </div>
<button type="button" id="add-el-btn" class="w-full py-2.5 rounded-lg bg-sky-400/20 text-sky-400 border border-sky-400/30 font-bold text-xs uppercase tracking-wider hover:bg-sky-400 hover:text-slate-950 transition-all"> <button type="button" id="add-el-btn" class="w-full py-2.5 rounded-lg bg-sky-400/20 text-sky-400 border border-sky-400/30 font-bold text-xs uppercase tracking-wider hover:bg-sky-400 hover:text-slate-950 transition-all">
+ Komponente auf Platz platzieren + Komponente in Sektion platzieren
</button> </button>
</div> </div>
</div> </div>
<!-- Elements List in Active Config --> <!-- Component Manager per Section (Positionieren, Bearbeiten, Loeschen) -->
<div class="glass-panel rounded-xl p-4 space-y-3"> <div class="glass-panel rounded-xl p-4 space-y-3">
<h3 class="text-xs font-bold text-sky-400 uppercase tracking-wider"> <h3 class="text-xs font-bold text-sky-400 uppercase tracking-wider flex items-center justify-between">
Element-Container (<span id="elements-count">{siteConfig.hero.elements?.length || 0}</span>) <span>Sektions-Komponenten (<span id="elements-count">{(siteConfig.hero.elements || []).length}</span>)</span>
</h3> </h3>
<div id="elements-container" class="space-y-2 max-h-60 overflow-y-auto pr-1">
{((siteConfig.hero.elements || [])).map((el) => ( <!-- Dynamic Filter per Section -->
<div class="p-2.5 rounded-lg bg-slate-900/60 border border-white/5 flex items-center justify-between text-xs"> <div class="flex items-center gap-2 mb-2">
<div> <label class="text-[10px] text-slate-400 uppercase font-bold">Filter:</label>
<span class="font-bold text-sky-400 uppercase text-[10px] block">{el.type} (Platz: {el.slotId})</span> <select id="filter-section-select" class="flex-1 glass-panel bg-slate-900 rounded p-1 text-xs text-white capitalize">
<span class="text-slate-300">{el.content}</span> <option value="all">Alle Sektionen</option>
{siteConfig.sectionsOrder.map(sec => (
<option value={sec}>{sec}</option>
))}
</select>
</div>
<div id="elements-container" class="space-y-2 max-h-80 overflow-y-auto pr-1 custom-scroll">
{((siteConfig.hero.elements || [])).map((el, idx) => (
<div data-id={el.id} class="p-3 rounded-lg bg-slate-900/70 border border-white/10 space-y-2 text-xs group">
<div class="flex items-center justify-between">
<div>
<span class="font-bold text-sky-400 uppercase text-[10px] block">{el.type} (Slot: {el.slotId})</span>
<span id={`el-label-${el.id}`} class="text-slate-200 font-semibold">{el.content}</span>
</div>
<div class="flex items-center gap-1">
<button type="button" onclick={`moveElement('${el.id}', -1)`} class="p-1 hover:text-sky-400 text-xs font-bold" title="Nach oben verschieben">Up</button>
<button type="button" onclick={`moveElement('${el.id}', 1)`} class="p-1 hover:text-sky-400 text-xs font-bold" title="Nach unten verschieben">Down</button>
<button type="button" onclick={`deleteElement('${el.id}')`} class="p-1 text-red-400/80 hover:text-red-400 text-xs font-bold ml-1" title="Komponente loeschen">Trash</button>
</div>
</div>
<!-- Inline Edit Form -->
<div class="pt-2 border-t border-white/5 flex gap-2">
<input type="text" id={`edit-input-${el.id}`} value={el.content} class="flex-1 glass-panel bg-slate-950 rounded px-2 py-1 text-xs text-white border border-white/10 focus:border-sky-400" />
<button type="button" onclick={`updateElementContent('${el.id}')`} class="px-2 py-1 rounded bg-sky-400/20 text-sky-400 border border-sky-400/30 text-[10px] font-bold hover:bg-sky-400 hover:text-slate-950 transition-all">Save</button>
</div> </div>
<span class="text-[9px] font-mono text-slate-500 bg-white/5 px-2 py-0.5 rounded">ID: {el.id}</span>
</div> </div>
))} ))}
</div> </div>
@@ -236,6 +272,8 @@ const { siteConfig, themeConfig } = loadWaasConfigs();
let siteConfigState = JSON.parse(siteConfigJSON); let siteConfigState = JSON.parse(siteConfigJSON);
let themeConfigState = JSON.parse(themeConfigJSON); let themeConfigState = JSON.parse(themeConfigJSON);
if (!siteConfigState.hero.elements) siteConfigState.hero.elements = [];
function reloadIframe() { function reloadIframe() {
const iframe = document.getElementById('preview-iframe'); const iframe = document.getElementById('preview-iframe');
if (iframe) iframe.src = iframe.src; if (iframe) iframe.src = iframe.src;
@@ -290,6 +328,7 @@ const { siteConfig, themeConfig } = loadWaasConfigs();
if (!siteConfigState.sectionsOrder.includes(newSec)) { if (!siteConfigState.sectionsOrder.includes(newSec)) {
siteConfigState.sectionsOrder.push(newSec); siteConfigState.sectionsOrder.push(newSec);
renderSectionsList(); renderSectionsList();
updateSectionSelectors();
} else { } else {
alert('Sektion ist bereits im Layout enthalten!'); alert('Sektion ist bereits im Layout enthalten!');
} }
@@ -299,6 +338,7 @@ const { siteConfig, themeConfig } = loadWaasConfigs();
window.deleteSection = function(secName) { window.deleteSection = function(secName) {
siteConfigState.sectionsOrder = siteConfigState.sectionsOrder.filter(s => s !== secName); siteConfigState.sectionsOrder = siteConfigState.sectionsOrder.filter(s => s !== secName);
renderSectionsList(); renderSectionsList();
updateSectionSelectors();
}; };
window.moveSection = function(secName, dir) { window.moveSection = function(secName, dir) {
@@ -333,43 +373,109 @@ const { siteConfig, themeConfig } = loadWaasConfigs();
)).join(''); )).join('');
} }
function updateSectionSelectors() {
const newElSec = document.getElementById('new-el-section');
const filterSec = document.getElementById('filter-section-select');
if (newElSec) {
newElSec.innerHTML = siteConfigState.sectionsOrder.map(s => '<option value="' + s + '">' + s + ' Sektion</option>').join('');
}
if (filterSec) {
const current = filterSec.value;
filterSec.innerHTML = '<option value="all">Alle Sektionen</option>' + siteConfigState.sectionsOrder.map(s => '<option value="' + s + '">' + s + '</option>').join('');
filterSec.value = current;
}
}
// --- UIComponent Management: Add, Edit, Delete, Position ---
const addBtn = document.getElementById('add-el-btn'); const addBtn = document.getElementById('add-el-btn');
if (addBtn) { if (addBtn) {
addBtn.addEventListener('click', () => { addBtn.addEventListener('click', () => {
const section = document.getElementById('new-el-section').value;
const slotId = document.getElementById('new-el-slot').value; const slotId = document.getElementById('new-el-slot').value;
const type = document.getElementById('new-el-type').value; const type = document.getElementById('new-el-type').value;
const content = document.getElementById('new-el-content').value || 'Neues Element'; const content = document.getElementById('new-el-content').value || 'Neues Element';
if (!siteConfigState.hero.elements) { const newComp = {
siteConfigState.hero.elements = [];
}
siteConfigState.hero.elements.push({
id: 'el-' + Date.now(), id: 'el-' + Date.now(),
slotId, slotId: section + '-' + slotId,
type, type,
content, content,
style: 'primary' style: 'primary'
}); };
siteConfigState.hero.elements.push(newComp);
document.getElementById('new-el-content').value = ''; document.getElementById('new-el-content').value = '';
renderElementsContainer(); renderElementsContainer();
}); });
} }
window.deleteElement = function(elId) {
siteConfigState.hero.elements = siteConfigState.hero.elements.filter(e => e.id !== elId);
renderElementsContainer();
};
window.moveElement = function(elId, dir) {
const arr = siteConfigState.hero.elements;
const idx = arr.findIndex(e => e.id === elId);
if (idx === -1) return;
const targetIdx = idx + dir;
if (targetIdx < 0 || targetIdx >= arr.length) return;
const temp = arr[idx];
arr[idx] = arr[targetIdx];
arr[targetIdx] = temp;
renderElementsContainer();
};
window.updateElementContent = function(elId) {
const input = document.getElementById('edit-input-' + elId);
if (!input) return;
const el = siteConfigState.hero.elements.find(e => e.id === elId);
if (el) {
el.content = input.value;
const label = document.getElementById('el-label-' + elId);
if (label) label.textContent = input.value;
}
};
const filterSecSelect = document.getElementById('filter-section-select');
if (filterSecSelect) {
filterSecSelect.addEventListener('change', renderElementsContainer);
}
function renderElementsContainer() { function renderElementsContainer() {
const container = document.getElementById('elements-container'); const container = document.getElementById('elements-container');
const countEl = document.getElementById('elements-count'); const countEl = document.getElementById('elements-count');
if (!container) return; if (!container) return;
const els = siteConfigState.hero.elements || [];
const filterSec = filterSecSelect ? filterSecSelect.value : 'all';
let els = siteConfigState.hero.elements || [];
if (filterSec !== 'all') {
els = els.filter(e => e.slotId && e.slotId.startsWith(filterSec));
}
if (countEl) countEl.textContent = els.length; if (countEl) countEl.textContent = els.length;
container.innerHTML = els.map(el => (
'<div class="p-2.5 rounded-lg bg-slate-900/60 border border-white/5 flex items-center justify-between text-xs">' + container.innerHTML = els.map((el, idx) => (
'<div data-id="' + el.id + '" class="p-3 rounded-lg bg-slate-900/70 border border-white/10 space-y-2 text-xs group">' +
'<div class="flex items-center justify-between">' +
'<div>' + '<div>' +
'<span class="font-bold text-sky-400 uppercase text-[10px] block">' + el.type + ' (Platz: ' + el.slotId + ')</span>' + '<span class="font-bold text-sky-400 uppercase text-[10px] block">' + el.type + ' (Slot: ' + el.slotId + ')</span>' +
'<span class="text-slate-300">' + el.content + '</span>' + '<span id="el-label-' + el.id + '" class="text-slate-200 font-semibold">' + el.content + '</span>' +
'</div>' +
'<div class="flex items-center gap-1">' +
'<button type="button" onclick="moveElement(\'' + el.id + '\', -1)" class="p-1 hover:text-sky-400 text-xs font-bold" title="Nach oben">Up</button>' +
'<button type="button" onclick="moveElement(\'' + el.id + '\', 1)" class="p-1 hover:text-sky-400 text-xs font-bold" title="Nach unten">Down</button>' +
'<button type="button" onclick="deleteElement(\'' + el.id + '\')" class="p-1 text-red-400/80 hover:text-red-400 text-xs font-bold ml-1" title="Loeschen">Trash</button>' +
'</div>' +
'</div>' +
'<div class="pt-2 border-t border-white/5 flex gap-2">' +
'<input type="text" id="edit-input-' + el.id + '" value="' + el.content.replace(/"/g, '&quot;') + '" class="flex-1 glass-panel bg-slate-950 rounded px-2 py-1 text-xs text-white border border-white/10 focus:border-sky-400" />' +
'<button type="button" onclick="updateElementContent(\'' + el.id + '\')" class="px-2 py-1 rounded bg-sky-400/20 text-sky-400 border border-sky-400/30 text-[10px] font-bold hover:bg-sky-400 hover:text-slate-950 transition-all">Save</button>' +
'</div>' + '</div>' +
'<span class="text-[9px] font-mono text-slate-500 bg-white/5 px-2 py-0.5 rounded">' + 'ID: ' + el.id + '</span>' +
'</div>' '</div>'
)).join(''); )).join('');
} }