Files
midrandbooks/MidrandBookshop/Components/Layout/ReconnectModal.razor
T
2026-07-18 12:08:14 +02:00

220 lines
9.3 KiB
Plaintext

@namespace MidrandBooks.Components.Layout
@inject IJSRuntime JSRuntime
<dialog id="components-reconnect-modal" data-nosnippet class="fixed inset-0 w-screen h-screen m-0 p-0 border-0 bg-transparent z-[99999] overflow-hidden flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-300">
<!-- Dark Frosted Jacket Layer -->
<div class="absolute inset-0 bg-slate-950/60 backdrop-blur-md cursor-wait z-[1] animate-in fade-in duration-300"></div>
<!-- Centered Content Wrapper -->
<div class="reconnect-content relative z-[2] flex flex-col items-center gap-6 text-center text-slate-100 max-w-sm px-6">
<!-- Waveform Animation -->
<svg class="waveform-svg text-violet-400" viewBox="0 0 54 50" width="60" height="60" xmlns="http://www.w3.org/2000/svg">
<rect class="bar bar-1" x="2" y="15" width="4" height="20" rx="2" fill="currentColor" />
<rect class="bar bar-2" x="14" y="5" width="4" height="40" rx="2" fill="currentColor" />
<rect class="bar bar-3" x="26" y="20" width="4" height="10" rx="2" fill="currentColor" />
<rect class="bar bar-4" x="38" y="10" width="4" height="30" rx="2" fill="currentColor" />
<rect class="bar bar-5" x="50" y="18" width="4" height="14" rx="2" fill="currentColor" />
</svg>
<!-- Message Body -->
<div class="sync-message-body font-mono uppercase text-xs tracking-widest text-slate-300 space-y-2">
<span class="components-reconnect-first-attempt-visible hidden">
Losing signal...
</span>
<span class="components-reconnect-repeated-attempt-visible hidden">
reconnecting in <span id="components-seconds-to-next-attempt" class="font-bold text-violet-400">0</span>s...
</span>
<span class="components-reconnect-failed-visible text-rose-400 font-semibold hidden">
Playback disconnected.
</span>
<span class="components-pause-visible hidden">
Reading layout paused.
</span>
<span class="components-resume-failed-visible text-rose-400 font-semibold hidden">
State alignment broken.
</span>
</div>
<!-- Glass Action Buttons -->
<div class="sync-action-node flex items-center justify-center gap-3">
<button id="components-reconnect-button" class="btn-glass-action hidden px-4 py-2.5 bg-white/5 border border-white/10 hover:bg-white/10 text-white font-mono uppercase text-[10px] tracking-widest rounded-xl transition-all items-center gap-2 cursor-pointer shadow-lg hover:scale-102 hover:border-violet-500/30">
<span>Retry Sync</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67" />
</svg>
</button>
<button id="components-resume-button" class="btn-glass-action hidden px-4 py-2.5 bg-white/5 border border-white/10 hover:bg-white/10 text-white font-mono uppercase text-[10px] tracking-widest rounded-xl transition-all items-center gap-2 cursor-pointer shadow-lg hover:scale-102 hover:border-violet-500/30">
<span>Reload Session</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
</svg>
</button>
</div>
</div>
</dialog>
<style>
/* --- Show the modal smoothly when Blazor attaches state classes --- */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
opacity: 1 !important;
pointer-events: all !important;
}
#components-reconnect-modal.components-reconnect-hide {
display: none !important;
}
/* --- SVG Waveform Animation --- */
#components-reconnect-modal .bar {
transform-origin: center;
animation: reconnectWavePulse 1.2s ease-in-out infinite alternate;
}
#components-reconnect-modal .bar-1 {
animation-delay: 0.0s;
}
#components-reconnect-modal .bar-2 {
animation-delay: 0.3s;
}
#components-reconnect-modal .bar-3 {
animation-delay: 0.6s;
}
#components-reconnect-modal .bar-4 {
animation-delay: 0.2s;
}
#components-reconnect-modal .bar-5 {
animation-delay: 0.5s;
}
@@keyframes reconnectWavePulse {
0% {
transform: scaleY(0.3);
opacity: 0.5;
}
100% {
transform: scaleY(1);
opacity: 1;
}
}
/* Stop the waveform if the connection completely fails */
#components-reconnect-modal.components-reconnect-failed .bar,
#components-reconnect-modal.components-reconnect-rejected .bar,
#components-reconnect-modal.components-reconnect-paused .bar {
animation: none !important;
transform: scaleY(0.1) !important;
opacity: 0.3 !important;
}
/* --- Display Mechanics Matrix Controllers --- */
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
display: inline-block !important;
}
#components-reconnect-modal.components-reconnect-failed .btn-glass-action,
#components-reconnect-modal.components-reconnect-paused .btn-glass-action,
#components-reconnect-modal.components-reconnect-resume-failed .btn-glass-action {
display: inline-flex !important;
}
</style>
<script>
(function() {
function initReconnectModal() {
const reconnectModal = document.getElementById("components-reconnect-modal");
if (!reconnectModal) {
setTimeout(initReconnectModal, 100);
return;
}
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
const retryButton = document.getElementById("components-reconnect-button");
if (retryButton) {
retryButton.addEventListener("click", retry);
}
const resumeButton = document.getElementById("components-resume-button");
if (resumeButton) {
resumeButton.addEventListener("click", resume);
}
function handleReconnectStateChanged(event) {
if (event.detail.state === "show") {
reconnectModal.showModal();
} else if (event.detail.state === "hide") {
reconnectModal.close();
} else if (event.detail.state === "failed") {
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
} else if (event.detail.state === "rejected") {
location.reload();
}
}
async function retry() {
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
try {
if (window.Blazor) {
const successful = await window.Blazor.reconnect();
if (!successful) {
const resumeSuccessful = await window.Blazor.resumeCircuit();
if (!resumeSuccessful) {
location.reload();
} else {
reconnectModal.close();
}
}
}
} catch (err) {
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
}
}
async function resume() {
try {
if (window.Blazor) {
const successful = await window.Blazor.resumeCircuit();
if (!successful) {
location.reload();
}
}
} catch {
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
}
}
async function retryWhenDocumentBecomesVisible() {
if (document.visibilityState === "visible") {
await retry();
}
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initReconnectModal);
} else {
initReconnectModal();
}
})();
</script>