From fba24c5ae37ff82734c5f99186175b590d7d0cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20G=C3=B6pel?= Date: Mon, 27 Jul 2026 00:31:40 +0800 Subject: [PATCH] feat: adopt ReconnectModal from design-blazor, delete local copy --- TECHNICAL.md | 2 +- .../Components/Layout/ReconnectModal.razor | 33 ---- .../Layout/ReconnectModal.razor.css | 157 ------------------ .../Components/Layout/ReconnectModal.razor.js | 63 ------- .../Resources/AppFoundationStrings.de.resx | 15 -- .../Resources/AppFoundationStrings.resx | 15 -- .../ReconnectModal.Localization.Tests.cs | 40 ----- 7 files changed, 1 insertion(+), 324 deletions(-) delete mode 100644 src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor delete mode 100644 src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.css delete mode 100644 src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.js delete mode 100644 tests/AndreGoepel.AppFoundation.Tests/Components/Layout/ReconnectModal.Localization.Tests.cs diff --git a/TECHNICAL.md b/TECHNICAL.md index 3a59106..79e0b92 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -166,7 +166,7 @@ routable pages are discovered. |---|---| | `Layout/MainLayout` | Radzen layout: header (profile menu + logout), sidebar (brand, `NavMenu`, copyright), body | | `Layout/NavMenu` | Setup-gated menu: authenticated **Account** submenu; **Administrator** section with an injectable admin slot + Users / Roles / User Cleanup | -| `Layout/EmptyLayout`, `Layout/ReconnectModal` | Bare layout for setup/errors; Blazor reconnect UI | +| `Layout/EmptyLayout` | Bare layout for setup/errors | | `Pages/Home` | Dashboard landing (`/dashboard`) | | `Pages/Setup` | First-run setup page | | `Administration/Pages/EmailSettingsPage` | Admin-only email settings editor (`/Administration/EmailSettings`, from 1.1.0; §5) | diff --git a/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor b/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor deleted file mode 100644 index 2cf1ba8..0000000 --- a/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor +++ /dev/null @@ -1,33 +0,0 @@ -@inherits AndreGoepel.AppFoundation.Components.AppFoundationLocalizedComponentBase - - - - -
- -

- @T("Reconnect.Rejoining") -

-

- @T("Reconnect.RejoinFailedPrefix") @T("Reconnect.RejoinFailedSuffix") -

-

- @T("Reconnect.FailedLine1")
@T("Reconnect.FailedLine2") -

- -

- @T("Reconnect.Paused") -

- -

- @T("Reconnect.ResumeFailedLine1")
@T("Reconnect.ResumeFailedLine2") -

-
-
diff --git a/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.css b/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.css deleted file mode 100644 index 3ad3773..0000000 --- a/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.css +++ /dev/null @@ -1,157 +0,0 @@ -.components-reconnect-first-attempt-visible, -.components-reconnect-repeated-attempt-visible, -.components-reconnect-failed-visible, -.components-pause-visible, -.components-resume-failed-visible, -.components-rejoining-animation { - display: none; -} - -#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible, -#components-reconnect-modal.components-reconnect-show .components-rejoining-animation, -#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-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible, -#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation, -#components-reconnect-modal.components-reconnect-failed, -#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible { - display: block; -} - - -#components-reconnect-modal { - background-color: white; - width: 20rem; - margin: 20vh auto; - padding: 2rem; - border: 0; - border-radius: 0.5rem; - box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3); - opacity: 0; - transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete; - animation: components-reconnect-modal-fadeOutOpacity 0.5s both; - &[open] - -{ - animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s; - animation-fill-mode: both; -} - -} - -#components-reconnect-modal::backdrop { - background-color: rgba(0, 0, 0, 0.4); - animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out; - opacity: 1; -} - -@keyframes components-reconnect-modal-slideUp { - 0% { - transform: translateY(30px) scale(0.95); - } - - 100% { - transform: translateY(0); - } -} - -@keyframes components-reconnect-modal-fadeInOpacity { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -@keyframes components-reconnect-modal-fadeOutOpacity { - 0% { - opacity: 1; - } - - 100% { - opacity: 0; - } -} - -.components-reconnect-container { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; -} - -#components-reconnect-modal p { - margin: 0; - text-align: center; -} - -#components-reconnect-modal button { - border: 0; - background-color: #6b9ed2; - color: white; - padding: 4px 24px; - border-radius: 4px; -} - - #components-reconnect-modal button:hover { - background-color: #3b6ea2; - } - - #components-reconnect-modal button:active { - background-color: #6b9ed2; - } - -.components-rejoining-animation { - position: relative; - width: 80px; - height: 80px; -} - - .components-rejoining-animation div { - position: absolute; - border: 3px solid #0087ff; - opacity: 1; - border-radius: 50%; - animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite; - } - - .components-rejoining-animation div:nth-child(2) { - animation-delay: -0.5s; - } - -@keyframes components-rejoining-animation { - 0% { - top: 40px; - left: 40px; - width: 0; - height: 0; - opacity: 0; - } - - 4.9% { - top: 40px; - left: 40px; - width: 0; - height: 0; - opacity: 0; - } - - 5% { - top: 40px; - left: 40px; - width: 0; - height: 0; - opacity: 1; - } - - 100% { - top: 0px; - left: 0px; - width: 80px; - height: 80px; - opacity: 0; - } -} diff --git a/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.js b/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.js deleted file mode 100644 index e52a190..0000000 --- a/src/AndreGoepel.AppFoundation/Components/Layout/ReconnectModal.razor.js +++ /dev/null @@ -1,63 +0,0 @@ -// Set up event handlers -const reconnectModal = document.getElementById("components-reconnect-modal"); -reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged); - -const retryButton = document.getElementById("components-reconnect-button"); -retryButton.addEventListener("click", retry); - -const resumeButton = document.getElementById("components-resume-button"); -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 { - // Reconnect will asynchronously return: - // - true to mean success - // - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID) - // - exception to mean we didn't reach the server (this can be sync or async) - const successful = await Blazor.reconnect(); - if (!successful) { - // We have been able to reach the server, but the circuit is no longer available. - // We'll reload the page so the user can continue using the app as quickly as possible. - const resumeSuccessful = await Blazor.resumeCircuit(); - if (!resumeSuccessful) { - location.reload(); - } else { - reconnectModal.close(); - } - } - } catch (err) { - // We got an exception, server is currently unavailable - document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible); - } -} - -async function resume() { - try { - const successful = await Blazor.resumeCircuit(); - if (!successful) { - location.reload(); - } - } catch { - location.reload(); - } -} - -async function retryWhenDocumentBecomesVisible() { - if (document.visibilityState === "visible") { - await retry(); - } -} diff --git a/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.de.resx b/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.de.resx index 39cb651..0df6bd7 100644 --- a/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.de.resx +++ b/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.de.resx @@ -63,21 +63,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - Gefolgt von einem live aktualisierten Sekunden-Countdown, dann RejoinFailedSuffix. - - - - - - - - - - diff --git a/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.resx b/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.resx index 57db9ff..1fcd99e 100644 --- a/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.resx +++ b/src/AndreGoepel.AppFoundation/Resources/AppFoundationStrings.resx @@ -64,21 +64,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - Followed by a live seconds-remaining counter, then RejoinFailedSuffix. - - - - - - - - - - diff --git a/tests/AndreGoepel.AppFoundation.Tests/Components/Layout/ReconnectModal.Localization.Tests.cs b/tests/AndreGoepel.AppFoundation.Tests/Components/Layout/ReconnectModal.Localization.Tests.cs deleted file mode 100644 index d87e20b..0000000 --- a/tests/AndreGoepel.AppFoundation.Tests/Components/Layout/ReconnectModal.Localization.Tests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Globalization; -using AndreGoepel.AppFoundation.Components.Layout; -using Bunit; - -namespace AndreGoepel.AppFoundation.Tests.Components.Layout; - -public sealed class ReconnectModalLocalizationTests : BunitContext -{ - [Fact] - public void Render_English_ShowsEnglishCopy() - { - // Arrange / Act - var cut = Render(); - - // Assert - Assert.Contains("Rejoining the server...", cut.Markup); - Assert.Contains("Retry", cut.Markup); - } - - [Fact] - public void Render_German_ShowsGermanCopy() - { - // Arrange - var original = CultureInfo.CurrentUICulture; - CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("de"); - try - { - // Act - var cut = Render(); - - // Assert - Assert.Contains("Verbindung zum Server wird wiederhergestellt", cut.Markup); - Assert.Contains("Erneut versuchen", cut.Markup); - } - finally - { - CultureInfo.CurrentUICulture = original; - } - } -}