init geek calc
Some checks failed
Deploy to GitHub Pages / build-and-deploy (push) Has been cancelled

This commit is contained in:
2025-10-04 10:53:41 +08:00
commit 54f427ea21
45 changed files with 4878 additions and 0 deletions

194
utils.js Normal file
View File

@@ -0,0 +1,194 @@
// 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
};
})();