// Loading UI - Manages the visual loading screen and user feedback class LoadingUI { constructor() { this.container = null; this.progressBar = null; this.systemStatusContainer = null; this.errorMessage = null; this.retryButton = null; this.continueButton = null; this.isVisible = false; this.systemCards = new Map(); } // Initialize loading screen initialize() { this.createLoadingScreen(); this.attachEventListeners(); } // Create the loading screen DOM createLoadingScreen() { this.container = document.createElement('div'); this.container.id = 'libreportal-loading-screen'; this.container.className = 'loading-screen aurora-bg aurora-static'; this.container.innerHTML = `
Drifting softly into your private universe...
$1');
}
// Update system check status
updateSystemCheck(systemId, checkName, status, error = null, message = null) {
const card = this.systemCards.get(systemId);
if (!card) return;
const statusElement = card.querySelector('.system-status');
const indicatorElement = card.querySelector('.status-icon');
if (!statusElement || !indicatorElement) return;
switch (status) {
case 'checking':
statusElement.textContent = `Checking: ${checkName}`;
indicatorElement.textContent = 'β³';
card.className = 'system-card checking';
// Auto-scroll to this card when checking starts
this.scrollToCard(card);
break;
case 'retrying':
statusElement.innerHTML = `Retrying: ${checkName}`;
indicatorElement.textContent = 'π';
card.className = 'system-card retrying';
// Don't auto-scroll during retry to prevent jumping around
if (message) {
statusElement.title = this.processMessage(message);
}
break;
case 'waiting':
statusElement.textContent = `Waiting: ${checkName}`;
indicatorElement.textContent = 'β°';
card.className = 'system-card waiting';
break;
case 'passed':
statusElement.textContent = 'Operational';
indicatorElement.textContent = 'β
';
card.className = 'system-card passed';
break;
case 'failed':
statusElement.textContent = `Failed: ${checkName}`;
indicatorElement.textContent = 'β';
card.className = 'system-card failed';
if (error) {
statusElement.title = error;
}
break;
case 'skipped':
statusElement.textContent = 'Skipped';
indicatorElement.textContent = 'βοΈ';
card.className = 'system-card skipped';
break;
}
}
// Scroll to specific card smoothly
scrollToCard(card) {
if (!card || !this.systemStatusContainer) return;
const containerHeight = this.systemStatusContainer.clientHeight;
const cardHeight = card.offsetHeight;
const cardOffsetTop = card.offsetTop;
const containerScrollHeight = this.systemStatusContainer.scrollHeight;
// Calculate target scroll position to center the card
let targetScrollTop = cardOffsetTop - (containerHeight / 2) + (cardHeight / 2);
// Ensure we don't scroll past the bottom
const maxScrollTop = containerScrollHeight - containerHeight;
if (targetScrollTop > maxScrollTop) {
targetScrollTop = maxScrollTop;
}
// Ensure we don't scroll before the top
if (targetScrollTop < 0) {
targetScrollTop = 0;
}
// Smooth scroll to the target position
this.systemStatusContainer.scrollTo({
top: targetScrollTop,
behavior: 'smooth'
});
}
// Update loading tip based on progress
updateLoadingTip(progress) {
const tipElement = document.getElementById('loading-tip');
if (!tipElement) return;
const tips = [
'Loading essential components...',
'Preparing your workspace...',
'Checking system dependencies...',
'Initializing data connections...',
'Almost ready...',
'Finalizing launch...'
];
const tipIndex = Math.floor((progress / 100) * tips.length);
const tip = tips[Math.min(tipIndex, tips.length - 1)];
if (tipElement.textContent !== tip) {
tipElement.textContent = tip;
}
}
// Show error message
showError(errors) {
// console.log('π LoadingUI.showError called with errors:', errors.length);
// console.log('π Existing error details elements:', document.querySelectorAll('.error-details').length);
// console.log('π Call stack:', new Error().stack);
const actionsContainer = document.getElementById('loading-actions');
// Check if this is a missing data files issue
const hasMissingDataFiles = errors.some(error =>
error.error && (error.error.includes('Missing required data file') ||
error.error.includes('Critical configuration files are missing or empty'))
);
// Create error details
const errorDetails = document.createElement('div');
errorDetails.className = 'error-details';
if (hasMissingDataFiles) {
// Special handling for missing data files
errorDetails.innerHTML = `
β Required data files are missing!
LibrePortal needs to generate JSON configuration files before it can run properly.
Run this command in your terminal to fix the issue.
After running the setup command, refresh this page to continue.
`; } else { // Regular error handling errorDetails.innerHTML = `You can retry loading or continue with limited functionality
`; } // Insert before actions if (actionsContainer && actionsContainer.parentNode) { actionsContainer.parentNode.insertBefore(errorDetails, actionsContainer); actionsContainer.style.display = 'flex'; this.retryButton.style.display = 'inline-flex'; this.continueButton.style.display = 'inline-flex'; } } // Hide loading screen hide() { if (this.container && this.isVisible) { // Show success message and trigger animations const successMessage = document.getElementById('success-message'); if (successMessage) { successMessage.style.display = 'block'; } // Add success class to trigger animations this.container.classList.add('success'); // Wait for success animations to play, then hide setTimeout(() => { this.container.classList.add('hiding'); setTimeout(() => { if (this.container && this.container.parentNode) { this.container.parentNode.removeChild(this.container); } this.isVisible = false; }, 500); }, 200); } } // Show loading screen show() { if (!this.isVisible && this.container) { if (!this.container.parentNode) { document.body.appendChild(this.container); } this.container.classList.remove('hiding'); this.isVisible = true; } } // Update status text updateStatus(text) { const statusElement = document.getElementById('loading-status-text'); if (statusElement) { statusElement.textContent = text; } } // Attach event listeners attachEventListeners() { if (this.retryButton) { this.retryButton.addEventListener('click', () => { // console.log('π Retry button clicked - refreshing page'); window.location.reload(); }); } if (this.continueButton) { this.continueButton.addEventListener('click', () => { this.hide(); // Trigger continue event window.dispatchEvent(new CustomEvent('loadingContinue')); }); } } // Cleanup destroy() { if (this.container && this.container.parentNode) { this.container.parentNode.removeChild(this.container); } this.isVisible = false; this.systemCards.clear(); } }