Add platform env-badge to app title via IAppTitleService
Introduced IAppTitleService for platform-agnostic environment badge in the app title ("[PROD]" or "[DEV] FruitBank"), with implementations for Web (document.title), MAUI (window title), and server (no-op). Registered the service per platform in DI. MainLayout now updates the title reactively on environment changes via FruitBankConstClient.IsProdChanged. Improved event handling and prevented redundant environment hydration calls. No business logic affected; changes are UI/infrastructure only.
This commit is contained in:
parent
42f5c86ce4
commit
25265c02f8
|
|
@ -31,7 +31,17 @@ public static class FruitBankConstClient
|
||||||
=> _isProd ?? throw new InvalidOperationException(
|
=> _isProd ?? throw new InvalidOperationException(
|
||||||
"FruitBankConstClient.IsProd nincs hidratálva — a FruitBankSignalRClient a kapcsolat felépülésekor tölti fel a szerver IsProdEnvironment() válaszából (await client.EnsureEnvironmentAsync()).");
|
"FruitBankConstClient.IsProd nincs hidratálva — a FruitBankSignalRClient a kapcsolat felépülésekor tölti fel a szerver IsProdEnvironment() válaszából (await client.EnsureEnvironmentAsync()).");
|
||||||
|
|
||||||
public static void SetIsProd(bool isProd) => _isProd = isProd;
|
/// <summary>Az IsProd TÉNYLEGES változásakor tüzel (első hidratálás + announce-detektált env-váltás) —
|
||||||
|
/// a megjelenítők (title-badge, UI-sáv) feliratkozási pontja. Az értékadás forrása változatlanul EGY hely.</summary>
|
||||||
|
public static event Action<bool>? IsProdChanged;
|
||||||
|
|
||||||
|
public static void SetIsProd(bool isProd)
|
||||||
|
{
|
||||||
|
var changed = _isProd != isProd;
|
||||||
|
_isProd = isProd;
|
||||||
|
|
||||||
|
if (changed) IsProdChanged?.Invoke(isProd);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>True, ha az IsProd már hidratált — kapu-logikához (első-connect await) és a push-összevetéshez.</summary>
|
/// <summary>True, ha az IsProd már hidratált — kapu-logikához (első-connect await) és a push-összevetéshez.</summary>
|
||||||
public static bool IsProdHydrated => _isProd != null;
|
public static bool IsProdHydrated => _isProd != null;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using AyCode.Core.Consts;
|
using AyCode.Core.Consts;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
|
using FruitBank.Common;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
using AyCode.Utils.Extensions;
|
using AyCode.Utils.Extensions;
|
||||||
using DevExpress.Blazor;
|
using DevExpress.Blazor;
|
||||||
|
|
@ -9,6 +11,7 @@ using FruitBank.Common.Models;
|
||||||
using FruitBank.Common.Models.SignalRs;
|
using FruitBank.Common.Models.SignalRs;
|
||||||
using FruitBank.Common.SignalRs;
|
using FruitBank.Common.SignalRs;
|
||||||
using FruitBankHybrid.Shared.Pages;
|
using FruitBankHybrid.Shared.Pages;
|
||||||
|
using FruitBankHybrid.Shared.Services;
|
||||||
using FruitBankHybrid.Shared.Services.Loggers;
|
using FruitBankHybrid.Shared.Services.Loggers;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
using Mango.Nop.Core.Loggers;
|
using Mango.Nop.Core.Loggers;
|
||||||
|
|
@ -23,6 +26,7 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
|
||||||
[Inject] public required LoggedInModel LoggedInModel { get; set; }
|
[Inject] public required LoggedInModel LoggedInModel { get; set; }
|
||||||
[Inject] IToastNotificationService ToastService { get; set; }
|
[Inject] IToastNotificationService ToastService { get; set; }
|
||||||
[Inject] public required FruitBankSignalRClient FruitBankSignalRClient { get; set; }
|
[Inject] public required FruitBankSignalRClient FruitBankSignalRClient { get; set; }
|
||||||
|
[Inject] public required IAppTitleService AppTitleService { get; set; }
|
||||||
|
|
||||||
private EventCallback RefreshMainLayoutEventCallback => EventCallback.Factory.Create(this, RefreshMainLayout);
|
private EventCallback RefreshMainLayoutEventCallback => EventCallback.Factory.Create(this, RefreshMainLayout);
|
||||||
private NavMenu _navMenu = null!;
|
private NavMenu _navMenu = null!;
|
||||||
|
|
@ -47,8 +51,16 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
|
||||||
LoggedInModel.GetRolesFunc = FruitBankSignalRClient.GetCustomerRolesByCustomerId;
|
LoggedInModel.GetRolesFunc = FruitBankSignalRClient.GetCustomerRolesByCustomerId;
|
||||||
|
|
||||||
FruitBankSignalRClient.OnMessageReceived += SignalRClientOnMessageReceived;
|
FruitBankSignalRClient.OnMessageReceived += SignalRClientOnMessageReceived;
|
||||||
|
|
||||||
|
// Env-badge a címsorba (a nop-szerver Console.Title mintája): azonnal, ha az IsProd már hidratált,
|
||||||
|
// és feliratkozva a változásra (első hidratálás / announce-detektált env-váltás → riasztó title-frissítés).
|
||||||
|
// A hidratálást nem itt indítjuk — az első SignalR-hívás connect-kapuja végzi (pontosan 1 hívás).
|
||||||
|
FruitBankConstClient.IsProdChanged += OnIsProdChanged;
|
||||||
|
if (FruitBankConstClient.IsProdHydrated) OnIsProdChanged(FruitBankConstClient.IsProd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnIsProdChanged(bool isProd) => AppTitleService.SetEnvironmentBadgeAsync(isProd).Forget();
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (!firstRender) return;
|
if (!firstRender) return;
|
||||||
|
|
@ -132,5 +144,6 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
FruitBankSignalRClient.OnMessageReceived -= SignalRClientOnMessageReceived;
|
FruitBankSignalRClient.OnMessageReceived -= SignalRClientOnMessageReceived;
|
||||||
|
FruitBankConstClient.IsProdChanged -= OnIsProdChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace FruitBankHybrid.Shared.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Platform címsor-szolgáltatás az env-badge-hez — a nop-szerver Console.Title mintájának kliens-párja
|
||||||
|
/// ("[PROD]/[DEV] FruitBank"). Host-onkénti implementáció (az IFormFactor/ISecureCredentialService mintára):
|
||||||
|
/// browser document.title / MAUI Window.Title / szerver-prerender no-op.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAppTitleService
|
||||||
|
{
|
||||||
|
Task SetEnvironmentBadgeAsync(bool isProd);
|
||||||
|
}
|
||||||
|
|
@ -83,14 +83,18 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
|
||||||
|
|
||||||
private Task? _environmentHydration;
|
private Task? _environmentHydration;
|
||||||
|
|
||||||
// ELSŐ felépülés: HARD GATE — a StartConnection bevárja a hidratálást, addig SEMMILYEN más művelet
|
// ELSŐ felépülés: HARD GATE — a StartConnection bevárja a hidratálást, addig a kiváltó kérés nem megy ki
|
||||||
// nem megy ki ezen a kapcsolaton. Hiba → a kiváltó kérés is elhasal (fail closed: env-tudás nélkül
|
// (fail closed: env-tudás nélkül nem beszélünk a szerverrel). PONTOSAN EGY wire-hívás: ha a hidratálás már
|
||||||
// nem beszélünk a szerverrel). Reconnect: NINCS újra-lekérdezés ("csak az elején 1x") — az env-váltást
|
// fut (pl. a teszt-guard EnsureEnvironmentAsync-je váltotta ki magát a connectet), nem indítunk másodikat —
|
||||||
// (szerver-restart MÁSIK DB-vel) a szerver push-on-connect announce-a jelzi (ServerEnvironment tag).
|
// és nem is várjuk meg (önmagát várná: promise-ciklus lenne), az elindítója úgyis await-eli.
|
||||||
|
// Reconnect: NINCS újra-lekérdezés ("csak az elején 1x") — az env-váltást (szerver-restart MÁSIK DB-vel)
|
||||||
|
// a szerver push-on-connect announce-a jelzi (ServerEnvironment tag).
|
||||||
protected override async Task OnConnectionEstablished()
|
protected override async Task OnConnectionEstablished()
|
||||||
{
|
{
|
||||||
if (FruitBankConstClient.IsProdHydrated) return;
|
if (FruitBankConstClient.IsProdHydrated) return;
|
||||||
|
|
||||||
|
if (_environmentHydration is { IsCompleted: false }) return;
|
||||||
|
|
||||||
_environmentHydration = HydrateEnvironmentAsync();
|
_environmentHydration = HydrateEnvironmentAsync();
|
||||||
await _environmentHydration;
|
await _environmentHydration;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ builder.Services.AddDevExpressBlazor(configure => configure.SizeMode = DevExpres
|
||||||
// Add device-specific services used by the FruitBankHybrid.Shared project
|
// Add device-specific services used by the FruitBankHybrid.Shared project
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||||
builder.Services.AddSingleton<ISecureCredentialService, WebSecureCredentialService>();
|
builder.Services.AddSingleton<ISecureCredentialService, WebSecureCredentialService>();
|
||||||
|
builder.Services.AddSingleton<IAppTitleService, WebAppTitleService>();
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
builder.Services.AddSingleton<IAcLogWriterClientBase, BrowserConsoleLogWriter>();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
using FruitBankHybrid.Shared.Services;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Web.Client.Services;
|
||||||
|
|
||||||
|
/// <summary>Böngésző-tab title env-badge: "[PROD]/[DEV] FruitBank" (a nop-szerver Console.Title mintája).</summary>
|
||||||
|
public class WebAppTitleService(IJSRuntime js) : IAppTitleService
|
||||||
|
{
|
||||||
|
public async Task SetEnvironmentBadgeAsync(bool isProd)
|
||||||
|
=> await js.InvokeVoidAsync("eval", $"document.title = '{(isProd ? "[PROD]" : "[DEV]")} FruitBank'");
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ builder.Services.AddMvc();
|
||||||
|
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||||
builder.Services.AddSingleton<ISecureCredentialService, ServerSecureCredentialService>();
|
builder.Services.AddSingleton<ISecureCredentialService, ServerSecureCredentialService>();
|
||||||
|
builder.Services.AddSingleton<IAppTitleService, ServerAppTitleService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<IAcLogWriterBase, ConsoleLogWriter>();
|
builder.Services.AddSingleton<IAcLogWriterBase, ConsoleLogWriter>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
using FruitBankHybrid.Shared.Services;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Web.Services;
|
||||||
|
|
||||||
|
/// <summary>No-op: a szerver-host (prerender) nem birtokol címsort — az env-badge a WASM/MAUI kliensé.</summary>
|
||||||
|
public class ServerAppTitleService : IAppTitleService
|
||||||
|
{
|
||||||
|
public Task SetEnvironmentBadgeAsync(bool isProd) => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
@ -75,6 +75,7 @@ namespace FruitBankHybrid
|
||||||
// Add device-specific services used by the FruitBankHybrid.Shared project
|
// Add device-specific services used by the FruitBankHybrid.Shared project
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||||
builder.Services.AddSingleton<ISecureCredentialService, MauiSecureCredentialService>();
|
builder.Services.AddSingleton<ISecureCredentialService, MauiSecureCredentialService>();
|
||||||
|
builder.Services.AddSingleton<IAppTitleService, MauiAppTitleService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<LoggedInModel>(sp => new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
builder.Services.AddSingleton<LoggedInModel>(sp => new LoggedInModel(sp.GetRequiredService<ISecureCredentialService>()));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
using FruitBankHybrid.Shared.Services;
|
||||||
|
|
||||||
|
namespace FruitBankHybrid.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MAUI ablak-cím env-badge: "[PROD]/[DEV] FruitBank" — Windows desktopon látszik;
|
||||||
|
/// Android/iOS-en nincs futásidőben írható ablakcím (ott hatástalan, majd egy UI-badge fedheti).
|
||||||
|
/// </summary>
|
||||||
|
public class MauiAppTitleService : IAppTitleService
|
||||||
|
{
|
||||||
|
public Task SetEnvironmentBadgeAsync(bool isProd)
|
||||||
|
{
|
||||||
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
var window = Application.Current?.Windows.FirstOrDefault();
|
||||||
|
if (window != null) window.Title = $"{(isProd ? "[PROD]" : "[DEV]")} FruitBank";
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue