Add table context URL serializer
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace RolemasterDb.App.Frontend.AppState;
|
||||
|
||||
public enum TableContextMode
|
||||
{
|
||||
Reference,
|
||||
Curation,
|
||||
Diagnostics
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace RolemasterDb.App.Frontend.AppState;
|
||||
|
||||
public sealed record TableContextSnapshot(
|
||||
string? TableSlug = null,
|
||||
string? GroupKey = null,
|
||||
string? ColumnKey = null,
|
||||
string? RollBand = null,
|
||||
int? RollJump = null,
|
||||
int? ResultId = null,
|
||||
TableContextMode? Mode = null);
|
||||
@@ -0,0 +1,82 @@
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
|
||||
namespace RolemasterDb.App.Frontend.AppState;
|
||||
|
||||
public sealed class TableContextUrlSerializer
|
||||
{
|
||||
private const string TableKey = "table";
|
||||
private const string GroupKey = "group";
|
||||
private const string ColumnKey = "column";
|
||||
private const string RollBandKey = "rollBand";
|
||||
private const string RollJumpKey = "roll";
|
||||
private const string ResultIdKey = "result";
|
||||
private const string ModeKey = "mode";
|
||||
|
||||
public TableContextSnapshot Parse(string uri)
|
||||
{
|
||||
var currentUri = new Uri(uri, UriKind.Absolute);
|
||||
var query = QueryHelpers.ParseQuery(currentUri.Query);
|
||||
|
||||
return new TableContextSnapshot(
|
||||
ReadValue(query, TableKey),
|
||||
ReadValue(query, GroupKey),
|
||||
ReadValue(query, ColumnKey),
|
||||
ReadValue(query, RollBandKey),
|
||||
ReadInt(query, RollJumpKey),
|
||||
ReadInt(query, ResultIdKey),
|
||||
ReadMode(ReadValue(query, ModeKey)));
|
||||
}
|
||||
|
||||
public string BuildRelativeUri(string basePath, TableContextSnapshot context)
|
||||
{
|
||||
var parameters = new Dictionary<string, string?>();
|
||||
AddIfPresent(parameters, TableKey, context.TableSlug);
|
||||
AddIfPresent(parameters, GroupKey, context.GroupKey);
|
||||
AddIfPresent(parameters, ColumnKey, context.ColumnKey);
|
||||
AddIfPresent(parameters, RollBandKey, context.RollBand);
|
||||
AddIfPresent(parameters, RollJumpKey, context.RollJump?.ToString());
|
||||
AddIfPresent(parameters, ResultIdKey, context.ResultId?.ToString());
|
||||
AddIfPresent(parameters, ModeKey, WriteMode(context.Mode));
|
||||
|
||||
return parameters.Count == 0
|
||||
? basePath
|
||||
: QueryHelpers.AddQueryString(basePath, parameters);
|
||||
}
|
||||
|
||||
private static void AddIfPresent(IDictionary<string, string?> parameters, string key, string? value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
parameters[key] = value.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ReadValue(IReadOnlyDictionary<string, Microsoft.Extensions.Primitives.StringValues> query, string key) =>
|
||||
query.TryGetValue(key, out var value)
|
||||
? value.ToString()
|
||||
: null;
|
||||
|
||||
private static int? ReadInt(IReadOnlyDictionary<string, Microsoft.Extensions.Primitives.StringValues> query, string key)
|
||||
{
|
||||
var value = ReadValue(query, key);
|
||||
return int.TryParse(value, out var parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
private static TableContextMode? ReadMode(string? value) =>
|
||||
value?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"reference" => TableContextMode.Reference,
|
||||
"curation" => TableContextMode.Curation,
|
||||
"diagnostics" => TableContextMode.Diagnostics,
|
||||
_ => null
|
||||
};
|
||||
|
||||
private static string? WriteMode(TableContextMode? mode) =>
|
||||
mode switch
|
||||
{
|
||||
TableContextMode.Reference => "reference",
|
||||
TableContextMode.Curation => "curation",
|
||||
TableContextMode.Diagnostics => "diagnostics",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -15,6 +15,7 @@ builder.Services.AddScoped<LookupService>();
|
||||
builder.Services.AddScoped<BrowserStorageService>();
|
||||
builder.Services.AddScoped<PinnedTablesState>();
|
||||
builder.Services.AddScoped<RecentTablesState>();
|
||||
builder.Services.AddSingleton<TableContextUrlSerializer>();
|
||||
builder.Services.AddScoped<ThemeState>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
Reference in New Issue
Block a user