/** * Main JavaScript Application for Markov Economics * * Provides utility functions and global application behavior */ // Global application state window.MarkovEconomics = { socket: null, currentSimulationId: null, isConnected: false, // Configuration config: { maxRetries: 3, retryDelay: 1000, updateInterval: 100 } }; /** * Initialize Socket.IO connection */ function initializeSocket() { if (typeof io !== 'undefined') { window.MarkovEconomics.socket = io(); window.MarkovEconomics.socket.on('connect', function() { console.log('Connected to server'); window.MarkovEconomics.isConnected = true; updateConnectionStatus(true); }); window.MarkovEconomics.socket.on('disconnect', function() { console.log('Disconnected from server'); window.MarkovEconomics.isConnected = false; updateConnectionStatus(false); }); window.MarkovEconomics.socket.on('connect_error', function(error) { console.error('Connection error:', error); updateConnectionStatus(false); }); } } /** * Update connection status indicator */ function updateConnectionStatus(connected) { // You can add a connection status indicator here if needed if (connected) { console.log('✅ Real-time connection established'); } else { console.log('❌ Real-time connection lost'); } } /** * Format numbers for display */ function formatNumber(num, decimals = 2) { if (num === null || num === undefined || isNaN(num)) { return '0'; } if (Math.abs(num) >= 1e9) { return (num / 1e9).toFixed(decimals) + 'B'; } else if (Math.abs(num) >= 1e6) { return (num / 1e6).toFixed(decimals) + 'M'; } else if (Math.abs(num) >= 1e3) { return (num / 1e3).toFixed(decimals) + 'K'; } else { return num.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }); } } /** * Format currency values */ function formatCurrency(amount, decimals = 0) { if (amount === null || amount === undefined || isNaN(amount)) { return '$0'; } return '$' + formatNumber(amount, decimals); } /** * Format percentage values */ function formatPercentage(value, decimals = 1) { if (value === null || value === undefined || isNaN(value)) { return '0%'; } return (value * 100).toFixed(decimals) + '%'; } /** * Show notification message */ function showNotification(message, type = 'info', duration = 3000) { // Create notification element const notification = document.createElement('div'); notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`; notification.style.cssText = ` top: 20px; right: 20px; z-index: 9999; min-width: 300px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); `; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); // Auto-remove after duration setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, duration); return notification; } /** * Show loading spinner */ function showLoading(element, text = 'Loading...') { if (typeof element === 'string') { element = document.getElementById(element); } if (element) { element.innerHTML = `
${text}
`; } } /** * Hide loading spinner */ function hideLoading(element, originalContent = '') { if (typeof element === 'string') { element = document.getElementById(element); } if (element) { element.innerHTML = originalContent; } } /** * Make API request with error handling */ async function apiRequest(url, options = {}) { const defaultOptions = { headers: { 'Content-Type': 'application/json', }, }; const finalOptions = { ...defaultOptions, ...options }; try { const response = await fetch(url, finalOptions); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { console.error('API Request failed:', error); showNotification(`Error: ${error.message}`, 'danger'); throw error; } } /** * Validate parameter ranges */ function validateParameters(params) { const errors = []; if (params.r_rate < 0 || params.r_rate > 0.25) { errors.push('Capital rate must be between 0% and 25%'); } if (params.g_rate < 0 || params.g_rate > 0.20) { errors.push('Growth rate must be between 0% and 20%'); } if (params.num_agents < 10 || params.num_agents > 10000) { errors.push('Number of agents must be between 10 and 10,000'); } if (params.iterations < 100 || params.iterations > 100000) { errors.push('Iterations must be between 100 and 100,000'); } return errors; } /** * Debounce function for parameter changes */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * Initialize application */ document.addEventListener('DOMContentLoaded', function() { // Initialize Socket.IO if available initializeSocket(); // Initialize tooltips const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Add fade-in animation to cards const cards = document.querySelectorAll('.card'); cards.forEach((card, index) => { card.style.animationDelay = `${index * 0.1}s`; card.classList.add('fade-in'); }); console.log('🚀 Markov Economics application initialized'); }); /** * Handle page visibility changes */ document.addEventListener('visibilitychange', function() { if (document.hidden) { console.log('Page hidden - pausing updates'); } else { console.log('Page visible - resuming updates'); } }); /** * Export functions to global scope */ window.MarkovEconomics.utils = { formatNumber, formatCurrency, formatPercentage, showNotification, showLoading, hideLoading, apiRequest, validateParameters, debounce };