Refactor critical tables layout to CSS grid
This commit is contained in:
@@ -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>
|
||||
<div class="critical-table-grid" role="table" aria-label="@detail.DisplayName">
|
||||
@if (detail.Groups.Count > 0)
|
||||
{
|
||||
<tr>
|
||||
<th class="roll-band-header" rowspan="2"></th>
|
||||
<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)
|
||||
{
|
||||
<th colspan="@detail.Columns.Count">@group.Label</th>
|
||||
<div
|
||||
class="critical-table-grid-header-cell critical-table-grid-group-header"
|
||||
role="columnheader"
|
||||
style="@BuildColumnSpanStyle(detail.Columns.Count)">
|
||||
<span>@group.Label</span>
|
||||
</div>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
@foreach (var group in detail.Groups)
|
||||
</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)
|
||||
{
|
||||
foreach (var column in detail.Columns)
|
||||
{
|
||||
<th>
|
||||
<span>@column.Label</span>
|
||||
</th>
|
||||
<div class="critical-table-grid-header-cell critical-table-grid-column-header" role="columnheader">
|
||||
<span>@displayColumn.ColumnLabel</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
<tr>
|
||||
<th class="roll-band-header"></th>
|
||||
@foreach (var column in detail.Columns)
|
||||
{
|
||||
<th>
|
||||
<span>@column.Label</span>
|
||||
</th>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</thead>
|
||||
<tbody>
|
||||
</div>
|
||||
|
||||
@foreach (var rollBand in detail.RollBands)
|
||||
{
|
||||
<tr>
|
||||
<th class="roll-band-header">@rollBand.Label</th>
|
||||
@if (detail.Groups.Count > 0)
|
||||
<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)
|
||||
{
|
||||
foreach (var group in detail.Groups)
|
||||
@if (TryGetCell(rollBand.Label, displayColumn.GroupKey, displayColumn.ColumnKey, out var cell))
|
||||
{
|
||||
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>
|
||||
@RenderCriticalTableCell(cell)
|
||||
}
|
||||
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>
|
||||
@RenderEmptyCriticalTableCell()
|
||||
}
|
||||
}
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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>
|
||||
}
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</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>;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user