// UI controller module const UIController = (function() { class UIController { constructor(calculator, rpnCalculator, stateManager) { this.calculator = calculator; this.rpnCalculator = rpnCalculator; this.stateManager = stateManager; // Current mode: 'standard' or 'rpn' this.mode = this.stateManager.getMode(); // Current expression for standard calculator this.currentExpression = '0'; // Track if we're in the middle of an RPN operation this.rpnInputBuffer = ''; } // Initialize the UI init() { this.setupEventListeners(); this.updateDisplay(); this.updateHistoryDisplay(); this.updateModeDisplay(); this.setupKeyboardControls(); } // Set up event listeners for calculator buttons setupEventListeners() { // Get all calculator buttons const buttons = document.querySelectorAll('.btn'); buttons.forEach(button => { button.addEventListener('click', (e) => { this.handleButtonClick(e.target.dataset.value); }); // Add ARIA roles for accessibility button.setAttribute('role', 'button'); button.setAttribute('tabindex', '0'); // Add keyboard support for buttons button.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.handleButtonClick(button.dataset.value); } }); }); // RPN mode toggle button const rpnModeBtn = document.getElementById('rpn-mode-btn'); rpnModeBtn.addEventListener('click', () => { this.toggleRPNMode(); }); // Add ARIA for RPN button rpnModeBtn.setAttribute('role', 'switch'); rpnModeBtn.setAttribute('aria-checked', this.mode === 'rpn'); rpnModeBtn.setAttribute('aria-label', 'RPN Mode Toggle'); // Command palette input const commandInput = document.getElementById('command-input'); commandInput.setAttribute('aria-label', 'Command palette input'); commandInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.handleCommand(commandInput.value); commandInput.value = ''; } }); // Set up focus management for the display const displayElement = document.querySelector('.display'); displayElement.setAttribute('role', 'application'); displayElement.setAttribute('aria-label', 'Calculator display'); displayElement.setAttribute('tabindex', '0'); } // Handle button click handleButtonClick(value) { if (this.mode === 'standard') { this.handleStandardInput(value); } else { this.handleRPNInput(value); } this.updateDisplay(); this.updateHistoryDisplay(); } // Handle standard calculator input handleStandardInput(value) { this.calculator.processInput(value); this.currentExpression = this.calculator.getCurrentDisplay(); // Add to history if it's an equals operation if (value === '=') { const expression = this.currentExpression; // This would need to capture the actual expression const result = this.calculator.getCurrentDisplay(); this.stateManager.addToHistory(expression, parseFloat(result)); } } // Handle RPN calculator input handleRPNInput(value) { if (value === 'ENTER' || value === 'E') { // In our UI, we'll treat this as a way to push numbers to the RPN stack if (this.rpnInputBuffer) { this.rpnCalculator.push(this.rpnInputBuffer); this.rpnInputBuffer = ''; } // Process the operation if it's an operator } else if (['+', '-', '*', '/'].includes(value)) { // Process the operation this.rpnCalculator.operate(value); } else if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'].includes(value)) { // Build the number in the input buffer if (this.rpnInputBuffer === '0' || this.rpnInputBuffer === 'Error') { this.rpnInputBuffer = value; } else { this.rpnInputBuffer += value; } } else if (value === 'C' || value === 'CE') { this.rpnCalculator.clear(); this.rpnInputBuffer = ''; } else { // Another operator, push the current number first if (this.rpnInputBuffer) { this.rpnCalculator.push(this.rpnInputBuffer); this.rpnInputBuffer = ''; } // Process the operator if (['+', '-', '*', '/'].includes(value)) { this.rpnCalculator.operate(value); } } } // Toggle between standard and RPN mode toggleRPNMode() { this.mode = this.stateManager.toggleMode(); this.updateModeDisplay(); // Clear calculators when switching modes this.calculator.clear(); this.rpnCalculator.clear(); this.rpnInputBuffer = ''; this.currentExpression = '0'; this.updateDisplay(); } // Update the display to show current value updateDisplay() { const resultElement = document.getElementById('result'); const expressionElement = document.getElementById('expression'); if (this.mode === 'standard') { resultElement.textContent = this.calculator.getCurrentDisplay(); // Note: We don't have an expression display in our implementation expressionElement.textContent = ''; } else { // For RPN, show the top of the stack or the input buffer const stack = this.rpnCalculator.getStack(); if (stack.length > 0) { // Show the top of the stack resultElement.textContent = stack[stack.length - 1]; } else if (this.rpnInputBuffer) { // Show the input buffer resultElement.textContent = this.rpnInputBuffer; } else { // Default display resultElement.textContent = '0'; } expressionElement.textContent = `RPN: ${stack.length} items`; } // Update cursor visibility and text content const cursorElement = document.getElementById('cursor'); if (this.mode === 'standard') { cursorElement.style.display = 'inline-block'; } else { cursorElement.style.display = 'none'; // Hide cursor in RPN mode } } // Update history display updateHistoryDisplay() { const historyElement = document.getElementById('history'); const history = this.stateManager.getHistory(); // Clear previous history historyElement.innerHTML = ''; // Add history items history.slice(0, 10).forEach(item => { // Show only last 10 items const historyItem = document.createElement('div'); historyItem.className = 'history-item'; historyItem.textContent = `${item.expression} = ${item.result}`; historyItem.addEventListener('click', () => { // On click, load this calculation back into the calculator if (this.mode === 'standard') { this.calculator.clear(); // For simplicity, just set the result back // In a full implementation, you'd restore the expression this.currentExpression = item.result.toString(); this.updateDisplay(); } }); historyElement.appendChild(historyItem); }); } // Update mode display updateModeDisplay() { const modeBtn = document.getElementById('rpn-mode-btn'); modeBtn.textContent = `RPN: ${this.mode === 'rpn' ? 'ON' : 'OFF'}`; modeBtn.className = this.mode === 'rpn' ? 'mode-btn active' : 'mode-btn'; } // Set up keyboard controls setupKeyboardControls() { document.addEventListener('keydown', (e) => { // Prevent default behavior for calculator keys to avoid conflicts if (!e.ctrlKey && !e.metaKey) { this.handleKeyboardInput(e); } }); } // Handle keyboard input handleKeyboardInput(event) { // Handle command palette activation if (event.key === '@') { event.preventDefault(); this.toggleCommandPalette(); return; } // Handle help/shortcuts if (event.key === '?') { event.preventDefault(); alert('Geek Calculator Shortcuts:\\n' + '0-9: Number input\\n' + 'Arithmetic: + - * /\\n' + '= or Enter: Evaluate\\n' + 'Escape: Clear\\n' + 'R: Toggle RPN mode\\n' + '@: Command palette\\n' + '↑/↓: Navigate history\\n' + '?: Show this help'); return; } // Handle clear if (event.key === 'Escape' || event.key === 'c' || event.key === 'C') { this.handleButtonClick('C'); return; } // Handle enter if (event.key === 'Enter' || event.key === '=') { this.handleButtonClick('='); return; } // Handle backspace if (event.key === 'Backspace') { // In a real implementation, you'd handle backspace // For now, just ignore return; } // Handle numbers if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes(event.key)) { this.handleButtonClick(event.key); return; } // Handle decimal point if (event.key === '.') { this.handleButtonClick('.'); return; } // Handle operators if (['+', '-', '*', '/'].includes(event.key)) { this.handleButtonClick(event.key); return; } // Toggle RPN mode if (event.key === 'r' || event.key === 'R') { this.toggleRPNMode(); return; } // History navigation if (event.key === 'ArrowUp') { // In a real implementation, you'd navigate history up return; } if (event.key === 'ArrowDown') { // In a real implementation, you'd navigate history down return; } } // Toggle command palette visibility toggleCommandPalette() { const commandPalette = document.getElementById('command-palette'); if (commandPalette.style.display === 'none') { commandPalette.style.display = 'block'; document.getElementById('command-input').focus(); } else { commandPalette.style.display = 'none'; } } // Handle command input handleCommand(command) { const commandPalette = document.getElementById('command-palette'); commandPalette.style.display = 'none'; // Process commands command = command.trim().toLowerCase(); if (command === 'clear' || command === 'cls' || command === 'c') { this.handleButtonClick('C'); } else if (command === 'history') { // Show history - already displayed document.getElementById('history').scrollIntoView(); } else if (command === 'theme' || command.startsWith('theme ')) { // Handle theme commands const newTheme = command.split(' ')[1]; if (newTheme) { this.stateManager.updateSettings({ theme: newTheme }); } } else if (command === 'help') { alert('Available commands:\\n' + 'clear/cls/c - Clear calculator\\n' + 'history - Show calculation history\\n' + 'theme [dark|light] - Change theme\\n' + 'help - Show this help'); } } // Get current result for testing purposes getCurrentResult() { if (this.mode === 'standard') { return this.calculator.getCurrentDisplay(); } else { const stack = this.rpnCalculator.getStack(); if (stack.length > 0) { return stack[stack.length - 1].toString(); } else if (this.rpnInputBuffer) { return this.rpnInputBuffer; } else { return '0'; } } } // Get history for testing purposes getHistory() { return this.stateManager.getHistory(); } } return { UIController }; })();