Files
RolemasterDB/src/RolemasterDb.App/Components/Tables/TablesCanvas.razor

352 lines
13 KiB
Plaintext

<div class="table-scroll">
<div class="critical-table-grid @BuildGridCssClass()" role="group" aria-label="@Detail.DisplayName" style="@gridTemplateStyle">
@if (Detail.Groups.Count > 0)
{
<div class="critical-table-grid-header-cell critical-table-grid-corner" aria-hidden="true"></div>
@foreach (var group in visibleGroups)
{
<div
class="@BuildGroupHeaderCssClass(group.Key)"
style="@BuildColumnSpanStyle(visibleColumns.Count)">
<span>@group.Label</span>
</div>
}
}
<div class="critical-table-grid-header-cell critical-table-grid-roll-band-header" aria-hidden="true"></div>
@foreach (var displayColumn in displayColumns)
{
<div class="@BuildColumnHeaderCssClass(displayColumn.GroupKey, displayColumn.ColumnKey)">
<span>@displayColumn.ColumnLabel</span>
</div>
}
@foreach (var rollBand in Detail.RollBands)
{
<div class="@BuildRollBandCssClass(rollBand.Label)">@rollBand.Label</div>
@foreach (var displayColumn in displayColumns)
{
if (TryGetCell(rollBand.Label, displayColumn.GroupKey, displayColumn.ColumnKey, out var resolvedCell) && resolvedCell is not null)
{
var cell = resolvedCell;
var isSelectedCell = IsSelectedCell(cell);
@if (MatchesModeFilter(cell))
{
<div
class="@GetCellCssClass(cell, displayColumn.GroupKey)"
role="button"
tabindex="0"
aria-pressed="@isSelectedCell"
@onclick="() => SelectCell(cell)"
@onkeydown="args => HandleCellKeyDown(args, cell)">
<div class="critical-table-cell-shell">
<div class="critical-table-cell-actions">
@if (string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal))
{
<StatusIndicator Tone="@(cell.IsCurated ? "success" : "warning")" CssClass="tables-cell-status-indicator"/>
}
else if (cell.IsCurated)
{
<span class="critical-cell-status-chip is-curated">Curated</span>
}
else
{
<span class="critical-cell-status-chip needs-curation">Needs Curation</span>
}
@if (isSelectedCell)
{
<StatusChip Tone="accent">Selected</StatusChip>
}
</div>
<CompactCriticalCell
Description="@(cell.Description ?? string.Empty)"
Effects="@(cell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
Branches="@(cell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())"/>
</div>
</div>
}
else
{
<div class="critical-table-cell critical-table-cell-empty tables-filtered-cell">
<span class="empty-cell">Filtered</span>
</div>
}
}
else
{
<div class="critical-table-cell critical-table-cell-empty">
<span class="empty-cell">—</span>
</div>
}
}
}
</div>
</div>
@code {
private readonly Dictionary<(string RollBand, string? GroupKey, string ColumnKey), CriticalTableCellDetail> cellIndex = new();
private readonly List<(string? GroupKey, string ColumnKey, string ColumnLabel)> displayColumns = new();
private readonly List<CriticalGroupReference> visibleGroups = new();
private readonly List<CriticalColumnReference> visibleColumns = new();
private string gridTemplateStyle = string.Empty;
[Parameter, EditorRequired]
public CriticalTableDetail Detail { get; set; } = default!;
[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 TablesCellSelection? SelectedCell { get; set; }
[Parameter]
public EventCallback<TablesCellSelection> OnSelectCell { get; set; }
protected override void OnParametersSet()
{
cellIndex.Clear();
displayColumns.Clear();
visibleGroups.Clear();
visibleColumns.Clear();
foreach (var cell in Detail.Cells)
{
cellIndex[(cell.RollBand, cell.GroupKey, cell.ColumnKey)] = cell;
}
var columnsToDisplay = ResolveVisibleColumns();
if (Detail.Groups.Count == 0)
{
foreach (var column in columnsToDisplay)
{
visibleColumns.Add(column);
displayColumns.Add((null, column.Key, column.Label));
}
}
else
{
var groupsToDisplay = ResolveVisibleGroups();
foreach (var group in groupsToDisplay)
{
visibleGroups.Add(group);
}
foreach (var column in columnsToDisplay)
{
visibleColumns.Add(column);
}
foreach (var group in visibleGroups)
{
foreach (var column in visibleColumns)
{
displayColumns.Add((group.Key, column.Key, column.Label));
}
}
}
var dataColumnCount = displayColumns.Count;
gridTemplateStyle = $"grid-template-columns: max-content repeat({dataColumnCount}, minmax(0, 1fr));";
}
private bool TryGetCell(string rollBand, string? groupKey, string columnKey, out CriticalTableCellDetail? cell) =>
cellIndex.TryGetValue((rollBand, groupKey, columnKey), out cell);
private bool MatchesGroupFilter(CriticalGroupReference group) =>
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);
private IReadOnlyList<CriticalGroupReference> ResolveVisibleGroups()
{
var filteredGroups = Detail.Groups.Where(MatchesGroupFilter).ToList();
return filteredGroups.Count > 0 ? filteredGroups : Detail.Groups;
}
private IReadOnlyList<CriticalColumnReference> ResolveVisibleColumns()
{
var filteredColumns = Detail.Columns.Where(MatchesColumnFilter).ToList();
return filteredColumns.Count > 0 ? filteredColumns : Detail.Columns;
}
private bool MatchesModeFilter(CriticalTableCellDetail cell) =>
CurrentMode switch
{
TablesReferenceMode.NeedsCuration => !cell.IsCurated,
TablesReferenceMode.Curated => cell.IsCurated,
_ => true
};
private string? ActiveRollBand =>
!string.IsNullOrWhiteSpace(SelectedCell?.RollBand) ? SelectedCell.RollBand : ResolveRollJumpBandLabel();
private string? ActiveColumnKey =>
!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);
private string BuildGridCssClass()
{
var classes = new List<string>
{
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<string>
{
"critical-table-grid-header-cell",
"critical-table-grid-group-header"
};
if (string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
{
classes.Add("is-active-group");
}
return string.Join(' ', classes);
}
private string BuildColumnHeaderCssClass(string? groupKey, string columnKey)
{
var classes = new List<string>
{
"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))
{
classes.Add("is-active-group");
}
return string.Join(' ', classes);
}
private string BuildRollBandCssClass(string rollBandLabel)
{
var classes = new List<string>
{
"critical-table-grid-header-cell",
"critical-table-grid-roll-band"
};
if (string.Equals(rollBandLabel, ActiveRollBand, StringComparison.OrdinalIgnoreCase))
{
classes.Add("is-active-row");
}
if (string.Equals(rollBandLabel, ResolveRollJumpBandLabel(), StringComparison.OrdinalIgnoreCase))
{
classes.Add("is-roll-target");
}
return string.Join(' ', classes);
}
private string GetCellCssClass(CriticalTableCellDetail cell, string? groupKey)
{
var classes = new List<string>
{
"critical-table-cell",
cell.IsCurated ? "is-curated" : "needs-curation"
};
if (string.Equals(cell.RollBand, ActiveRollBand, StringComparison.OrdinalIgnoreCase))
{
classes.Add("is-active-row");
}
if (string.Equals(cell.ColumnKey, ActiveColumnKey, StringComparison.OrdinalIgnoreCase))
{
classes.Add("is-active-column");
}
if (!string.IsNullOrWhiteSpace(groupKey) && string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
{
classes.Add("is-active-group");
}
if (SelectedCell is not null && cell.ResultId == SelectedCell.ResultId)
{
classes.Add("is-selected-cell");
}
if (string.Equals(cell.RollBand, ResolveRollJumpBandLabel(), StringComparison.OrdinalIgnoreCase))
{
classes.Add("is-roll-target");
}
return string.Join(' ', classes);
}
private string? ResolveRollJumpBandLabel()
{
if (!int.TryParse(RollJumpValue, out var targetRoll))
{
return null;
}
foreach (var rollBand in Detail.RollBands)
{
if (targetRoll < rollBand.MinRoll)
{
continue;
}
if (rollBand.MaxRoll is null || targetRoll <= rollBand.MaxRoll.Value)
{
return rollBand.Label;
}
}
return null;
}
private Task SelectCell(CriticalTableCellDetail cell) =>
OnSelectCell.InvokeAsync(new TablesCellSelection(cell.ResultId, cell.RollBand, cell.ColumnKey, cell.GroupKey));
private bool IsSelectedCell(CriticalTableCellDetail cell) =>
SelectedCell is not null && cell.ResultId == SelectedCell.ResultId;
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))
{
return SelectCell(cell);
}
return Task.CompletedTask;
}
private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};";
}