@page "/tables" @rendermode InteractiveServer @using System @using System.Linq @using RolemasterDb.App.Frontend.AppState @inject NavigationManager NavigationManager @inject LookupService LookupService @inject RolemasterDb.App.Frontend.AppState.PinnedTablesState PinnedTablesState @inject RolemasterDb.App.Frontend.AppState.RecentTablesState RecentTablesState @inject RolemasterDb.App.Frontend.AppState.TableContextState TableContextState Critical Tables
@if (referenceData is null) {

Loading table list...

} else if (!referenceData.CriticalTables.Any()) {

No critical tables are available yet.

} else {
@if (!hasResolvedStoredTableSelection) {

Restoring table context...

} else if (isDetailLoading) {

Loading the selected table...

} else if (!string.IsNullOrWhiteSpace(detailError)) {

@detailError

} else if (tableDetail is null) {

The selected table could not be loaded.

} else if (tableDetail is { } detail) {
@if (isLegendOpen) { }
}
}
@if (isEditorOpen) { } @code { private const string ContextDestination = "tables"; private LookupReferenceData? referenceData; private CriticalTableDetail? tableDetail; private string selectedTableSlug = string.Empty; private bool isDetailLoading; private string? detailError; private bool isEditorOpen; private bool isEditorLoading; private bool isEditorReparsing; private bool isEditorSaving; private string? editorLoadError; private string? editorReparseError; private string? editorSaveError; private int? editingResultId; private CriticalCellEditorModel? editorModel; private CriticalCellEditorModel? editorComparisonBaselineModel; private string referenceMode = TablesReferenceMode.Reference; private string selectedGroupKey = string.Empty; private string selectedColumnKey = string.Empty; private string rollJumpValue = string.Empty; private string densityMode = TablesDensityMode.Comfortable; private TablesCellSelection? selectedCell; private bool isLegendOpen; private bool hasResolvedStoredTableSelection; private CriticalTableReference? SelectedTableReference => referenceData?.CriticalTables.FirstOrDefault(item => string.Equals(item.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase)); private CriticalTableCellDetail? SelectedCellDetail => selectedCell is null ? null : tableDetail?.Cells.FirstOrDefault(cell => cell.ResultId == selectedCell.ResultId); protected override async Task OnInitializedAsync() { referenceData = await LookupService.GetReferenceDataAsync(); } private async Task SelectTableAsync(string tableSlug) { selectedTableSlug = tableSlug; await LoadTableDetailAsync(); await PersistAndSyncTableContextAsync(); } private async Task LoadTableDetailAsync() { if (string.IsNullOrWhiteSpace(selectedTableSlug)) { tableDetail = null; return; } isDetailLoading = true; detailError = null; tableDetail = null; try { tableDetail = await LookupService.GetCriticalTableAsync(selectedTableSlug); if (tableDetail is null) { detailError = "The selected table could not be loaded."; NormalizeViewStateForCurrentDetail(); return; } await RecordRecentTableVisitAsync(); NormalizeViewStateForCurrentDetail(); } catch (Exception exception) { detailError = exception.Message; NormalizeViewStateForCurrentDetail(); } finally { isDetailLoading = false; } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await RecentTablesState.InitializeAsync(); await PinnedTablesState.InitializeAsync(); } if (!hasResolvedStoredTableSelection && referenceData?.CriticalTables.Count > 0) { var initialContext = await TableContextState.RestoreAsync(NavigationManager.Uri, ContextDestination, referenceData.CriticalTables, RolemasterDb.App.Frontend.AppState.TableContextMode.Reference); hasResolvedStoredTableSelection = true; var resolvedTableSlug = initialContext.TableSlug ?? string.Empty; if (string.IsNullOrWhiteSpace(selectedTableSlug) || !string.Equals(resolvedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase)) { selectedTableSlug = resolvedTableSlug; await LoadTableDetailAsync(); await PersistAndSyncTableContextAsync(); await InvokeAsync(StateHasChanged); return; } await PersistAndSyncTableContextAsync(); } } private async Task OpenCellEditorAsync(int resultId) { if (string.IsNullOrWhiteSpace(selectedTableSlug)) { return; } editorLoadError = null; editorReparseError = null; editorSaveError = null; editorModel = null; editorComparisonBaselineModel = null; editingResultId = resultId; isEditorReparsing = false; isEditorSaving = false; isEditorLoading = true; isEditorOpen = true; try { var response = await LookupService.GetCriticalCellEditorAsync(selectedTableSlug, resultId); if (response is null) { editorLoadError = "The selected cell could not be loaded for editing."; editorModel = null; return; } editorModel = CriticalCellEditorModel.FromResponse(response); } catch (Exception exception) { editorLoadError = exception.Message; editorModel = null; } finally { isEditorLoading = false; } } private async Task CloseCellEditorAsync() { isEditorOpen = false; isEditorLoading = false; isEditorReparsing = false; isEditorSaving = false; editorLoadError = null; editorReparseError = null; editorSaveError = null; editingResultId = null; editorModel = null; editorComparisonBaselineModel = null; await InvokeAsync(StateHasChanged); } private async Task ReparseCellEditorAsync() { if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null) { return; } isEditorReparsing = true; editorReparseError = null; try { var comparisonBaseline = editorModel.Clone(); var response = await ReparseCriticalCellAsync(editorModel, editingResultId.Value); if (response is null) { editorReparseError = "The selected cell could not be re-parsed."; return; } editorComparisonBaselineModel = comparisonBaseline; editorModel = CriticalCellEditorModel.FromResponse(response); await InvokeAsync(StateHasChanged); } catch (Exception exception) { editorReparseError = exception.Message; } finally { isEditorReparsing = false; } } private Task ReparseCriticalCellAsync(CriticalCellEditorModel model, int resultId) => LookupService.ReparseCriticalCellAsync(selectedTableSlug, resultId, model.ToRequest()); private async Task SaveCellEditorAsync() { if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null) { return; } isEditorSaving = true; editorSaveError = null; try { var response = await LookupService.UpdateCriticalCellAsync(selectedTableSlug, editingResultId.Value, editorModel.ToRequest()); if (response is null) { editorSaveError = "The selected cell could not be saved."; return; } await LoadTableDetailAsync(); await CloseCellEditorAsync(); } catch (Exception exception) { editorSaveError = exception.Message; } finally { isEditorSaving = false; } } private Task TogglePinnedTableAsync() { if (SelectedTableReference is not { } selectedTable) { return Task.CompletedTask; } return PinnedTablesState.ToggleAsync(selectedTable.Key, selectedTable.Label, selectedTable.Family, selectedTable.CurationPercentage); } private Task RecordRecentTableVisitAsync() { if (SelectedTableReference is not { } selectedTable) { return Task.CompletedTask; } return RecentTablesState.RecordVisitAsync(selectedTable.Key, selectedTable.Label, selectedTable.Family, selectedTable.CurationPercentage); } private async Task PersistAndSyncTableContextAsync() { var snapshot = BuildCurrentTableContext(); await TableContextState.PersistAsync(ContextDestination, snapshot); var targetUri = TableContextState.BuildUri("/tables", snapshot); if (string.Equals(NavigationManager.ToBaseRelativePath(NavigationManager.Uri), targetUri.TrimStart('/'), StringComparison.Ordinal)) { return; } NavigationManager.NavigateTo(targetUri, replace: true); } private RolemasterDb.App.Frontend.AppState.TableContextSnapshot BuildCurrentTableContext() => new(TableSlug: selectedTableSlug, Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Reference); private void SelectCell(TablesCellSelection selection) { if (tableDetail?.Cells.Any(cell => cell.ResultId == selection.ResultId) != true) { selectedCell = null; return; } selectedCell = selection; } private Task OpenSelectedCellEditorAsync() => selectedCell is null ? Task.CompletedTask : OpenCellEditorAsync(selectedCell.ResultId); private async Task OpenSelectedCellCurationAsync() { if (SelectedCellDetail is not { } cellDetail || string.IsNullOrWhiteSpace(selectedTableSlug)) { return; } var snapshot = new TableContextSnapshot(TableSlug: selectedTableSlug, GroupKey: cellDetail.GroupKey, ColumnKey: cellDetail.ColumnKey, RollBand: cellDetail.RollBand, ResultId: cellDetail.ResultId, Mode: TableContextMode.Curation, QueueScope: CurationQueueScopes.SelectedTable); await TableContextState.PersistAsync("curation", snapshot); NavigationManager.NavigateTo(TableContextState.BuildUri("/curation", snapshot)); } private Task ToggleLegend() { isLegendOpen = !isLegendOpen; return Task.CompletedTask; } private void NormalizeViewStateForCurrentDetail() { referenceMode = NormalizeMode(referenceMode); if (tableDetail is null) { selectedGroupKey = string.Empty; selectedColumnKey = string.Empty; rollJumpValue = string.Empty; densityMode = NormalizeDensityMode(densityMode); selectedCell = null; return; } if (tableDetail.Groups.All(group => !string.Equals(group.Key, selectedGroupKey, StringComparison.OrdinalIgnoreCase))) { selectedGroupKey = string.Empty; } var matchingColumns = tableDetail.Columns.Where(column => string.IsNullOrWhiteSpace(selectedColumnKey) || string.Equals(column.Key, selectedColumnKey, StringComparison.OrdinalIgnoreCase)).ToList(); if (matchingColumns.Count == 0) { selectedColumnKey = string.Empty; } rollJumpValue = NormalizeRollInput(rollJumpValue); densityMode = NormalizeDensityMode(densityMode); if (selectedCell is not null && tableDetail.Cells.All(cell => cell.ResultId != selectedCell.ResultId)) { selectedCell = null; } NormalizeSelectedCellForCurrentView(); } private static string NormalizeMode(string? mode) => mode switch { TablesReferenceMode.NeedsCuration => TablesReferenceMode.NeedsCuration, TablesReferenceMode.Curated => TablesReferenceMode.Curated, _ => TablesReferenceMode.Reference }; private static string NormalizeDensityMode(string? mode) => string.Equals(mode, TablesDensityMode.Dense, StringComparison.Ordinal) ? TablesDensityMode.Dense : TablesDensityMode.Comfortable; private static string NormalizeRollInput(string? value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } var digitsOnly = new string(value.Where(char.IsDigit).ToArray()); return digitsOnly.Length == 0 ? string.Empty : digitsOnly; } private void NormalizeSelectedCellForCurrentView() { if (selectedCell is null || tableDetail is null) { return; } var cell = tableDetail.Cells.FirstOrDefault(item => item.ResultId == selectedCell.ResultId); if (cell is null || !MatchesCurrentView(cell)) { selectedCell = null; } } private bool MatchesCurrentView(CriticalTableCellDetail cell) { if (!string.IsNullOrWhiteSpace(selectedGroupKey) && !string.Equals(cell.GroupKey, selectedGroupKey, StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.IsNullOrWhiteSpace(selectedColumnKey) && !string.Equals(cell.ColumnKey, selectedColumnKey, StringComparison.OrdinalIgnoreCase)) { return false; } return referenceMode switch { TablesReferenceMode.NeedsCuration => !cell.IsCurated, TablesReferenceMode.Curated => cell.IsCurated, _ => true }; } }