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>
|
<p class="table-browser-edit-hint">Use the curation action or edit action on any filled result.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var displayColumns = GetDisplayColumns(detail);
|
||||||
|
var gridTemplateStyle = BuildGridTemplateStyle(detail);
|
||||||
|
}
|
||||||
|
|
||||||
<div class="table-scroll">
|
<div class="table-scroll">
|
||||||
<table class="critical-table">
|
<div class="critical-table-grid" role="table" aria-label="@detail.DisplayName">
|
||||||
<thead>
|
@if (detail.Groups.Count > 0)
|
||||||
@if (detail.Groups.Count > 0)
|
{
|
||||||
{
|
<div class="critical-table-grid-row critical-table-grid-group-row" role="row" style="@gridTemplateStyle">
|
||||||
<tr>
|
<div class="critical-table-grid-header-cell critical-table-grid-corner" aria-hidden="true"></div>
|
||||||
<th class="roll-band-header" rowspan="2"></th>
|
@foreach (var group in detail.Groups)
|
||||||
@foreach (var group in detail.Groups)
|
{
|
||||||
{
|
<div
|
||||||
<th colspan="@detail.Columns.Count">@group.Label</th>
|
class="critical-table-grid-header-cell critical-table-grid-group-header"
|
||||||
}
|
role="columnheader"
|
||||||
</tr>
|
style="@BuildColumnSpanStyle(detail.Columns.Count)">
|
||||||
<tr>
|
<span>@group.Label</span>
|
||||||
@foreach (var group in detail.Groups)
|
</div>
|
||||||
{
|
}
|
||||||
foreach (var column in detail.Columns)
|
</div>
|
||||||
{
|
}
|
||||||
<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>
|
|
||||||
}
|
|
||||||
|
|
||||||
<button
|
<div class="critical-table-grid-row critical-table-grid-column-row" role="row" style="@gridTemplateStyle">
|
||||||
type="button"
|
<div class="critical-table-grid-header-cell critical-table-grid-roll-band-header" aria-hidden="true"></div>
|
||||||
class="critical-cell-action-button is-edit"
|
@foreach (var displayColumn in displayColumns)
|
||||||
title="Open the full editor for this cell."
|
{
|
||||||
@onclick="() => OpenCellEditorAsync(groupedCell.ResultId)">
|
<div class="critical-table-grid-header-cell critical-table-grid-column-header" role="columnheader">
|
||||||
Edit
|
<span>@displayColumn.ColumnLabel</span>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<CompactCriticalCell
|
@foreach (var rollBand in detail.RollBands)
|
||||||
Description="@(groupedCell.Description ?? string.Empty)"
|
{
|
||||||
Effects="@(groupedCell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
|
<div class="critical-table-grid-row critical-table-grid-body-row" role="row" style="@gridTemplateStyle">
|
||||||
Branches="@(groupedCell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" />
|
<div class="critical-table-grid-header-cell critical-table-grid-roll-band" role="rowheader">@rollBand.Label</div>
|
||||||
</div>
|
@foreach (var displayColumn in displayColumns)
|
||||||
</td>
|
{
|
||||||
}
|
@if (TryGetCell(rollBand.Label, displayColumn.GroupKey, displayColumn.ColumnKey, out var cell))
|
||||||
else
|
{
|
||||||
{
|
@RenderCriticalTableCell(cell)
|
||||||
<td class="critical-table-cell">
|
|
||||||
<span class="empty-cell">—</span>
|
|
||||||
</td>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var column in detail.Columns)
|
@RenderEmptyCriticalTableCell()
|
||||||
{
|
|
||||||
@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>
|
}
|
||||||
}
|
</div>
|
||||||
</tbody>
|
}
|
||||||
</table>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
@@ -319,8 +225,6 @@
|
|||||||
private bool isReferenceDataLoading = true;
|
private bool isReferenceDataLoading = true;
|
||||||
private string? detailError;
|
private string? detailError;
|
||||||
private Dictionary<(string RollBand, string? GroupKey, string ColumnKey), CriticalTableCellDetail>? cellIndex;
|
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 IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0;
|
||||||
private bool isEditorOpen;
|
private bool isEditorOpen;
|
||||||
private bool isEditorLoading;
|
private bool isEditorLoading;
|
||||||
@@ -381,7 +285,6 @@
|
|||||||
{
|
{
|
||||||
tableDetail = null;
|
tableDetail = null;
|
||||||
cellIndex = null;
|
cellIndex = null;
|
||||||
tableLayoutVersion++;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +309,6 @@
|
|||||||
{
|
{
|
||||||
isDetailLoading = false;
|
isDetailLoading = false;
|
||||||
BuildCellIndex();
|
BuildCellIndex();
|
||||||
tableLayoutVersion++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,14 +342,6 @@
|
|||||||
// During prerender localStorage is unavailable. Retry after interactive render.
|
// 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()
|
private void BuildCellIndex()
|
||||||
@@ -838,6 +732,28 @@
|
|||||||
? "critical-table-cell is-curated"
|
? "critical-table-cell is-curated"
|
||||||
: "critical-table-cell needs-curation";
|
: "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() =>
|
private string GetSelectedTableLabel() =>
|
||||||
SelectedTableReference?.Label ?? "Select a table";
|
SelectedTableReference?.Label ?? "Select a table";
|
||||||
|
|
||||||
@@ -872,4 +788,42 @@
|
|||||||
classes.Add(table.CurationPercentage >= 100 ? "is-curated" : "needs-curation");
|
classes.Add(table.CurationPercentage >= 100 ? "is-curated" : "needs-curation");
|
||||||
return string.Join(' ', classes);
|
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;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table {
|
.critical-table-grid {
|
||||||
width: 100%;
|
width: max-content;
|
||||||
border-collapse: collapse;
|
min-width: 100%;
|
||||||
font-size: 1.5rem;
|
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-grid-row {
|
||||||
.critical-table td {
|
display: grid;
|
||||||
border: 1px solid rgba(127, 96, 55, 0.2);
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid-header-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 0;
|
||||||
padding: 0.35rem;
|
padding: 0.35rem;
|
||||||
vertical-align: top;
|
border-right: 1px solid rgba(127, 96, 55, 0.2);
|
||||||
}
|
border-bottom: 1px solid rgba(127, 96, 55, 0.2);
|
||||||
|
|
||||||
.critical-table th {
|
|
||||||
background: rgba(238, 223, 193, 0.45);
|
background: rgba(238, 223, 193, 0.45);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table td {
|
.critical-table-grid-column-row .critical-table-grid-header-cell,
|
||||||
background: rgba(255, 255, 255, 0.85);
|
.critical-table-grid-group-row .critical-table-grid-header-cell {
|
||||||
min-width: 190px;
|
font-size: 2rem;
|
||||||
max-width: 250px;
|
}
|
||||||
padding: 0.55rem;
|
|
||||||
|
.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 {
|
.critical-table-cell {
|
||||||
position: relative;
|
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 {
|
.critical-table-cell.is-curated {
|
||||||
@@ -785,8 +806,8 @@ textarea {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.55rem;
|
gap: 0.55rem;
|
||||||
height: 100%;
|
flex: 1 1 auto;
|
||||||
min-height: 100%;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table-cell-shell > .critical-cell {
|
.critical-table-cell-shell > .critical-cell {
|
||||||
@@ -826,23 +847,16 @@ textarea {
|
|||||||
background: rgba(255, 248, 236, 0.98);
|
background: rgba(255, 248, 236, 0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table td .critical-cell {
|
.critical-table-grid .critical-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table .roll-band-header {
|
.critical-table-cell-empty {
|
||||||
width: 96px;
|
align-items: center;
|
||||||
background: rgba(255, 247, 230, 0.52);
|
justify-content: center;
|
||||||
font-size: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.critical-table thead th {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-cell {
|
.empty-cell {
|
||||||
|
|||||||
@@ -1,36 +1,3 @@
|
|||||||
window.rolemasterTables = window.rolemasterTables || {
|
window.rolemasterTables = window.rolemasterTables || {
|
||||||
alignCriticalCells() {
|
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`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("resize", () => window.rolemasterTables.alignCriticalCells());
|
|
||||||
|
|||||||
Reference in New Issue
Block a user