init geek calc
Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled
Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled
This commit is contained in:
369
ui.js
Normal file
369
ui.js
Normal file
@@ -0,0 +1,369 @@
|
||||
// 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 };
|
||||
})();
|
||||
Reference in New Issue
Block a user