Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled
369 lines
14 KiB
JavaScript
369 lines
14 KiB
JavaScript
// 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 };
|
|
})(); |