ijsbreker/editor.js

438 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Globale state
let config = null;
let stellingkastItems = [];
let dragSrcEl = 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');
// Initialize
syncColorInputs();
// Start beide laad-acties onafhankelijk van elkaar
loadConfig();
loadStellingkast();
// Laad config bij start
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 = `
<div class="drag-handle" title="Sleep om te verplaatsen">☰</div>
<div class="statement-input">
<label>Stelling Links</label>
<input
type="text"
class="statement-left"
value="${stelling.links}"
placeholder="Bijv. Koffie"
data-index="${index}"
>
</div>
<div class="statement-input">
<label>Stelling Rechts</label>
<input
type="text"
class="statement-right"
value="${stelling.rechts}"
placeholder="Bijv. Thee"
data-index="${index}"
>
</div>
<button class="btn btn-remove" data-index="${index}"></button>
`;
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;
}
// POST naar server
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');
// Reload om lege stellingen te verwijderen
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 = '<div style="padding:1rem; text-align:center; color:#666;">Stellingen laden...</div>';
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 = '<div style="padding:1rem; text-align:center; color:red;">Kon stellingkast niet laden (' + response.status + ').</div>';
}
} catch (error) {
console.error('Fout bij laden stellingkast:', error);
listContainer.innerHTML = '<div style="padding:1rem; text-align:center; color:red;">Fout: ' + error.message + '</div>';
}
}
function renderStellingkastList(items) {
if (!stellingkastList) return;
stellingkastList.innerHTML = '';
if (!items || items.length === 0) {
stellingkastList.innerHTML = '<div style="padding:1rem; text-align:center; color:#666;">Geen stellingen gevonden.</div>';
return;
}
items.forEach((item, index) => {
const el = document.createElement('div');
el.className = 'stellingkast-item';
el.innerHTML = `
<div class="stelling-text">
<div>
<span class="stelling-label">Links</span>
${item.links}
</div>
<div style="text-align: right;">
<span class="stelling-label">Rechts</span>
${item.rechts}
</div>
</div>
<button class="btn-import" data-index="${index}">+ Toevoegen</button>
`;
// 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);
// 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');
}
});