From 89f393ca35386bd19f7854f612082adf49a60630 Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Sat, 21 Mar 2026 11:44:37 +0100 Subject: [PATCH] Refactor critical tables layout to CSS grid --- .../Components/Pages/Tables.razor | 254 +++++++----------- src/RolemasterDb.App/wwwroot/app.css | 72 +++-- src/RolemasterDb.App/wwwroot/tables.js | 35 +-- 3 files changed, 148 insertions(+), 213 deletions(-) diff --git a/src/RolemasterDb.App/Components/Pages/Tables.razor b/src/RolemasterDb.App/Components/Pages/Tables.razor index 9a1c5b9..8736c55 100644 --- a/src/RolemasterDb.App/Components/Pages/Tables.razor +++ b/src/RolemasterDb.App/Components/Pages/Tables.razor @@ -97,151 +97,57 @@

Use the curation action or edit action on any filled result.

+ @{ + var displayColumns = GetDisplayColumns(detail); + var gridTemplateStyle = BuildGridTemplateStyle(detail); + } +
- - - @if (detail.Groups.Count > 0) - { - - - @foreach (var group in detail.Groups) - { - - } - - - @foreach (var group in detail.Groups) - { - foreach (var column in detail.Columns) - { - - } - } - - } - else - { - - - @foreach (var column in detail.Columns) - { - - } - - } - - - @foreach (var rollBand in detail.RollBands) - { - - - @if (detail.Groups.Count > 0) - { - foreach (var group in detail.Groups) - { - foreach (var column in detail.Columns) - { - @if (TryGetCell(rollBand.Label, group.Key, column.Key, out var groupedCell)) - { - - } - else - { - - } - } - } + @foreach (var rollBand in detail.RollBands) + { +
+
@rollBand.Label
+ @foreach (var displayColumn in displayColumns) + { + @if (TryGetCell(rollBand.Label, displayColumn.GroupKey, displayColumn.ColumnKey, out var cell)) + { + @RenderCriticalTableCell(cell) } else { - foreach (var column in detail.Columns) - { - @if (TryGetCell(rollBand.Label, null, column.Key, out var cell)) - { -
- } - else - { - - } - } + @RenderEmptyCriticalTableCell() } - - } - -
@group.Label
- @column.Label -
- @column.Label -
@rollBand.Label -
-
- @if (groupedCell.IsCurated) - { - Curated - } - else - { - - } +
+ @if (detail.Groups.Count > 0) + { +
+ + @foreach (var group in detail.Groups) + { +
+ @group.Label +
+ } +
+ } - -
+
+ + @foreach (var displayColumn in displayColumns) + { +
+ @displayColumn.ColumnLabel +
+ } +
- -
-
- - -
-
- @if (cell.IsCurated) - { - Curated - } - else - { - - } - - -
- - -
-
- -
+ } +
+ } + @{ @@ -319,8 +225,6 @@ private bool isReferenceDataLoading = true; private string? detailError; private Dictionary<(string RollBand, string? GroupKey, string ColumnKey), CriticalTableCellDetail>? cellIndex; - private int tableLayoutVersion; - private int appliedLayoutVersion = -1; private bool IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0; private bool isEditorOpen; private bool isEditorLoading; @@ -381,7 +285,6 @@ { tableDetail = null; cellIndex = null; - tableLayoutVersion++; return; } @@ -406,7 +309,6 @@ { isDetailLoading = false; BuildCellIndex(); - tableLayoutVersion++; } } @@ -440,14 +342,6 @@ // During prerender localStorage is unavailable. Retry after interactive render. } } - - if (tableDetail is null || appliedLayoutVersion == tableLayoutVersion) - { - return; - } - - await JSRuntime.InvokeVoidAsync("rolemasterTables.alignCriticalCells"); - appliedLayoutVersion = tableLayoutVersion; } private void BuildCellIndex() @@ -838,6 +732,28 @@ ? "critical-table-cell is-curated" : "critical-table-cell needs-curation"; + private static IReadOnlyList<(string? GroupKey, string ColumnKey, string ColumnLabel)> GetDisplayColumns(CriticalTableDetail detail) + { + if (detail.Groups.Count == 0) + { + return detail.Columns + .Select(column => ((string?)null, column.Key, column.Label)) + .ToList(); + } + + return detail.Groups + .SelectMany(group => detail.Columns.Select(column => ((string?)group.Key, column.Key, column.Label))) + .ToList(); + } + + private static string BuildGridTemplateStyle(CriticalTableDetail detail) + { + var dataColumnCount = detail.Columns.Count * Math.Max(detail.Groups.Count, 1); + return $"grid-template-columns: 96px repeat({dataColumnCount}, minmax(190px, 250px));"; + } + + private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};"; + private string GetSelectedTableLabel() => SelectedTableReference?.Label ?? "Select a table"; @@ -872,4 +788,42 @@ classes.Add(table.CurationPercentage >= 100 ? "is-curated" : "needs-curation"); return string.Join(' ', classes); } + + private RenderFragment RenderCriticalTableCell(CriticalTableCellDetail cell) => @
+
+
+ @if (cell.IsCurated) + { + Curated + } + else + { + + } + + +
+ + +
+
; + + private static RenderFragment RenderEmptyCriticalTableCell() => @
+ +
; } diff --git a/src/RolemasterDb.App/wwwroot/app.css b/src/RolemasterDb.App/wwwroot/app.css index cc5e1f7..a762436 100644 --- a/src/RolemasterDb.App/wwwroot/app.css +++ b/src/RolemasterDb.App/wwwroot/app.css @@ -737,36 +737,57 @@ textarea { overflow-x: auto; } -.critical-table { - width: 100%; - border-collapse: collapse; +.critical-table-grid { + width: max-content; + min-width: 100%; font-size: 1.5rem; + border-top: 1px solid rgba(127, 96, 55, 0.2); + border-left: 1px solid rgba(127, 96, 55, 0.2); } -.critical-table th, -.critical-table td { - border: 1px solid rgba(127, 96, 55, 0.2); +.critical-table-grid-row { + display: grid; + align-items: stretch; +} + +.critical-table-grid-header-cell { + display: flex; + align-items: center; + justify-content: center; + min-width: 0; padding: 0.35rem; - vertical-align: top; -} - -.critical-table th { + 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); text-transform: uppercase; text-align: center; - vertical-align: middle; letter-spacing: 0.08em; + box-sizing: border-box; } -.critical-table td { - background: rgba(255, 255, 255, 0.85); - min-width: 190px; - max-width: 250px; - padding: 0.55rem; +.critical-table-grid-column-row .critical-table-grid-header-cell, +.critical-table-grid-group-row .critical-table-grid-header-cell { + font-size: 2rem; +} + +.critical-table-grid-corner, +.critical-table-grid-roll-band-header { + background: rgba(255, 247, 230, 0.52); +} + +.critical-table-grid-roll-band { + background: rgba(255, 247, 230, 0.52); + font-size: 1.5rem; } .critical-table-cell { position: relative; + display: flex; + min-width: 0; + padding: 0.55rem; + border-right: 1px solid rgba(127, 96, 55, 0.2); + border-bottom: 1px solid rgba(127, 96, 55, 0.2); + box-sizing: border-box; } .critical-table-cell.is-curated { @@ -785,8 +806,8 @@ textarea { display: flex; flex-direction: column; gap: 0.55rem; - height: 100%; - min-height: 100%; + flex: 1 1 auto; + min-height: 0; } .critical-table-cell-shell > .critical-cell { @@ -826,23 +847,16 @@ textarea { background: rgba(255, 248, 236, 0.98); } -.critical-table td .critical-cell { +.critical-table-grid .critical-cell { display: flex; flex-direction: column; gap: 0.25rem; box-sizing: border-box; } -.critical-table .roll-band-header { - width: 96px; - background: rgba(255, 247, 230, 0.52); - font-size: 1.5rem; - text-align: center; - vertical-align: middle; -} - -.critical-table thead th { - font-size: 2rem; +.critical-table-cell-empty { + align-items: center; + justify-content: center; } .empty-cell { diff --git a/src/RolemasterDb.App/wwwroot/tables.js b/src/RolemasterDb.App/wwwroot/tables.js index c13c5e7..f0bb29b 100644 --- a/src/RolemasterDb.App/wwwroot/tables.js +++ b/src/RolemasterDb.App/wwwroot/tables.js @@ -1,36 +1,3 @@ window.rolemasterTables = window.rolemasterTables || { - alignCriticalCells() { - const tables = document.querySelectorAll(".critical-table"); - - for (const table of tables) { - for (const shell of table.querySelectorAll("td > .critical-table-cell-shell")) { - shell.style.minHeight = ""; - - const criticalCell = shell.querySelector(":scope > .critical-cell"); - if (criticalCell) { - criticalCell.style.minHeight = ""; - } - } - - for (const row of table.tBodies) { - for (const tr of row.rows) { - for (const cell of tr.cells) { - const shell = cell.querySelector(":scope > .critical-table-cell-shell"); - if (!shell) { - continue; - } - - const computedStyle = window.getComputedStyle(cell); - const paddingTop = Number.parseFloat(computedStyle.paddingTop) || 0; - const paddingBottom = Number.parseFloat(computedStyle.paddingBottom) || 0; - const contentHeight = Math.max(0, cell.clientHeight - paddingTop - paddingBottom); - - shell.style.minHeight = `${contentHeight}px`; - } - } - } - } - } + alignCriticalCells() {} }; - -window.addEventListener("resize", () => window.rolemasterTables.alignCriticalCells());