379 lines
12 KiB
JavaScript
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('Contoso Frontend v' + app.version + ' - Initializing...');
|
|
|
|
// Initialize configuration
|
|
initializeApp();
|
|
|
|
// Set up global event listeners
|
|
setupGlobalEventListeners();
|
|
|
|
// Check initial configuration state
|
|
checkInitialConfiguration();
|
|
|
|
app.initialized = true;
|
|
console.log('Contoso 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 Contoso!</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 Contoso', 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.ContosoApp = app;
|
|
|
|
// Add helpful console messages
|
|
console.log('%cContoso 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;');
|