Add table context URL serializer

This commit is contained in:
2026-03-21 14:00:41 +01:00
parent 58df648bd5
commit aa0639ef66
7 changed files with 258 additions and 6 deletions

View File

@@ -4,10 +4,12 @@
@using System.Collections.Generic
@using System.Diagnostics.CodeAnalysis
@using System.Linq
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
@inject LookupService LookupService
@inject RolemasterDb.App.Frontend.AppState.PinnedTablesState PinnedTablesState
@inject RolemasterDb.App.Frontend.AppState.RecentTablesState RecentTablesState
@inject RolemasterDb.App.Frontend.AppState.TableContextUrlSerializer TableContextUrlSerializer
<PageTitle>Critical Tables</PageTitle>
@@ -285,6 +287,7 @@
isTableMenuOpen = false;
await PersistSelectedTableAsync(tableSlug);
await LoadTableDetailAsync();
SyncTableContextUrl();
}
private async Task LoadTableDetailAsync()
@@ -335,16 +338,18 @@
{
try
{
var routeContext = TableContextUrlSerializer.Parse(NavigationManager.Uri);
var storedTableSlug = await JSRuntime.InvokeAsync<string?>("localStorage.getItem", SelectedTableStorageKey);
hasResolvedStoredTableSelection = true;
var resolvedTableSlug = ResolveSelectedTableSlug(storedTableSlug);
var resolvedTableSlug = ResolveSelectedTableSlug(routeContext.TableSlug ?? storedTableSlug);
if (string.IsNullOrWhiteSpace(selectedTableSlug) ||
!string.Equals(resolvedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase))
{
selectedTableSlug = resolvedTableSlug;
await LoadTableDetailAsync();
await PersistSelectedTableAsync(selectedTableSlug);
SyncTableContextUrl();
await InvokeAsync(StateHasChanged);
return;
}
@@ -353,6 +358,8 @@
{
await PersistSelectedTableAsync(selectedTableSlug);
}
SyncTableContextUrl();
}
catch (InvalidOperationException)
{
@@ -793,6 +800,27 @@
private Task PersistSelectedTableAsync(string tableSlug) =>
JSRuntime.InvokeVoidAsync("localStorage.setItem", SelectedTableStorageKey, tableSlug).AsTask();
private void SyncTableContextUrl()
{
if (string.IsNullOrWhiteSpace(selectedTableSlug))
{
return;
}
var targetUri = TableContextUrlSerializer.BuildRelativeUri(
"/tables",
new RolemasterDb.App.Frontend.AppState.TableContextSnapshot(
TableSlug: selectedTableSlug,
Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Reference));
if (string.Equals(NavigationManager.ToBaseRelativePath(NavigationManager.Uri), targetUri.TrimStart('/'), StringComparison.Ordinal))
{
return;
}
NavigationManager.NavigateTo(targetUri, replace: true);
}
private Task TogglePinnedTableAsync()
{
if (SelectedTableReference is not { } selectedTable)

View File

@@ -1,7 +1,9 @@
@using System
@using System.Collections.Generic
@using System.Linq
@inject NavigationManager NavigationManager
@inject LookupService LookupService
@inject RolemasterDb.App.Frontend.AppState.TableContextUrlSerializer TableContextUrlSerializer
<section class="panel diagnostics-page tooling-surface">
<header class="diagnostics-page-header">
@@ -164,8 +166,9 @@
protected override async Task OnInitializedAsync()
{
referenceData = await LookupService.GetReferenceDataAsync();
selectedTableSlug = referenceData.CriticalTables.FirstOrDefault()?.Key ?? string.Empty;
await LoadTableDetailAsync();
var routeContext = TableContextUrlSerializer.Parse(NavigationManager.Uri);
selectedTableSlug = ResolveSelectedTableSlug(routeContext.TableSlug);
await LoadTableDetailAsync(routeContext);
}
private async Task HandleTableChanged(ChangeEventArgs args)
@@ -178,6 +181,7 @@
{
selectedRollBand = args.Value?.ToString() ?? string.Empty;
ResolveSelectedCell();
SyncRouteContext();
await LoadSelectedCellDiagnosticsAsync();
}
@@ -185,6 +189,7 @@
{
selectedGroupKey = NormalizeOptionalText(args.Value?.ToString());
ResolveSelectedCell();
SyncRouteContext();
await LoadSelectedCellDiagnosticsAsync();
}
@@ -192,10 +197,11 @@
{
selectedColumnKey = args.Value?.ToString() ?? string.Empty;
ResolveSelectedCell();
SyncRouteContext();
await LoadSelectedCellDiagnosticsAsync();
}
private async Task LoadTableDetailAsync()
private async Task LoadTableDetailAsync(RolemasterDb.App.Frontend.AppState.TableContextSnapshot? routeContext = null)
{
if (string.IsNullOrWhiteSpace(selectedTableSlug))
{
@@ -220,8 +226,19 @@
return;
}
SetDefaultSelection(tableDetail);
if (!TryApplySelectionFromContext(tableDetail, routeContext))
{
SetDefaultSelection(tableDetail);
}
ResolveSelectedCell();
if (selectedCell is null && routeContext is not null)
{
SetDefaultSelection(tableDetail);
ResolveSelectedCell();
}
SyncRouteContext();
await LoadSelectedCellDiagnosticsAsync();
}
catch (Exception exception)
@@ -290,6 +307,47 @@
string.Equals(cell.GroupKey ?? string.Empty, selectedGroupKey ?? string.Empty, StringComparison.Ordinal));
}
private bool TryApplySelectionFromContext(
CriticalTableDetail detail,
RolemasterDb.App.Frontend.AppState.TableContextSnapshot? routeContext)
{
if (routeContext is null)
{
return false;
}
CriticalTableCellDetail? matchedCell = null;
if (routeContext.ResultId is { } resultId)
{
matchedCell = detail.Cells.FirstOrDefault(cell => cell.ResultId == resultId);
}
matchedCell ??= detail.Cells.FirstOrDefault(cell =>
string.Equals(cell.RollBand, routeContext.RollBand, StringComparison.Ordinal) &&
string.Equals(cell.ColumnKey, routeContext.ColumnKey, StringComparison.Ordinal) &&
string.Equals(cell.GroupKey ?? string.Empty, routeContext.GroupKey ?? string.Empty, StringComparison.Ordinal));
if (matchedCell is not null)
{
selectedRollBand = matchedCell.RollBand;
selectedColumnKey = matchedCell.ColumnKey;
selectedGroupKey = matchedCell.GroupKey;
return true;
}
if (string.IsNullOrWhiteSpace(routeContext.RollBand) &&
string.IsNullOrWhiteSpace(routeContext.ColumnKey) &&
string.IsNullOrWhiteSpace(routeContext.GroupKey))
{
return false;
}
selectedRollBand = ResolveRollBand(detail, routeContext.RollBand);
selectedColumnKey = ResolveColumnKey(detail, routeContext.ColumnKey);
selectedGroupKey = ResolveGroupKey(detail, routeContext.GroupKey);
return true;
}
private async Task LoadSelectedCellDiagnosticsAsync()
{
diagnosticsError = null;
@@ -312,6 +370,7 @@
}
diagnosticsModel = CriticalCellEditorModel.FromResponse(response);
SyncRouteContext();
}
catch (Exception exception)
{
@@ -325,4 +384,66 @@
private static string? NormalizeOptionalText(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private string ResolveSelectedTableSlug(string? tableSlug)
{
if (referenceData is null || referenceData.CriticalTables.Count == 0)
{
return string.Empty;
}
if (!string.IsNullOrWhiteSpace(tableSlug) &&
referenceData.CriticalTables.Any(item => string.Equals(item.Key, tableSlug, StringComparison.OrdinalIgnoreCase)))
{
return tableSlug;
}
return referenceData.CriticalTables.First().Key;
}
private void SyncRouteContext()
{
if (string.IsNullOrWhiteSpace(selectedTableSlug))
{
return;
}
var targetUri = TableContextUrlSerializer.BuildRelativeUri(
"/tools/diagnostics",
new RolemasterDb.App.Frontend.AppState.TableContextSnapshot(
TableSlug: selectedTableSlug,
GroupKey: selectedGroupKey,
ColumnKey: selectedColumnKey,
RollBand: selectedRollBand,
ResultId: selectedCell?.ResultId,
Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Diagnostics));
if (string.Equals(NavigationManager.ToBaseRelativePath(NavigationManager.Uri), targetUri.TrimStart('/'), StringComparison.Ordinal))
{
return;
}
NavigationManager.NavigateTo(targetUri, replace: true);
}
private static string ResolveRollBand(CriticalTableDetail detail, string? rollBand) =>
detail.RollBands.FirstOrDefault(item => string.Equals(item.Label, rollBand, StringComparison.Ordinal))?.Label
?? detail.RollBands.FirstOrDefault()?.Label
?? string.Empty;
private static string ResolveColumnKey(CriticalTableDetail detail, string? columnKey) =>
detail.Columns.FirstOrDefault(item => string.Equals(item.Key, columnKey, StringComparison.Ordinal))?.Key
?? detail.Columns.FirstOrDefault()?.Key
?? string.Empty;
private static string? ResolveGroupKey(CriticalTableDetail detail, string? groupKey)
{
if (detail.Groups.Count == 0)
{
return null;
}
return detail.Groups.FirstOrDefault(item => string.Equals(item.Key, groupKey, StringComparison.Ordinal))?.Key
?? detail.Groups.First().Key;
}
}