diff --git a/docs/tables_frontend_overhaul_implementation_plan.md b/docs/tables_frontend_overhaul_implementation_plan.md index 9afebdd..0760a92 100644 --- a/docs/tables_frontend_overhaul_implementation_plan.md +++ b/docs/tables_frontend_overhaul_implementation_plan.md @@ -74,6 +74,7 @@ It is intentionally implementation-focused: | 2026-03-21 | P3.11 | Completed | Moved the live editor and curation entry points into the shared inspector content and removed the last remaining grid-owned action buttons. | | 2026-03-21 | P3.12 | Completed | Added keyboard-selectable cells, visible focus treatment, and selection normalization so changing filters or modes cannot leave the inspector pointing at hidden cells. | | 2026-03-21 | Post-P3 fix 1 | Completed | Added a defensive visible-column fallback in the table canvas and tightened view-state normalization so a stale severity filter cannot collapse the grid to roll bands only. | +| 2026-04-11 | Post-P3 fix 2 | Completed | Simplified `/tables` by removing static prose and context controls, dropped the redundant selected-result inspector in favor of a floating action menu, and moved the canvas onto its own scroll region so sticky headers layer correctly beneath the context bar. | ### Lessons Learned diff --git a/src/RolemasterDb.App/Components/Pages/Tables.razor b/src/RolemasterDb.App/Components/Pages/Tables.razor index 82b681c..98673ce 100644 --- a/src/RolemasterDb.App/Components/Pages/Tables.razor +++ b/src/RolemasterDb.App/Components/Pages/Tables.razor @@ -11,7 +11,7 @@ Critical Tables
- + @if (referenceData is null) { @@ -31,7 +31,7 @@ PinnedTableSlugs="PinnedTablesState.Items.Select(item => item.Slug).ToArray()" RecentTableSlugs="RecentTablesState.Items.Select(item => item.Slug).ToArray()" IsPinned="PinnedTablesState.IsPinned" - OnSelectTable="SelectTableAsync" /> + OnSelectTable="SelectTableAsync"/>
@@ -57,19 +57,9 @@ + OnToggleLegend="ToggleLegend"/> + OnSelectCell="SelectCell"/> @if (isLegendOpen) { - + }
} - - @if (tableDetail is not null) - { - - } - + OnCurate="OpenSelectedCellCurationAsync"/> }
@@ -125,7 +104,7 @@ OnEdit="OpenEditorFromCurationAsync" OnEnterQuickParse="EnterCurationQuickParseMode" OnCancelQuickParse="CancelCurationQuickParseMode" - OnReparse="ReparseCurationCellAsync" /> + OnReparse="ReparseCurationCellAsync"/> } @if (isEditorOpen) @@ -142,7 +121,7 @@ SaveErrorMessage="@editorSaveError" OnClose="CloseCellEditorAsync" OnReparse="ReparseCellEditorAsync" - OnSave="SaveCellEditorAsync" /> + OnSave="SaveCellEditorAsync"/> } @code { @@ -151,9 +130,7 @@ private CriticalTableDetail? tableDetail; private string selectedTableSlug = string.Empty; private bool isDetailLoading; - private bool isReferenceDataLoading = true; private string? detailError; - private bool IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0; private bool isEditorOpen; private bool isEditorLoading; private bool isEditorReparsing; @@ -181,17 +158,16 @@ 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); + selectedCell is null ? null : tableDetail?.Cells.FirstOrDefault(cell => cell.ResultId == selectedCell.ResultId); protected override async Task OnInitializedAsync() { referenceData = await LookupService.GetReferenceDataAsync(); - isReferenceDataLoading = false; } private async Task SelectTableAsync(string tableSlug) @@ -247,17 +223,12 @@ if (!hasResolvedStoredTableSelection && referenceData?.CriticalTables.Count > 0) { - var initialContext = await TableContextState.RestoreAsync( - NavigationManager.Uri, - ContextDestination, - referenceData.CriticalTables, - RolemasterDb.App.Frontend.AppState.TableContextMode.Reference); + 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)) + if (string.IsNullOrWhiteSpace(selectedTableSlug) || !string.Equals(resolvedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase)) { selectedTableSlug = resolvedTableSlug; await LoadTableDetailAsync(); @@ -489,11 +460,7 @@ return null; } - var orderedCells = tableDetail.Cells - .OrderBy(cell => GetGroupSortOrder(cell.GroupKey)) - .ThenBy(cell => GetColumnSortOrder(cell.ColumnKey)) - .ThenBy(cell => GetRollBandSortOrder(cell.RollBand)) - .ToList(); + var orderedCells = tableDetail.Cells.OrderBy(cell => GetGroupSortOrder(cell.GroupKey)).ThenBy(cell => GetColumnSortOrder(cell.ColumnKey)).ThenBy(cell => GetRollBandSortOrder(cell.RollBand)).ToList(); var currentIndex = orderedCells.FindIndex(cell => cell.ResultId == currentResultId); for (var index = currentIndex + 1; index < orderedCells.Count; index++) @@ -514,9 +481,7 @@ return 0; } - return tableDetail.Groups - .FirstOrDefault(group => string.Equals(group.Key, groupKey, StringComparison.OrdinalIgnoreCase)) - ?.SortOrder ?? int.MaxValue; + return tableDetail.Groups.FirstOrDefault(group => string.Equals(group.Key, groupKey, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue; } private int GetColumnSortOrder(string columnKey) @@ -526,9 +491,7 @@ return int.MaxValue; } - return tableDetail.Columns - .FirstOrDefault(column => string.Equals(column.Key, columnKey, StringComparison.OrdinalIgnoreCase)) - ?.SortOrder ?? int.MaxValue; + return tableDetail.Columns.FirstOrDefault(column => string.Equals(column.Key, columnKey, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue; } private int GetRollBandSortOrder(string rollBandLabel) @@ -538,9 +501,7 @@ return int.MaxValue; } - return tableDetail.RollBands - .FirstOrDefault(rollBand => string.Equals(rollBand.Label, rollBandLabel, StringComparison.OrdinalIgnoreCase)) - ?.SortOrder ?? int.MaxValue; + return tableDetail.RollBands.FirstOrDefault(rollBand => string.Equals(rollBand.Label, rollBandLabel, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue; } private async Task CloseCellEditorAsync() @@ -634,11 +595,7 @@ return Task.CompletedTask; } - return PinnedTablesState.ToggleAsync( - selectedTable.Key, - selectedTable.Label, - selectedTable.Family, - selectedTable.CurationPercentage); + return PinnedTablesState.ToggleAsync(selectedTable.Key, selectedTable.Label, selectedTable.Family, selectedTable.CurationPercentage); } private Task RecordRecentTableVisitAsync() @@ -648,11 +605,7 @@ return Task.CompletedTask; } - return RecentTablesState.RecordVisitAsync( - selectedTable.Key, - selectedTable.Label, - selectedTable.Family, - selectedTable.CurationPercentage); + return RecentTablesState.RecordVisitAsync(selectedTable.Key, selectedTable.Label, selectedTable.Family, selectedTable.CurationPercentage); } private async Task PersistAndSyncTableContextAsync() @@ -670,42 +623,7 @@ } private RolemasterDb.App.Frontend.AppState.TableContextSnapshot BuildCurrentTableContext() => - new( - TableSlug: selectedTableSlug, - Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Reference); - - private Task UpdateReferenceModeAsync(string mode) - { - referenceMode = NormalizeMode(mode); - NormalizeSelectedCellForCurrentView(); - return Task.CompletedTask; - } - - private Task UpdateSelectedGroupAsync(string groupKey) - { - selectedGroupKey = NormalizeOptionalFilter(groupKey); - NormalizeSelectedCellForCurrentView(); - return Task.CompletedTask; - } - - private Task UpdateSelectedColumnAsync(string columnKey) - { - selectedColumnKey = NormalizeOptionalFilter(columnKey); - NormalizeSelectedCellForCurrentView(); - return Task.CompletedTask; - } - - private Task UpdateRollJumpAsync(string rollValue) - { - rollJumpValue = NormalizeRollInput(rollValue); - return Task.CompletedTask; - } - - private Task UpdateDensityModeAsync(string mode) - { - densityMode = NormalizeDensityMode(mode); - return Task.CompletedTask; - } + new(TableSlug: selectedTableSlug, Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Reference); private void SelectCell(TablesCellSelection selection) { @@ -718,21 +636,11 @@ selectedCell = selection; } - private Task ClearSelectedCell() - { - selectedCell = null; - return Task.CompletedTask; - } - private Task OpenSelectedCellEditorAsync() => - selectedCell is null - ? Task.CompletedTask - : OpenCellEditorAsync(selectedCell.ResultId); + selectedCell is null ? Task.CompletedTask : OpenCellEditorAsync(selectedCell.ResultId); private Task OpenSelectedCellCurationAsync() => - selectedCell is null - ? Task.CompletedTask - : OpenCellCurationAsync(selectedCell.ResultId); + selectedCell is null ? Task.CompletedTask : OpenCellCurationAsync(selectedCell.ResultId); private Task ToggleLegend() { @@ -759,10 +667,7 @@ selectedGroupKey = string.Empty; } - var matchingColumns = tableDetail.Columns - .Where(column => string.IsNullOrWhiteSpace(selectedColumnKey) - || string.Equals(column.Key, selectedColumnKey, StringComparison.OrdinalIgnoreCase)) - .ToList(); + var matchingColumns = tableDetail.Columns.Where(column => string.IsNullOrWhiteSpace(selectedColumnKey) || string.Equals(column.Key, selectedColumnKey, StringComparison.OrdinalIgnoreCase)).ToList(); if (matchingColumns.Count == 0) { @@ -784,17 +689,12 @@ mode switch { TablesReferenceMode.NeedsCuration => TablesReferenceMode.NeedsCuration, - TablesReferenceMode.Curated => TablesReferenceMode.Curated, - _ => TablesReferenceMode.Reference + TablesReferenceMode.Curated => TablesReferenceMode.Curated, + _ => TablesReferenceMode.Reference }; - private static string NormalizeOptionalFilter(string? value) => - string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim(); - private static string NormalizeDensityMode(string? mode) => - string.Equals(mode, TablesDensityMode.Dense, StringComparison.Ordinal) - ? TablesDensityMode.Dense - : TablesDensityMode.Comfortable; + string.Equals(mode, TablesDensityMode.Dense, StringComparison.Ordinal) ? TablesDensityMode.Dense : TablesDensityMode.Comfortable; private static string NormalizeRollInput(string? value) { @@ -823,14 +723,12 @@ private bool MatchesCurrentView(CriticalTableCellDetail cell) { - if (!string.IsNullOrWhiteSpace(selectedGroupKey) && - !string.Equals(cell.GroupKey, selectedGroupKey, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(selectedGroupKey) && !string.Equals(cell.GroupKey, selectedGroupKey, StringComparison.OrdinalIgnoreCase)) { return false; } - if (!string.IsNullOrWhiteSpace(selectedColumnKey) && - !string.Equals(cell.ColumnKey, selectedColumnKey, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(selectedColumnKey) && !string.Equals(cell.ColumnKey, selectedColumnKey, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -838,8 +736,9 @@ return referenceMode switch { TablesReferenceMode.NeedsCuration => !cell.IsCurated, - TablesReferenceMode.Curated => cell.IsCurated, - _ => true + TablesReferenceMode.Curated => cell.IsCurated, + _ => true }; } -} + +} \ No newline at end of file diff --git a/src/RolemasterDb.App/Components/Tables/TablesCanvas.razor b/src/RolemasterDb.App/Components/Tables/TablesCanvas.razor index 29cfa22..1b73035 100644 --- a/src/RolemasterDb.App/Components/Tables/TablesCanvas.razor +++ b/src/RolemasterDb.App/Components/Tables/TablesCanvas.razor @@ -44,7 +44,7 @@
@if (string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal)) { - + } else if (cell.IsCurated) { @@ -64,7 +64,7 @@ + Branches="@(cell.Branches ?? Array.Empty())"/>
} @@ -170,66 +170,60 @@ cellIndex.TryGetValue((rollBand, groupKey, columnKey), out cell); private bool MatchesGroupFilter(CriticalGroupReference group) => - string.IsNullOrWhiteSpace(SelectedGroupKey) - || string.Equals(group.Key, SelectedGroupKey, StringComparison.OrdinalIgnoreCase); + string.IsNullOrWhiteSpace(SelectedGroupKey) || string.Equals(group.Key, SelectedGroupKey, StringComparison.OrdinalIgnoreCase); private bool MatchesColumnFilter(CriticalColumnReference column) => - string.IsNullOrWhiteSpace(SelectedColumnKey) - || string.Equals(column.Key, SelectedColumnKey, StringComparison.OrdinalIgnoreCase); + string.IsNullOrWhiteSpace(SelectedColumnKey) || string.Equals(column.Key, SelectedColumnKey, StringComparison.OrdinalIgnoreCase); private IReadOnlyList ResolveVisibleGroups() { - var filteredGroups = Detail.Groups - .Where(MatchesGroupFilter) - .ToList(); + var filteredGroups = Detail.Groups.Where(MatchesGroupFilter).ToList(); - return filteredGroups.Count > 0 - ? filteredGroups - : Detail.Groups; + return filteredGroups.Count > 0 ? filteredGroups : Detail.Groups; } private IReadOnlyList ResolveVisibleColumns() { - var filteredColumns = Detail.Columns - .Where(MatchesColumnFilter) - .ToList(); + var filteredColumns = Detail.Columns.Where(MatchesColumnFilter).ToList(); - return filteredColumns.Count > 0 - ? filteredColumns - : Detail.Columns; + return filteredColumns.Count > 0 ? filteredColumns : Detail.Columns; } private bool MatchesModeFilter(CriticalTableCellDetail cell) => CurrentMode switch { TablesReferenceMode.NeedsCuration => !cell.IsCurated, - TablesReferenceMode.Curated => cell.IsCurated, - _ => true + TablesReferenceMode.Curated => cell.IsCurated, + _ => true }; private string? ActiveRollBand => - !string.IsNullOrWhiteSpace(SelectedCell?.RollBand) - ? SelectedCell.RollBand - : ResolveRollJumpBandLabel(); + !string.IsNullOrWhiteSpace(SelectedCell?.RollBand) ? SelectedCell.RollBand : ResolveRollJumpBandLabel(); private string? ActiveColumnKey => - !string.IsNullOrWhiteSpace(SelectedCell?.ColumnKey) - ? SelectedCell.ColumnKey - : (!string.IsNullOrWhiteSpace(SelectedColumnKey) ? SelectedColumnKey : null); + !string.IsNullOrWhiteSpace(SelectedCell?.ColumnKey) ? SelectedCell.ColumnKey : (!string.IsNullOrWhiteSpace(SelectedColumnKey) ? SelectedColumnKey : null); private string? ActiveGroupKey => - !string.IsNullOrWhiteSpace(SelectedCell?.GroupKey) - ? SelectedCell.GroupKey - : (!string.IsNullOrWhiteSpace(SelectedGroupKey) ? SelectedGroupKey : null); + !string.IsNullOrWhiteSpace(SelectedCell?.GroupKey) ? SelectedCell.GroupKey : (!string.IsNullOrWhiteSpace(SelectedGroupKey) ? SelectedGroupKey : null); - private string BuildGridCssClass() => - string.Equals(DensityMode, TablesDensityMode.Dense, StringComparison.Ordinal) - ? "is-dense" - : "is-comfortable"; + private string BuildGridCssClass() + { + var classes = new List + { + string.Equals(DensityMode, TablesDensityMode.Dense, StringComparison.Ordinal) ? "is-dense" : "is-comfortable", + Detail.Groups.Count > 0 ? "has-groups" : "has-no-groups" + }; + + return string.Join(' ', classes); + } private string BuildGroupHeaderCssClass(string groupKey) { - var classes = new List { "critical-table-grid-header-cell", "critical-table-grid-group-header" }; + var classes = new List + { + "critical-table-grid-header-cell", + "critical-table-grid-group-header" + }; if (string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase)) { classes.Add("is-active-group"); @@ -240,15 +234,18 @@ private string BuildColumnHeaderCssClass(string? groupKey, string columnKey) { - var classes = new List { "critical-table-grid-header-cell", "critical-table-grid-column-header" }; + var classes = new List + { + "critical-table-grid-header-cell", + "critical-table-grid-column-header" + }; if (string.Equals(columnKey, ActiveColumnKey, StringComparison.OrdinalIgnoreCase)) { classes.Add("is-active-column"); } - if (!string.IsNullOrWhiteSpace(groupKey) && - string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(groupKey) && string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase)) { classes.Add("is-active-group"); } @@ -258,7 +255,11 @@ private string BuildRollBandCssClass(string rollBandLabel) { - var classes = new List { "critical-table-grid-header-cell", "critical-table-grid-roll-band" }; + var classes = new List + { + "critical-table-grid-header-cell", + "critical-table-grid-roll-band" + }; if (string.Equals(rollBandLabel, ActiveRollBand, StringComparison.OrdinalIgnoreCase)) { classes.Add("is-active-row"); @@ -290,8 +291,7 @@ classes.Add("is-active-column"); } - if (!string.IsNullOrWhiteSpace(groupKey) && - string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(groupKey) && string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase)) { classes.Add("is-active-group"); } @@ -340,9 +340,7 @@ private Task HandleCellKeyDown(KeyboardEventArgs args, CriticalTableCellDetail cell) { - if (string.Equals(args.Key, "Enter", StringComparison.Ordinal) || - string.Equals(args.Key, " ", StringComparison.Ordinal) || - string.Equals(args.Key, "Spacebar", StringComparison.Ordinal)) + if (string.Equals(args.Key, "Enter", StringComparison.Ordinal) || string.Equals(args.Key, " ", StringComparison.Ordinal) || string.Equals(args.Key, "Spacebar", StringComparison.Ordinal)) { return SelectCell(cell); } @@ -351,4 +349,4 @@ } private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};"; -} +} \ No newline at end of file diff --git a/src/RolemasterDb.App/Components/Tables/TablesContextBar.razor b/src/RolemasterDb.App/Components/Tables/TablesContextBar.razor index 9be4ebf..a5ea586 100644 --- a/src/RolemasterDb.App/Components/Tables/TablesContextBar.razor +++ b/src/RolemasterDb.App/Components/Tables/TablesContextBar.razor @@ -1,9 +1,6 @@
-
-

@Detail.DisplayName

-

@GetReadingHint()

-
+

@Detail.DisplayName

-

Select a result to inspect it beside the table.

- -
-
- - - -
- -
- @if (Detail.Groups.Count > 1) - { - - } - - @if (Detail.Columns.Count > 1) - { - - } - - -
- - @if (HasActiveFilters()) - { -
- @if (!string.IsNullOrWhiteSpace(SelectedGroupLabel)) - { - - } - - @if (!string.IsNullOrWhiteSpace(SelectedColumnLabel)) - { - - } - - @if (!string.IsNullOrWhiteSpace(RollJumpValue)) - { - - } - - @if (!string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal)) - { - - } -
- } -
@code { - private readonly IReadOnlyList modeTabs = - [ - new(TablesReferenceMode.Reference, "Reference"), - new(TablesReferenceMode.NeedsCuration, "Needs Curation"), - new(TablesReferenceMode.Curated, "Curated") - ]; - - private readonly IReadOnlyList densityTabs = - [ - new(TablesDensityMode.Comfortable, "Comfortable"), - new(TablesDensityMode.Dense, "Dense") - ]; [Parameter, EditorRequired] public CriticalTableDetail Detail { get; set; } = default!; @@ -128,21 +20,6 @@ [Parameter] public bool IsPinned { get; set; } - [Parameter] - public string CurrentMode { get; set; } = TablesReferenceMode.Reference; - - [Parameter] - public string SelectedGroupKey { get; set; } = string.Empty; - - [Parameter] - public string SelectedColumnKey { get; set; } = string.Empty; - - [Parameter] - public string RollJumpValue { get; set; } = string.Empty; - - [Parameter] - public string DensityMode { get; set; } = TablesDensityMode.Comfortable; - [Parameter] public EventCallback OnTogglePin { get; set; } @@ -152,52 +29,4 @@ [Parameter] public EventCallback OnToggleLegend { get; set; } - [Parameter] - public EventCallback OnModeChanged { get; set; } - - [Parameter] - public EventCallback OnGroupChanged { get; set; } - - [Parameter] - public EventCallback OnColumnChanged { get; set; } - - [Parameter] - public EventCallback OnRollJumpChanged { get; set; } - - [Parameter] - public EventCallback OnDensityChanged { get; set; } - - private string GetReadingHint() => - Detail.Groups.Count > 0 - ? "Find the roll band on the left, then read across to the group and severity you need." - : "Find the roll band on the left, then read across to the severity you need."; - - private string? SelectedGroupLabel => - Detail.Groups.FirstOrDefault(group => string.Equals(group.Key, SelectedGroupKey, StringComparison.OrdinalIgnoreCase))?.Label; - - private string? SelectedColumnLabel => - Detail.Columns.FirstOrDefault(column => string.Equals(column.Key, SelectedColumnKey, StringComparison.OrdinalIgnoreCase))?.Label; - - private bool HasActiveFilters() => - !string.IsNullOrWhiteSpace(SelectedGroupKey) - || !string.IsNullOrWhiteSpace(SelectedColumnKey) - || !string.IsNullOrWhiteSpace(RollJumpValue) - || !string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal); - - private Task HandleGroupChanged(ChangeEventArgs args) => - OnGroupChanged.InvokeAsync(args.Value?.ToString() ?? string.Empty); - - private Task HandleColumnChanged(ChangeEventArgs args) => - OnColumnChanged.InvokeAsync(args.Value?.ToString() ?? string.Empty); - - private Task HandleRollJumpChanged(ChangeEventArgs args) => - OnRollJumpChanged.InvokeAsync(args.Value?.ToString() ?? string.Empty); - - private static string GetModeLabel(string mode) => - mode switch - { - TablesReferenceMode.NeedsCuration => "Needs Curation", - TablesReferenceMode.Curated => "Curated", - _ => "Reference" - }; -} +} \ No newline at end of file diff --git a/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor b/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor index dcb3242..96424fc 100644 --- a/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor +++ b/src/RolemasterDb.App/Components/Tables/TablesIndexRail.razor @@ -1,7 +1,6 @@

Table Index

-

Choose a table, then read from the roll band across to the result you need.

@@ -12,7 +11,7 @@ type="search" placeholder="Search tables" value="@searchText" - @oninput="HandleSearchInput" /> + @oninput="HandleSearchInput"/> @if (familyFilters.Count > 1) { @@ -74,7 +73,7 @@ @if (filteredTables.Count == 0) { -

No tables match the current search.

+
No tables match the current search.
} else { @@ -130,11 +129,7 @@ familyFilters.Clear(); familyFilters.Add(string.Empty); - foreach (var family in Tables - .Select(table => table.Family) - .Where(family => !string.IsNullOrWhiteSpace(family)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(family => family, StringComparer.OrdinalIgnoreCase)) + foreach (var family in Tables.Select(table => table.Family).Where(family => !string.IsNullOrWhiteSpace(family)).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(family => family, StringComparer.OrdinalIgnoreCase)) { familyFilters.Add(family); } @@ -203,8 +198,7 @@ private bool MatchesFilters(CriticalTableReference table) { - if (!string.IsNullOrEmpty(selectedFamily) && - !string.Equals(table.Family, selectedFamily, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(selectedFamily) && !string.Equals(table.Family, selectedFamily, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -214,9 +208,7 @@ return true; } - return table.Label.Contains(searchText, StringComparison.OrdinalIgnoreCase) - || table.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase) - || table.Family.Contains(searchText, StringComparison.OrdinalIgnoreCase); + return table.Label.Contains(searchText, StringComparison.OrdinalIgnoreCase) || table.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase) || table.Family.Contains(searchText, StringComparison.OrdinalIgnoreCase); } private void HandleSearchInput(ChangeEventArgs args) @@ -237,9 +229,7 @@ string.Equals(selectedFamily, family, StringComparison.OrdinalIgnoreCase); private string GetFamilyFilterCssClass(string family) => - IsFamilyFilterSelected(family) - ? "tables-family-filter is-selected" - : "tables-family-filter"; + IsFamilyFilterSelected(family) ? "tables-family-filter is-selected" : "tables-family-filter"; private void HandleRailKeyDown(KeyboardEventArgs args) { @@ -280,9 +270,7 @@ currentIndex = keyboardOptions.FindIndex(item => string.Equals(item.Key, SelectedTableSlug, StringComparison.OrdinalIgnoreCase)); } - var nextIndex = currentIndex < 0 - ? 0 - : Math.Clamp(currentIndex + offset, 0, keyboardOptions.Count - 1); + var nextIndex = currentIndex < 0 ? 0 : Math.Clamp(currentIndex + offset, 0, keyboardOptions.Count - 1); activeOptionSlug = keyboardOptions[nextIndex].Key; } @@ -295,8 +283,7 @@ return; } - if (!string.IsNullOrWhiteSpace(activeOptionSlug) && - keyboardOptions.Any(item => string.Equals(item.Key, activeOptionSlug, StringComparison.OrdinalIgnoreCase))) + if (!string.IsNullOrWhiteSpace(activeOptionSlug) && keyboardOptions.Any(item => string.Equals(item.Key, activeOptionSlug, StringComparison.OrdinalIgnoreCase))) { return; } @@ -338,22 +325,23 @@ private void SetActiveOption(string tableSlug) => activeOptionSlug = tableSlug; private RenderFragment RenderTableOption(CriticalTableReference table) => @; -} + type="button" + role="option" + aria-selected="@string.Equals(table.Key, SelectedTableSlug, StringComparison.OrdinalIgnoreCase)" + class="table-index-option @GetTableOptionCssClass(table)" + @onfocus="() => SetActiveOption(table.Key)" + @onclick="() => OnSelectTable.InvokeAsync(table.Key)"> + + @table.Label + @table.Family + + + @if (GetIsPinned(table.Key)) + { + Pinned + } + @($"{table.CurationPercentage}%") + + ; + +} \ No newline at end of file diff --git a/src/RolemasterDb.App/Components/Tables/TablesLegend.razor b/src/RolemasterDb.App/Components/Tables/TablesLegend.razor index b707a74..3dacfbe 100644 --- a/src/RolemasterDb.App/Components/Tables/TablesLegend.razor +++ b/src/RolemasterDb.App/Components/Tables/TablesLegend.razor @@ -3,7 +3,6 @@

Reading help

-

These symbols show the effects attached to a result at a glance.

@foreach (var entry in LegendEntries) @@ -21,6 +20,8 @@ } @code { + [Parameter] public IReadOnlyList LegendEntries { get; set; } = Array.Empty(); -} + +} \ No newline at end of file diff --git a/src/RolemasterDb.App/Components/Tables/TablesPageHeader.razor b/src/RolemasterDb.App/Components/Tables/TablesPageHeader.razor index 8df0e8c..4ea04d7 100644 --- a/src/RolemasterDb.App/Components/Tables/TablesPageHeader.razor +++ b/src/RolemasterDb.App/Components/Tables/TablesPageHeader.razor @@ -1,7 +1,5 @@
-

Reference

Critical Tables

-

Browse the index, open a table, and read the result directly from the grid without leaving the page.

-
+ \ No newline at end of file diff --git a/src/RolemasterDb.App/Components/Tables/TablesSelectionMenu.razor b/src/RolemasterDb.App/Components/Tables/TablesSelectionMenu.razor new file mode 100644 index 0000000..701efc4 --- /dev/null +++ b/src/RolemasterDb.App/Components/Tables/TablesSelectionMenu.razor @@ -0,0 +1,24 @@ +@if (SelectedCellDetail is not null) +{ +
+ @if (!SelectedCellDetail.IsCurated) + { + + } + + +
+} + +@code { + + [Parameter] + public CriticalTableCellDetail? SelectedCellDetail { get; set; } + + [Parameter] + public EventCallback OnEdit { get; set; } + + [Parameter] + public EventCallback OnCurate { get; set; } + +} \ No newline at end of file diff --git a/src/RolemasterDb.App/wwwroot/app.css b/src/RolemasterDb.App/wwwroot/app.css index 0840d0c..28a3e06 100644 --- a/src/RolemasterDb.App/wwwroot/app.css +++ b/src/RolemasterDb.App/wwwroot/app.css @@ -51,6 +51,7 @@ --button-secondary-bg-hover: rgba(250, 236, 210, 0.95); --button-secondary-text: #6a4b28; --button-secondary-border: rgba(127, 96, 55, 0.18); + --control-height: 3rem; --font-display: "Fraunces", Georgia, serif; --font-body: "IBM Plex Sans", "Segoe UI", sans-serif; --font-ui: "IBM Plex Sans", "Segoe UI", sans-serif; @@ -315,11 +316,13 @@ pre, display: grid; gap: 0.95rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + align-items: start; } .field-shell { display: grid; gap: 0.35rem; + align-content: start; } .roll-input-row { @@ -341,12 +344,19 @@ pre, .input-shell { width: 100%; + min-height: var(--control-height); border-radius: 14px; border: 1px solid var(--button-secondary-border); background: var(--surface-input); padding: 0.8rem 0.9rem; color: var(--text-primary); box-sizing: border-box; + line-height: 1.25; +} + +input.input-shell, +select.input-shell { + height: var(--control-height); } .input-shell:focus { @@ -425,6 +435,7 @@ pre, padding: 1rem; background: var(--surface-card); border: 1px solid rgba(127, 96, 55, 0.14); + min-width: 0; } .result-card h3, @@ -510,6 +521,9 @@ pre, flex-direction: column; gap: 0.35rem; min-height: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; } .critical-cell-status-chip, @@ -543,6 +557,7 @@ pre, display: flex; flex-direction: column; gap: 0.65rem; + min-width: 0; } .critical-cell-description { @@ -550,12 +565,14 @@ pre, color: #2c1a10; font-size: 1.2rem; line-height: 1.4; + overflow-wrap: anywhere; } .critical-branch-stack { display: flex; flex-direction: column; gap: 0.4rem; + min-width: 0; } .critical-branch-card { @@ -563,6 +580,7 @@ pre, border-radius: 12px; border: 1px solid rgba(127, 96, 55, 0.12); background: rgba(255, 255, 255, 0.85); + min-width: 0; } .critical-branch-header { @@ -571,6 +589,7 @@ pre, justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; + min-width: 0; } .critical-branch-condition { @@ -579,6 +598,7 @@ pre, letter-spacing: 0.05em; text-transform: uppercase; color: #6b4c29; + overflow-wrap: anywhere; } .critical-branch-description { @@ -586,6 +606,7 @@ pre, font-size: 0.85rem; line-height: 1.35; color: #3b2a21; + overflow-wrap: anywhere; } .critical-branch-header .affix-badge-list { @@ -645,6 +666,8 @@ pre, flex-wrap: wrap; gap: 0.25rem; margin-top: 0.5rem; + min-width: 0; + max-width: 100%; } .affix-badge { @@ -1146,7 +1169,7 @@ pre, .tables-reference-layout { display: grid; - grid-template-columns: minmax(17rem, 20rem) minmax(0, 1fr) minmax(19rem, 24rem); + grid-template-columns: minmax(17rem, 20rem) minmax(0, 1fr); gap: 1rem; align-items: start; } @@ -1341,6 +1364,23 @@ pre, min-width: 0; } +.tables-selection-menu { + position: fixed; + right: 1rem; + bottom: calc(var(--shell-mobile-nav-height, 0rem) + 1rem); + z-index: 40; + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + padding: 0.75rem; + border-radius: 18px; + background: color-mix(in srgb, var(--surface-card-strong) 96%, transparent); + border: 1px solid var(--border-default); + box-shadow: var(--shadow-2); + backdrop-filter: blur(12px); +} + .tables-reference-inspector-shell { position: sticky; top: calc(var(--shell-header-height) + 1rem); @@ -1382,6 +1422,9 @@ pre, } .table-shell { + display: grid; + gap: 0.75rem; + min-height: 0; border-radius: 20px; padding: 1.2rem; background: var(--surface-card-strong); @@ -1394,15 +1437,14 @@ pre, align-items: flex-start; justify-content: space-between; gap: 1rem; - margin-bottom: 1rem; + margin-bottom: 0; } .tables-context-bar { position: sticky; - top: calc(var(--shell-header-height) + 1rem); - z-index: 4; - padding-bottom: 1rem; - margin-bottom: 1rem; + top: calc(var(--shell-header-height, 5.75rem) + 1rem); + z-index: 8; + padding-bottom: 0.75rem; background: linear-gradient(180deg, rgba(255, 251, 245, 0.96), rgba(255, 251, 245, 0.92)); backdrop-filter: blur(10px); border-bottom: 1px solid rgba(127, 96, 55, 0.14); @@ -1414,14 +1456,21 @@ pre, .tables-context-primary, .tables-context-controls { - display: grid; + display: flex; + align-items: center; + justify-content: space-between; gap: 0.85rem; + flex-wrap: wrap; } .tables-context-controls { margin-top: 0.85rem; } +.tables-context-primary .panel-title { + margin: 0; +} + .tables-context-tab-row { display: flex; flex-wrap: wrap; @@ -1495,6 +1544,12 @@ pre, display: none; } + .tables-selection-menu { + right: 0.75rem; + left: 0.75rem; + justify-content: flex-end; + } + .tables-inspector-sheet { position: fixed; inset: 0; @@ -1544,19 +1599,30 @@ pre, } .table-shell .table-scroll { - overflow-x: auto; + overflow: auto; + min-width: 0; + max-height: min(72dvh, calc(100dvh - var(--shell-header-height, 5.75rem) - var(--shell-mobile-nav-height, 0rem) - 5rem)); + border-radius: 18px; + overscroll-behavior: contain; + scrollbar-gutter: stable both-edges; } .critical-table-grid { display: grid; align-items: stretch; - width: 100%; + min-width: 100%; + width: max-content; font-size: 1.5rem; border-top: 1px solid rgba(127, 96, 55, 0.2); border-left: 1px solid rgba(127, 96, 55, 0.2); isolation: isolate; + --tables-header-row-height: 3.2rem; --tables-group-header-top: 0; - --tables-column-header-top: 3.2rem; + --tables-column-header-top: 0; +} + +.critical-table-grid.has-groups { + --tables-column-header-top: var(--tables-header-row-height); } .critical-table-grid-header-cell { @@ -1564,10 +1630,11 @@ pre, align-items: center; justify-content: center; min-width: 0; + min-height: var(--tables-header-row-height); padding: 0.35rem; border-right: 1px solid rgba(127, 96, 55, 0.2); border-bottom: 1px solid rgba(127, 96, 55, 0.2); - background: rgba(238, 223, 193, 0.45); + background: color-mix(in srgb, var(--surface-card-strong) 94%, var(--accent-1)); text-transform: uppercase; text-align: center; letter-spacing: 0.08em; @@ -1582,18 +1649,18 @@ pre, .critical-table-grid-group-header { position: sticky; top: var(--tables-group-header-top); - z-index: 3; + z-index: 5; } .critical-table-grid-column-header { position: sticky; top: var(--tables-column-header-top); - z-index: 3; + z-index: 4; } .critical-table-grid.is-dense { font-size: 1.2rem; - --tables-column-header-top: 2.6rem; + --tables-header-row-height: 2.6rem; } .critical-table-grid.is-dense .critical-table-grid-header-cell { @@ -1606,34 +1673,35 @@ pre, .critical-table-grid-corner, .critical-table-grid-roll-band-header { - background: rgba(255, 247, 230, 0.52); + background: color-mix(in srgb, var(--surface-card-strong) 94%, var(--accent-1)); } .critical-table-grid-corner { position: sticky; top: var(--tables-group-header-top); left: 0; - z-index: 5; + z-index: 7; } .critical-table-grid-roll-band-header { position: sticky; top: var(--tables-column-header-top); left: 0; - z-index: 5; + z-index: 6; } .critical-table-grid-roll-band { - background: rgba(255, 247, 230, 0.52); + background: color-mix(in srgb, var(--surface-card-strong) 94%, var(--accent-1)); font-size: 1.5rem; position: sticky; left: 0; - z-index: 2; + z-index: 3; } .critical-table-cell { position: relative; display: flex; + z-index: 1; min-width: 0; padding: 0.55rem; border-right: 1px solid rgba(127, 96, 55, 0.2); @@ -2412,10 +2480,6 @@ pre, flex-direction: column; } - .table-browser-edit-hint { - white-space: normal; - } - .tables-context-fields { flex-direction: column; }