Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled
194 lines
5.9 KiB
JavaScript
194 lines
5.9 KiB
JavaScript
// Utility functions module
|
|
const Utils = (function() {
|
|
// Format a number for display
|
|
function formatNumber(num) {
|
|
if (typeof num === 'number') {
|
|
// Check if it's an integer or a simple decimal
|
|
if (Number.isInteger(num)) {
|
|
return num.toString();
|
|
} else {
|
|
// Format to avoid floating point precision issues
|
|
return parseFloat(num.toFixed(10)).toString();
|
|
}
|
|
}
|
|
return num.toString();
|
|
}
|
|
|
|
// Check if a value is a number
|
|
function isNumber(value) {
|
|
return !isNaN(parseFloat(value)) && isFinite(value);
|
|
}
|
|
|
|
// Parse a string expression safely
|
|
function parseExpression(expr) {
|
|
// Remove any whitespace
|
|
expr = expr.replace(/\s/g, '');
|
|
|
|
// Check if the expression contains only allowed characters
|
|
if (!/^[0-9+\-*/().%]+$/.test(expr)) {
|
|
return null; // Invalid expression
|
|
}
|
|
|
|
return expr;
|
|
}
|
|
|
|
// Calculate expression with error handling
|
|
function calculateExpression(expr) {
|
|
try {
|
|
// For security, avoid using eval directly
|
|
// Instead, use the Function constructor with validation
|
|
const validatedExpr = parseExpression(expr);
|
|
if (!validatedExpr) {
|
|
return 'Error';
|
|
}
|
|
|
|
// Replace % with proper calculation
|
|
let processedExpr = validatedExpr;
|
|
if (processedExpr.includes('%')) {
|
|
// Handle percentage expressions - this is a simplified approach
|
|
// In a real implementation, you'd want more sophisticated percentage handling
|
|
processedExpr = processedExpr.replace(/(\d+(\.\d+)?)%/g, (match, number) => {
|
|
return `*${number}/100`;
|
|
});
|
|
}
|
|
|
|
const result = new Function(`"use strict"; return (${processedExpr})`)();
|
|
|
|
if (isNaN(result) || !isFinite(result)) {
|
|
return 'Error';
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
return 'Error';
|
|
}
|
|
}
|
|
|
|
// Get expression precedence for operator
|
|
function getPrecedence(operator) {
|
|
switch (operator) {
|
|
case '+':
|
|
case '-':
|
|
return 1;
|
|
case '*':
|
|
case '/':
|
|
return 2;
|
|
case '^':
|
|
return 3;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Check if character is an operator
|
|
function isOperator(char) {
|
|
return ['+', '-', '*', '/', '^'].includes(char);
|
|
}
|
|
|
|
// Convert infix expression to postfix (for more complex expression evaluation)
|
|
function infixToPostfix(expression) {
|
|
const output = [];
|
|
const operators = [];
|
|
|
|
// Simple tokenization (in a real implementation, you'd want more robust parsing)
|
|
const tokens = expression.match(/\d+(\.\d+)?|[+\-*/()]/g) || [];
|
|
|
|
for (const token of tokens) {
|
|
if (isNumber(token)) {
|
|
output.push(token);
|
|
} else if (token === '(') {
|
|
operators.push(token);
|
|
} else if (token === ')') {
|
|
while (operators.length && operators[operators.length - 1] !== '(') {
|
|
output.push(operators.pop());
|
|
}
|
|
operators.pop(); // Remove the '('
|
|
} else if (isOperator(token)) {
|
|
while (
|
|
operators.length &&
|
|
operators[operators.length - 1] !== '(' &&
|
|
getPrecedence(operators[operators.length - 1]) >= getPrecedence(token)
|
|
) {
|
|
output.push(operators.pop());
|
|
}
|
|
operators.push(token);
|
|
}
|
|
}
|
|
|
|
while (operators.length) {
|
|
output.push(operators.pop());
|
|
}
|
|
|
|
return output.join(' ');
|
|
}
|
|
|
|
// Debounce function to limit execution frequency
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
// Serialize state for storage
|
|
function serializeState(state) {
|
|
return JSON.stringify(state, (key, value) => {
|
|
// Handle dates properly
|
|
if (value instanceof Date) {
|
|
return { __type: 'Date', value: value.toISOString() };
|
|
}
|
|
return value;
|
|
});
|
|
}
|
|
|
|
// Deserialize state from storage
|
|
function deserializeState(str) {
|
|
return JSON.parse(str, (key, value) => {
|
|
// Restore dates properly
|
|
if (value && typeof value === 'object' && value.__type === 'Date') {
|
|
return new Date(value.value);
|
|
}
|
|
return value;
|
|
});
|
|
}
|
|
|
|
// Update page title dynamically
|
|
function updatePageTitle(title) {
|
|
document.title = title || 'Geek Calculator';
|
|
}
|
|
|
|
// Check if running in offline mode
|
|
function isOffline() {
|
|
return !navigator.onLine;
|
|
}
|
|
|
|
// Format file size for display
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
return {
|
|
formatNumber,
|
|
isNumber,
|
|
parseExpression,
|
|
calculateExpression,
|
|
getPrecedence,
|
|
isOperator,
|
|
infixToPostfix,
|
|
debounce,
|
|
serializeState,
|
|
deserializeState,
|
|
updatePageTitle,
|
|
isOffline,
|
|
formatFileSize
|
|
};
|
|
})(); |