Add manual critical table cell editor

This commit is contained in:
2026-03-14 15:09:16 +01:00
parent 4e518244a2
commit 6e28ad975f
16 changed files with 1105 additions and 27 deletions

View File

@@ -0,0 +1,315 @@
@if (Model is not null)
{
<div class="critical-editor-backdrop" @onclick="HandleBackdropClicked">
<div class="critical-editor-dialog" @onclick:stopPropagation="true">
<header class="critical-editor-header">
<div>
<span class="eyebrow">@Model.SourceDocument</span>
<h3 class="panel-title">Edit @Model.TableName</h3>
<p class="muted critical-editor-meta">
Roll band <strong>@Model.RollBand</strong>, column <strong>@Model.ColumnLabel</strong>
@if (!string.IsNullOrWhiteSpace(Model.GroupLabel))
{
<span>, group <strong>@Model.GroupLabel</strong></span>
}
</p>
</div>
<button type="button" class="btn btn-link critical-editor-close" @onclick="OnClose">Close</button>
</header>
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
{
<p class="error-text">@ErrorMessage</p>
}
@if (IsLoading)
{
<p class="muted">Loading editor...</p>
}
else
{
<div class="critical-editor-body">
<section class="critical-editor-section">
<h4>Base Cell</h4>
<div class="field-shell">
<label>Raw Cell Text</label>
<InputTextArea class="input-shell critical-editor-textarea tall" @bind-Value="Model.RawCellText" />
</div>
<div class="field-shell">
<label>Description / Prose</label>
<InputTextArea class="input-shell critical-editor-textarea" @bind-Value="Model.DescriptionText" />
</div>
<div class="field-shell">
<label>Affix Text</label>
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="Model.RawAffixText" />
</div>
<div class="form-grid">
<div class="field-shell">
<label>Parse Status</label>
<InputText class="input-shell" @bind-Value="Model.ParseStatus" />
</div>
<div class="field-shell">
<label>Parsed Json</label>
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="Model.ParsedJson" />
</div>
</div>
</section>
<section class="critical-editor-section">
<div class="critical-editor-section-header">
<h4>Base Effects</h4>
<button type="button" class="btn-ritual" @onclick="AddBaseEffect">Add Effect</button>
</div>
@if (Model.Effects.Count == 0)
{
<p class="muted">No normalized base effects for this cell.</p>
}
else
{
@for (var index = 0; index < Model.Effects.Count; index++)
{
var effect = Model.Effects[index];
<div class="critical-editor-card">
<div class="critical-editor-card-header">
<strong>Effect @(index + 1)</strong>
<button type="button" class="btn btn-link" @onclick="() => RemoveBaseEffect(index)">Remove</button>
</div>
@EffectFields(effect)
</div>
}
}
</section>
<section class="critical-editor-section">
<div class="critical-editor-section-header">
<h4>Branches</h4>
<button type="button" class="btn-ritual" @onclick="AddBranch">Add Branch</button>
</div>
@if (Model.Branches.Count == 0)
{
<p class="muted">No branch records on this cell.</p>
}
else
{
@for (var index = 0; index < Model.Branches.Count; index++)
{
var branch = Model.Branches[index];
<div class="critical-editor-card branch-card-editor">
<div class="critical-editor-card-header">
<strong>Branch @(index + 1)</strong>
<button type="button" class="btn btn-link" @onclick="() => RemoveBranch(index)">Remove</button>
</div>
<div class="form-grid">
<div class="field-shell">
<label>Branch Kind</label>
<InputText class="input-shell" @bind-Value="branch.BranchKind" />
</div>
<div class="field-shell">
<label>Condition Key</label>
<InputText class="input-shell" @bind-Value="branch.ConditionKey" />
</div>
<div class="field-shell">
<label>Sort Order</label>
<InputNumber TValue="int" class="input-shell" @bind-Value="branch.SortOrder" />
</div>
</div>
<div class="field-shell">
<label>Condition Text</label>
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="branch.ConditionText" />
</div>
<div class="field-shell">
<label>Raw Text</label>
<InputTextArea class="input-shell critical-editor-textarea" @bind-Value="branch.RawText" />
</div>
<div class="field-shell">
<label>Description / Prose</label>
<InputTextArea class="input-shell critical-editor-textarea" @bind-Value="branch.DescriptionText" />
</div>
<div class="field-shell">
<label>Affix Text</label>
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="branch.RawAffixText" />
</div>
<div class="form-grid">
<div class="field-shell">
<label>Condition Json</label>
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="branch.ConditionJson" />
</div>
<div class="field-shell">
<label>Parsed Json</label>
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="branch.ParsedJson" />
</div>
</div>
<div class="critical-editor-subsection">
<div class="critical-editor-section-header">
<h5>Branch Effects</h5>
<button type="button" class="btn-ritual" @onclick="() => AddBranchEffect(branch)">Add Effect</button>
</div>
@if (branch.Effects.Count == 0)
{
<p class="muted">No normalized branch effects.</p>
}
else
{
@for (var effectIndex = 0; effectIndex < branch.Effects.Count; effectIndex++)
{
var effect = branch.Effects[effectIndex];
<div class="critical-editor-card nested">
<div class="critical-editor-card-header">
<strong>Branch Effect @(effectIndex + 1)</strong>
<button type="button" class="btn btn-link" @onclick="() => RemoveBranchEffect(branch, effectIndex)">Remove</button>
</div>
@EffectFields(effect)
</div>
}
}
</div>
</div>
}
}
</section>
</div>
}
<footer class="critical-editor-footer">
<button type="button" class="btn btn-link" @onclick="OnClose" disabled="@IsSaving">Cancel</button>
<button type="button" class="btn-ritual" @onclick="OnSave" disabled="@IsLoading || IsSaving">
@(IsSaving ? "Saving..." : "Save Cell")
</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, EditorRequired]
public EventCallback OnClose { get; set; }
[Parameter, EditorRequired]
public EventCallback OnSave { get; set; }
private async Task HandleBackdropClicked()
{
await OnClose.InvokeAsync();
}
private void AddBaseEffect()
{
Model?.Effects.Add(new CriticalEffectEditorModel());
}
private void RemoveBaseEffect(int index)
{
if (Model is null || index < 0 || index >= Model.Effects.Count)
{
return;
}
Model.Effects.RemoveAt(index);
}
private void AddBranch()
{
if (Model is null)
{
return;
}
Model.Branches.Add(new CriticalBranchEditorModel
{
SortOrder = Model.Branches.Count + 1
});
}
private void RemoveBranch(int index)
{
if (Model is null || index < 0 || index >= Model.Branches.Count)
{
return;
}
Model.Branches.RemoveAt(index);
}
private static void AddBranchEffect(CriticalBranchEditorModel branch)
{
branch.Effects.Add(new CriticalEffectEditorModel());
}
private static void RemoveBranchEffect(CriticalBranchEditorModel branch, int index)
{
if (index < 0 || index >= branch.Effects.Count)
{
return;
}
branch.Effects.RemoveAt(index);
}
}
@functions {
private RenderFragment EffectFields(CriticalEffectEditorModel effect) => @<div>
<div class="form-grid">
<div class="field-shell">
<label>Effect Code</label>
<InputText class="input-shell" @bind-Value="effect.EffectCode" />
</div>
<div class="field-shell">
<label>Target</label>
<InputText class="input-shell" @bind-Value="effect.Target" />
</div>
<div class="field-shell">
<label>Source Type</label>
<InputText class="input-shell" @bind-Value="effect.SourceType" />
</div>
<div class="field-shell">
<label>Source Text</label>
<InputText class="input-shell" @bind-Value="effect.SourceText" />
</div>
<div class="field-shell">
<label>Value Integer</label>
<InputNumber TValue="int?" class="input-shell" @bind-Value="effect.ValueInteger" />
</div>
<div class="field-shell">
<label>Value Decimal</label>
<InputNumber TValue="decimal?" class="input-shell" @bind-Value="effect.ValueDecimal" step="0.01" />
</div>
<div class="field-shell">
<label>Value Expression</label>
<InputText class="input-shell" @bind-Value="effect.ValueExpression" />
</div>
<div class="field-shell">
<label>Duration Rounds</label>
<InputNumber TValue="int?" class="input-shell" @bind-Value="effect.DurationRounds" />
</div>
<div class="field-shell">
<label>Per Round</label>
<InputNumber TValue="int?" class="input-shell" @bind-Value="effect.PerRound" />
</div>
<div class="field-shell">
<label>Modifier</label>
<InputNumber TValue="int?" class="input-shell" @bind-Value="effect.Modifier" />
</div>
<div class="field-shell">
<label>Body Part</label>
<InputText class="input-shell" @bind-Value="effect.BodyPart" />
</div>
</div>
<label class="critical-editor-checkbox">
<InputCheckbox class="form-check-input" @bind-Value="effect.IsPermanent" />
<span>Permanent</span>
</label>
</div>;
}