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:
Frank Meeuwsen 2025-12-16 07:36:55 +01:00
parent 0e1eb25649
commit 0775ee6161
7 changed files with 609 additions and 60 deletions

View file

@ -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

View file

@ -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
} }

View file

@ -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;

View file

@ -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
View file

@ -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', () => {

View file

@ -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
View 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
}
}
]
}