2025-05-24 15:47:40 +02:00

379 lines
12 KiB
JavaScript

// 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('App.js DOMContentLoaded fired');
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() {
// Initialize UI components
initializeUI();
// 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 = `
<div class="text-center mb-4">
<i class="fas fa-rocket fa-3x text-primary mb-3"></i>
<h4>Welcome to SeReact!</h4>
<p class="lead">AI-powered image management and semantic search platform</p>
</div>
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-body text-center">
<i class="fas fa-cog fa-2x text-primary mb-3"></i>
<h6>Configure API</h6>
<p class="small">Set up your API connection to get started</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-body text-center">
<i class="fas fa-upload fa-2x text-success mb-3"></i>
<h6>Upload Images</h6>
<p class="small">Start building your image collection</p>
</div>
</div>
</div>
</div>
<div class="mt-4 text-center">
<p class="text-muted">Ready to begin? Let's configure your API connection first.</p>
</div>
`;
const modalFooter = `
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Maybe Later</button>
<button type="button" class="btn btn-primary" onclick="showPage('config')" data-bs-dismiss="modal">
<i class="fas fa-cog me-1"></i>Configure Now
</button>
`;
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 = `
<span class="nav-link">
<i class="fas fa-circle ${isOnline ? 'status-online' : 'status-offline'}"
title="${isOnline ? 'Connected' : 'Disconnected'}"></i>
</span>
`;
}
// Initialize routing
function initializeRouting() {
// Handle hash changes for deep linking
window.addEventListener('hashchange', handleRouteChange);
// Don't handle initial route here - let initializeUI handle it
}
// Handle route changes
function handleRouteChange() {
const hash = window.location.hash.substring(1);
const route = hash || 'home';
console.log('=== handleRouteChange called ===');
console.log('Current hash:', hash);
console.log('Route to show:', route);
console.log('Current app page:', app.currentPage);
// Validate route
const validRoutes = ['home', 'config', 'images', 'search', 'teams', 'users', 'apiKeys'];
if (validRoutes.includes(route)) {
// Only call showPage if we're not already on this page
if (app.currentPage !== route) {
console.log('Route changed, calling showPage for:', route);
app.currentPage = route;
showPage(route);
} else {
console.log('Already on page:', route, '- not calling showPage');
}
} else {
console.log('Invalid route:', route, '- redirecting to home');
// Invalid route, redirect to home
window.location.hash = 'home';
}
console.log('=== handleRouteChange completed ===');
}
// 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', 'apiKeys'];
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 'apiKeys':
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;');