Refactor critical tables layout to CSS grid

This commit is contained in:
2026-03-21 11:44:37 +01:00
parent 7322b93120
commit 89f393ca35
3 changed files with 148 additions and 213 deletions

View File

@@ -97,151 +97,57 @@
<p class="table-browser-edit-hint">Use the curation action or edit action on any filled result.</p>
</header>
@{
var displayColumns = GetDisplayColumns(detail);
var gridTemplateStyle = BuildGridTemplateStyle(detail);
}
<div class="table-scroll">
<table class="critical-table">
<thead>
@if (detail.Groups.Count > 0)
{
<tr>
<th class="roll-band-header" rowspan="2"></th>
@foreach (var group in detail.Groups)
{
<th colspan="@detail.Columns.Count">@group.Label</th>
}
</tr>
<tr>
@foreach (var group in detail.Groups)
{
foreach (var column in detail.Columns)
{
<th>
<span>@column.Label</span>
</th>
}
}
</tr>
}
else
{
<tr>
<th class="roll-band-header"></th>
@foreach (var column in detail.Columns)
{
<th>
<span>@column.Label</span>
</th>
}
</tr>
}
</thead>
<tbody>
@foreach (var rollBand in detail.RollBands)
{
<tr>
<th class="roll-band-header">@rollBand.Label</th>
@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))
{
<td class="@GetCellCssClass(groupedCell)">
<div class="critical-table-cell-shell">
<div class="critical-table-cell-actions">
@if (groupedCell.IsCurated)
{
<span class="critical-cell-status-chip is-curated">Curated</span>
}
else
{
<button
type="button"
class="critical-cell-action-button is-curation"
title="Open the curation preview for this cell."
@onclick="() => OpenCellCurationAsync(groupedCell.ResultId)">
Needs Curation
</button>
}
<div class="critical-table-grid" role="table" aria-label="@detail.DisplayName">
@if (detail.Groups.Count > 0)
{
<div class="critical-table-grid-row critical-table-grid-group-row" role="row" style="@gridTemplateStyle">
<div class="critical-table-grid-header-cell critical-table-grid-corner" aria-hidden="true"></div>
@foreach (var group in detail.Groups)
{
<div
class="critical-table-grid-header-cell critical-table-grid-group-header"
role="columnheader"
style="@BuildColumnSpanStyle(detail.Columns.Count)">
<span>@group.Label</span>
</div>
}
</div>
}
<button
type="button"
class="critical-cell-action-button is-edit"
title="Open the full editor for this cell."
@onclick="() => OpenCellEditorAsync(groupedCell.ResultId)">
Edit
</button>
</div>
<div class="critical-table-grid-row critical-table-grid-column-row" role="row" style="@gridTemplateStyle">
<div class="critical-table-grid-header-cell critical-table-grid-roll-band-header" aria-hidden="true"></div>
@foreach (var displayColumn in displayColumns)
{
<div class="critical-table-grid-header-cell critical-table-grid-column-header" role="columnheader">
<span>@displayColumn.ColumnLabel</span>
</div>
}
</div>
<CompactCriticalCell
Description="@(groupedCell.Description ?? string.Empty)"
Effects="@(groupedCell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
Branches="@(groupedCell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" />
</div>
</td>
}
else
{
<td class="critical-table-cell">
<span class="empty-cell">—</span>
</td>
}
}
}
@foreach (var rollBand in detail.RollBands)
{
<div class="critical-table-grid-row critical-table-grid-body-row" role="row" style="@gridTemplateStyle">
<div class="critical-table-grid-header-cell critical-table-grid-roll-band" role="rowheader">@rollBand.Label</div>
@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))
{
<td class="@GetCellCssClass(cell)">
<div class="critical-table-cell-shell">
<div class="critical-table-cell-actions">
@if (cell.IsCurated)
{
<span class="critical-cell-status-chip is-curated">Curated</span>
}
else
{
<button
type="button"
class="critical-cell-action-button is-curation"
title="Open the curation preview for this cell."
@onclick="() => OpenCellCurationAsync(cell.ResultId)">
Needs Curation
</button>
}
<button
type="button"
class="critical-cell-action-button is-edit"
title="Open the full editor for this cell."
@onclick="() => OpenCellEditorAsync(cell.ResultId)">
Edit
</button>
</div>
<CompactCriticalCell
Description="@(cell.Description ?? string.Empty)"
Effects="@(cell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
Branches="@(cell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" />
</div>
</td>
}
else
{
<td class="critical-table-cell">
<span class="empty-cell">—</span>
</td>
}
}
@RenderEmptyCriticalTableCell()
}
</tr>
}
</tbody>
</table>
}
</div>
}
</div>
</div>
@{
@@ -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) => @<div class="@GetCellCssClass(cell)" role="cell">
<div class="critical-table-cell-shell">
<div class="critical-table-cell-actions">
@if (cell.IsCurated)
{
<span class="critical-cell-status-chip is-curated">Curated</span>
}
else
{
<button
type="button"
class="critical-cell-action-button is-curation"
title="Open the curation preview for this cell."
@onclick="() => OpenCellCurationAsync(cell.ResultId)">
Needs Curation
</button>
}
<button
type="button"
class="critical-cell-action-button is-edit"
title="Open the full editor for this cell."
@onclick="() => OpenCellEditorAsync(cell.ResultId)">
Edit
</button>
</div>
<CompactCriticalCell
Description="@(cell.Description ?? string.Empty)"
Effects="@(cell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
Branches="@(cell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" />
</div>
</div>;
private static RenderFragment RenderEmptyCriticalTableCell() => @<div class="critical-table-cell critical-table-cell-empty" role="cell">
<span class="empty-cell">—</span>
</div>;
}