feat: workshop sets management systeem toegevoegd
Nieuw sets systeem voor het beheren van meerdere workshop configuraties. Nieuwe functionaliteit: - Sets dropdown bovenaan editor voor eenvoudig switchen - "Opslaan als Nieuwe Set" knop voor nieuwe configuraties - Meerdere complete workshop sets opslaan en laden - Elke set bevat eigen stellingen, kleuren, timer en teksten - Automatische migratie van huidige config naar default set Backend wijzigingen: - GET/POST /api/sets endpoints toegevoegd - Helper functies voor sets management en config updates - Automatische initialisatie van sets.json bij eerste gebruik Frontend wijzigingen: - Sets sectie met dropdown en acties knoppen (editor.html) - Styling met blauwe accent border (editor.css) - Complete sets management logic (editor.js) - Event handlers voor set selectie en opslaan Data structuur: - sets.json: database met alle workshop sets - config.json: blijft actieve configuratie voor presentatie Backwards compatible: - Systeem werkt zonder sets.json (legacy fallback) - Presentatie modus ongewijzigd (gebruikt config.json) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0e1eb25649
commit
0775ee6161
7 changed files with 609 additions and 60 deletions
83
CLAUDE.md
83
CLAUDE.md
|
|
@ -25,7 +25,7 @@ Een presentatie tool voor workshops waar deelnemers tussen twee stellingen moete
|
||||||
### Tech Stack
|
### Tech Stack
|
||||||
- Frontend: Pure HTML/CSS/JavaScript
|
- Frontend: Pure HTML/CSS/JavaScript
|
||||||
- Backend: Python Flask + flask-cors
|
- Backend: Python Flask + flask-cors
|
||||||
- Config driven via JSON
|
- Config driven via JSON (sets.json voor meerdere configuraties)
|
||||||
- Geen externe dependencies voor presentatie modus
|
- Geen externe dependencies voor presentatie modus
|
||||||
|
|
||||||
## Bestandsstructuur
|
## Bestandsstructuur
|
||||||
|
|
@ -35,11 +35,13 @@ Een presentatie tool voor workshops waar deelnemers tussen twee stellingen moete
|
||||||
├── index.html # Presentatie modus (hoofdspel)
|
├── index.html # Presentatie modus (hoofdspel)
|
||||||
├── app.js # Game logic en timer animatie
|
├── app.js # Game logic en timer animatie
|
||||||
├── style.css # Presentatie styling
|
├── style.css # Presentatie styling
|
||||||
├── config.json # Stellingen database (JSON)
|
├── config.json # Actieve configuratie (gebruikt door presentatie)
|
||||||
├── editor.html # Web-based config editor
|
├── sets.json # Database met alle workshop sets
|
||||||
├── editor.js # Editor functionaliteit
|
├── stellingkast.json # Bibliotheek met stellingen voor import
|
||||||
|
├── editor.html # Web-based config editor met sets beheer
|
||||||
|
├── editor.js # Editor functionaliteit en sets management
|
||||||
├── editor.css # Editor styling
|
├── editor.css # Editor styling
|
||||||
├── server.py # Flask backend voor editor
|
├── server.py # Flask backend voor editor en sets API
|
||||||
├── requirements.txt # Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
├── .gitignore # Exclude venv, .DS_Store, etc
|
├── .gitignore # Exclude venv, .DS_Store, etc
|
||||||
└── README.md # Uitgebreide documentatie
|
└── README.md # Uitgebreide documentatie
|
||||||
|
|
@ -67,15 +69,17 @@ pip install -r requirements.txt
|
||||||
python server.py
|
python server.py
|
||||||
|
|
||||||
# Open editor
|
# Open editor
|
||||||
open http://localhost:5000/editor.html
|
open http://localhost:8000/editor.html
|
||||||
```
|
```
|
||||||
|
|
||||||
**Editor features:**
|
**Editor features:**
|
||||||
- Toevoegen/verwijderen stellingen
|
- **Sets beheer:** Meerdere workshop configuraties opslaan en laden
|
||||||
- Timer aanpassen per stelling
|
- **Sets dropdown:** Selecteer actieve set of maak nieuwe set aan
|
||||||
- Kleuren aanpassen
|
- Toevoegen/verwijderen stellingen via drag & drop
|
||||||
- Preview kleuren
|
- Stellingkast met bibliotheek voor snelle import
|
||||||
- Auto-save naar config.json
|
- Timer aanpassen per set
|
||||||
|
- Kleuren aanpassen per set
|
||||||
|
- Auto-save naar sets.json en config.json
|
||||||
|
|
||||||
## Session History
|
## Session History
|
||||||
|
|
||||||
|
|
@ -168,4 +172,59 @@ Tijdens gebruik bleek de knop onderaan moeilijk leesbaar door de overlay, en de
|
||||||
|
|
||||||
**Status:**
|
**Status:**
|
||||||
- Features volledig werkend en getest in browser
|
- Features volledig werkend en getest in browser
|
||||||
- Editor robuuster gemaakt tegen laadfouten
|
- Editor robuuster gemaakt tegen laadfouten
|
||||||
|
|
||||||
|
### 2025-12-16 - Workshop Sets Management
|
||||||
|
**Nieuwe features:**
|
||||||
|
- **Sets systeem:** Meerdere complete workshop configuraties kunnen opslaan en laden
|
||||||
|
- **Sets dropdown:** Bovenaan editor voor eenvoudig switchen tussen sets
|
||||||
|
- **"Opslaan als Nieuwe Set"** knop voor nieuwe configuraties aanmaken
|
||||||
|
- **Automatische migratie:** Huidige config.json wordt bij eerste gebruik omgezet naar default set
|
||||||
|
- **Backwards compatibility:** Systeem blijft werken zonder sets.json (legacy fallback)
|
||||||
|
|
||||||
|
**Gewijzigde bestanden:**
|
||||||
|
- `server.py` - Nieuwe endpoints: GET/POST `/api/sets`, helper functies voor sets management
|
||||||
|
- `editor.html` - Sets sectie met dropdown en knoppen toegevoegd
|
||||||
|
- `editor.css` - Styling voor sets UI (blauwe border-left accent)
|
||||||
|
- `editor.js` - Complete sets management logica: laden, switchen, opslaan, nieuwe sets aanmaken
|
||||||
|
- `config.json` - Blijft actieve configuratie voor presentatie modus
|
||||||
|
|
||||||
|
**Nieuwe bestanden:**
|
||||||
|
- `sets.json` - Database met alle workshop sets (wordt automatisch aangemaakt)
|
||||||
|
|
||||||
|
**Hoe het werkt:**
|
||||||
|
1. Editor openen → Zie dropdown met alle sets
|
||||||
|
2. Set selecteren → Laadt stellingen en instellingen van die set
|
||||||
|
3. Bewerken en opslaan → Overschrijft huidige set
|
||||||
|
4. "Opslaan als Nieuwe Set" → Maakt nieuwe set aan met custom naam
|
||||||
|
5. Presentatie starten → Gebruikt laatst opgeslagen set uit config.json
|
||||||
|
|
||||||
|
**Data structuur sets.json:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sets": [
|
||||||
|
{
|
||||||
|
"id": "default",
|
||||||
|
"name": "Standaard Set",
|
||||||
|
"config": { /* complete config */ }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-1234567890",
|
||||||
|
"name": "PKM Workshop 2025",
|
||||||
|
"config": { /* complete config */ }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"activeSetId": "default"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Waarom:**
|
||||||
|
Facilitators kunnen nu meerdere workshop configuraties voorbereiden (bijv. verschillende doelgroepen, thema's) en eenvoudig tussen sets wisselen zonder telkens stellingen handmatig te moeten aanpassen.
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
- Backend endpoints volledig werkend (GET/POST /api/sets)
|
||||||
|
- Frontend UI en logic compleet geïmplementeerd
|
||||||
|
- Automatische migratie van bestaande config.json getest
|
||||||
|
- Sets kunnen worden aangemaakt, geladen, gewijzigd en opgeslagen
|
||||||
|
- Backwards compatible met oude systeem
|
||||||
|
- Klaar voor productie gebruik
|
||||||
56
config.json
56
config.json
|
|
@ -1,60 +1,36 @@
|
||||||
{
|
{
|
||||||
"buttonText": "Volgende stelling",
|
"buttonText": "Volgende",
|
||||||
"colors": {
|
"colors": {
|
||||||
"left": "#3b82f6",
|
"left": "#10b981",
|
||||||
"right": "#ef4444"
|
"right": "#f59e0b"
|
||||||
},
|
},
|
||||||
"finishText": "Dat was het! Having fun yet?",
|
"finishText": "Bedankt!",
|
||||||
"fontSize": "3rem",
|
"fontSize": "2.5rem",
|
||||||
"stellingen": [
|
"stellingen": [
|
||||||
{
|
{
|
||||||
"links": "Koffie",
|
"links": "Inbox Zero held",
|
||||||
"rechts": "Thee"
|
"rechts": "1000+ ongelezen mails"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Structurele planner",
|
"links": "Alles digitaal",
|
||||||
"rechts": "Creatieve chaoot"
|
"rechts": "Mijn papieren notitieboek is heilig"
|
||||||
},
|
|
||||||
{
|
|
||||||
"links": "Ik gebruik meer sneltoetsen",
|
|
||||||
"rechts": "Ik ben van team muisgebruik"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"links": "We hebben duidelijke afspraken over naamgeving van bestanden",
|
|
||||||
"rechts": "Mijn naamgeving van bestanden is veel logischer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"links": "Mappenstructuur",
|
|
||||||
"rechts": "Zoekfunctie"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"links": "Ik maak eigen notities op één plek",
|
|
||||||
"rechts": "Ik maak overal notities en zoek me suf"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Samenwerken in één document",
|
"links": "Samenwerken in één document",
|
||||||
"rechts": "Concept_versie_3_def_final.docx mailen"
|
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Mijn TO-DO lijst is actueel",
|
"links": "Camera aan tijdens Teams",
|
||||||
"rechts": "Mijn TO-DO lijst is fictie"
|
"rechts": "Lekker onzichtbaar luisteren"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Ik gebruik tags en labels",
|
"links": "Agenda blokken voor focus",
|
||||||
"rechts": "Ik stop alles in mapjes"
|
"rechts": "Mijn deur staat altijd open (digitaal)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Browser met 50+ open tabbladen",
|
"links": "Ik weet wat de AVG van me vraagt",
|
||||||
"rechts": "Opgeruimde browser"
|
"rechts": "Privacy is voor de juristen"
|
||||||
},
|
|
||||||
{
|
|
||||||
"links": "Kennis zit in mijn hoofd",
|
|
||||||
"rechts": "Kennis staat in het systeem"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"links": "Ik kom vandaag vooral halen",
|
|
||||||
"rechts": "Ik kom vandaag ook brengen"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timer": 10
|
"timer": 3
|
||||||
}
|
}
|
||||||
60
editor.css
60
editor.css
|
|
@ -77,6 +77,66 @@ section h2 {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sets Section Styling */
|
||||||
|
.sets-section {
|
||||||
|
border-left: 4px solid #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sets-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-selector-group {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-selector-group label {
|
||||||
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-select:hover {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
/* Settings grid */
|
/* Settings grid */
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
||||||
17
editor.html
17
editor.html
|
|
@ -16,6 +16,23 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
<!-- Workshop Sets -->
|
||||||
|
<section class="sets-section">
|
||||||
|
<h2>📂 Workshop Sets</h2>
|
||||||
|
<div class="sets-controls">
|
||||||
|
<div class="set-selector-group">
|
||||||
|
<label for="setSelector">Actieve Set:</label>
|
||||||
|
<select id="setSelector" class="set-select">
|
||||||
|
<option value="">Laden...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="set-actions">
|
||||||
|
<button id="saveAsNewSet" class="btn btn-secondary">💾 Opslaan als Nieuwe Set</button>
|
||||||
|
<button id="deleteSet" class="btn btn-danger" style="display:none;">🗑️ Verwijder Set</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Basis instellingen -->
|
<!-- Basis instellingen -->
|
||||||
<section class="settings-section">
|
<section class="settings-section">
|
||||||
<h2>⚙️ Instellingen</h2>
|
<h2>⚙️ Instellingen</h2>
|
||||||
|
|
|
||||||
233
editor.js
233
editor.js
|
|
@ -2,6 +2,8 @@
|
||||||
let config = null;
|
let config = null;
|
||||||
let stellingkastItems = [];
|
let stellingkastItems = [];
|
||||||
let dragSrcEl = null;
|
let dragSrcEl = null;
|
||||||
|
let allSets = null;
|
||||||
|
let currentSetId = null;
|
||||||
|
|
||||||
// DOM elementen
|
// DOM elementen
|
||||||
const timerInput = document.getElementById('timer');
|
const timerInput = document.getElementById('timer');
|
||||||
|
|
@ -25,26 +27,229 @@ const stellingkastPanel = document.getElementById('stellingkastPanel');
|
||||||
const stellingkastList = document.getElementById('stellingkastList');
|
const stellingkastList = document.getElementById('stellingkastList');
|
||||||
const searchStellingkastInput = document.getElementById('searchStellingkast');
|
const searchStellingkastInput = document.getElementById('searchStellingkast');
|
||||||
|
|
||||||
|
// Sets elementen
|
||||||
|
const setSelector = document.getElementById('setSelector');
|
||||||
|
const saveAsNewSetBtn = document.getElementById('saveAsNewSet');
|
||||||
|
const deleteSetBtn = document.getElementById('deleteSet');
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
syncColorInputs();
|
syncColorInputs();
|
||||||
// Start beide laad-acties onafhankelijk van elkaar
|
// Start alle laad-acties onafhankelijk van elkaar
|
||||||
loadConfig();
|
loadSets();
|
||||||
loadStellingkast();
|
loadStellingkast();
|
||||||
|
|
||||||
// Laad config bij start
|
// --- 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 = '<option value="">-- Nieuwe set --</option>';
|
||||||
|
|
||||||
|
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() {
|
async function loadConfig() {
|
||||||
try {
|
try {
|
||||||
console.log('Fetching config...');
|
console.log('Fetching config...');
|
||||||
const response = await fetch('/api/config?t=' + Date.now());
|
const response = await fetch('/api/config?t=' + Date.now());
|
||||||
if (!response.ok) throw new Error('Kon config niet laden (' + response.status + ')');
|
if (!response.ok) throw new Error('Kon config niet laden (' + response.status + ')');
|
||||||
|
|
||||||
config = await response.json();
|
config = await response.json();
|
||||||
console.log('Config loaded:', config);
|
console.log('Config loaded:', config);
|
||||||
|
|
||||||
// Ensure minimal config structure
|
// Ensure minimal config structure
|
||||||
if (!config.stellingen) config.stellingen = [];
|
if (!config.stellingen) config.stellingen = [];
|
||||||
if (!config.colors) config.colors = { left: '#3B82F6', right: '#EF4444' };
|
if (!config.colors) config.colors = { left: '#3B82F6', right: '#EF4444' };
|
||||||
|
|
||||||
populateForm();
|
populateForm();
|
||||||
showStatus('Config geladen!', 'success');
|
showStatus('Config geladen!', 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -276,7 +481,16 @@ async function saveConfig() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST naar server
|
// 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', {
|
const response = await fetch('/api/save-config', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -289,7 +503,6 @@ async function saveConfig() {
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
showStatus('✅ Config succesvol opgeslagen!', 'success');
|
showStatus('✅ Config succesvol opgeslagen!', 'success');
|
||||||
// Reload om lege stellingen te verwijderen
|
|
||||||
setTimeout(() => loadConfig(), 500);
|
setTimeout(() => loadConfig(), 500);
|
||||||
} else {
|
} else {
|
||||||
showStatus(`❌ Fout: ${result.error}`, 'error');
|
showStatus(`❌ Fout: ${result.error}`, 'error');
|
||||||
|
|
@ -388,6 +601,10 @@ function importStelling(item) {
|
||||||
if (addStatementBtn) addStatementBtn.addEventListener('click', addNewStatement);
|
if (addStatementBtn) addStatementBtn.addEventListener('click', addNewStatement);
|
||||||
if (saveConfigBtn) saveConfigBtn.addEventListener('click', saveConfig);
|
if (saveConfigBtn) saveConfigBtn.addEventListener('click', saveConfig);
|
||||||
|
|
||||||
|
// Sets events
|
||||||
|
if (setSelector) setSelector.addEventListener('change', onSetSelected);
|
||||||
|
if (saveAsNewSetBtn) saveAsNewSetBtn.addEventListener('click', saveAsNewSet);
|
||||||
|
|
||||||
// Stellingkast events
|
// Stellingkast events
|
||||||
if (openStellingkastBtn) {
|
if (openStellingkastBtn) {
|
||||||
openStellingkastBtn.addEventListener('click', () => {
|
openStellingkastBtn.addEventListener('click', () => {
|
||||||
|
|
|
||||||
71
server.py
71
server.py
|
|
@ -13,6 +13,7 @@ app = Flask(__name__)
|
||||||
CORS(app) # Enable CORS voor alle routes
|
CORS(app) # Enable CORS voor alle routes
|
||||||
|
|
||||||
CONFIG_FILE = 'config.json'
|
CONFIG_FILE = 'config.json'
|
||||||
|
SETS_FILE = 'sets.json'
|
||||||
|
|
||||||
# Serve static files
|
# Serve static files
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
|
@ -59,6 +60,76 @@ def save_config():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
# API endpoint om alle sets te laden
|
||||||
|
@app.route('/api/sets', methods=['GET'])
|
||||||
|
def get_sets():
|
||||||
|
try:
|
||||||
|
# Als sets.json niet bestaat, maak initieel bestand aan
|
||||||
|
if not os.path.exists(SETS_FILE):
|
||||||
|
initialize_sets_file()
|
||||||
|
|
||||||
|
with open(SETS_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
sets_data = json.load(f)
|
||||||
|
return jsonify(sets_data)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
# API endpoint om sets op te slaan
|
||||||
|
@app.route('/api/sets', methods=['POST'])
|
||||||
|
def save_sets():
|
||||||
|
try:
|
||||||
|
data = request.json
|
||||||
|
|
||||||
|
# Validatie: check of sets array bestaat
|
||||||
|
if 'sets' not in data:
|
||||||
|
return jsonify({'error': 'Geen sets gevonden'}), 400
|
||||||
|
|
||||||
|
# Schrijf naar sets.json met mooie formatting
|
||||||
|
with open(SETS_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
# Update config.json met actieve set als die gezet is
|
||||||
|
if 'activeSetId' in data and data['activeSetId']:
|
||||||
|
active_set = next((s for s in data['sets'] if s['id'] == data['activeSetId']), None)
|
||||||
|
if active_set and 'config' in active_set:
|
||||||
|
update_active_config(active_set['config'])
|
||||||
|
|
||||||
|
return jsonify({'success': True, 'message': 'Sets opgeslagen!'})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
# Helper: Update config.json met set configuratie
|
||||||
|
def update_active_config(set_config):
|
||||||
|
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(set_config, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
# Helper: Initialiseer sets.json met huidige config als default set
|
||||||
|
def initialize_sets_file():
|
||||||
|
try:
|
||||||
|
# Lees huidige config.json
|
||||||
|
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
current_config = json.load(f)
|
||||||
|
|
||||||
|
# Maak initiële sets structuur
|
||||||
|
initial_sets = {
|
||||||
|
'sets': [
|
||||||
|
{
|
||||||
|
'id': 'default',
|
||||||
|
'name': 'Standaard Set',
|
||||||
|
'config': current_config
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'activeSetId': 'default'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Schrijf naar sets.json
|
||||||
|
with open(SETS_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(initial_sets, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
print("✅ sets.json aangemaakt met default set")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Fout bij initialiseren sets.json: {e}")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("🎯 IJsbreker Server gestart!")
|
print("🎯 IJsbreker Server gestart!")
|
||||||
print("📝 Editor: http://localhost:8000/editor.html")
|
print("📝 Editor: http://localhost:8000/editor.html")
|
||||||
|
|
|
||||||
149
sets.json
Normal file
149
sets.json
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
{
|
||||||
|
"activeSetId": "set-1765866890733",
|
||||||
|
"sets": [
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"buttonText": "Volgende stelling",
|
||||||
|
"colors": {
|
||||||
|
"left": "#3b82f6",
|
||||||
|
"right": "#ef4444"
|
||||||
|
},
|
||||||
|
"finishText": "Dat was het! Having fun yet?",
|
||||||
|
"fontSize": "3rem",
|
||||||
|
"stellingen": [
|
||||||
|
{
|
||||||
|
"links": "Koffie",
|
||||||
|
"rechts": "Thee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Structurele planner",
|
||||||
|
"rechts": "Creatieve chaoot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik gebruik meer sneltoetsen",
|
||||||
|
"rechts": "Ik ben van team muisgebruik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "We hebben duidelijke afspraken over naamgeving van bestanden",
|
||||||
|
"rechts": "Mijn naamgeving van bestanden is veel logischer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Mappenstructuur",
|
||||||
|
"rechts": "Zoekfunctie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik maak eigen notities op één plek",
|
||||||
|
"rechts": "Ik maak overal notities en zoek me suf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Samenwerken in één document",
|
||||||
|
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Mijn TO-DO lijst is actueel",
|
||||||
|
"rechts": "Mijn TO-DO lijst is fictie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik gebruik tags en labels",
|
||||||
|
"rechts": "Ik stop alles in mapjes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Browser met 50+ open tabbladen",
|
||||||
|
"rechts": "Opgeruimde browser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Kennis zit in mijn hoofd",
|
||||||
|
"rechts": "Kennis staat in het systeem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik kom vandaag vooral halen",
|
||||||
|
"rechts": "Ik kom vandaag ook brengen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timer": 10
|
||||||
|
},
|
||||||
|
"id": "default",
|
||||||
|
"name": "Standaard Set"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"buttonText": "Volgende",
|
||||||
|
"colors": {
|
||||||
|
"left": "#10b981",
|
||||||
|
"right": "#f59e0b"
|
||||||
|
},
|
||||||
|
"finishText": "Bedankt!",
|
||||||
|
"fontSize": "2.5rem",
|
||||||
|
"stellingen": [
|
||||||
|
{
|
||||||
|
"links": "Inbox Zero held",
|
||||||
|
"rechts": "1000+ ongelezen mails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Alles digitaal",
|
||||||
|
"rechts": "Mijn papieren notitieboek is heilig"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Samenwerken in één document",
|
||||||
|
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Camera aan tijdens Teams",
|
||||||
|
"rechts": "Lekker onzichtbaar luisteren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Agenda blokken voor focus",
|
||||||
|
"rechts": "Mijn deur staat altijd open (digitaal)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik weet wat de AVG van me vraagt",
|
||||||
|
"rechts": "Privacy is voor de juristen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timer": 3
|
||||||
|
},
|
||||||
|
"id": "pkm-workshop",
|
||||||
|
"name": "PKM Workshop 2025"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "set-1765866890733",
|
||||||
|
"name": "Kerstboom",
|
||||||
|
"config": {
|
||||||
|
"buttonText": "Volgende",
|
||||||
|
"colors": {
|
||||||
|
"left": "#10b981",
|
||||||
|
"right": "#f59e0b"
|
||||||
|
},
|
||||||
|
"finishText": "Bedankt!",
|
||||||
|
"fontSize": "2.5rem",
|
||||||
|
"stellingen": [
|
||||||
|
{
|
||||||
|
"links": "Inbox Zero held",
|
||||||
|
"rechts": "1000+ ongelezen mails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Alles digitaal",
|
||||||
|
"rechts": "Mijn papieren notitieboek is heilig"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Samenwerken in één document",
|
||||||
|
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Camera aan tijdens Teams",
|
||||||
|
"rechts": "Lekker onzichtbaar luisteren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Agenda blokken voor focus",
|
||||||
|
"rechts": "Mijn deur staat altijd open (digitaal)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik weet wat de AVG van me vraagt",
|
||||||
|
"rechts": "Privacy is voor de juristen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timer": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue