From 638c4669c84954f514bd952a29c897c2cb807633 Mon Sep 17 00:00:00 2001 From: "hualin.zhu" Date: Mon, 9 Dec 2024 21:22:17 +0800 Subject: [PATCH 1/5] OnlineStatusService --- .../Components/NetworkStatusIndicator.razor | 43 ++++++++++++ src/CleanAspire.ClientApp/Layout/Appbar.razor | 2 + src/CleanAspire.ClientApp/Program.cs | 2 + .../Services/JsInterop/OnlineStatusService.cs | 65 +++++++++++++++++++ src/CleanAspire.ClientApp/Themes/Theme.cs | 2 +- src/CleanAspire.ClientApp/wwwroot/css/app.css | 10 ++- .../wwwroot/green-point.svg | 47 ++++++++++++++ .../wwwroot/js/onlinestatus.js | 11 ++++ src/CleanAspire.ClientApp/wwwroot/loading.svg | 20 ++++++ .../wwwroot/red-point.svg | 52 +++++++++++++++ .../wwwroot/service-worker.js | 25 ++++++- 11 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor create mode 100644 src/CleanAspire.ClientApp/Services/JsInterop/OnlineStatusService.cs create mode 100644 src/CleanAspire.ClientApp/wwwroot/green-point.svg create mode 100644 src/CleanAspire.ClientApp/wwwroot/js/onlinestatus.js create mode 100644 src/CleanAspire.ClientApp/wwwroot/loading.svg create mode 100644 src/CleanAspire.ClientApp/wwwroot/red-point.svg diff --git a/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor b/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor new file mode 100644 index 0000000..c94bb63 --- /dev/null +++ b/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor @@ -0,0 +1,43 @@ +@inject OnlineStatusService OnlineStatusService + +
+ + + +
+ +@code { + private string statusImage = "loading.svg"; // Default loading image + private string statusTooltip = "Loading..."; // Default tooltip message + + protected override async Task OnInitializedAsync() + { + // Initialize the service and JavaScript module + await OnlineStatusService.InitializeAsync(); + + // Get the current network status and update the image and tooltip + bool isOnline = await OnlineStatusService.GetOnlineStatusAsync(); + UpdateStatus(isOnline); + + // Subscribe to network status change events + OnlineStatusService.OnlineStatusChanged += UpdateStatus; + + // Register the network status listener + await OnlineStatusService.RegisterOnlineStatusListenerAsync(); + } + + private void UpdateStatus(bool isOnline) + { + // Update the image and tooltip based on the network status + statusImage = isOnline ? "green-point.svg" : "red-point.svg"; + statusTooltip = isOnline ? "Online" : "Offline"; + InvokeAsync(StateHasChanged); + } + + public async ValueTask DisposeAsync() + { + // Unsubscribe from events and dispose of resources + OnlineStatusService.OnlineStatusChanged -= UpdateStatus; + await OnlineStatusService.DisposeAsync(); + } +} diff --git a/src/CleanAspire.ClientApp/Layout/Appbar.razor b/src/CleanAspire.ClientApp/Layout/Appbar.razor index d21526d..688516c 100644 --- a/src/CleanAspire.ClientApp/Layout/Appbar.razor +++ b/src/CleanAspire.ClientApp/Layout/Appbar.razor @@ -2,6 +2,8 @@ @L[AppSettings.AppName] + + diff --git a/src/CleanAspire.ClientApp/Program.cs b/src/CleanAspire.ClientApp/Program.cs index 4e23399..75f5f49 100644 --- a/src/CleanAspire.ClientApp/Program.cs +++ b/src/CleanAspire.ClientApp/Program.cs @@ -15,6 +15,7 @@ using Microsoft.Kiota.Serialization.Multipart; using CleanAspire.ClientApp.Services; using Microsoft.Extensions.DependencyInjection; +using CleanAspire.ClientApp.Services.JsInterop; var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -25,6 +26,7 @@ builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); var clientAppSettings = builder.Configuration.GetSection(ClientAppSettings.KEY).Get(); builder.Services.AddSingleton(clientAppSettings!); diff --git a/src/CleanAspire.ClientApp/Services/JsInterop/OnlineStatusService.cs b/src/CleanAspire.ClientApp/Services/JsInterop/OnlineStatusService.cs new file mode 100644 index 0000000..b8bd105 --- /dev/null +++ b/src/CleanAspire.ClientApp/Services/JsInterop/OnlineStatusService.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.JSInterop; + +namespace CleanAspire.ClientApp.Services.JsInterop; + +using Microsoft.JSInterop; +using System; +using System.Threading.Tasks; + +public class OnlineStatusService : IAsyncDisposable +{ + private readonly IJSRuntime _jsRuntime; + private IJSObjectReference? _jsModule; + private DotNetObjectReference? _dotNetRef; + + public event Action? OnlineStatusChanged; + + public OnlineStatusService(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + public async Task InitializeAsync() + { + _jsModule = await _jsRuntime.InvokeAsync("import", "/js/onlinestatus.js"); + _dotNetRef = DotNetObjectReference.Create(this); + } + + public async Task GetOnlineStatusAsync() + { + if (_jsModule == null) + { + throw new InvalidOperationException("JavaScript module is not initialized. Call InitializeAsync first."); + } + return await _jsModule.InvokeAsync("getOnlineStatus"); + } + public async Task RegisterOnlineStatusListenerAsync() + { + if (_jsModule == null) + { + throw new InvalidOperationException("JavaScript module is not initialized. Call InitializeAsync first."); + } + await _jsModule.InvokeVoidAsync("addOnlineStatusListener", _dotNetRef); + } + + [JSInvokable] + public void UpdateOnlineStatus(bool isOnline) + { + OnlineStatusChanged?.Invoke(isOnline); + } + + public async ValueTask DisposeAsync() + { + if (_dotNetRef != null) + { + _dotNetRef.Dispose(); + } + if (_jsModule != null) + { + await _jsModule.DisposeAsync(); + } + } +} diff --git a/src/CleanAspire.ClientApp/Themes/Theme.cs b/src/CleanAspire.ClientApp/Themes/Theme.cs index ccd139f..16cf629 100644 --- a/src/CleanAspire.ClientApp/Themes/Theme.cs +++ b/src/CleanAspire.ClientApp/Themes/Theme.cs @@ -65,7 +65,7 @@ public static MudTheme ApplicationTheme() PaletteDark = new() { // **Primary Colors** - Primary = "#bd93ff", // Deep blue, used for highlights and key elements + Primary = "#8a2be2", // Deep blue, used for highlights and key elements Secondary = "#B0BEC5", // Light gray, secondary text color // **Background and Surface** diff --git a/src/CleanAspire.ClientApp/wwwroot/css/app.css b/src/CleanAspire.ClientApp/wwwroot/css/app.css index bfce92a..9ebb658 100644 --- a/src/CleanAspire.ClientApp/wwwroot/css/app.css +++ b/src/CleanAspire.ClientApp/wwwroot/css/app.css @@ -136,7 +136,15 @@ padding: 0rem !important; } - +.network-status-indicator { + width: 48px; + height: 48px; + display: flex; + justify-content: center; + align-items: center; + margin: 0 auto; /* Center horizontally if needed */ + box-sizing: border-box; +} .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; padding: 1rem 1rem 1rem 3.7rem; diff --git a/src/CleanAspire.ClientApp/wwwroot/green-point.svg b/src/CleanAspire.ClientApp/wwwroot/green-point.svg new file mode 100644 index 0000000..b0ff9b9 --- /dev/null +++ b/src/CleanAspire.ClientApp/wwwroot/green-point.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CleanAspire.ClientApp/wwwroot/js/onlinestatus.js b/src/CleanAspire.ClientApp/wwwroot/js/onlinestatus.js new file mode 100644 index 0000000..deb2a1f --- /dev/null +++ b/src/CleanAspire.ClientApp/wwwroot/js/onlinestatus.js @@ -0,0 +1,11 @@ +export function getOnlineStatus() { + return navigator.onLine; +} +export function addOnlineStatusListener(dotNetObjectRef) { + window.addEventListener('online', () => { + dotNetObjectRef.invokeMethodAsync('UpdateOnlineStatus', true); + }); + window.addEventListener('offline', () => { + dotNetObjectRef.invokeMethodAsync('UpdateOnlineStatus', false); + }); +} \ No newline at end of file diff --git a/src/CleanAspire.ClientApp/wwwroot/loading.svg b/src/CleanAspire.ClientApp/wwwroot/loading.svg new file mode 100644 index 0000000..4f6c6d0 --- /dev/null +++ b/src/CleanAspire.ClientApp/wwwroot/loading.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/src/CleanAspire.ClientApp/wwwroot/red-point.svg b/src/CleanAspire.ClientApp/wwwroot/red-point.svg new file mode 100644 index 0000000..818240a --- /dev/null +++ b/src/CleanAspire.ClientApp/wwwroot/red-point.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CleanAspire.ClientApp/wwwroot/service-worker.js b/src/CleanAspire.ClientApp/wwwroot/service-worker.js index fe614da..9b1531b 100644 --- a/src/CleanAspire.ClientApp/wwwroot/service-worker.js +++ b/src/CleanAspire.ClientApp/wwwroot/service-worker.js @@ -1,4 +1,27 @@ // In development, always fetch from the network and do not enable offline support. // This is because caching would make development more difficult (changes would not // be reflected on the first load after each change). -self.addEventListener('fetch', () => { }); + +const CACHE_NAME = 'network-status-cache-v1'; +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll([ + 'green-point.svg', + 'red-point.svg', + 'loading.svg' + ]); + }) + ); +}); +self.addEventListener('fetch', (event) => { + console.log('Fetching:', event.request.url); + event.respondWith( + caches.match(event.request).then((response) => { + if (response) { + console.log('Serving from cache:', event.request.url); + } + return response || fetch(event.request); + }) + ); +}); From be8c2bb4f82a5e8ee1c7158895ca43b63bd4d37a Mon Sep 17 00:00:00 2001 From: "hualin.zhu" Date: Mon, 9 Dec 2024 21:27:59 +0800 Subject: [PATCH 2/5] embed svg to code --- .../Components/NetworkStatusIndicator.razor | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor b/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor index c94bb63..047e804 100644 --- a/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor +++ b/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor @@ -2,16 +2,24 @@
- +
@code { - private string statusImage = "loading.svg"; // Default loading image + private string statusImageBase64 = ""; // Default Base64 for loading private string statusTooltip = "Loading..."; // Default tooltip message + // Base64 strings for green, red, and loading SVGs + private readonly string greenPointBase64 = "data:image/svg+xml;base64,77u/PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNSIgaGVpZ2h0PSIyNSIgdmlld0JveD0iMCAwIDI1IDI1Ij4NCiAgICA8IS0tIOWFieaZleaViOaenCAtLT4NCiAgICA8ZGVmcz4NCiAgICAgICAgPGZpbHRlciBpZD0iZ2xvdyIgeD0iLTUwJSIgeT0iLTUwJSIgd2lkdGg9IjIwMCUiIGhlaWdodD0iMjAwJSI+DQogICAgICAgICAgICA8ZmVHYXVzc2lhbkJsdXIgaW49IlNvdXJjZUdyYXBoaWMiIHN0ZERldmlhdGlvbj0iNSIgcmVzdWx0PSJibHVyIiAvPg0KICAgICAgICAgICAgPGZlTWVyZ2U+DQogICAgICAgICAgICAgICAgPGZlTWVyZ2VOb2RlIGluPSJibHVyIiAvPg0KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZSBpbj0iU291cmNlR3JhcGhpYyIgLz4NCiAgICAgICAgICAgIDwvZmVNZXJnZT4NCiAgICAgICAgPC9maWx0ZXI+DQoNCiAgICAgICAgPCEtLSDliqjnlLvmlYjmnpwgLS0+DQogICAgICAgIDxzdHlsZT4NCiAgICAgICAgICAgIEBrZXlmcmFtZXMgZ2xvd1B1bHNlIHsNCiAgICAgICAgICAgIDAlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDVweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICAyNSUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMjBweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICA1MCUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMzBweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICA3NSUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMjBweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICAxMDAlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDVweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICB9DQoNCiAgICAgICAgICAgIC5nbG93aW5nIHsNCiAgICAgICAgICAgIGFuaW1hdGlvbjogZ2xvd1B1bHNlIDEuNXMgaW5maW5pdGU7DQogICAgICAgICAgICB9DQogICAgICAgIDwvc3R5bGU+DQogICAgPC9kZWZzPg0KDQogICAgPCEtLSDnkIPlvaLmlYjmnpwgLS0+DQogICAgPHJhZGlhbEdyYWRpZW50IGlkPSJzcGhlcmVHcmFkaWVudCIgY3g9IjUwJSIgY3k9IjUwJSIgcj0iNTAlIiBmeD0iMzAlIiBmeT0iMzAlIj4NCiAgICAgICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwRkYwMCIgLz4NCiAgICAgICAgPHN0b3Agb2Zmc2V0PSI3MCUiIHN0b3AtY29sb3I9IiMwMDdGMDAiIC8+DQogICAgICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwNEYwMCIgLz4NCiAgICA8L3JhZGlhbEdyYWRpZW50Pg0KDQogICAgPCEtLSDlsI/nu7/ngrkgd2l0aCAzRCBzcGhlcmUgZWZmZWN0IC0tPg0KICAgIDxjaXJjbGUgY3g9IjEyLjUiIGN5PSIxMi41IiByPSIxMCIgZmlsbD0idXJsKCNzcGhlcmVHcmFkaWVudCkiIGNsYXNzPSJnbG93aW5nIiAvPg0KPC9zdmc+DQo="; + private readonly string redPointBase64 = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNSIgaGVpZ2h0PSIyNSIgdmlld0JveD0iMCAwIDI1IDI1Ij4NCiAgICA8IS0tIOWFieaZleaViOaenCAtLT4NCiAgICA8ZGVmcz4NCiAgICAgICAgPGZpbHRlciBpZD0iZ2xvdyIgeD0iLTUwJSIgeT0iLTUwJSIgd2lkdGg9IjIwMCUiIGhlaWdodD0iMjAwJSI+DQogICAgICAgICAgICA8ZmVHYXVzc2lhbkJsdXIgaW49IlNvdXJjZUdyYXBoaWMiIHN0ZERldmlhdGlvbj0iNSIgcmVzdWx0PSJibHVyIiAvPg0KICAgICAgICAgICAgPGZlTWVyZ2U+DQogICAgICAgICAgICAgICAgPGZlTWVyZ2VOb2RlIGluPSJibHVyIiAvPg0KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZSBpbj0iU291cmNlR3JhcGhpYyIgLz4NCiAgICAgICAgICAgIDwvZmVNZXJnZT4NCiAgICAgICAgPC9maWx0ZXI+DQoNCiAgICAgICAgPCEtLSDliqjnlLvmlYjmnpwgLS0+DQogICAgICAgIDxzdHlsZT4NCiAgICAgICAgICAgIEBrZXlmcmFtZXMgZ2xvd1B1bHNlIHsNCiAgICAgICAgICAgIDAlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDVweCByZWQpOw0KICAgICAgICAgICAgb3BhY2l0eTogMTsNCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgIDI1JSB7DQogICAgICAgICAgICBmaWx0ZXI6IGRyb3Atc2hhZG93KDAgMCAyMHB4IHJlZCk7DQogICAgICAgICAgICBvcGFjaXR5OiAwLjg7DQogICAgICAgICAgICB9DQogICAgICAgICAgICA1MCUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMzBweCByZWQpOw0KICAgICAgICAgICAgb3BhY2l0eTogMC42Ow0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgNzUlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDIwcHggcmVkKTsNCiAgICAgICAgICAgIG9wYWNpdHk6IDAuODsNCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgIDEwMCUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgNXB4IHJlZCk7DQogICAgICAgICAgICBvcGFjaXR5OiAxOw0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgfQ0KDQogICAgICAgICAgICAuZ2xvd2luZyB7DQogICAgICAgICAgICBhbmltYXRpb246IGdsb3dQdWxzZSAxLjVzIGluZmluaXRlOw0KICAgICAgICAgICAgfQ0KICAgICAgICA8L3N0eWxlPg0KICAgIDwvZGVmcz4NCg0KICAgIDwhLS0g55CD5b2i5pWI5p6cIC0tPg0KICAgIDxyYWRpYWxHcmFkaWVudCBpZD0ic3BoZXJlR3JhZGllbnQiIGN4PSI1MCUiIGN5PSI1MCUiIHI9IjUwJSIgZng9IjMwJSIgZnk9IjMwJSI+DQogICAgICAgIDxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNGRjAwMDAiIC8+DQogICAgICAgIDxzdG9wIG9mZnNldD0iNzAlIiBzdG9wLWNvbG9yPSIjN0YwMDAwIiAvPg0KICAgICAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM0RjAwMDAiIC8+DQogICAgPC9yYWRpYWxHcmFkaWVudD4NCg0KICAgIDwhLS0g5bCP57qi54K5IHdpdGggM0Qgc3BoZXJlIGVmZmVjdCAtLT4NCiAgICA8Y2lyY2xlIGN4PSIxMi41IiBjeT0iMTIuNSIgcj0iMTAiIGZpbGw9InVybCgjc3BoZXJlR3JhZGllbnQpIiBjbGFzcz0iZ2xvd2luZyIgLz4NCjwvc3ZnPg=="; + private readonly string loadingBase64 = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNSIgaGVpZ2h0PSIyNSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMzUiIHN0cm9rZS13aWR0aD0iOCIgc3Ryb2tlPSIjMzQ5OGRiIiBzdHJva2UtZGFzaGFycmF5PSI1NSA1NSIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj4NCiAgICA8YW5pbWF0ZVRyYW5zZm9ybSANCiAgICAgIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgDQogICAgICB0eXBlPSJyb3RhdGUiIA0KICAgICAgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIA0KICAgICAgZHVyPSIwLjhzIiANCiAgICAgIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiIA0KICAgICAga2V5VGltZXM9IjA7MSIvPg0KICA8L2NpcmNsZT4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMjUiIHN0cm9rZS13aWR0aD0iNiIgc3Ryb2tlPSIjZTc0YzNjIiBzdHJva2UtZGFzaGFycmF5PSI0MCA0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj4NCiAgICA8YW5pbWF0ZVRyYW5zZm9ybSANCiAgICAgIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgDQogICAgICB0eXBlPSJyb3RhdGUiIA0KICAgICAgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIA0KICAgICAgZHVyPSIxLjJzIiANCiAgICAgIHZhbHVlcz0iMzYwIDUwIDUwOzAgNTAgNTAiIA0KICAgICAga2V5VGltZXM9IjA7MSIvPg0KICA8L2NpcmNsZT4NCjwvc3ZnPg=="; + protected override async Task OnInitializedAsync() { + // Set default loading image + statusImageBase64 = loadingBase64; + // Initialize the service and JavaScript module await OnlineStatusService.InitializeAsync(); @@ -28,8 +36,8 @@ private void UpdateStatus(bool isOnline) { - // Update the image and tooltip based on the network status - statusImage = isOnline ? "green-point.svg" : "red-point.svg"; + // Update the Base64 image and tooltip based on the network status + statusImageBase64 = isOnline ? greenPointBase64 : redPointBase64; statusTooltip = isOnline ? "Online" : "Offline"; InvokeAsync(StateHasChanged); } From 2e3c2f6afd3d5272b4d786f656b5c691a7bae814 Mon Sep 17 00:00:00 2001 From: hualin Date: Tue, 10 Dec 2024 10:12:35 +0800 Subject: [PATCH 3/5] remove NetworkStatusIndicator --- src/CleanAspire.ClientApp/Layout/Appbar.razor | 2 +- .../Layout/UserMenu.razor | 55 +++++++++++++++---- src/CleanAspire.ClientApp/wwwroot/css/app.css | 42 ++++++++++++++ 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/CleanAspire.ClientApp/Layout/Appbar.razor b/src/CleanAspire.ClientApp/Layout/Appbar.razor index 688516c..318ce2c 100644 --- a/src/CleanAspire.ClientApp/Layout/Appbar.razor +++ b/src/CleanAspire.ClientApp/Layout/Appbar.razor @@ -2,7 +2,7 @@ @L[AppSettings.AppName] - + diff --git a/src/CleanAspire.ClientApp/Layout/UserMenu.razor b/src/CleanAspire.ClientApp/Layout/UserMenu.razor index 7bb1d32..894e13a 100644 --- a/src/CleanAspire.ClientApp/Layout/UserMenu.razor +++ b/src/CleanAspire.ClientApp/Layout/UserMenu.razor @@ -1,21 +1,29 @@ - -@using CleanAspire.ClientApp.Services.Identity +@using CleanAspire.ClientApp.Services.Identity @using Microsoft.AspNetCore.Components.Authorization @inject IIdentityManagement IdentityManagement +@inject OnlineStatusService OnlineStatusService - @if (string.IsNullOrEmpty(userModel?.AvatarUrl)) - { - @userModel?.Username?.FirstOrDefault() - } - else - { - - - - } + +
+ @if (_isOnline == false) + { +
+ } + @if (string.IsNullOrEmpty(userModel?.AvatarUrl)) + { + @userModel?.Username?.FirstOrDefault() + } + else + { + + + + } +
+
@userModel?.Username @userModel?.Email @@ -54,10 +62,33 @@ public AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!; private ProfileResponse? userModel => UserProfileStore.Profile; + private string statusTooltip = "Loading..."; + private bool _isOnline = false; private async Task OnSignOut() { await IdentityManagement.LogoutAsync(); StateHasChanged(); } + protected override async Task OnInitializedAsync() + { + await OnlineStatusService.InitializeAsync(); + bool isOnline = await OnlineStatusService.GetOnlineStatusAsync(); + UpdateStatus(isOnline); + OnlineStatusService.OnlineStatusChanged += UpdateStatus; + await OnlineStatusService.RegisterOnlineStatusListenerAsync(); + } + private void UpdateStatus(bool isOnline) + { + // Update the Base64 image and tooltip based on the network status + statusTooltip = isOnline ? "Online" : "Offline"; + _isOnline = isOnline; + InvokeAsync(StateHasChanged); + } + public async ValueTask DisposeAsync() + { + // Unsubscribe from events and dispose of resources + OnlineStatusService.OnlineStatusChanged -= UpdateStatus; + await OnlineStatusService.DisposeAsync(); + } } diff --git a/src/CleanAspire.ClientApp/wwwroot/css/app.css b/src/CleanAspire.ClientApp/wwwroot/css/app.css index 9ebb658..6850e3e 100644 --- a/src/CleanAspire.ClientApp/wwwroot/css/app.css +++ b/src/CleanAspire.ClientApp/wwwroot/css/app.css @@ -145,6 +145,48 @@ margin: 0 auto; /* Center horizontally if needed */ box-sizing: border-box; } + +.avatar-container { + position: relative; + width: 40px; /* ȣͷһ */ + height: 40px; /* ߶ȣͷһ */ +} + +.rotating-border { + position: absolute; + top: -4px; /* չ4px */ + left: -4px; /* չ4px */ + width: 48px; /* ȦĿȱͷ */ + height: 48px; /* Ȧĸ߶ȱͷ */ + border: 3px solid transparent; /* Ĭ͸ */ + border-top: 3px solid rgba(255, 0, 0, 0.8); /* ɫת߿ */ + border-radius: 50%; /* ԲιȦ */ + animation: spin 2s linear infinite; /* ת */ + z-index: 1; /* ͷ· */ + box-sizing: border-box; +} + +MudAvatar { + position: relative; + z-index: 2; /* ȷͷڹȦϷ */ +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +MudAvatar { + position: relative; + z-index: 2; /* ֤ͷڹȦϷ */ +} + + .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; padding: 1rem 1rem 1rem 3.7rem; From a596d4041ff3484d5192d164fdd3ddc07cb5dc31 Mon Sep 17 00:00:00 2001 From: hualin Date: Tue, 10 Dec 2024 13:29:12 +0800 Subject: [PATCH 4/5] improve css --- .../Components/NetworkStatusIndicator.razor | 51 ------------ .../Layout/UserMenu.razor | 8 +- src/CleanAspire.ClientApp/wwwroot/css/app.css | 83 +++++++++++++++---- 3 files changed, 72 insertions(+), 70 deletions(-) delete mode 100644 src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor diff --git a/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor b/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor deleted file mode 100644 index 047e804..0000000 --- a/src/CleanAspire.ClientApp/Components/NetworkStatusIndicator.razor +++ /dev/null @@ -1,51 +0,0 @@ -@inject OnlineStatusService OnlineStatusService - -
- - - -
- -@code { - private string statusImageBase64 = ""; // Default Base64 for loading - private string statusTooltip = "Loading..."; // Default tooltip message - - // Base64 strings for green, red, and loading SVGs - private readonly string greenPointBase64 = "data:image/svg+xml;base64,77u/PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNSIgaGVpZ2h0PSIyNSIgdmlld0JveD0iMCAwIDI1IDI1Ij4NCiAgICA8IS0tIOWFieaZleaViOaenCAtLT4NCiAgICA8ZGVmcz4NCiAgICAgICAgPGZpbHRlciBpZD0iZ2xvdyIgeD0iLTUwJSIgeT0iLTUwJSIgd2lkdGg9IjIwMCUiIGhlaWdodD0iMjAwJSI+DQogICAgICAgICAgICA8ZmVHYXVzc2lhbkJsdXIgaW49IlNvdXJjZUdyYXBoaWMiIHN0ZERldmlhdGlvbj0iNSIgcmVzdWx0PSJibHVyIiAvPg0KICAgICAgICAgICAgPGZlTWVyZ2U+DQogICAgICAgICAgICAgICAgPGZlTWVyZ2VOb2RlIGluPSJibHVyIiAvPg0KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZSBpbj0iU291cmNlR3JhcGhpYyIgLz4NCiAgICAgICAgICAgIDwvZmVNZXJnZT4NCiAgICAgICAgPC9maWx0ZXI+DQoNCiAgICAgICAgPCEtLSDliqjnlLvmlYjmnpwgLS0+DQogICAgICAgIDxzdHlsZT4NCiAgICAgICAgICAgIEBrZXlmcmFtZXMgZ2xvd1B1bHNlIHsNCiAgICAgICAgICAgIDAlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDVweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICAyNSUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMjBweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICA1MCUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMzBweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICA3NSUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMjBweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICAxMDAlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDVweCBncmVlbik7DQogICAgICAgICAgICB9DQogICAgICAgICAgICB9DQoNCiAgICAgICAgICAgIC5nbG93aW5nIHsNCiAgICAgICAgICAgIGFuaW1hdGlvbjogZ2xvd1B1bHNlIDEuNXMgaW5maW5pdGU7DQogICAgICAgICAgICB9DQogICAgICAgIDwvc3R5bGU+DQogICAgPC9kZWZzPg0KDQogICAgPCEtLSDnkIPlvaLmlYjmnpwgLS0+DQogICAgPHJhZGlhbEdyYWRpZW50IGlkPSJzcGhlcmVHcmFkaWVudCIgY3g9IjUwJSIgY3k9IjUwJSIgcj0iNTAlIiBmeD0iMzAlIiBmeT0iMzAlIj4NCiAgICAgICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwRkYwMCIgLz4NCiAgICAgICAgPHN0b3Agb2Zmc2V0PSI3MCUiIHN0b3AtY29sb3I9IiMwMDdGMDAiIC8+DQogICAgICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwNEYwMCIgLz4NCiAgICA8L3JhZGlhbEdyYWRpZW50Pg0KDQogICAgPCEtLSDlsI/nu7/ngrkgd2l0aCAzRCBzcGhlcmUgZWZmZWN0IC0tPg0KICAgIDxjaXJjbGUgY3g9IjEyLjUiIGN5PSIxMi41IiByPSIxMCIgZmlsbD0idXJsKCNzcGhlcmVHcmFkaWVudCkiIGNsYXNzPSJnbG93aW5nIiAvPg0KPC9zdmc+DQo="; - private readonly string redPointBase64 = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNSIgaGVpZ2h0PSIyNSIgdmlld0JveD0iMCAwIDI1IDI1Ij4NCiAgICA8IS0tIOWFieaZleaViOaenCAtLT4NCiAgICA8ZGVmcz4NCiAgICAgICAgPGZpbHRlciBpZD0iZ2xvdyIgeD0iLTUwJSIgeT0iLTUwJSIgd2lkdGg9IjIwMCUiIGhlaWdodD0iMjAwJSI+DQogICAgICAgICAgICA8ZmVHYXVzc2lhbkJsdXIgaW49IlNvdXJjZUdyYXBoaWMiIHN0ZERldmlhdGlvbj0iNSIgcmVzdWx0PSJibHVyIiAvPg0KICAgICAgICAgICAgPGZlTWVyZ2U+DQogICAgICAgICAgICAgICAgPGZlTWVyZ2VOb2RlIGluPSJibHVyIiAvPg0KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZSBpbj0iU291cmNlR3JhcGhpYyIgLz4NCiAgICAgICAgICAgIDwvZmVNZXJnZT4NCiAgICAgICAgPC9maWx0ZXI+DQoNCiAgICAgICAgPCEtLSDliqjnlLvmlYjmnpwgLS0+DQogICAgICAgIDxzdHlsZT4NCiAgICAgICAgICAgIEBrZXlmcmFtZXMgZ2xvd1B1bHNlIHsNCiAgICAgICAgICAgIDAlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDVweCByZWQpOw0KICAgICAgICAgICAgb3BhY2l0eTogMTsNCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgIDI1JSB7DQogICAgICAgICAgICBmaWx0ZXI6IGRyb3Atc2hhZG93KDAgMCAyMHB4IHJlZCk7DQogICAgICAgICAgICBvcGFjaXR5OiAwLjg7DQogICAgICAgICAgICB9DQogICAgICAgICAgICA1MCUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgMzBweCByZWQpOw0KICAgICAgICAgICAgb3BhY2l0eTogMC42Ow0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgNzUlIHsNCiAgICAgICAgICAgIGZpbHRlcjogZHJvcC1zaGFkb3coMCAwIDIwcHggcmVkKTsNCiAgICAgICAgICAgIG9wYWNpdHk6IDAuODsNCiAgICAgICAgICAgIH0NCiAgICAgICAgICAgIDEwMCUgew0KICAgICAgICAgICAgZmlsdGVyOiBkcm9wLXNoYWRvdygwIDAgNXB4IHJlZCk7DQogICAgICAgICAgICBvcGFjaXR5OiAxOw0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgfQ0KDQogICAgICAgICAgICAuZ2xvd2luZyB7DQogICAgICAgICAgICBhbmltYXRpb246IGdsb3dQdWxzZSAxLjVzIGluZmluaXRlOw0KICAgICAgICAgICAgfQ0KICAgICAgICA8L3N0eWxlPg0KICAgIDwvZGVmcz4NCg0KICAgIDwhLS0g55CD5b2i5pWI5p6cIC0tPg0KICAgIDxyYWRpYWxHcmFkaWVudCBpZD0ic3BoZXJlR3JhZGllbnQiIGN4PSI1MCUiIGN5PSI1MCUiIHI9IjUwJSIgZng9IjMwJSIgZnk9IjMwJSI+DQogICAgICAgIDxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNGRjAwMDAiIC8+DQogICAgICAgIDxzdG9wIG9mZnNldD0iNzAlIiBzdG9wLWNvbG9yPSIjN0YwMDAwIiAvPg0KICAgICAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM0RjAwMDAiIC8+DQogICAgPC9yYWRpYWxHcmFkaWVudD4NCg0KICAgIDwhLS0g5bCP57qi54K5IHdpdGggM0Qgc3BoZXJlIGVmZmVjdCAtLT4NCiAgICA8Y2lyY2xlIGN4PSIxMi41IiBjeT0iMTIuNSIgcj0iMTAiIGZpbGw9InVybCgjc3BoZXJlR3JhZGllbnQpIiBjbGFzcz0iZ2xvd2luZyIgLz4NCjwvc3ZnPg=="; - private readonly string loadingBase64 = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNSIgaGVpZ2h0PSIyNSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMzUiIHN0cm9rZS13aWR0aD0iOCIgc3Ryb2tlPSIjMzQ5OGRiIiBzdHJva2UtZGFzaGFycmF5PSI1NSA1NSIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj4NCiAgICA8YW5pbWF0ZVRyYW5zZm9ybSANCiAgICAgIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgDQogICAgICB0eXBlPSJyb3RhdGUiIA0KICAgICAgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIA0KICAgICAgZHVyPSIwLjhzIiANCiAgICAgIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiIA0KICAgICAga2V5VGltZXM9IjA7MSIvPg0KICA8L2NpcmNsZT4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iMjUiIHN0cm9rZS13aWR0aD0iNiIgc3Ryb2tlPSIjZTc0YzNjIiBzdHJva2UtZGFzaGFycmF5PSI0MCA0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj4NCiAgICA8YW5pbWF0ZVRyYW5zZm9ybSANCiAgICAgIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgDQogICAgICB0eXBlPSJyb3RhdGUiIA0KICAgICAgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIA0KICAgICAgZHVyPSIxLjJzIiANCiAgICAgIHZhbHVlcz0iMzYwIDUwIDUwOzAgNTAgNTAiIA0KICAgICAga2V5VGltZXM9IjA7MSIvPg0KICA8L2NpcmNsZT4NCjwvc3ZnPg=="; - - protected override async Task OnInitializedAsync() - { - // Set default loading image - statusImageBase64 = loadingBase64; - - // Initialize the service and JavaScript module - await OnlineStatusService.InitializeAsync(); - - // Get the current network status and update the image and tooltip - bool isOnline = await OnlineStatusService.GetOnlineStatusAsync(); - UpdateStatus(isOnline); - - // Subscribe to network status change events - OnlineStatusService.OnlineStatusChanged += UpdateStatus; - - // Register the network status listener - await OnlineStatusService.RegisterOnlineStatusListenerAsync(); - } - - private void UpdateStatus(bool isOnline) - { - // Update the Base64 image and tooltip based on the network status - statusImageBase64 = isOnline ? greenPointBase64 : redPointBase64; - statusTooltip = isOnline ? "Online" : "Offline"; - InvokeAsync(StateHasChanged); - } - - public async ValueTask DisposeAsync() - { - // Unsubscribe from events and dispose of resources - OnlineStatusService.OnlineStatusChanged -= UpdateStatus; - await OnlineStatusService.DisposeAsync(); - } -} diff --git a/src/CleanAspire.ClientApp/Layout/UserMenu.razor b/src/CleanAspire.ClientApp/Layout/UserMenu.razor index 894e13a..fb5568b 100644 --- a/src/CleanAspire.ClientApp/Layout/UserMenu.razor +++ b/src/CleanAspire.ClientApp/Layout/UserMenu.razor @@ -2,7 +2,7 @@ @using Microsoft.AspNetCore.Components.Authorization @inject IIdentityManagement IdentityManagement @inject OnlineStatusService OnlineStatusService - +@inject LayoutService LayoutService @@ -10,7 +10,11 @@
@if (_isOnline == false) { -
+
+ } + else + { +
} @if (string.IsNullOrEmpty(userModel?.AvatarUrl)) { diff --git a/src/CleanAspire.ClientApp/wwwroot/css/app.css b/src/CleanAspire.ClientApp/wwwroot/css/app.css index 6850e3e..c335000 100644 --- a/src/CleanAspire.ClientApp/wwwroot/css/app.css +++ b/src/CleanAspire.ClientApp/wwwroot/css/app.css @@ -148,27 +148,81 @@ .avatar-container { position: relative; - width: 40px; /* ȣͷһ */ - height: 40px; /* ߶ȣͷһ */ + width: 40px; /* Fixed container width, same as avatar */ + height: 40px; /* Fixed container height, same as avatar */ } +/* Red rotating border (common style) */ .rotating-border { position: absolute; - top: -4px; /* չ4px */ - left: -4px; /* չ4px */ - width: 48px; /* ȦĿȱͷ */ - height: 48px; /* Ȧĸ߶ȱͷ */ - border: 3px solid transparent; /* Ĭ͸ */ - border-top: 3px solid rgba(255, 0, 0, 0.8); /* ɫת߿ */ - border-radius: 50%; /* ԲιȦ */ - animation: spin 2s linear infinite; /* ת */ - z-index: 1; /* ͷ· */ + top: -4px; + left: -4px; + width: 48px; + height: 48px; + border: 3px solid transparent; /* Default transparent */ + border-radius: 50%; + animation: spin 2s linear infinite; /* Rotating animation */ + z-index: 1; box-sizing: border-box; } + /* Red border in dark mode */ + .rotating-border.dark-mode { + border-top: 3px solid rgba(255, 0, 0, 0.8); /* Dark red border */ + box-shadow: 0 0 8px rgba(255, 0, 0, 0.6), 0 0 12px rgba(255, 50, 50, 0.4); /* Enhanced shadow */ + } + + /* Red border in light mode */ + .rotating-border.light-mode { + border-top: 3px solid rgba(255, 50, 50, 0.8); /* Light red border */ + box-shadow: 0 0 6px rgba(255, 50, 50, 0.5), 0 0 10px rgba(255, 100, 100, 0.3); /* Soft shadow */ + } + +/* Dynamic green border (common style) */ +.online-border { + position: absolute; + top: -4px; + left: -4px; + width: 48px; + height: 48px; + border-radius: 50%; + animation: green-pulse 2s infinite; /* Breathing animation */ + z-index: 1; + box-sizing: border-box; +} + + /* Green border in dark mode */ + .online-border.dark-mode { + border: 3px solid rgba(0, 128, 0, 1); /* Dark green border */ + box-shadow: 0 0 8px rgba(0, 128, 0, 0.6), 0 0 12px rgba(0, 255, 0, 0.4); + } + + /* Green border in light mode */ + .online-border.light-mode { + border: 3px solid rgba(0, 200, 0, 1); /* Light green border */ + box-shadow: 0 0 6px rgba(0, 200, 0, 0.5), 0 0 10px rgba(0, 255, 0, 0.3); + } +/* Breathing animation */ +@keyframes green-pulse { + 0% { + box-shadow: 0 0 8px rgba(0, 128, 0, 0.6), 0 0 12px rgba(0, 255, 0, 0.4); + transform: scale(1); + } + + 50% { + box-shadow: 0 0 10px rgba(0, 128, 0, 0.8), 0 0 14px rgba(0, 255, 0, 0.5); + transform: scale(1.03); /* Reduce scaling amplitude */ + } + + 100% { + box-shadow: 0 0 8px rgba(0, 128, 0, 0.6), 0 0 12px rgba(0, 255, 0, 0.4); + transform: scale(1); + } +} + MudAvatar { position: relative; - z-index: 2; /* ȷͷڹȦϷ */ + z-index: 2; /* Ensure avatar is above the border */ } @keyframes spin { @@ -181,11 +235,6 @@ MudAvatar { } } -MudAvatar { - position: relative; - z-index: 2; /* ֤ͷڹȦϷ */ -} - .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; From a2152d795f4d6ef7654e7b142b44049e23390f76 Mon Sep 17 00:00:00 2001 From: hualin Date: Tue, 10 Dec 2024 14:10:41 +0800 Subject: [PATCH 5/5] Update service-worker.js --- src/CleanAspire.ClientApp/wwwroot/service-worker.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/CleanAspire.ClientApp/wwwroot/service-worker.js b/src/CleanAspire.ClientApp/wwwroot/service-worker.js index 9b1531b..10aafe8 100644 --- a/src/CleanAspire.ClientApp/wwwroot/service-worker.js +++ b/src/CleanAspire.ClientApp/wwwroot/service-worker.js @@ -2,15 +2,11 @@ // This is because caching would make development more difficult (changes would not // be reflected on the first load after each change). -const CACHE_NAME = 'network-status-cache-v1'; +const CACHE_NAME = 'cache-v1'; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { - return cache.addAll([ - 'green-point.svg', - 'red-point.svg', - 'loading.svg' - ]); + return cache.addAll([]); }) ); });