// Globale state let config = null; let stellingkastItems = []; let dragSrcEl = null; let allSets = null; let currentSetId = null; // DOM elementen const timerInput = document.getElementById('timer'); const fontSizeInput = document.getElementById('fontSize'); const buttonTextInput = document.getElementById('buttonText'); const finishTextInput = document.getElementById('finishText'); const colorLeftInput = document.getElementById('colorLeft'); const colorLeftTextInput = document.getElementById('colorLeftText'); const colorRightInput = document.getElementById('colorRight'); const colorRightTextInput = document.getElementById('colorRightText'); const statementsList = document.getElementById('statementsList'); const addStatementBtn = document.getElementById('addStatement'); const saveConfigBtn = document.getElementById('saveConfig'); const statusMessage = document.getElementById('statusMessage'); const statementCount = document.getElementById('statementCount'); // Stellingkast elementen const openStellingkastBtn = document.getElementById('openStellingkast'); const closeStellingkastBtn = document.getElementById('closeStellingkast'); const stellingkastPanel = document.getElementById('stellingkastPanel'); const stellingkastList = document.getElementById('stellingkastList'); const searchStellingkastInput = document.getElementById('searchStellingkast'); // Sets elementen const setSelector = document.getElementById('setSelector'); const saveAsNewSetBtn = document.getElementById('saveAsNewSet'); const deleteSetBtn = document.getElementById('deleteSet'); // Initialize syncColorInputs(); // Start alle laad-acties onafhankelijk van elkaar loadSets(); loadStellingkast(); // --- SETS MANAGEMENT --- // Laad alle sets bij start async function loadSets() { try { console.log('Fetching sets...'); const response = await fetch('/api/sets?t=' + Date.now()); if (!response.ok) throw new Error('Kon sets niet laden (' + response.status + ')'); allSets = await response.json(); console.log('Sets loaded:', allSets); // Ensure structure if (!allSets.sets) allSets.sets = []; populateSetSelector(); // Laad actieve set of eerste set if (allSets.activeSetId) { currentSetId = allSets.activeSetId; } else if (allSets.sets.length > 0) { currentSetId = allSets.sets[0].id; } if (currentSetId) { loadSetById(currentSetId); } showStatus('Sets geladen!', 'success'); } catch (error) { console.error('Fout bij laden sets:', error); showStatus('Fout bij laden sets: ' + error.message, 'error'); // Fallback naar oude config.json manier loadConfig(); } } // Vul set selector dropdown function populateSetSelector() { if (!setSelector || !allSets) return; setSelector.innerHTML = ''; allSets.sets.forEach(set => { const option = document.createElement('option'); option.value = set.id; option.textContent = set.name; if (set.id === currentSetId) { option.selected = true; } setSelector.appendChild(option); }); } // Laad een specifieke set in de editor function loadSetById(setId) { const set = allSets.sets.find(s => s.id === setId); if (!set || !set.config) { console.error('Set niet gevonden:', setId); return; } config = set.config; currentSetId = setId; // Ensure minimal config structure if (!config.stellingen) config.stellingen = []; if (!config.colors) config.colors = { left: '#3B82F6', right: '#EF4444' }; populateForm(); showStatus(`Set geladen: ${set.name}`, 'success'); } // Event handler voor set selectie function onSetSelected(event) { const setId = event.target.value; if (!setId) { // "Nieuwe set" optie gekozen currentSetId = null; config = { timer: 30, fontSize: '3rem', buttonText: 'Volgende Stelling', finishText: 'Einde!', colors: { left: '#3B82F6', right: '#EF4444' }, stellingen: [] }; populateForm(); showStatus('Nieuwe set - vul gegevens in en gebruik "Opslaan als Nieuwe Set"', 'info'); } else { loadSetById(setId); } } // Sla huidige config op naar actieve set async function saveCurrentSet() { if (!currentSetId) { showStatus('Geen actieve set - gebruik "Opslaan als Nieuwe Set"', 'error'); return false; } try { // Update set in allSets const setIndex = allSets.sets.findIndex(s => s.id === currentSetId); if (setIndex === -1) { showStatus('Set niet gevonden', 'error'); return false; } // Update config van deze set allSets.sets[setIndex].config = { ...config }; allSets.activeSetId = currentSetId; // Sla op naar server const response = await fetch('/api/sets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(allSets) }); const result = await response.json(); if (result.success) { showStatus('✅ Set opgeslagen!', 'success'); return true; } else { showStatus(`❌ Fout: ${result.error}`, 'error'); return false; } } catch (error) { console.error('Fout bij opslaan set:', error); showStatus('❌ Fout bij opslaan set', 'error'); return false; } } // Sla op als nieuwe set (met naam prompt) async function saveAsNewSet() { const setName = prompt('Geef een naam voor deze set:', 'Nieuwe Workshop Set'); if (!setName || setName.trim() === '') { showStatus('Opslaan geannuleerd', 'info'); return; } try { // Genereer unieke ID const newId = 'set-' + Date.now(); // Maak nieuwe set const newSet = { id: newId, name: setName.trim(), config: { ...config } }; // Voeg toe aan sets allSets.sets.push(newSet); allSets.activeSetId = newId; currentSetId = newId; // Sla op naar server const response = await fetch('/api/sets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(allSets) }); const result = await response.json(); if (result.success) { populateSetSelector(); setSelector.value = newId; showStatus(`✅ Nieuwe set "${setName}" aangemaakt!`, 'success'); } else { showStatus(`❌ Fout: ${result.error}`, 'error'); } } catch (error) { console.error('Fout bij aanmaken set:', error); showStatus('❌ Fout bij aanmaken set', 'error'); } } // Toon status bericht function showStatus(message, type = 'info') { if (!statusMessage) return; statusMessage.textContent = message; statusMessage.className = 'status-message ' + type; statusMessage.style.display = 'block'; // Auto-hide na 5 seconden setTimeout(() => { statusMessage.style.display = 'none'; }, 5000); } // Fallback: Laad config op oude manier (voor backwards compatibility) async function loadConfig() { try { console.log('Fetching config...'); const response = await fetch('/api/config?t=' + Date.now()); if (!response.ok) throw new Error('Kon config niet laden (' + response.status + ')'); config = await response.json(); console.log('Config loaded:', config); // Ensure minimal config structure if (!config.stellingen) config.stellingen = []; if (!config.colors) config.colors = { left: '#3B82F6', right: '#EF4444' }; populateForm(); showStatus('Config geladen!', 'success'); } catch (error) { console.error('Fout bij laden config:', error); showStatus('Fout bij laden config: ' + error.message, 'error'); } } // Vul formulier met config data function populateForm() { if (!config) return; // Basis instellingen (met fallback values) timerInput.value = config.timer || 30; fontSizeInput.value = config.fontSize || '3rem'; buttonTextInput.value = config.buttonText || 'Volgende Stelling'; finishTextInput.value = config.finishText || 'Einde!'; // Safety check voor colors object const leftColor = (config.colors && config.colors.left) ? config.colors.left : '#3B82F6'; const rightColor = (config.colors && config.colors.right) ? config.colors.right : '#EF4444'; colorLeftInput.value = leftColor; colorLeftTextInput.value = leftColor; colorRightInput.value = rightColor; colorRightTextInput.value = rightColor; // Render stellingen renderStatements(); } // Render alle stellingen function renderStatements() { statementsList.innerHTML = ''; if (config.stellingen) { config.stellingen.forEach((stelling, index) => { addStatementRow(stelling, index); }); } updateCount(); } // Voeg een stelling rij toe aan de UI function addStatementRow(stelling = { links: '', rechts: '' }, index) { const row = document.createElement('div'); row.className = 'statement-row'; row.dataset.index = index; row.setAttribute('draggable', 'true'); row.innerHTML = `
`; statementsList.appendChild(row); // Event listeners voor inputs const leftInput = row.querySelector('.statement-left'); const rightInput = row.querySelector('.statement-right'); const removeBtn = row.querySelector('.btn-remove'); leftInput.addEventListener('input', (e) => { config.stellingen[index].links = e.target.value; updateCount(); }); rightInput.addEventListener('input', (e) => { config.stellingen[index].rechts = e.target.value; updateCount(); }); removeBtn.addEventListener('click', () => { if (confirm('Weet je zeker dat je deze stelling wilt verwijderen?')) { removeStatement(index); } }); // Drag and Drop listeners addDragListeners(row); } // Drag and Drop functionaliteit function addDragListeners(row) { row.addEventListener('dragstart', handleDragStart); row.addEventListener('dragover', handleDragOver); row.addEventListener('drop', handleDrop); row.addEventListener('dragend', handleDragEnd); } function handleDragStart(e) { dragSrcEl = this; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', this.dataset.index); this.classList.add('dragging'); } function handleDragOver(e) { if (e.preventDefault) { e.preventDefault(); } e.dataTransfer.dropEffect = 'move'; return false; } function handleDrop(e) { if (e.stopPropagation) { e.stopPropagation(); } const sourceIndex = parseInt(e.dataTransfer.getData('text/plain')); const targetIndex = parseInt(this.dataset.index); if (dragSrcEl !== this && !isNaN(sourceIndex) && !isNaN(targetIndex)) { // Swap in array const itemToMove = config.stellingen[sourceIndex]; config.stellingen.splice(sourceIndex, 1); config.stellingen.splice(targetIndex, 0, itemToMove); // Re-render renderStatements(); showStatus('Volgorde aangepast', 'success'); } return false; } function handleDragEnd(e) { this.classList.remove('dragging'); const rows = document.querySelectorAll('.statement-row'); rows.forEach(row => row.classList.remove('dragging')); } // Verwijder stelling function removeStatement(index) { config.stellingen.splice(index, 1); renderStatements(); showStatus('Stelling verwijderd', 'success'); } // Voeg nieuwe lege stelling toe function addNewStatement() { config.stellingen.push({ links: '', rechts: '' }); renderStatements(); // Focus op eerste input van nieuwe rij const newRow = statementsList.lastElementChild; const firstInput = newRow.querySelector('.statement-left'); firstInput.focus(); // Scroll naar beneden newRow.scrollIntoView({ behavior: 'smooth' }); showStatus('Nieuwe stelling toegevoegd', 'success'); } // Update stelling count function updateCount() { if (!config || !config.stellingen) return; const count = config.stellingen.filter( s => s.links.trim() || s.rechts.trim() ).length; statementCount.textContent = `${count} stelling${count !== 1 ? 'en' : ''}`; } // Sync kleur inputs (color picker <-> text) function syncColorInputs() { colorLeftInput.addEventListener('input', (e) => { colorLeftTextInput.value = e.target.value; }); colorLeftTextInput.addEventListener('input', (e) => { if (e.target.value.match(/^#[0-9A-Fa-f]{6}$/)) { colorLeftInput.value = e.target.value; } }); colorRightInput.addEventListener('input', (e) => { colorRightTextInput.value = e.target.value; }); colorRightTextInput.addEventListener('input', (e) => { if (e.target.value.match(/^#[0-9A-Fa-f]{6}$/)) { colorRightInput.value = e.target.value; } }); } // Sla config op naar server async function saveConfig() { try { // Update config met form values config.timer = parseInt(timerInput.value); config.fontSize = fontSizeInput.value; config.buttonText = buttonTextInput.value; config.finishText = finishTextInput.value; config.colors.left = colorLeftInput.value; config.colors.right = colorRightInput.value; // Filter lege stellingen eruit config.stellingen = config.stellingen.filter( s => s.links.trim() || s.rechts.trim() ); // Validatie if (config.stellingen.length === 0) { showStatus('⚠️ Je moet minimaal 1 stelling toevoegen', 'error'); return; } // Als we sets gebruiken, sla via sets op if (allSets && currentSetId) { const saved = await saveCurrentSet(); if (saved) { setTimeout(() => loadSetById(currentSetId), 500); } return; } // Fallback naar oude methode (legacy) const response = await fetch('/api/save-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const result = await response.json(); if (result.success) { showStatus('✅ Config succesvol opgeslagen!', 'success'); setTimeout(() => loadConfig(), 500); } else { showStatus(`❌ Fout: ${result.error}`, 'error'); } } catch (error) { console.error('Fout bij opslaan:', error); showStatus('❌ Fout bij opslaan config', 'error'); } } // --- Stellingkast Functionaliteit --- async function loadStellingkast() { const listContainer = document.getElementById('stellingkastList'); if (!listContainer) return; // Safety check listContainer.innerHTML = '
Stellingen laden...
'; try { console.log('Fetching stellingkast...'); const response = await fetch('/stellingkast.json?t=' + Date.now()); if (response.ok) { stellingkastItems = await response.json(); console.log('Stellingkast loaded:', stellingkastItems.length, 'items'); renderStellingkastList(stellingkastItems); } else { console.error('Kon stellingkast.json niet laden', response.status); listContainer.innerHTML = '
Kon stellingkast niet laden (' + response.status + ').
'; } } catch (error) { console.error('Fout bij laden stellingkast:', error); listContainer.innerHTML = '
Fout: ' + error.message + '
'; } } function renderStellingkastList(items) { if (!stellingkastList) return; stellingkastList.innerHTML = ''; if (!items || items.length === 0) { stellingkastList.innerHTML = '
Geen stellingen gevonden.
'; return; } items.forEach((item, index) => { const el = document.createElement('div'); el.className = 'stellingkast-item'; el.innerHTML = `
Links ${item.links}
Rechts ${item.rechts}
`; // Add event listener const btn = el.querySelector('.btn-import'); if (btn) { btn.addEventListener('click', () => { importStelling(item); }); } stellingkastList.appendChild(el); }); } function importStelling(item) { if (!config) config = { stellingen: [] }; if (!config.stellingen) config.stellingen = []; // Voeg toe aan config config.stellingen.push({ ...item }); // Kopie om referentie issues te voorkomen // Render opnieuw renderStatements(); // Scroll naar beneden const newRow = statementsList.lastElementChild; if (newRow) { newRow.scrollIntoView({ behavior: 'smooth' }); } // Feedback showStatus('Stelling geïmporteerd!', 'success'); } // Event listeners if (addStatementBtn) addStatementBtn.addEventListener('click', addNewStatement); if (saveConfigBtn) saveConfigBtn.addEventListener('click', saveConfig); // Sets events if (setSelector) setSelector.addEventListener('change', onSetSelected); if (saveAsNewSetBtn) saveAsNewSetBtn.addEventListener('click', saveAsNewSet); // Stellingkast events if (openStellingkastBtn) { openStellingkastBtn.addEventListener('click', () => { stellingkastPanel.classList.add('open'); }); } if (closeStellingkastBtn) { closeStellingkastBtn.addEventListener('click', () => { stellingkastPanel.classList.remove('open'); }); } // Zoek functionaliteit if (searchStellingkastInput) { searchStellingkastInput.addEventListener('input', (e) => { const term = e.target.value.toLowerCase(); if (stellingkastItems) { const filtered = stellingkastItems.filter(item => (item.links && item.links.toLowerCase().includes(term)) || (item.rechts && item.rechts.toLowerCase().includes(term)) ); renderStellingkastList(filtered); } }); } // Klik buiten panel om te sluiten document.addEventListener('click', (e) => { if (stellingkastPanel && stellingkastPanel.classList.contains('open') && !stellingkastPanel.contains(e.target) && e.target !== openStellingkastBtn) { stellingkastPanel.classList.remove('open'); } }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl/Cmd + S = opslaan if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveConfig(); } // Escape sluit panel if (e.key === 'Escape' && stellingkastPanel && stellingkastPanel.classList.contains('open')) { stellingkastPanel.classList.remove('open'); } });