feat: Complete tablet UI overhaul with popup menu system and help bubble
Major Changes: - 🪟 Popup Menu System: Converted slide-down navigation to draggable, minimizable popup window - ❓ Help Bubble: Added contextual help system for admin dashboard (accessible via ? bubble) - 🎨 Universal Theme Engine: All elements now properly match selected theme across all panels - 📱 Enhanced Tablet Support: Comprehensive touch gestures, handwriting recognition, and Apple Pencil optimization - 🖊️ Unified Write Mode: Combined handwriting and keyboard input with large canvas interface Technical Implementation: - Draggable popup with minimize/restore functionality and boundary constraints - Dynamic theme switching using CSS variables throughout all components - Help system with modal overlay and keyboard shortcuts (ESC to close) - Responsive design optimizations for mobile, tablet, and desktop - Comprehensive CSS overrides to eliminate hardcoded colors - JavaScript fixes for canvas stroke colors and input styling UI/UX Improvements: - Clean admin dashboard without instruction clutter - Professional popup interface with smooth animations - Better contrast and readability across all themes (default, playground, chalkboard, galaxy, nature) - Improved accessibility with proper focus management - Enhanced mobile responsiveness with touch-optimized controls Files Modified: - Menu system: tablet-menu.js, tablet-menu.css - Theme engine: styles.css, themes integration - Admin interface: index.html, ui.js - Input systems: game.js, unified-write.js, handwriting.js - Authentication: auth.js with tablet menu cleanup
This commit is contained in:
1 parent
0b9d46fed1
commit
8d37038177
24 files changed
+9506
-126
No files matched your search
@@ -0,0 +1,905 @@
|
||||
/**
|
||||
* Tablet Menu Management System
|
||||
* Handles collapsible navigation, touch gestures, and optimized menu interactions for tablets
|
||||
*/
|
||||
|
||||
class TabletMenuManager {
|
||||
constructor() {
|
||||
this.isMenuExpanded = false;
|
||||
this.isMinimized = false;
|
||||
this.isWritingActive = false;
|
||||
this.touchStartX = 0;
|
||||
this.touchStartY = 0;
|
||||
this.swipeThreshold = 100;
|
||||
this.isDragging = false;
|
||||
this.dragOffset = { x: 0, y: 0 };
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createMenuToggle();
|
||||
this.createMenuPopup();
|
||||
this.setupEventListeners();
|
||||
this.setupPopupDragging();
|
||||
this.setupHelpBubble();
|
||||
this.optimizeAdminTabs();
|
||||
this.createQuickActions();
|
||||
this.setupWritingAreaDetection();
|
||||
|
||||
console.log('📱 Universal Popup Menu Manager initialized');
|
||||
}
|
||||
|
||||
isTabletDevice() {
|
||||
// Check for tablet characteristics
|
||||
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
const screenSize = window.innerWidth >= 600 && window.innerWidth <= 1200;
|
||||
const isLandscape = window.innerWidth > window.innerHeight;
|
||||
const aspectRatio = window.innerWidth / window.innerHeight;
|
||||
|
||||
return hasTouch && (screenSize || (isLandscape && aspectRatio < 2));
|
||||
}
|
||||
|
||||
createMenuToggle() {
|
||||
// Remove existing toggle if present
|
||||
const existingToggle = document.querySelector('.menu-toggle');
|
||||
if (existingToggle) existingToggle.remove();
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.className = 'menu-toggle';
|
||||
toggle.innerHTML = '☰';
|
||||
toggle.setAttribute('aria-label', 'Toggle Navigation Menu');
|
||||
toggle.addEventListener('click', () => this.toggleMenu());
|
||||
|
||||
document.body.appendChild(toggle);
|
||||
this.menuToggle = toggle;
|
||||
}
|
||||
|
||||
createMenuPopup() {
|
||||
// Remove existing popup if present
|
||||
const existingPopup = document.querySelector('.menu-popup');
|
||||
if (existingPopup) existingPopup.remove();
|
||||
|
||||
// Create popup container
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'menu-popup hidden';
|
||||
popup.id = 'menuPopup';
|
||||
|
||||
// Create popup header
|
||||
const header = document.createElement('div');
|
||||
header.className = 'popup-header';
|
||||
header.innerHTML = `
|
||||
<div class="popup-title">
|
||||
<span class="popup-icon">📋</span>
|
||||
<span>Admin Functions</span>
|
||||
</div>
|
||||
<div class="popup-controls">
|
||||
<button class="minimize-btn" title="Minimize">−</button>
|
||||
<button class="close-btn" title="Close">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Create popup content (will be populated by existing nav content)
|
||||
const content = document.createElement('div');
|
||||
content.className = 'popup-content';
|
||||
content.id = 'popupContent';
|
||||
|
||||
popup.appendChild(header);
|
||||
popup.appendChild(content);
|
||||
document.body.appendChild(popup);
|
||||
|
||||
this.menuPopup = popup;
|
||||
this.setupPopupControls();
|
||||
}
|
||||
|
||||
setupPopupControls() {
|
||||
const minimizeBtn = this.menuPopup.querySelector('.minimize-btn');
|
||||
const closeBtn = this.menuPopup.querySelector('.close-btn');
|
||||
|
||||
minimizeBtn.addEventListener('click', () => this.minimizePopup());
|
||||
closeBtn.addEventListener('click', () => this.closePopup());
|
||||
}
|
||||
|
||||
setupPopupDragging() {
|
||||
const header = this.menuPopup.querySelector('.popup-header');
|
||||
|
||||
header.addEventListener('mousedown', (e) => this.startDrag(e));
|
||||
header.addEventListener('touchstart', (e) => this.startDrag(e), { passive: false });
|
||||
|
||||
document.addEventListener('mousemove', (e) => this.onDrag(e));
|
||||
document.addEventListener('touchmove', (e) => this.onDrag(e), { passive: false });
|
||||
|
||||
document.addEventListener('mouseup', () => this.endDrag());
|
||||
document.addEventListener('touchend', () => this.endDrag());
|
||||
}
|
||||
|
||||
startDrag(e) {
|
||||
this.isDragging = true;
|
||||
const rect = this.menuPopup.getBoundingClientRect();
|
||||
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
|
||||
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
|
||||
|
||||
this.dragOffset.x = clientX - rect.left;
|
||||
this.dragOffset.y = clientY - rect.top;
|
||||
|
||||
this.menuPopup.style.cursor = 'grabbing';
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onDrag(e) {
|
||||
if (!this.isDragging) return;
|
||||
|
||||
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
|
||||
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
|
||||
|
||||
const newX = clientX - this.dragOffset.x;
|
||||
const newY = clientY - this.dragOffset.y;
|
||||
|
||||
// Keep popup within viewport bounds
|
||||
const maxX = window.innerWidth - this.menuPopup.offsetWidth;
|
||||
const maxY = window.innerHeight - this.menuPopup.offsetHeight;
|
||||
|
||||
const constrainedX = Math.max(0, Math.min(newX, maxX));
|
||||
const constrainedY = Math.max(0, Math.min(newY, maxY));
|
||||
|
||||
this.menuPopup.style.left = constrainedX + 'px';
|
||||
this.menuPopup.style.top = constrainedY + 'px';
|
||||
this.menuPopup.style.transform = 'none';
|
||||
}
|
||||
|
||||
endDrag() {
|
||||
this.isDragging = false;
|
||||
this.menuPopup.style.cursor = 'default';
|
||||
}
|
||||
|
||||
minimizePopup() {
|
||||
this.isMinimized = !this.isMinimized;
|
||||
const content = this.menuPopup.querySelector('.popup-content');
|
||||
const minimizeBtn = this.menuPopup.querySelector('.minimize-btn');
|
||||
|
||||
if (this.isMinimized) {
|
||||
content.style.display = 'none';
|
||||
this.menuPopup.classList.add('minimized');
|
||||
minimizeBtn.innerHTML = '+';
|
||||
minimizeBtn.title = 'Restore';
|
||||
} else {
|
||||
content.style.display = 'block';
|
||||
this.menuPopup.classList.remove('minimized');
|
||||
minimizeBtn.innerHTML = '−';
|
||||
minimizeBtn.title = 'Minimize';
|
||||
}
|
||||
}
|
||||
|
||||
closePopup() {
|
||||
this.menuPopup.classList.add('hidden');
|
||||
this.menuToggle.classList.remove('active');
|
||||
this.menuToggle.innerHTML = '☰';
|
||||
this.isMenuExpanded = false;
|
||||
this.isMinimized = false;
|
||||
this.showToast('Menu closed');
|
||||
}
|
||||
|
||||
setupHelpBubble() {
|
||||
const helpBubble = document.getElementById('adminHelpBubble');
|
||||
if (!helpBubble) return;
|
||||
|
||||
helpBubble.addEventListener('click', () => this.showHelpPopup());
|
||||
|
||||
// Only show help bubble for admin users
|
||||
this.updateHelpBubbleVisibility();
|
||||
}
|
||||
|
||||
updateHelpBubbleVisibility() {
|
||||
const helpBubble = document.getElementById('adminHelpBubble');
|
||||
const adminPanel = document.getElementById('adminPanel');
|
||||
|
||||
if (helpBubble && adminPanel) {
|
||||
if (!adminPanel.classList.contains('hidden')) {
|
||||
helpBubble.style.display = 'flex';
|
||||
} else {
|
||||
helpBubble.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showHelpPopup() {
|
||||
// Create overlay if it doesn't exist
|
||||
let overlay = document.getElementById('helpOverlay');
|
||||
if (!overlay) {
|
||||
overlay = document.createElement('div');
|
||||
overlay.id = 'helpOverlay';
|
||||
overlay.className = 'help-overlay';
|
||||
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'help-popup';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'help-popup-header';
|
||||
header.innerHTML = `
|
||||
<div class="help-popup-title">
|
||||
<span>📱</span>
|
||||
<span>Admin Functions Guide</span>
|
||||
</div>
|
||||
<button class="help-close-btn" onclick="window.tabletMenuManager.closeHelpPopup()">×</button>
|
||||
`;
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'help-popup-content';
|
||||
|
||||
// Copy content from hidden instructions
|
||||
const helpContent = document.getElementById('adminHelpContent');
|
||||
if (helpContent) {
|
||||
content.innerHTML = helpContent.innerHTML;
|
||||
}
|
||||
|
||||
popup.appendChild(header);
|
||||
popup.appendChild(content);
|
||||
overlay.appendChild(popup);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Close on overlay click
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
this.closeHelpPopup();
|
||||
}
|
||||
});
|
||||
|
||||
// Close on escape key
|
||||
const handleEscape = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.closeHelpPopup();
|
||||
document.removeEventListener('keydown', handleEscape);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
}
|
||||
|
||||
// Show overlay
|
||||
setTimeout(() => {
|
||||
overlay.classList.add('visible');
|
||||
}, 10);
|
||||
|
||||
this.showToast('Help opened');
|
||||
}
|
||||
|
||||
closeHelpPopup() {
|
||||
const overlay = document.getElementById('helpOverlay');
|
||||
if (overlay) {
|
||||
overlay.classList.remove('visible');
|
||||
setTimeout(() => {
|
||||
overlay.remove();
|
||||
}, 300);
|
||||
}
|
||||
this.showToast('Help closed');
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.isMenuExpanded) {
|
||||
this.closeMenu();
|
||||
}
|
||||
if (e.ctrlKey && e.key === 'm') {
|
||||
e.preventDefault();
|
||||
this.toggleMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Click outside to close menu
|
||||
document.addEventListener('click', (e) => {
|
||||
const nav = document.getElementById('nav');
|
||||
if (this.isMenuExpanded &&
|
||||
!nav.contains(e.target) &&
|
||||
!this.menuToggle.contains(e.target)) {
|
||||
this.closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Orientation change handling
|
||||
window.addEventListener('orientationchange', () => {
|
||||
setTimeout(() => this.handleOrientationChange(), 100);
|
||||
});
|
||||
|
||||
// Resize handling
|
||||
window.addEventListener('resize', () => {
|
||||
this.handleResize();
|
||||
});
|
||||
}
|
||||
|
||||
setupSwipeGestures() {
|
||||
let startX, startY, startTime;
|
||||
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
if (e.touches.length === 1) {
|
||||
startX = e.touches[0].clientX;
|
||||
startY = e.touches[0].clientY;
|
||||
startTime = Date.now();
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchend', (e) => {
|
||||
if (e.changedTouches.length === 1) {
|
||||
const endX = e.changedTouches[0].clientX;
|
||||
const endY = e.changedTouches[0].clientY;
|
||||
const endTime = Date.now();
|
||||
|
||||
const deltaX = endX - startX;
|
||||
const deltaY = endY - startY;
|
||||
const deltaTime = endTime - startTime;
|
||||
|
||||
// Swipe detection
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) &&
|
||||
Math.abs(deltaX) > this.swipeThreshold &&
|
||||
deltaTime < 500) {
|
||||
|
||||
if (deltaX > 0 && startX < 50) {
|
||||
// Swipe right from left edge - open menu
|
||||
this.openMenu();
|
||||
} else if (deltaX < 0 && this.isMenuExpanded) {
|
||||
// Swipe left when menu open - close menu
|
||||
this.closeMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
optimizeAdminTabs() {
|
||||
const adminTabs = document.getElementById('adminTabs');
|
||||
if (!adminTabs) return;
|
||||
|
||||
// Add scroll indicators
|
||||
this.addScrollIndicators(adminTabs);
|
||||
|
||||
// Smooth scrolling for tab navigation
|
||||
adminTabs.addEventListener('wheel', (e) => {
|
||||
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
|
||||
e.preventDefault();
|
||||
adminTabs.scrollLeft += e.deltaY;
|
||||
}, { passive: false });
|
||||
|
||||
// Center active tab
|
||||
const activeTab = adminTabs.querySelector('button.active');
|
||||
if (activeTab) {
|
||||
this.scrollToTab(activeTab);
|
||||
}
|
||||
}
|
||||
|
||||
addScrollIndicators(container) {
|
||||
const leftIndicator = document.createElement('div');
|
||||
leftIndicator.className = 'scroll-indicator left';
|
||||
leftIndicator.innerHTML = '‹';
|
||||
|
||||
const rightIndicator = document.createElement('div');
|
||||
rightIndicator.className = 'scroll-indicator right';
|
||||
rightIndicator.innerHTML = '›';
|
||||
|
||||
container.parentNode.insertBefore(leftIndicator, container);
|
||||
container.parentNode.appendChild(rightIndicator);
|
||||
|
||||
// Update indicators on scroll
|
||||
const updateIndicators = () => {
|
||||
leftIndicator.style.opacity = container.scrollLeft > 0 ? '1' : '0.3';
|
||||
rightIndicator.style.opacity =
|
||||
container.scrollLeft < container.scrollWidth - container.clientWidth ? '1' : '0.3';
|
||||
};
|
||||
|
||||
container.addEventListener('scroll', updateIndicators);
|
||||
updateIndicators();
|
||||
|
||||
// Click handlers
|
||||
leftIndicator.addEventListener('click', () => {
|
||||
container.scrollBy({ left: -120, behavior: 'smooth' });
|
||||
});
|
||||
|
||||
rightIndicator.addEventListener('click', () => {
|
||||
container.scrollBy({ left: 120, behavior: 'smooth' });
|
||||
});
|
||||
}
|
||||
|
||||
scrollToTab(tab) {
|
||||
const container = tab.parentNode;
|
||||
const tabRect = tab.getBoundingClientRect();
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
if (tabRect.left < containerRect.left || tabRect.right > containerRect.right) {
|
||||
tab.scrollIntoView({ behavior: 'smooth', inline: 'center' });
|
||||
}
|
||||
}
|
||||
|
||||
createQuickActions() {
|
||||
// Remove existing quick actions
|
||||
const existing = document.querySelector('.quick-actions');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const quickActions = document.createElement('div');
|
||||
quickActions.className = 'quick-actions';
|
||||
|
||||
// Back to top button
|
||||
const backToTop = document.createElement('button');
|
||||
backToTop.className = 'quick-action-btn secondary';
|
||||
backToTop.innerHTML = '↑';
|
||||
backToTop.title = 'Back to Top';
|
||||
backToTop.addEventListener('click', () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
|
||||
quickActions.appendChild(backToTop);
|
||||
|
||||
// Show/hide based on scroll position
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset > 200;
|
||||
backToTop.style.opacity = scrolled ? '1' : '0';
|
||||
backToTop.style.pointerEvents = scrolled ? 'auto' : 'none';
|
||||
});
|
||||
|
||||
document.body.appendChild(quickActions);
|
||||
}
|
||||
|
||||
setupWritingAreaDetection() {
|
||||
// Detect when user is actively writing
|
||||
const writingAreas = [
|
||||
'#unifiedWriteContainer canvas',
|
||||
'#handwritingCanvas',
|
||||
'#userInput'
|
||||
];
|
||||
|
||||
writingAreas.forEach(selector => {
|
||||
const element = document.querySelector(selector);
|
||||
if (element) {
|
||||
element.addEventListener('focus', () => this.setWritingMode(true));
|
||||
element.addEventListener('blur', () => this.setWritingMode(false));
|
||||
|
||||
if (element.tagName === 'CANVAS') {
|
||||
element.addEventListener('pointerdown', () => this.setWritingMode(true));
|
||||
element.addEventListener('touchstart', () => this.setWritingMode(true));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-close menu when writing starts
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
const isWritingArea = writingAreas.some(selector => {
|
||||
const element = document.querySelector(selector);
|
||||
return element && element.contains(e.target);
|
||||
});
|
||||
|
||||
if (isWritingArea && this.isMenuExpanded) {
|
||||
setTimeout(() => this.closeMenu(), 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setWritingMode(active) {
|
||||
this.isWritingActive = active;
|
||||
document.body.classList.toggle('writing-focused', active);
|
||||
|
||||
if (active && this.isMenuExpanded) {
|
||||
this.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
if (this.isMenuExpanded) {
|
||||
this.closePopup();
|
||||
} else {
|
||||
this.openPopup();
|
||||
}
|
||||
}
|
||||
|
||||
openPopup() {
|
||||
// Move navigation content to popup
|
||||
this.populatePopupContent();
|
||||
|
||||
// Show popup
|
||||
this.menuPopup.classList.remove('hidden');
|
||||
this.menuToggle.classList.add('active');
|
||||
this.menuToggle.innerHTML = '✕';
|
||||
|
||||
// Position popup in center initially
|
||||
if (!this.menuPopup.style.left || !this.menuPopup.style.top) {
|
||||
this.centerPopup();
|
||||
}
|
||||
|
||||
this.isMenuExpanded = true;
|
||||
}
|
||||
|
||||
populatePopupContent() {
|
||||
const content = this.menuPopup.querySelector('.popup-content');
|
||||
const nav = document.getElementById('nav');
|
||||
|
||||
if (nav && content) {
|
||||
// Clone the navigation content
|
||||
content.innerHTML = nav.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
centerPopup() {
|
||||
const rect = this.menuPopup.getBoundingClientRect();
|
||||
const x = (window.innerWidth - rect.width) / 2;
|
||||
const y = (window.innerHeight - rect.height) / 2;
|
||||
|
||||
this.menuPopup.style.left = Math.max(20, x) + 'px';
|
||||
this.menuPopup.style.top = Math.max(20, y) + 'px';
|
||||
this.menuPopup.style.transform = 'none';
|
||||
|
||||
this.showToast('Menu opened');
|
||||
|
||||
// Focus first interactive element
|
||||
setTimeout(() => {
|
||||
const firstButton = this.menuPopup.querySelector('button');
|
||||
if (firstButton) firstButton.focus();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
// Keep this method for backward compatibility
|
||||
this.closePopup();
|
||||
}
|
||||
|
||||
showToast(message, duration = 2000) {
|
||||
// Remove existing toasts
|
||||
document.querySelectorAll('.toast').forEach(toast => toast.remove());
|
||||
|
||||
// Create toast container if needed
|
||||
let container = document.querySelector('.toast-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.className = 'toast-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Create toast
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.textContent = message;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
// Animate in
|
||||
setTimeout(() => toast.classList.add('show'), 100);
|
||||
|
||||
// Auto remove
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
handleOrientationChange() {
|
||||
const nav = document.getElementById('nav');
|
||||
if (!nav) return;
|
||||
|
||||
const isLandscape = window.innerWidth > window.innerHeight;
|
||||
|
||||
if (isLandscape && window.innerWidth >= 900) {
|
||||
nav.classList.add('landscape');
|
||||
document.body.classList.toggle('landscape-nav-expanded', this.isMenuExpanded);
|
||||
} else {
|
||||
nav.classList.remove('landscape');
|
||||
document.body.classList.remove('landscape-nav-expanded');
|
||||
}
|
||||
|
||||
// Reoptimize admin tabs
|
||||
setTimeout(() => this.optimizeAdminTabs(), 100);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
if (!this.isTabletDevice() && this.isMenuExpanded) {
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
// Re-evaluate tablet status
|
||||
if (this.isTabletDevice() && !this.menuToggle) {
|
||||
this.createMenuToggle();
|
||||
} else if (!this.isTabletDevice() && this.menuToggle) {
|
||||
this.menuToggle.remove();
|
||||
this.menuToggle = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced navigation functions
|
||||
updateUserInfo() {
|
||||
const userInfo = document.getElementById('userInfo');
|
||||
const userBadge = document.getElementById('userBadge');
|
||||
|
||||
if (window.currentUser) {
|
||||
const { username, role } = window.currentUser;
|
||||
|
||||
// Don't override if userInfo already has content from main UI
|
||||
if (userInfo && !userInfo.textContent.includes('(')) {
|
||||
userInfo.textContent = `Welcome, ${username}`;
|
||||
}
|
||||
|
||||
if (userBadge) {
|
||||
userBadge.textContent = role === 'admin' ? '👨💼 Admin' : '🎓 Student';
|
||||
userBadge.style.background = role === 'admin' ? 'var(--error)' : 'var(--primary)';
|
||||
}
|
||||
|
||||
this.showRoleSpecificMenu(role);
|
||||
}
|
||||
}
|
||||
|
||||
showRoleSpecificMenu(role) {
|
||||
const studentNav = document.getElementById('studentNavItems');
|
||||
const adminNav = document.getElementById('adminNavItems');
|
||||
|
||||
if (role === 'admin') {
|
||||
if (studentNav) studentNav.classList.add('hidden');
|
||||
if (adminNav) adminNav.classList.remove('hidden');
|
||||
} else {
|
||||
if (adminNav) adminNav.classList.add('hidden');
|
||||
if (studentNav) studentNav.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Public API methods
|
||||
isMenuOpen() {
|
||||
return this.isMenuExpanded;
|
||||
}
|
||||
|
||||
forceCloseMenu() {
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
updateNavigation() {
|
||||
this.updateUserInfo();
|
||||
}
|
||||
|
||||
setTabActive(tabId) {
|
||||
const adminTabs = document.getElementById('adminTabs');
|
||||
if (!adminTabs) return;
|
||||
|
||||
const tab = adminTabs.querySelector(`[data-tab="${tabId}"]`);
|
||||
if (tab) {
|
||||
// Remove active class from all tabs
|
||||
adminTabs.querySelectorAll('button').forEach(btn => btn.classList.remove('active'));
|
||||
|
||||
// Add active class to selected tab
|
||||
tab.classList.add('active');
|
||||
|
||||
// Scroll to active tab
|
||||
this.scrollToTab(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize tablet menu manager when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Wait for other systems to initialize
|
||||
setTimeout(() => {
|
||||
try {
|
||||
window.tabletMenuManager = new TabletMenuManager();
|
||||
} catch (error) {
|
||||
console.warn('Tablet Menu Manager failed to initialize:', error);
|
||||
}
|
||||
}, 1000); // Increased delay to ensure other systems are ready
|
||||
});
|
||||
|
||||
// Handle admin tab clicks to update active state
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.matches('#adminTabs button[data-tab]')) {
|
||||
if (window.tabletMenuManager) {
|
||||
window.tabletMenuManager.setTabActive(e.target.dataset.tab);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions for enhanced navigation
|
||||
function closeMenuIfTablet() {
|
||||
if (window.tabletMenuManager && window.tabletMenuManager.isMenuOpen()) {
|
||||
window.tabletMenuManager.forceCloseMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function showAdminTab(tabId) {
|
||||
// Show admin panel first
|
||||
if (window.showAdmin) {
|
||||
window.showAdmin();
|
||||
}
|
||||
|
||||
// Then activate the specific tab
|
||||
setTimeout(() => {
|
||||
// Hide the dashboard overview
|
||||
const adminOverview = document.querySelector('.admin-overview');
|
||||
const menuInstructions = document.querySelector('.menu-instructions');
|
||||
if (adminOverview) adminOverview.style.display = 'none';
|
||||
if (menuInstructions) menuInstructions.style.display = 'none';
|
||||
|
||||
// Don't show the old admin tabs interface - keep it hidden
|
||||
const adminTabsContainer = document.querySelector('#adminTabs').parentElement;
|
||||
if (adminTabsContainer) {
|
||||
adminTabsContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
// Show the specific admin tab content directly
|
||||
document.querySelectorAll('.adminTab').forEach(tab => {
|
||||
tab.classList.add('hidden');
|
||||
});
|
||||
|
||||
const targetTab = document.getElementById(tabId);
|
||||
if (targetTab) {
|
||||
targetTab.classList.remove('hidden');
|
||||
targetTab.style.display = 'block';
|
||||
}
|
||||
|
||||
// Add a back to dashboard button
|
||||
addBackToDashboardButton();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function showChallenges() {
|
||||
// Navigate to student dashboard first
|
||||
if (window.showStudent) {
|
||||
window.showStudent();
|
||||
}
|
||||
|
||||
// Scroll to challenges section
|
||||
setTimeout(() => {
|
||||
const challengesContainer = document.getElementById('challengesContainer');
|
||||
if (challengesContainer) {
|
||||
challengesContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function showLeaderboards() {
|
||||
// Navigate to student dashboard first
|
||||
if (window.showStudent) {
|
||||
window.showStudent();
|
||||
}
|
||||
|
||||
// Scroll to leaderboards section
|
||||
setTimeout(() => {
|
||||
const leaderboardsContainer = document.getElementById('leaderboardsContainer');
|
||||
if (leaderboardsContainer) {
|
||||
leaderboardsContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function showBadges() {
|
||||
// Navigate to student dashboard first
|
||||
if (window.showStudent) {
|
||||
window.showStudent();
|
||||
}
|
||||
|
||||
// Scroll to user stats which includes badges
|
||||
setTimeout(() => {
|
||||
const userStatsContainer = document.getElementById('userStatsContainer');
|
||||
if (userStatsContainer) {
|
||||
userStatsContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function showThemeSelector() {
|
||||
// Navigate to student dashboard first
|
||||
if (window.showStudent) {
|
||||
window.showStudent();
|
||||
}
|
||||
|
||||
// Focus on theme selector
|
||||
setTimeout(() => {
|
||||
const themeSelect = document.getElementById('studentThemeSelect');
|
||||
if (themeSelect) {
|
||||
themeSelect.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
themeSelect.focus();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function showInputSettings() {
|
||||
// Show unified write mode if available
|
||||
if (window.unifiedWriteMode) {
|
||||
// Navigate to student dashboard first
|
||||
if (window.showStudent) {
|
||||
window.showStudent();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
// Start a game to show the input system
|
||||
if (window.startGame) {
|
||||
window.startGame();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// Update navigation when user changes
|
||||
function updateNavigationForUser() {
|
||||
try {
|
||||
if (window.tabletMenuManager) {
|
||||
window.tabletMenuManager.updateNavigation();
|
||||
} else {
|
||||
// Fallback: update navigation elements directly
|
||||
updateNavigationFallback();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Navigation update failed:', error);
|
||||
updateNavigationFallback();
|
||||
}
|
||||
}
|
||||
|
||||
function updateNavigationFallback() {
|
||||
if (window.currentUser) {
|
||||
const { role } = window.currentUser;
|
||||
const studentNav = document.getElementById('studentNavItems');
|
||||
const adminNav = document.getElementById('adminNavItems');
|
||||
|
||||
if (role === 'admin') {
|
||||
if (studentNav) studentNav.classList.add('hidden');
|
||||
if (adminNav) adminNav.classList.remove('hidden');
|
||||
} else {
|
||||
if (adminNav) adminNav.classList.add('hidden');
|
||||
if (studentNav) studentNav.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addBackToDashboardButton() {
|
||||
// Remove existing back button
|
||||
const existingButton = document.querySelector('.back-to-dashboard');
|
||||
if (existingButton) existingButton.remove();
|
||||
|
||||
// Add back button to admin panel
|
||||
const adminPanel = document.getElementById('adminPanel');
|
||||
if (adminPanel) {
|
||||
const backButton = document.createElement('button');
|
||||
backButton.className = 'back-to-dashboard btn-secondary';
|
||||
backButton.innerHTML = '← Back to Dashboard';
|
||||
backButton.style.cssText = 'margin-bottom: 1rem; display: inline-block;';
|
||||
backButton.onclick = showAdminDashboard;
|
||||
|
||||
// Insert at the beginning of admin panel
|
||||
const firstChild = adminPanel.firstElementChild;
|
||||
if (firstChild) {
|
||||
adminPanel.insertBefore(backButton, firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showAdminDashboard() {
|
||||
// Show the dashboard overview
|
||||
const adminOverview = document.querySelector('.admin-overview');
|
||||
const menuInstructions = document.querySelector('.menu-instructions');
|
||||
if (adminOverview) adminOverview.style.display = 'block';
|
||||
if (menuInstructions) menuInstructions.style.display = 'block';
|
||||
|
||||
// Hide the admin tabs container
|
||||
const adminTabs = document.getElementById('adminTabs');
|
||||
if (adminTabs && adminTabs.parentElement) {
|
||||
adminTabs.parentElement.style.display = 'none';
|
||||
}
|
||||
|
||||
// Hide all admin tab content
|
||||
document.querySelectorAll('.adminTab').forEach(tab => {
|
||||
tab.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Remove back button
|
||||
const backButton = document.querySelector('.back-to-dashboard');
|
||||
if (backButton) backButton.remove();
|
||||
|
||||
// Load dashboard stats
|
||||
loadAdminDashboardStats();
|
||||
}
|
||||
|
||||
function loadAdminDashboardStats() {
|
||||
// Update stats with real data
|
||||
try {
|
||||
// Total users
|
||||
if (window.users && Array.isArray(window.users)) {
|
||||
document.getElementById('totalUsers').textContent = window.users.length;
|
||||
}
|
||||
|
||||
// You can add more stats here as needed
|
||||
document.getElementById('totalWordLists').textContent = '5+';
|
||||
document.getElementById('totalSessions').textContent = '0';
|
||||
document.getElementById('totalBadges').textContent = '8';
|
||||
} catch (error) {
|
||||
console.warn('Could not load dashboard stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other scripts
|
||||
window.TabletMenuManager = TabletMenuManager;
|
||||
Reference in new issue
Block a user