Refine critical curation table workflows
This commit is contained in:
@@ -2,12 +2,6 @@
|
||||
@using RolemasterDb.App.Features
|
||||
|
||||
<div class="critical-cell">
|
||||
<div class="critical-cell-status-row">
|
||||
<span class="critical-cell-status-chip @(IsCurated ? "is-curated" : "needs-curation")">
|
||||
@(IsCurated ? "Curated" : "Needs Curation")
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
<p class="critical-cell-description">@Description</p>
|
||||
@@ -46,9 +40,6 @@
|
||||
[Parameter, EditorRequired]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public bool IsCurated { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<CriticalEffectLookupResponse>? Effects { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
@using System.Collections.Generic
|
||||
@using System.Linq
|
||||
@using Microsoft.JSInterop
|
||||
@using RolemasterDb.App.Features
|
||||
@implements IAsyncDisposable
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<div class="critical-editor-backdrop"
|
||||
@onpointerdown="HandleBackdropPointerDown"
|
||||
@onpointerup="HandleBackdropPointerUp"
|
||||
@onpointercancel="HandleBackdropPointerCancel">
|
||||
<div class="critical-editor-dialog critical-curation-dialog"
|
||||
@onpointerdown="HandleDialogPointerDown"
|
||||
@onpointerup="HandleDialogPointerUp"
|
||||
@onpointercancel="HandleDialogPointerCancel"
|
||||
@onpointerdown:stopPropagation="true"
|
||||
@onpointerup:stopPropagation="true"
|
||||
@onpointercancel:stopPropagation="true">
|
||||
<header class="critical-editor-header">
|
||||
<div>
|
||||
<h3 class="panel-title">Curate Result Card</h3>
|
||||
@if (Model is not null)
|
||||
{
|
||||
<p class="muted critical-editor-meta">
|
||||
<strong>@Model.TableName</strong>
|
||||
<span> · Column <strong>@BuildColumnDisplayText(Model)</strong></span>
|
||||
<span> · Roll band <strong>@Model.RollBand</strong></span>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-link critical-editor-close" @onclick="OnClose">Close</button>
|
||||
</header>
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
<div class="critical-editor-body">
|
||||
<p class="muted">Loading curation preview...</p>
|
||||
</div>
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(ErrorMessage))
|
||||
{
|
||||
<div class="critical-editor-body">
|
||||
<p class="error-text critical-editor-error">@ErrorMessage</p>
|
||||
</div>
|
||||
}
|
||||
else if (Model is not null)
|
||||
{
|
||||
<div class="critical-editor-body critical-curation-body">
|
||||
<div class="critical-curation-grid">
|
||||
<div class="critical-editor-card critical-curation-preview-card">
|
||||
<CompactCriticalCell
|
||||
Description="@Model.DescriptionText"
|
||||
Effects="@BuildPreviewEffects(Model)"
|
||||
Branches="@BuildPreviewBranches(Model)" />
|
||||
</div>
|
||||
|
||||
<div class="critical-editor-card critical-curation-source-card">
|
||||
@if (!string.IsNullOrWhiteSpace(Model.SourceImageUrl))
|
||||
{
|
||||
<img
|
||||
class="critical-curation-source-image"
|
||||
src="@Model.SourceImageUrl"
|
||||
alt="@BuildSourceImageAltText(Model)" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="critical-curation-source-empty">
|
||||
<p class="muted">No source image is available for this cell yet.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@{
|
||||
var usedLegendEntries = GetUsedLegendEntries(Model, LegendEntries);
|
||||
}
|
||||
|
||||
@if (usedLegendEntries.Count > 0)
|
||||
{
|
||||
<div class="critical-curation-legend">
|
||||
@foreach (var entry in usedLegendEntries)
|
||||
{
|
||||
<div class="critical-curation-legend-item" title="@entry.Tooltip">
|
||||
<span class="legend-symbol">@entry.Symbol</span>
|
||||
<strong>@entry.Label</strong>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<footer class="critical-editor-footer">
|
||||
<button type="button" class="btn btn-link" @onclick="OnEdit" disabled="@(IsSaving)">Edit</button>
|
||||
<button type="button" class="btn-ritual" @onclick="OnMarkCurated" disabled="@(IsSaving)">
|
||||
@(IsSaving ? "Saving..." : "Mark as Curated")
|
||||
</button>
|
||||
</footer>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired]
|
||||
public CriticalCellEditorModel? Model { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool IsLoading { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool IsSaving { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<CriticalTableLegendEntry>? LegendEntries { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public EventCallback OnClose { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public EventCallback OnMarkCurated { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public EventCallback OnEdit { get; set; }
|
||||
|
||||
private IJSObjectReference? jsModule;
|
||||
private bool isBackdropPointerDown;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>(
|
||||
"import",
|
||||
"./Components/Shared/CriticalCellEditorDialog.razor.js");
|
||||
|
||||
await jsModule.InvokeVoidAsync("lockBackgroundScroll");
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (jsModule is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await jsModule.InvokeVoidAsync("unlockBackgroundScroll");
|
||||
await jsModule.DisposeAsync();
|
||||
}
|
||||
catch (JSDisconnectedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBackdropPointerDown()
|
||||
{
|
||||
isBackdropPointerDown = true;
|
||||
}
|
||||
|
||||
private async Task HandleBackdropPointerUp()
|
||||
{
|
||||
if (!isBackdropPointerDown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isBackdropPointerDown = false;
|
||||
await OnClose.InvokeAsync();
|
||||
}
|
||||
|
||||
private void HandleBackdropPointerCancel()
|
||||
{
|
||||
isBackdropPointerDown = false;
|
||||
}
|
||||
|
||||
private void HandleDialogPointerDown()
|
||||
{
|
||||
isBackdropPointerDown = false;
|
||||
}
|
||||
|
||||
private void HandleDialogPointerUp()
|
||||
{
|
||||
isBackdropPointerDown = false;
|
||||
}
|
||||
|
||||
private void HandleDialogPointerCancel()
|
||||
{
|
||||
isBackdropPointerDown = false;
|
||||
}
|
||||
|
||||
private static string BuildSourceImageAltText(CriticalCellEditorModel model)
|
||||
{
|
||||
var segments = new List<string>
|
||||
{
|
||||
model.TableName,
|
||||
$"roll band {model.RollBand}",
|
||||
$"column {model.ColumnLabel}"
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(model.GroupLabel))
|
||||
{
|
||||
segments.Add($"variant {model.GroupLabel}");
|
||||
}
|
||||
|
||||
return string.Join(", ", segments);
|
||||
}
|
||||
|
||||
private static string BuildColumnDisplayText(CriticalCellEditorModel model) =>
|
||||
string.IsNullOrWhiteSpace(model.GroupLabel)
|
||||
? model.ColumnLabel
|
||||
: $"{model.GroupLabel} / {model.ColumnLabel}";
|
||||
|
||||
private static IReadOnlyList<CriticalEffectLookupResponse> BuildPreviewEffects(CriticalCellEditorModel model) =>
|
||||
model.Effects
|
||||
.Select(effect => new CriticalEffectLookupResponse(
|
||||
effect.EffectCode,
|
||||
effect.Target,
|
||||
effect.ValueInteger,
|
||||
effect.ValueExpression,
|
||||
effect.DurationRounds,
|
||||
effect.PerRound,
|
||||
effect.Modifier,
|
||||
effect.BodyPart,
|
||||
effect.IsPermanent,
|
||||
effect.SourceType,
|
||||
effect.SourceText))
|
||||
.ToList();
|
||||
|
||||
private static IReadOnlyList<CriticalBranchLookupResponse> BuildPreviewBranches(CriticalCellEditorModel model) =>
|
||||
model.Branches
|
||||
.OrderBy(branch => branch.SortOrder)
|
||||
.Select(branch => new CriticalBranchLookupResponse(
|
||||
branch.BranchKind,
|
||||
branch.ConditionKey,
|
||||
branch.ConditionText,
|
||||
branch.DescriptionText,
|
||||
branch.RawAffixText,
|
||||
branch.Effects
|
||||
.Select(effect => new CriticalEffectLookupResponse(
|
||||
effect.EffectCode,
|
||||
effect.Target,
|
||||
effect.ValueInteger,
|
||||
effect.ValueExpression,
|
||||
effect.DurationRounds,
|
||||
effect.PerRound,
|
||||
effect.Modifier,
|
||||
effect.BodyPart,
|
||||
effect.IsPermanent,
|
||||
effect.SourceType,
|
||||
effect.SourceText))
|
||||
.ToList(),
|
||||
branch.RawText,
|
||||
branch.SortOrder))
|
||||
.ToList();
|
||||
|
||||
private static IReadOnlyList<CriticalTableLegendEntry> GetUsedLegendEntries(
|
||||
CriticalCellEditorModel model,
|
||||
IReadOnlyList<CriticalTableLegendEntry>? legendEntries)
|
||||
{
|
||||
if (legendEntries is null || legendEntries.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var usedEffectCodes = model.Effects
|
||||
.Select(effect => effect.EffectCode)
|
||||
.Concat(model.Branches.SelectMany(branch => branch.Effects.Select(effect => effect.EffectCode)))
|
||||
.Where(effectCode => !string.IsNullOrWhiteSpace(effectCode))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return legendEntries
|
||||
.Where(entry => usedEffectCodes.Contains(entry.EffectCode))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -32,15 +32,6 @@
|
||||
<span> · Variant <strong>@Model.GroupLabel</strong></span>
|
||||
}
|
||||
</p>
|
||||
<div class="critical-editor-status-row">
|
||||
<span class="critical-editor-curation-badge @(Model.IsCurated ? "is-curated" : "needs-curation")">
|
||||
@(Model.IsCurated ? "Curated" : "Needs Curation")
|
||||
</span>
|
||||
@if (Model.SourcePageNumber is not null)
|
||||
{
|
||||
<span class="chip">Source page @Model.SourcePageNumber</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -76,47 +67,6 @@
|
||||
<p class="error-text critical-editor-error">@SaveErrorMessage</p>
|
||||
}
|
||||
|
||||
<section class="critical-editor-source-grid">
|
||||
<div class="critical-editor-card critical-editor-status-card">
|
||||
<div class="critical-editor-section-header">
|
||||
<div>
|
||||
<h4>Curation State</h4>
|
||||
<p class="muted">Curated cells are protected from importer content overwrites until you unmark them.</p>
|
||||
</div>
|
||||
</div>
|
||||
<label class="critical-editor-curation-toggle">
|
||||
<InputCheckbox @bind-Value="Model.IsCurated" />
|
||||
<span>Mark this result curated</span>
|
||||
</label>
|
||||
<p class="muted critical-editor-inline-copy">
|
||||
@(Model.IsCurated
|
||||
? "This result will keep its current text, branches, and effects on later imports."
|
||||
: "This result will be refreshed from the importer on later imports.")
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="critical-editor-card critical-editor-source-card">
|
||||
<div class="critical-editor-section-header">
|
||||
<div>
|
||||
<h4>Source Cell</h4>
|
||||
<p class="muted">Use the importer crop as a visual reference while curating the result.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.SourceImageUrl))
|
||||
{
|
||||
<img
|
||||
class="critical-editor-source-image"
|
||||
src="@Model.SourceImageUrl"
|
||||
alt="@BuildSourceImageAltText(Model)" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="muted critical-editor-inline-copy">No source image is available for this cell yet.</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="critical-editor-section">
|
||||
<div class="critical-editor-section-header">
|
||||
<div>
|
||||
@@ -133,12 +83,38 @@
|
||||
@(IsReparsing ? "Generating..." : "Generate From Quick Input")
|
||||
</button>
|
||||
</div>
|
||||
<div class="field-shell">
|
||||
<label>Quick Parse Input</label>
|
||||
<textarea
|
||||
class="input-shell critical-editor-textarea tall"
|
||||
value="@Model.QuickParseInput"
|
||||
@oninput="HandleQuickParseInputChanged"></textarea>
|
||||
<div class="critical-editor-quick-parse-grid">
|
||||
<div class="field-shell">
|
||||
<label>Quick Parse Input</label>
|
||||
<textarea
|
||||
class="input-shell critical-editor-textarea tall"
|
||||
value="@Model.QuickParseInput"
|
||||
@oninput="HandleQuickParseInputChanged"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="critical-editor-quick-parse-source">
|
||||
<div class="critical-editor-quick-parse-source-header">
|
||||
<label>Source Cell</label>
|
||||
@if (Model.SourcePageNumber is not null)
|
||||
{
|
||||
<span class="chip">Page @Model.SourcePageNumber</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="critical-editor-quick-parse-source-frame">
|
||||
@if (!string.IsNullOrWhiteSpace(Model.SourceImageUrl))
|
||||
{
|
||||
<img
|
||||
class="critical-editor-source-image is-inline"
|
||||
src="@Model.SourceImageUrl"
|
||||
alt="@BuildSourceImageAltText(Model)" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="muted critical-editor-inline-copy">No source image is available for this cell yet.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="muted critical-editor-advanced-hint">Example: <code>Foe brings his guard up, frightened by your display.</code> then <code>+5, 1mp</code> or <code>w/o shield: glancing blow, +15, 3s, 3np</code>.</p>
|
||||
<div class="critical-editor-quick-legend">
|
||||
|
||||
Reference in New Issue
Block a user