Fix: Blazor Server loading screen now works correctly

Problem:
- Loading screen was getting stuck and not hiding properly
- Conflicting logic between pwa.js and inline scripts
- Blazor Server lifecycle not properly integrated with loading screen

Solution (Meziantou-inspired approach for Blazor Server):
1. **blazor-integration.js** - Now manages loading screen lifecycle:
   - Shows loading screen only on first load (sessionStorage check)
   - Hides screen when Blazor.start() promise resolves (SignalR connected)
   - Added reconnection UI for Blazor Server disconnections
   - Proper error handling if Blazor fails to start

2. **_Layout.cshtml** - Simplified loading screen management:
   - Removed inline script that was conflicting
   - Moved blazor-integration.js before pwa.js (load order critical)
   - Loading screen now controlled by Blazor lifecycle

3. **pwa.js** - Removed conflicting logic:
   - Removed hideLoadingScreen() method
   - Removed 5-second fallback timeout
   - PWA initialization no longer interferes with Blazor loading

Key Differences from WebAssembly Approach:
- WASM: Downloads .NET runtime + shows download progress
- Server: Establishes SignalR connection + shows spinner
- Loading screen hides when SignalR connection is ready

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
SysAdmin 2025-10-06 11:45:08 +01:00
parent dd494603f5
commit db2443c7ac
3 changed files with 107 additions and 50 deletions

View File

@ -40,7 +40,7 @@
@await RenderSectionAsync("Head", required: false) @await RenderSectionAsync("Head", required: false)
</head> </head>
<body> <body>
<!-- PWA Loading Screen - Only on first load --> <!-- PWA Loading Screen - Managed by blazor-integration.js -->
<div id="pwa-loading-screen" class="pwa-loading-screen" style="display: none;"> <div id="pwa-loading-screen" class="pwa-loading-screen" style="display: none;">
<div class="pwa-loading-content"> <div class="pwa-loading-content">
<div class="pwa-loading-logo"> <div class="pwa-loading-logo">
@ -56,21 +56,6 @@
</div> </div>
</div> </div>
<script>
// Show loading screen only on initial app load, not on navigation
(function() {
const isFirstLoad = !sessionStorage.getItem('appLoaded');
if (isFirstLoad) {
const loadingScreen = document.getElementById('pwa-loading-screen');
if (loadingScreen) {
loadingScreen.style.display = 'flex';
}
sessionStorage.setItem('appLoaded', 'true');
}
})();
</script>
<header> <header>
<nav class="navbar navbar-expand-sm navbar-light bg-white"> <nav class="navbar navbar-expand-sm navbar-light bg-white">
<div class="container-fluid"> <div class="container-fluid">
@ -170,10 +155,11 @@
<script src="/lib/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/_framework/blazor.server.js" autostart="false"></script> <script src="/_framework/blazor.server.js" autostart="false"></script>
<script src="/_content/Radzen.Blazor/Radzen.Blazor.js"></script> <script src="/_content/Radzen.Blazor/Radzen.Blazor.js"></script>
<!-- Blazor integration MUST load before PWA to control loading screen -->
<script src="/js/blazor-integration.js"></script>
<script src="/js/pwa.js"></script> <script src="/js/pwa.js"></script>
<script src="/js/notifications.js"></script> <script src="/js/notifications.js"></script>
<script src="/js/modern-mobile.js"></script> <script src="/js/modern-mobile.js"></script>
<script src="/js/blazor-integration.js"></script>
@await RenderSectionAsync("Scripts", required: false) @await RenderSectionAsync("Scripts", required: false)
<!-- Mobile Bottom Navigation --> <!-- Mobile Bottom Navigation -->
<nav class="mobile-bottom-nav"> <nav class="mobile-bottom-nav">

View File

@ -1,15 +1,109 @@
// Blazor Server Integration Script // Blazor Server Integration Script
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', async function() {
console.log('Blazor: DOM Content Loaded');
// Show loading screen initially (only on first load)
const isFirstLoad = !sessionStorage.getItem('blazorLoaded');
const loadingScreen = document.getElementById('pwa-loading-screen');
if (isFirstLoad && loadingScreen) {
loadingScreen.style.display = 'flex';
console.log('Blazor: Showing loading screen for first load');
}
// Check if we're on a page that should use Blazor // Check if we're on a page that should use Blazor
const blazorContainers = document.querySelectorAll('[data-blazor-component]'); const blazorContainers = document.querySelectorAll('[data-blazor-component]');
if (blazorContainers.length > 0 || window.location.pathname.includes('/Admin/Products/Blazor')) { if (blazorContainers.length > 0 || window.location.pathname.includes('/Admin/Products/Blazor') || window.location.pathname.includes('/blazor')) {
// Start Blazor try {
Blazor.start(); console.log('Blazor: Starting Blazor Server...');
// Start Blazor Server with reconnection UI
await Blazor.start({
reconnectionOptions: {
maxRetries: 8,
retryIntervalMilliseconds: 2000
},
reconnectionHandler: {
onConnectionDown: () => {
console.log('Blazor: Connection lost, attempting to reconnect...');
showReconnectingUI();
},
onConnectionUp: () => {
console.log('Blazor: Reconnected successfully');
hideReconnectingUI();
}
} }
}); });
console.log('Blazor: Started successfully');
// Mark as loaded and hide loading screen
sessionStorage.setItem('blazorLoaded', 'true');
hideLoadingScreen();
} catch (error) {
console.error('Blazor: Failed to start:', error);
hideLoadingScreen();
}
} else {
// Not a Blazor page, hide loading screen immediately
hideLoadingScreen();
}
});
// Loading screen management
function hideLoadingScreen() {
const loadingScreen = document.getElementById('pwa-loading-screen');
if (loadingScreen && loadingScreen.style.display !== 'none') {
console.log('Blazor: Hiding loading screen');
loadingScreen.classList.add('fade-out');
setTimeout(() => {
loadingScreen.style.display = 'none';
}, 500);
}
}
// Reconnection UI for Blazor Server
function showReconnectingUI() {
let reconnectUI = document.getElementById('blazor-reconnect-ui');
if (!reconnectUI) {
reconnectUI = document.createElement('div');
reconnectUI.id = 'blazor-reconnect-ui';
reconnectUI.className = 'alert alert-warning';
reconnectUI.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
min-width: 300px;
text-align: center;
`;
reconnectUI.innerHTML = `
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Connection lost</strong><br>
<small>Attempting to reconnect...</small>
`;
document.body.appendChild(reconnectUI);
}
}
function hideReconnectingUI() {
const reconnectUI = document.getElementById('blazor-reconnect-ui');
if (reconnectUI) {
reconnectUI.remove();
}
}
// Helper function to navigate to Blazor components from MVC // Helper function to navigate to Blazor components from MVC
window.navigateToBlazor = function(componentPath) { window.navigateToBlazor = function(componentPath) {
window.location.href = '/blazor#' + componentPath; window.location.href = '/blazor#' + componentPath;
}; };
// Export functions for use by other scripts
window.hideBlazorLoadingScreen = hideLoadingScreen;
window.showBlazorReconnectingUI = showReconnectingUI;
window.hideBlazorReconnectingUI = hideReconnectingUI;

View File

@ -39,22 +39,9 @@ class PWAManager {
// Setup push notifications // Setup push notifications
this.setupPushNotifications(); this.setupPushNotifications();
// Hide loading screen after initialization // Loading screen is now managed by blazor-integration.js
this.hideLoadingScreen(); // Don't interfere with Blazor Server's loading lifecycle
} console.log('PWA: Initialization complete (loading screen managed by Blazor)');
hideLoadingScreen() {
const loadingScreen = document.getElementById('pwa-loading-screen');
if (loadingScreen && loadingScreen.style.display !== 'none') {
// Add fade-out class
loadingScreen.classList.add('fade-out');
// Hide after animation completes (don't remove from DOM to avoid layout issues)
setTimeout(() => {
loadingScreen.style.display = 'none';
console.log('PWA: Loading screen hidden');
}, 500);
}
} }
setupInstallPrompt() { setupInstallPrompt() {
@ -701,15 +688,5 @@ window.addEventListener('load', () => {
} }
}); });
// Fallback: Ensure loading screen is hidden after maximum timeout // Loading screen fallback timeout removed - now managed by blazor-integration.js
// This guarantees the loading screen won't stay visible indefinitely // Blazor Server controls loading screen lifecycle based on SignalR connection state
setTimeout(() => {
const loadingScreen = document.getElementById('pwa-loading-screen');
if (loadingScreen && loadingScreen.style.display !== 'none') {
console.log('PWA: Hiding loading screen via fallback timeout');
loadingScreen.classList.add('fade-out');
setTimeout(() => {
loadingScreen.style.display = 'none';
}, 500);
}
}, 5000); // 5 second maximum