From 124491e5db161e6e36cd2b7030faeef48345a1da Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 18 Jun 2024 20:45:26 +0200 Subject: [PATCH] WASM app tweaks for UI (#27) --- src/AliasVault.WebApp/Auth/Pages/Logout.razor | 4 +- .../Auth/Providers/AuthStateProvider.cs | 2 +- src/AliasVault.WebApp/Layout/TopMenu.razor | 80 +++++++++++-------- .../Pages/Aliases/AddEdit.razor | 2 +- .../Mailbox/Models/Spamok/EmailApiModel.cs | 2 +- .../Mailbox/Models/Spamok/MailboxApiModel.cs | 2 +- src/AliasVault.WebApp/Pages/Base/PageBase.cs | 2 +- .../Services/GlobalNotificationService.cs | 9 +++ .../wwwroot/css/tailwind.css | 8 ++ src/AliasVault.WebApp/wwwroot/index.html | 16 ++++ src/AliasVault.WebApp/wwwroot/js/dark-mode.js | 6 +- .../Infrastructure/BlazorWasmAppManager.cs | 4 +- 12 files changed, 93 insertions(+), 44 deletions(-) diff --git a/src/AliasVault.WebApp/Auth/Pages/Logout.razor b/src/AliasVault.WebApp/Auth/Pages/Logout.razor index d8cf03e3c..3811b5849 100644 --- a/src/AliasVault.WebApp/Auth/Pages/Logout.razor +++ b/src/AliasVault.WebApp/Auth/Pages/Logout.razor @@ -1,10 +1,11 @@ @page "/user/logout" -@using AliasVault.WebApp.Auth.Services @attribute [AllowAnonymous] @layout Auth.Layout.MainLayout @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavigationManager @inject AuthService AuthService +@inject GlobalNotificationService GlobalNotificationService +@using AliasVault.WebApp.Auth.Services @code { /// @@ -13,6 +14,7 @@ await base.OnInitializedAsync(); await AuthService.RemoveTokensAsync(); await AuthStateProvider.GetAuthenticationStateAsync(); + GlobalNotificationService.ClearMessages(); // Redirect to home page NavigationManager.NavigateTo("/"); diff --git a/src/AliasVault.WebApp/Auth/Providers/AuthStateProvider.cs b/src/AliasVault.WebApp/Auth/Providers/AuthStateProvider.cs index 948a6ea9c..2ec2de70e 100644 --- a/src/AliasVault.WebApp/Auth/Providers/AuthStateProvider.cs +++ b/src/AliasVault.WebApp/Auth/Providers/AuthStateProvider.cs @@ -28,7 +28,7 @@ public class AuthStateProvider(AuthService authService) : AuthenticationStatePro var jsonBytes = ParseBase64WithoutPadding(payload); var keyValuePairs = JsonSerializer.Deserialize>(jsonBytes); - if (keyValuePairs == null) + if (keyValuePairs is null) { throw new InvalidOperationException("Failed to parse JWT token."); } diff --git a/src/AliasVault.WebApp/Layout/TopMenu.razor b/src/AliasVault.WebApp/Layout/TopMenu.razor index 80e5d5840..55b6fa3d6 100644 --- a/src/AliasVault.WebApp/Layout/TopMenu.razor +++ b/src/AliasVault.WebApp/Layout/TopMenu.razor @@ -1,4 +1,5 @@ @inherits PageBase +@implements IDisposable
- + @if (isMobileMenuOpen) + { + + }
@code { private bool isMenuOpen = false; + private bool isMobileMenuOpen = false; private string _username { get; set; } = ""; /// @@ -108,6 +95,7 @@ { await base.OnInitializedAsync(); _username = await GetUsernameAsync(); + NavigationManager.LocationChanged += LocationChanged; } /// @@ -117,16 +105,38 @@ if (firstRender) { await Js.InvokeVoidAsync("window.initTopMenu"); + DotNetObjectReference objRef = DotNetObjectReference.Create(this); + await Js.InvokeVoidAsync("window.registerClickOutsideHandler", objRef); } } + void LocationChanged(object? sender, LocationChangedEventArgs e) + { + isMenuOpen = false; + isMobileMenuOpen = false; + StateHasChanged(); + } + private void ToggleMenu() { isMenuOpen = !isMenuOpen; } - private void CloseMenu() + private void ToggleMobileMenu() + { + isMobileMenuOpen = !isMobileMenuOpen; + } + + [JSInvokable] + public void CloseMenu() { isMenuOpen = false; + isMobileMenuOpen = false; + StateHasChanged(); + } + + public void Dispose() + { + NavigationManager.LocationChanged -= LocationChanged; } } diff --git a/src/AliasVault.WebApp/Pages/Aliases/AddEdit.razor b/src/AliasVault.WebApp/Pages/Aliases/AddEdit.razor index 9c994c04d..28d2d8a5b 100644 --- a/src/AliasVault.WebApp/Pages/Aliases/AddEdit.razor +++ b/src/AliasVault.WebApp/Pages/Aliases/AddEdit.razor @@ -64,7 +64,7 @@ else

Login credentials

- + @if (IsIdentityLoading) { diff --git a/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/EmailApiModel.cs b/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/EmailApiModel.cs index f19a899c5..c69564df1 100644 --- a/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/EmailApiModel.cs +++ b/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/EmailApiModel.cs @@ -75,5 +75,5 @@ public class EmailApiModel /// /// Gets or sets the list of attachments in the email. /// - public List Attachments { get; set; } = new(); + public List Attachments { get; set; } = []; } diff --git a/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/MailboxApiModel.cs b/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/MailboxApiModel.cs index 45ba35741..d5f2c8a3e 100644 --- a/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/MailboxApiModel.cs +++ b/src/AliasVault.WebApp/Pages/Aliases/Mailbox/Models/Spamok/MailboxApiModel.cs @@ -25,5 +25,5 @@ public class MailboxApiModel /// /// Gets or sets the list of mailbox email API models. /// - public List Mails { get; set; } = new(); + public List Mails { get; set; } = []; } diff --git a/src/AliasVault.WebApp/Pages/Base/PageBase.cs b/src/AliasVault.WebApp/Pages/Base/PageBase.cs index b92f88f83..f089915c0 100644 --- a/src/AliasVault.WebApp/Pages/Base/PageBase.cs +++ b/src/AliasVault.WebApp/Pages/Base/PageBase.cs @@ -49,7 +49,7 @@ public class PageBase : OwningComponentBase /// /// Gets or sets the breadcrumb items for the page. A default set of breadcrumbs is added in the parent OnInitialized method. /// - protected List BreadcrumbItems { get; set; } = new List(); + protected List BreadcrumbItems { get; set; } = []; /// /// Initializes the component asynchronously. diff --git a/src/AliasVault.WebApp/Services/GlobalNotificationService.cs b/src/AliasVault.WebApp/Services/GlobalNotificationService.cs index 11ab08181..4b0f9a74b 100644 --- a/src/AliasVault.WebApp/Services/GlobalNotificationService.cs +++ b/src/AliasVault.WebApp/Services/GlobalNotificationService.cs @@ -90,5 +90,14 @@ public class GlobalNotificationService return messages; } + /// + /// Clear all messages. + /// + public void ClearMessages() + { + SuccessMessages.Clear(); + ErrorMessages.Clear(); + } + private void NotifyStateChanged() => OnChange?.Invoke(); } diff --git a/src/AliasVault.WebApp/wwwroot/css/tailwind.css b/src/AliasVault.WebApp/wwwroot/css/tailwind.css index 814fc57f5..6160a2eca 100644 --- a/src/AliasVault.WebApp/wwwroot/css/tailwind.css +++ b/src/AliasVault.WebApp/wwwroot/css/tailwind.css @@ -603,6 +603,14 @@ video { right: 0px; } +.top-12 { + top: 3rem; +} + +.top-10 { + top: 2.5rem; +} + .z-10 { z-index: 10; } diff --git a/src/AliasVault.WebApp/wwwroot/index.html b/src/AliasVault.WebApp/wwwroot/index.html index fd3f97985..818b8b825 100644 --- a/src/AliasVault.WebApp/wwwroot/index.html +++ b/src/AliasVault.WebApp/wwwroot/index.html @@ -33,6 +33,22 @@ initDarkModeSwitcher(); }; + window.registerClickOutsideHandler = (dotNetHelper) => { + document.addEventListener('click', (event) => { + const menu = document.getElementById('userMenuDropdown'); + const menuButton = document.getElementById('userMenuDropdownButton'); + if (menu && !menu.contains(event.target) && !menuButton.contains(event.target)) { + dotNetHelper.invokeMethodAsync('CloseMenu'); + } + + const mobileMenu = document.getElementById('mobileMenu'); + const mobileMenuButton = document.getElementById('toggleMobileMenuButton'); + if (mobileMenu && !mobileMenu.contains(event.target) && !mobileMenuButton.contains(event.target)) { + dotNetHelper.invokeMethodAsync('CloseMenu'); + } + }); + }; + window.clipboardCopy = { copyText: function (text) { navigator.clipboard.writeText(text).then(function () { }) diff --git a/src/AliasVault.WebApp/wwwroot/js/dark-mode.js b/src/AliasVault.WebApp/wwwroot/js/dark-mode.js index ff464c7ec..212580e7e 100644 --- a/src/AliasVault.WebApp/wwwroot/js/dark-mode.js +++ b/src/AliasVault.WebApp/wwwroot/js/dark-mode.js @@ -1,5 +1,4 @@ function initDarkModeSwitcher() { - console.log('initDarkModeSwitcher'); const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon'); const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon'); @@ -16,6 +15,11 @@ function initDarkModeSwitcher() { themeToggleDarkIcon.classList.remove('hidden'); } } + else { + // Default to light mode if not set. + document.documentElement.classList.remove('dark'); + themeToggleLightIcon.classList.remove('hidden'); + } const themeToggleBtn = document.getElementById('theme-toggle'); diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/BlazorWasmAppManager.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/BlazorWasmAppManager.cs index af3a81d11..17d83f6d6 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/BlazorWasmAppManager.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/BlazorWasmAppManager.cs @@ -106,13 +106,13 @@ public class BlazorWasmAppManager } catch (Exception e) { + await TestContext.Out.WriteLineAsync(e.Message); + if (_blazorWasmErrors.Count > 0) { Assert.Fail($"WASM failed to start: {string.Join(Environment.NewLine, _blazorWasmErrors)}"); return; } - - Console.WriteLine(e.Message); } } }