chore: session end - stellingkast en drag-drop features toegevoegd
This commit is contained in:
parent
299b603e53
commit
0e1eb25649
8 changed files with 675 additions and 49 deletions
17
CLAUDE.md
17
CLAUDE.md
|
|
@ -152,3 +152,20 @@ Tijdens gebruik bleek de knop onderaan moeilijk leesbaar door de overlay, en de
|
||||||
- Alle wijzigingen getest en werkend
|
- Alle wijzigingen getest en werkend
|
||||||
- Gecommit en gepusht naar remote (commit e1f1659)
|
- Gecommit en gepusht naar remote (commit e1f1659)
|
||||||
- UX verbeterd voor workshop gebruik
|
- UX verbeterd voor workshop gebruik
|
||||||
|
|
||||||
|
### 2025-12-12 - Stellingkast en Drag & Drop
|
||||||
|
**Nieuwe features:**
|
||||||
|
- **Stellingkast:** Nieuw inschuif-paneel in de editor om direct stellingen uit een bibliotheek (`stellingkast.json`) te importeren.
|
||||||
|
- **Drag & Drop:** Stellingen kunnen nu in de editor versleept worden om de volgorde aan te passen.
|
||||||
|
- **Cache Busting:** Toegevoegd aan fetch calls om laadproblemen met verouderde JSON bestanden te voorkomen.
|
||||||
|
- **Validatie:** `stellingkast.json` is opgeschoond en bevat nu valide JSON.
|
||||||
|
|
||||||
|
**Gewijzigde bestanden:**
|
||||||
|
- `editor.html` - HTML structuur voor Stellingkast panel en nieuwe header knop
|
||||||
|
- `editor.css` - Styling voor slide-in panel, sleep-handvatten en responsive design
|
||||||
|
- `editor.js` - Logica voor drag & drop, stellingkast import, verbeterde foutafhandeling en loading states
|
||||||
|
- `stellingkast.json` - Opgeschoond (regelnummers verwijderd)
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
- Features volledig werkend en getest in browser
|
||||||
|
- Editor robuuster gemaakt tegen laadfouten
|
||||||
20
agents.md
20
agents.md
|
|
@ -1,7 +1,6 @@
|
||||||
# agents.md
|
# agents.md
|
||||||
> Deze file wordt automatisch gesynchroniseerd met CLAUDE.md
|
> Deze file wordt automatisch gesynchroniseerd met CLAUDE.md
|
||||||
> Laatste sync: 2025-11-14 21:45
|
> Laatste sync: 2025-12-12 08:35
|
||||||
|
|
||||||
# IJsbreker Workshop Spel
|
# IJsbreker Workshop Spel
|
||||||
|
|
||||||
Browser-based interactief workshop spel waarbij deelnemers fysiek kiezen tussen twee stellingen die op een beamer worden getoond.
|
Browser-based interactief workshop spel waarbij deelnemers fysiek kiezen tussen twee stellingen die op een beamer worden getoond.
|
||||||
|
|
@ -156,3 +155,20 @@ Tijdens gebruik bleek de knop onderaan moeilijk leesbaar door de overlay, en de
|
||||||
- Alle wijzigingen getest en werkend
|
- Alle wijzigingen getest en werkend
|
||||||
- Gecommit en gepusht naar remote (commit e1f1659)
|
- Gecommit en gepusht naar remote (commit e1f1659)
|
||||||
- UX verbeterd voor workshop gebruik
|
- UX verbeterd voor workshop gebruik
|
||||||
|
|
||||||
|
### 2025-12-12 - Stellingkast en Drag & Drop
|
||||||
|
**Nieuwe features:**
|
||||||
|
- **Stellingkast:** Nieuw inschuif-paneel in de editor om direct stellingen uit een bibliotheek (`stellingkast.json`) te importeren.
|
||||||
|
- **Drag & Drop:** Stellingen kunnen nu in de editor versleept worden om de volgorde aan te passen.
|
||||||
|
- **Cache Busting:** Toegevoegd aan fetch calls om laadproblemen met verouderde JSON bestanden te voorkomen.
|
||||||
|
- **Validatie:** `stellingkast.json` is opgeschoond en bevat nu valide JSON.
|
||||||
|
|
||||||
|
**Gewijzigde bestanden:**
|
||||||
|
- `editor.html` - HTML structuur voor Stellingkast panel en nieuwe header knop
|
||||||
|
- `editor.css` - Styling voor slide-in panel, sleep-handvatten en responsive design
|
||||||
|
- `editor.js` - Logica voor drag & drop, stellingkast import, verbeterde foutafhandeling en loading states
|
||||||
|
- `stellingkast.json` - Opgeschoond (regelnummers verwijderd)
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
- Features volledig werkend en getest in browser
|
||||||
|
- Editor robuuster gemaakt tegen laadfouten
|
||||||
38
config.json
38
config.json
|
|
@ -12,33 +12,49 @@
|
||||||
"rechts": "Thee"
|
"rechts": "Thee"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Friet",
|
"links": "Structurele planner",
|
||||||
"rechts": "Patat"
|
"rechts": "Creatieve chaoot"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Ik gebruik meer sneltoetsen",
|
"links": "Ik gebruik meer sneltoetsen",
|
||||||
"rechts": "Ik ben van team muisgebruik"
|
"rechts": "Ik ben van team muisgebruik"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"links": "Ik vind eenvoudig mijn eigen informatie terug",
|
|
||||||
"rechts": "Ik ben constant alles kwijt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"links": "We hebben duidelijke afspraken over naamgeving van bestanden",
|
"links": "We hebben duidelijke afspraken over naamgeving van bestanden",
|
||||||
"rechts": "Mijn naamgeving van bestanden is veel logischer"
|
"rechts": "Mijn naamgeving van bestanden is veel logischer"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"links": "Mappenstructuur",
|
||||||
|
"rechts": "Zoekfunctie"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"links": "Ik maak eigen notities op één plek",
|
"links": "Ik maak eigen notities op één plek",
|
||||||
"rechts": "Ik maak overal notities en zoek me suf"
|
"rechts": "Ik maak overal notities en zoek me suf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "❤️ ❤️ ❤️ Notitie en memo templates ",
|
"links": "Samenwerken in één document",
|
||||||
"rechts": "☠️☠️☠️ Wie bedenkt die templates?"
|
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"links": "Zin in de dag!",
|
"links": "Mijn TO-DO lijst is actueel",
|
||||||
"rechts": "Wat gaan we doen?"
|
"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": 2
|
"timer": 10
|
||||||
}
|
}
|
||||||
149
editor.css
149
editor.css
|
|
@ -62,6 +62,12 @@ section h2 {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.count-badge {
|
.count-badge {
|
||||||
background: #E5E7EB;
|
background: #E5E7EB;
|
||||||
color: #4B5563;
|
color: #4B5563;
|
||||||
|
|
@ -121,20 +127,38 @@ section h2 {
|
||||||
|
|
||||||
.statement-row {
|
.statement-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr auto;
|
grid-template-columns: auto 1fr 1fr auto;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #F9FAFB;
|
background: #F9FAFB;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 2px solid #E5E7EB;
|
border: 2px solid #E5E7EB;
|
||||||
transition: border-color 0.2s;
|
transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||||
|
user-select: none; /* Prevent text selection while dragging */
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-row:hover {
|
.statement-row:hover {
|
||||||
border-color: #D1D5DB;
|
border-color: #D1D5DB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statement-row.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
background: #E5E7EB;
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
cursor: grab;
|
||||||
|
color: #9CA3AF;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle:hover {
|
||||||
|
color: #4B5563;
|
||||||
|
}
|
||||||
|
|
||||||
.statement-input {
|
.statement-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -251,6 +275,109 @@ section h2 {
|
||||||
color: #991B1B;
|
color: #991B1B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Stellingkast Panel */
|
||||||
|
.stellingkast-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: -400px; /* Start hidden */
|
||||||
|
width: 400px;
|
||||||
|
height: 100vh;
|
||||||
|
background: white;
|
||||||
|
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
transition: right 0.3s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellingkast-panel.open {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: #F3F4F6;
|
||||||
|
border-bottom: 1px solid #E5E7EB;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #6B7280;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 2px solid #E5E7EB;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellingkast-item {
|
||||||
|
background: #F9FAFB;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellingkast-item:hover {
|
||||||
|
border-color: #3B82F6;
|
||||||
|
background: #F0F9FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stelling-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stelling-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6B7280;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-import {
|
||||||
|
width: 100%;
|
||||||
|
background: #3B82F6;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-import:hover {
|
||||||
|
background: #2563EB;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.container {
|
.container {
|
||||||
|
|
@ -264,10 +391,24 @@ section h2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-row {
|
.statement-row {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: auto 1fr; /* Adjusted for handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-row .statement-input {
|
||||||
|
grid-column: 2; /* Move inputs to second column */
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-row .btn-remove {
|
||||||
|
grid-column: 2;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellingkast-panel {
|
||||||
|
width: 100%; /* Full width on mobile */
|
||||||
|
right: -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
19
editor.html
19
editor.html
|
|
@ -53,7 +53,10 @@
|
||||||
<section class="statements-section">
|
<section class="statements-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>📝 Stellingen</h2>
|
<h2>📝 Stellingen</h2>
|
||||||
<span id="statementCount" class="count-badge">0 stellingen</span>
|
<div class="header-controls">
|
||||||
|
<button id="openStellingkast" class="btn btn-secondary">📚 Stellingkast</button>
|
||||||
|
<span id="statementCount" class="count-badge">0 stellingen</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="statementsList" class="statements-list">
|
<div id="statementsList" class="statements-list">
|
||||||
|
|
@ -65,6 +68,20 @@
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Stellingkast Panel -->
|
||||||
|
<div id="stellingkastPanel" class="stellingkast-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>📚 Stellingkast</h2>
|
||||||
|
<button id="closeStellingkast" class="btn-close">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<input type="text" id="searchStellingkast" placeholder="Zoek stelling..." class="search-input">
|
||||||
|
<div id="stellingkastList" class="stellingkast-list">
|
||||||
|
<!-- Stellingen uit kast worden hier geladen -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Opslaan -->
|
<!-- Opslaan -->
|
||||||
<section class="actions-section">
|
<section class="actions-section">
|
||||||
<button id="saveConfig" class="btn btn-primary">
|
<button id="saveConfig" class="btn btn-primary">
|
||||||
|
|
|
||||||
259
editor.js
259
editor.js
|
|
@ -1,5 +1,7 @@
|
||||||
// Globale state
|
// Globale state
|
||||||
let config = null;
|
let config = null;
|
||||||
|
let stellingkastItems = [];
|
||||||
|
let dragSrcEl = null;
|
||||||
|
|
||||||
// DOM elementen
|
// DOM elementen
|
||||||
const timerInput = document.getElementById('timer');
|
const timerInput = document.getElementById('timer');
|
||||||
|
|
@ -16,30 +18,59 @@ const saveConfigBtn = document.getElementById('saveConfig');
|
||||||
const statusMessage = document.getElementById('statusMessage');
|
const statusMessage = document.getElementById('statusMessage');
|
||||||
const statementCount = document.getElementById('statementCount');
|
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
|
// Laad config bij start
|
||||||
async function loadConfig() {
|
async function loadConfig() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/config');
|
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();
|
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();
|
populateForm();
|
||||||
showStatus('Config geladen!', 'success');
|
showStatus('Config geladen!', 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fout bij laden config:', error);
|
console.error('Fout bij laden config:', error);
|
||||||
showStatus('Fout bij laden config', 'error');
|
showStatus('Fout bij laden config: ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vul formulier met config data
|
// Vul formulier met config data
|
||||||
function populateForm() {
|
function populateForm() {
|
||||||
// Basis instellingen
|
if (!config) return;
|
||||||
timerInput.value = config.timer;
|
|
||||||
fontSizeInput.value = config.fontSize;
|
// Basis instellingen (met fallback values)
|
||||||
buttonTextInput.value = config.buttonText;
|
timerInput.value = config.timer || 30;
|
||||||
finishTextInput.value = config.finishText;
|
fontSizeInput.value = config.fontSize || '3rem';
|
||||||
colorLeftInput.value = config.colors.left;
|
buttonTextInput.value = config.buttonText || 'Volgende Stelling';
|
||||||
colorLeftTextInput.value = config.colors.left;
|
finishTextInput.value = config.finishText || 'Einde!';
|
||||||
colorRightInput.value = config.colors.right;
|
|
||||||
colorRightTextInput.value = config.colors.right;
|
// 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
|
// Render stellingen
|
||||||
renderStatements();
|
renderStatements();
|
||||||
|
|
@ -49,9 +80,11 @@ function populateForm() {
|
||||||
function renderStatements() {
|
function renderStatements() {
|
||||||
statementsList.innerHTML = '';
|
statementsList.innerHTML = '';
|
||||||
|
|
||||||
config.stellingen.forEach((stelling, index) => {
|
if (config.stellingen) {
|
||||||
addStatementRow(stelling, index);
|
config.stellingen.forEach((stelling, index) => {
|
||||||
});
|
addStatementRow(stelling, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateCount();
|
updateCount();
|
||||||
}
|
}
|
||||||
|
|
@ -61,8 +94,10 @@ function addStatementRow(stelling = { links: '', rechts: '' }, index) {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'statement-row';
|
row.className = 'statement-row';
|
||||||
row.dataset.index = index;
|
row.dataset.index = index;
|
||||||
|
row.setAttribute('draggable', 'true');
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
|
<div class="drag-handle" title="Sleep om te verplaatsen">☰</div>
|
||||||
<div class="statement-input">
|
<div class="statement-input">
|
||||||
<label>Stelling Links</label>
|
<label>Stelling Links</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -108,6 +143,60 @@ function addStatementRow(stelling = { links: '', rechts: '' }, index) {
|
||||||
removeStatement(index);
|
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
|
// Verwijder stelling
|
||||||
|
|
@ -126,12 +215,16 @@ function addNewStatement() {
|
||||||
const newRow = statementsList.lastElementChild;
|
const newRow = statementsList.lastElementChild;
|
||||||
const firstInput = newRow.querySelector('.statement-left');
|
const firstInput = newRow.querySelector('.statement-left');
|
||||||
firstInput.focus();
|
firstInput.focus();
|
||||||
|
// Scroll naar beneden
|
||||||
|
newRow.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
|
||||||
showStatus('Nieuwe stelling toegevoegd', 'success');
|
showStatus('Nieuwe stelling toegevoegd', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stelling count
|
// Update stelling count
|
||||||
function updateCount() {
|
function updateCount() {
|
||||||
|
if (!config || !config.stellingen) return;
|
||||||
|
|
||||||
const count = config.stellingen.filter(
|
const count = config.stellingen.filter(
|
||||||
s => s.links.trim() || s.rechts.trim()
|
s => s.links.trim() || s.rechts.trim()
|
||||||
).length;
|
).length;
|
||||||
|
|
@ -207,21 +300,129 @@ async function saveConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toon status bericht
|
// --- Stellingkast Functionaliteit ---
|
||||||
function showStatus(message, type = 'success') {
|
|
||||||
statusMessage.textContent = message;
|
|
||||||
statusMessage.className = `status-message ${type}`;
|
|
||||||
|
|
||||||
// Verberg na 3 seconden
|
async function loadStellingkast() {
|
||||||
setTimeout(() => {
|
const listContainer = document.getElementById('stellingkastList');
|
||||||
statusMessage.textContent = '';
|
if (!listContainer) return; // Safety check
|
||||||
statusMessage.className = 'status-message';
|
|
||||||
}, 3000);
|
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
|
// Event listeners
|
||||||
addStatementBtn.addEventListener('click', addNewStatement);
|
if (addStatementBtn) addStatementBtn.addEventListener('click', addNewStatement);
|
||||||
saveConfigBtn.addEventListener('click', saveConfig);
|
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
|
// Keyboard shortcuts
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
|
|
@ -230,8 +431,8 @@ document.addEventListener('keydown', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
|
// Escape sluit panel
|
||||||
|
if (e.key === 'Escape' && stellingkastPanel && stellingkastPanel.classList.contains('open')) {
|
||||||
|
stellingkastPanel.classList.remove('open');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize
|
|
||||||
syncColorInputs();
|
|
||||||
loadConfig();
|
|
||||||
|
|
|
||||||
20
gemini.md
20
gemini.md
|
|
@ -1,7 +1,6 @@
|
||||||
# gemini.md
|
# gemini.md
|
||||||
> Deze file wordt automatisch gesynchroniseerd met CLAUDE.md
|
> Deze file wordt automatisch gesynchroniseerd met CLAUDE.md
|
||||||
> Laatste sync: 2025-11-14 21:45
|
> Laatste sync: 2025-12-12 08:35
|
||||||
|
|
||||||
# IJsbreker Workshop Spel
|
# IJsbreker Workshop Spel
|
||||||
|
|
||||||
Browser-based interactief workshop spel waarbij deelnemers fysiek kiezen tussen twee stellingen die op een beamer worden getoond.
|
Browser-based interactief workshop spel waarbij deelnemers fysiek kiezen tussen twee stellingen die op een beamer worden getoond.
|
||||||
|
|
@ -156,3 +155,20 @@ Tijdens gebruik bleek de knop onderaan moeilijk leesbaar door de overlay, en de
|
||||||
- Alle wijzigingen getest en werkend
|
- Alle wijzigingen getest en werkend
|
||||||
- Gecommit en gepusht naar remote (commit e1f1659)
|
- Gecommit en gepusht naar remote (commit e1f1659)
|
||||||
- UX verbeterd voor workshop gebruik
|
- UX verbeterd voor workshop gebruik
|
||||||
|
|
||||||
|
### 2025-12-12 - Stellingkast en Drag & Drop
|
||||||
|
**Nieuwe features:**
|
||||||
|
- **Stellingkast:** Nieuw inschuif-paneel in de editor om direct stellingen uit een bibliotheek (`stellingkast.json`) te importeren.
|
||||||
|
- **Drag & Drop:** Stellingen kunnen nu in de editor versleept worden om de volgorde aan te passen.
|
||||||
|
- **Cache Busting:** Toegevoegd aan fetch calls om laadproblemen met verouderde JSON bestanden te voorkomen.
|
||||||
|
- **Validatie:** `stellingkast.json` is opgeschoond en bevat nu valide JSON.
|
||||||
|
|
||||||
|
**Gewijzigde bestanden:**
|
||||||
|
- `editor.html` - HTML structuur voor Stellingkast panel en nieuwe header knop
|
||||||
|
- `editor.css` - Styling voor slide-in panel, sleep-handvatten en responsive design
|
||||||
|
- `editor.js` - Logica voor drag & drop, stellingkast import, verbeterde foutafhandeling en loading states
|
||||||
|
- `stellingkast.json` - Opgeschoond (regelnummers verwijderd)
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
- Features volledig werkend en getest in browser
|
||||||
|
- Editor robuuster gemaakt tegen laadfouten
|
||||||
202
stellingkast.json
Normal file
202
stellingkast.json
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"links": "Inbox Zero held",
|
||||||
|
"rechts": "1000+ ongelezen mails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Mappenstructuur",
|
||||||
|
"rechts": "Zoekfunctie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "Direct archiveren",
|
||||||
|
"rechts": "Dat zoek ik later wel uit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Notificaties staan altijd uit",
|
||||||
|
"rechts": "Ik reageer direct op elke 'pling'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik deel ook onaf werk (WIP)",
|
||||||
|
"rechts": "Ik deel pas als het perfect is"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Eén groot beeldscherm",
|
||||||
|
"rechts": "Twee (of meer) schermen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Microsoft OneNote / Teams",
|
||||||
|
"rechts": "Obsidian / Notion / Roam"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Alles in de cloud",
|
||||||
|
"rechts": "Lokaal op mijn bureaublad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik lees uitgebreide stukken",
|
||||||
|
"rechts": "Geef mij maar een samenvatting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik gebruik tags en labels",
|
||||||
|
"rechts": "Ik stop alles in mapjes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "AI helpt me dagelijks (ChatGPT e.d.)",
|
||||||
|
"rechts": "Mag dat wel van de IT-afdeling?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Browser met 50+ open tabbladen",
|
||||||
|
"rechts": "Opgeruimde browser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Vergaderen is werken",
|
||||||
|
"rechts": "Vergaderen houdt me van mijn werk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Kennis zit in mijn hoofd",
|
||||||
|
"rechts": "Kennis staat in het systeem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik noteer bronnen meteen",
|
||||||
|
"rechts": "Waar had ik dat ook alweer gelezen?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Linkjes delen",
|
||||||
|
"rechts": "Bijlagen sturen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Structurele planner",
|
||||||
|
"rechts": "Creatieve chaoot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik leer door te lezen",
|
||||||
|
"rechts": "Ik leer door te doen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Mijn 'Te Lezen' lijst wordt korter",
|
||||||
|
"rechts": "Mijn 'Te Lezen' lijst is een kerkhof"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik kom vandaag vooral halen",
|
||||||
|
"rechts": "Ik kom vandaag ook brengen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Digitale agenda",
|
||||||
|
"rechts": "Papieren agenda"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Inbox zero is het doel",
|
||||||
|
"rechts": "Inbox zero is een mythe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik hou van een opgeruimd bureaublad",
|
||||||
|
"rechts": "Chaos is mijn systeem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik heb één centrale werkplek",
|
||||||
|
"rechts": "Ik werk waar het nodig is"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Structuur eerst, dan content",
|
||||||
|
"rechts": "Content eerst, structuur later"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Vergadernotulen zijn heilig",
|
||||||
|
"rechts": "Wie leest die notulen nou?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Mijn mappenstructuur is doordacht",
|
||||||
|
"rechts": "Ik gebruik vooral de zoekfunctie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik lees vergaderstukken vooraf",
|
||||||
|
"rechts": "Tijdens de vergadering is vroeg genoeg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Kennisdeling is prioriteit",
|
||||||
|
"rechts": "Kennisdeling kost te veel tijd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Taggen van documenten helpt echt",
|
||||||
|
"rechts": "Taggen is extra werk zonder meerwaarde"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Samenwerken in gedeelde documenten",
|
||||||
|
"rechts": "Liever versies heen en weer mailen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik neem notities tijdens vergaderingen",
|
||||||
|
"rechts": "Ik vertrouw op mijn geheugen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ons intranet is nuttig",
|
||||||
|
"rechts": "Ons intranet is een zwart gat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Afspraken vastleggen in het systeem",
|
||||||
|
"rechts": "Afspraken blijven in mijn hoofd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik archiveer regelmatig",
|
||||||
|
"rechts": "Alles staat er nog, voor de zekerheid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Liever te veel documenteren",
|
||||||
|
"rechts": "Liever te weinig documenteren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Workflows zijn nuttig",
|
||||||
|
"rechts": "Workflows remmen creativiteit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Digitaal ondertekenen is handig",
|
||||||
|
"rechts": "Geef mij maar papier en pen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "We delen kennis goed binnen het team",
|
||||||
|
"rechts": "Kennis zit vooral in hoofden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Mijn TO-DO lijst is actueel",
|
||||||
|
"rechts": "Mijn TO-DO lijst is fictie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik weet wat collega's aan het doen zijn",
|
||||||
|
"rechts": "Ik heb geen idee waar anderen mee bezig zijn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Minder vergaderen, meer documenteren",
|
||||||
|
"rechts": "Beter overleggen dan documenteren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Ik vertrouw op automatische backups",
|
||||||
|
"rechts": "Ik maak liever zelf kopieën"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Cloud-opslag voor alles",
|
||||||
|
"rechts": "Lokaal opslaan blijft veiliger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"links": "Onze afspraken over informatie werken",
|
||||||
|
"rechts": "Iedereen doet toch maar wat"
|
||||||
|
}
|
||||||
|
]
|
||||||
Loading…
Reference in a new issue