// Main application initialization and global functionality // Application state const app = { initialized: false, currentPage: 'home', version: '1.0.0' }; // Initialize the application document.addEventListener('DOMContentLoaded', () => { console.log('SeReact Frontend v' + app.version + ' - Initializing...'); // Initialize configuration initializeApp(); // Set up global event listeners setupGlobalEventListeners(); // Check initial configuration state checkInitialConfiguration(); app.initialized = true; console.log('SeReact Frontend - Initialization complete'); }); // Initialize the application function initializeApp() { // Update navigation state based on configuration updateNavigationState(); // Initialize page routing initializeRouting(); // Set up periodic health checks setupHealthChecks(); // Initialize keyboard shortcuts setupKeyboardShortcuts(); } // Set up global event listeners function setupGlobalEventListeners() { // Handle online/offline status window.addEventListener('online', () => { showAlert('Connection restored', 'success'); updateConnectionStatus(true); }); window.addEventListener('offline', () => { showAlert('Connection lost - working offline', 'warning', true); updateConnectionStatus(false); }); // Handle visibility changes (tab switching) document.addEventListener('visibilitychange', () => { if (!document.hidden && config.isConfigured()) { // Refresh current page data when tab becomes visible refreshCurrentPageData(); } }); // Handle window resize window.addEventListener('resize', debounce(() => { handleWindowResize(); }, 250)); // Handle beforeunload for unsaved changes warning window.addEventListener('beforeunload', (e) => { if (hasUnsavedChanges()) { e.preventDefault(); e.returnValue = ''; } }); } // Check initial configuration function checkInitialConfiguration() { if (!config.isConfigured()) { // Show welcome message for first-time users setTimeout(() => { showWelcomeMessage(); }, 1000); } else { // Test connection on startup setTimeout(() => { testConnectionSilently(); }, 500); } } // Show welcome message for new users function showWelcomeMessage() { const modalBody = `

Welcome to SeReact!

AI-powered image management and semantic search platform

Configure API

Set up your API connection to get started

Upload Images

Start building your image collection

Ready to begin? Let's configure your API connection first.

`; const modalFooter = ` `; const modal = createModal('welcomeModal', 'Welcome to SeReact', modalBody, modalFooter); modal.show(); } // Test connection silently (without showing alerts) async function testConnectionSilently() { try { const isHealthy = await apiClient.healthCheck(); updateConnectionStatus(isHealthy); } catch (error) { updateConnectionStatus(false); } } // Update connection status indicator function updateConnectionStatus(isOnline) { // Add a connection status indicator to the navbar let statusIndicator = document.getElementById('connectionStatus'); if (!statusIndicator) { statusIndicator = document.createElement('div'); statusIndicator.id = 'connectionStatus'; statusIndicator.className = 'nav-item'; const navbar = document.querySelector('.navbar-nav'); if (navbar) { navbar.appendChild(statusIndicator); } } statusIndicator.innerHTML = ` `; } // Initialize routing function initializeRouting() { // Handle hash changes for deep linking window.addEventListener('hashchange', handleRouteChange); // Handle initial route handleRouteChange(); } // Handle route changes function handleRouteChange() { const hash = window.location.hash.substring(1); const route = hash || 'home'; // Validate route const validRoutes = ['home', 'config', 'images', 'search', 'teams', 'users', 'api-keys']; if (validRoutes.includes(route)) { showPage(route); } else { // Invalid route, redirect to home window.location.hash = 'home'; } } // Set up periodic health checks function setupHealthChecks() { if (!config.isConfigured()) return; // Check health every 5 minutes setInterval(async () => { if (config.isConfigured() && navigator.onLine) { try { const isHealthy = await apiClient.healthCheck(); updateConnectionStatus(isHealthy); } catch (error) { updateConnectionStatus(false); } } }, 5 * 60 * 1000); } // Set up keyboard shortcuts function setupKeyboardShortcuts() { document.addEventListener('keydown', (e) => { // Only handle shortcuts when not in input fields if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } // Ctrl/Cmd + shortcuts if (e.ctrlKey || e.metaKey) { switch (e.key) { case 'k': e.preventDefault(); showPage('search'); setTimeout(() => { const searchInput = document.getElementById('searchQuery'); if (searchInput) searchInput.focus(); }, 100); break; case 'u': e.preventDefault(); if (app.currentPage === 'images') { showUploadModal(); } else { showPage('images'); setTimeout(() => showUploadModal(), 100); } break; case ',': e.preventDefault(); showPage('config'); break; } } // Number shortcuts for navigation if (e.key >= '1' && e.key <= '6' && !e.ctrlKey && !e.metaKey && !e.altKey) { const pages = ['home', 'images', 'search', 'teams', 'users', 'api-keys']; const pageIndex = parseInt(e.key) - 1; if (pages[pageIndex]) { e.preventDefault(); showPage(pages[pageIndex]); } } // Escape key to close modals if (e.key === 'Escape') { const openModals = document.querySelectorAll('.modal.show'); openModals.forEach(modal => { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) bsModal.hide(); }); } }); } // Refresh current page data function refreshCurrentPageData() { switch (app.currentPage) { case 'images': loadImages(currentPage); break; case 'teams': loadTeams(); break; case 'users': loadUsers(); break; case 'api-keys': loadApiKeys(); break; } } // Handle window resize function handleWindowResize() { // Reinitialize tooltips and popovers after resize initializeTooltips(); initializePopovers(); // Adjust image grid if on images page if (app.currentPage === 'images') { // Force reflow of image grid const imageGrid = document.querySelector('.image-grid'); if (imageGrid) { imageGrid.style.display = 'none'; imageGrid.offsetHeight; // Trigger reflow imageGrid.style.display = 'grid'; } } } // Check for unsaved changes function hasUnsavedChanges() { // Check if any forms have been modified const forms = document.querySelectorAll('form'); for (const form of forms) { if (form.dataset.modified === 'true') { return true; } } return false; } // Mark form as modified function markFormAsModified(formElement) { formElement.dataset.modified = 'true'; } // Mark form as saved function markFormAsSaved(formElement) { formElement.dataset.modified = 'false'; } // Global error handler window.addEventListener('error', (e) => { console.error('Global error:', e.error); // Don't show alerts for network errors or script loading errors if (e.error && !e.error.message.includes('Loading')) { showAlert('An unexpected error occurred. Please refresh the page if problems persist.', 'danger'); } }); // Global unhandled promise rejection handler window.addEventListener('unhandledrejection', (e) => { console.error('Unhandled promise rejection:', e.reason); // Don't show alerts for network-related rejections if (e.reason && !e.reason.message?.includes('fetch')) { showAlert('An unexpected error occurred. Please try again.', 'danger'); } // Prevent the default browser behavior e.preventDefault(); }); // Export app object for debugging window.SeReactApp = app; // Add helpful console messages console.log('%cSeReact Frontend', 'color: #0d6efd; font-size: 24px; font-weight: bold;'); console.log('%cVersion: ' + app.version, 'color: #6c757d; font-size: 14px;'); console.log('%cKeyboard Shortcuts:', 'color: #198754; font-size: 16px; font-weight: bold;'); console.log('%c Ctrl+K: Search', 'color: #6c757d;'); console.log('%c Ctrl+U: Upload Image', 'color: #6c757d;'); console.log('%c Ctrl+,: Settings', 'color: #6c757d;'); console.log('%c 1-6: Navigate to pages', 'color: #6c757d;'); console.log('%c Esc: Close modals', 'color: #6c757d;');