feat: user preferences sync — server-side storage, whiteboard defaults, dashboard widget visibility
- New table `user_preferences` (user_id PK, JSON blob, updated_at) - GET/PATCH/DELETE /api/preferences with deep-merge UPSERT - LS.prefs singleton in api.js: dot-notation get/set, debounced flush (1.5s), server sync - classroom.html: load wb.color/width/lineStyle/theme from prefs on init; save on change - dashboard.html: widget configurator panel (gear button) — toggle visibility per-user, persisted server-side Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4072,6 +4072,36 @@
|
||||
// Sync board theme selector to whiteboard default
|
||||
{ const sel = document.getElementById('wb-theme-select'); if (sel) sel.value = 'chalkboard'; }
|
||||
|
||||
// Apply saved whiteboard defaults from user preferences
|
||||
if (canEdit && LS.prefs) {
|
||||
LS.prefs.init().then(() => {
|
||||
const wb = LS.prefs.get('wb', {});
|
||||
if (wb.color) {
|
||||
_wb.setColor(wb.color);
|
||||
document.querySelectorAll('.cr-color-btn').forEach(b => b.classList.remove('active'));
|
||||
const match = document.querySelector(`.cr-color-btn[data-color="${wb.color}"]`);
|
||||
if (match) match.classList.add('active');
|
||||
}
|
||||
if (wb.width) {
|
||||
_wb.setWidth(wb.width);
|
||||
document.querySelectorAll('.cr-width-btn').forEach(b => b.classList.remove('active'));
|
||||
const wBtn = document.getElementById(`wb-w${wb.width}`);
|
||||
if (wBtn) wBtn.classList.add('active');
|
||||
}
|
||||
if (wb.lineStyle) {
|
||||
_wb.setLineStyle(wb.lineStyle);
|
||||
document.querySelectorAll('.cr-linestyle-btn').forEach(b => b.classList.remove('active'));
|
||||
const lBtn = document.getElementById(`wb-ls-${wb.lineStyle}`);
|
||||
if (lBtn) lBtn.classList.add('active');
|
||||
}
|
||||
if (wb.theme) {
|
||||
_wb.setBoardTheme(wb.theme);
|
||||
const sel = document.getElementById('wb-theme-select');
|
||||
if (sel) sel.value = wb.theme;
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// show/hide toolbar, thumbs panel, and readonly label
|
||||
document.getElementById('cr-toolbar').style.display = canEdit ? 'flex' : 'none';
|
||||
document.getElementById('wb-thumbs-panel').style.display = isTeacher ? 'flex' : 'none';
|
||||
@@ -4817,6 +4847,7 @@
|
||||
if (activeTools.some(t => document.getElementById(`wb-tool-${t}`)?.classList.contains('active')))
|
||||
wbSetTool('pencil');
|
||||
else wbUpdateCursorStyle();
|
||||
if (LS.prefs) LS.prefs.set('wb.color', color);
|
||||
}
|
||||
|
||||
function wbSetWidth(px, btn) {
|
||||
@@ -4825,6 +4856,7 @@
|
||||
document.querySelectorAll('.cr-width-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
wbUpdateCursorStyle();
|
||||
if (LS.prefs) LS.prefs.set('wb.width', px);
|
||||
}
|
||||
|
||||
function wbSetLineStyle(style, btn) {
|
||||
@@ -4832,6 +4864,7 @@
|
||||
_wb.setLineStyle(style);
|
||||
document.querySelectorAll('.cr-linestyle-btn').forEach(b => b.classList.remove('active'));
|
||||
if (btn) btn.classList.add('active');
|
||||
if (LS.prefs) LS.prefs.set('wb.lineStyle', style);
|
||||
}
|
||||
|
||||
function wbSetOpacity(val) {
|
||||
@@ -5478,6 +5511,7 @@
|
||||
// Update selector if called programmatically
|
||||
const sel = document.getElementById('wb-theme-select');
|
||||
if (sel) sel.value = theme;
|
||||
if (LS.prefs) LS.prefs.set('wb.theme', theme);
|
||||
}
|
||||
|
||||
/* ── Page context menu ── */
|
||||
@@ -6434,6 +6468,7 @@
|
||||
setTimeout(() => { if (input) input.style.outline = ''; }, 300);
|
||||
if (['eraser','laser'].some(t => document.getElementById(`wb-tool-${t}`)?.classList.contains('active')))
|
||||
wbSetTool('pencil');
|
||||
if (LS.prefs) LS.prefs.set('wb.color', input.value);
|
||||
}
|
||||
|
||||
function wbToggleFullscreen() {
|
||||
|
||||
Reference in New Issue
Block a user