Add manual critical table cell editor
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
using RolemasterDb.App.Features;
|
||||
|
||||
namespace RolemasterDb.App.Components.Shared;
|
||||
|
||||
public sealed class CriticalBranchEditorModel
|
||||
{
|
||||
public string BranchKind { get; set; } = "conditional";
|
||||
public string? ConditionKey { get; set; }
|
||||
public string ConditionText { get; set; } = string.Empty;
|
||||
public string ConditionJson { get; set; } = "{}";
|
||||
public string RawText { get; set; } = string.Empty;
|
||||
public string DescriptionText { get; set; } = string.Empty;
|
||||
public string? RawAffixText { get; set; }
|
||||
public string ParsedJson { get; set; } = "{}";
|
||||
public int SortOrder { get; set; }
|
||||
public List<CriticalEffectEditorModel> Effects { get; set; } = [];
|
||||
|
||||
public static CriticalBranchEditorModel FromItem(CriticalBranchEditorItem item) =>
|
||||
new()
|
||||
{
|
||||
BranchKind = item.BranchKind,
|
||||
ConditionKey = item.ConditionKey,
|
||||
ConditionText = item.ConditionText,
|
||||
ConditionJson = item.ConditionJson,
|
||||
RawText = item.RawText,
|
||||
DescriptionText = item.DescriptionText,
|
||||
RawAffixText = item.RawAffixText,
|
||||
ParsedJson = item.ParsedJson,
|
||||
SortOrder = item.SortOrder,
|
||||
Effects = item.Effects.Select(CriticalEffectEditorModel.FromItem).ToList()
|
||||
};
|
||||
|
||||
public CriticalBranchEditorItem ToItem() =>
|
||||
new(
|
||||
BranchKind,
|
||||
ConditionKey,
|
||||
ConditionText,
|
||||
ConditionJson,
|
||||
RawText,
|
||||
DescriptionText,
|
||||
RawAffixText,
|
||||
ParsedJson,
|
||||
SortOrder,
|
||||
Effects.Select(effect => effect.ToItem()).ToList());
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using RolemasterDb.App.Features;
|
||||
|
||||
namespace RolemasterDb.App.Components.Shared;
|
||||
|
||||
public sealed class CriticalCellEditorModel
|
||||
{
|
||||
public int ResultId { get; set; }
|
||||
public string TableSlug { get; set; } = string.Empty;
|
||||
public string TableName { get; set; } = string.Empty;
|
||||
public string SourceDocument { get; set; } = string.Empty;
|
||||
public string RollBand { get; set; } = string.Empty;
|
||||
public string? GroupKey { get; set; }
|
||||
public string? GroupLabel { get; set; }
|
||||
public string ColumnKey { get; set; } = string.Empty;
|
||||
public string ColumnLabel { get; set; } = string.Empty;
|
||||
public string ColumnRole { get; set; } = string.Empty;
|
||||
public string RawCellText { get; set; } = string.Empty;
|
||||
public string DescriptionText { get; set; } = string.Empty;
|
||||
public string? RawAffixText { get; set; }
|
||||
public string ParseStatus { get; set; } = string.Empty;
|
||||
public string ParsedJson { get; set; } = "{}";
|
||||
public List<CriticalEffectEditorModel> Effects { get; set; } = [];
|
||||
public List<CriticalBranchEditorModel> Branches { get; set; } = [];
|
||||
|
||||
public static CriticalCellEditorModel FromResponse(CriticalCellEditorResponse response) =>
|
||||
new()
|
||||
{
|
||||
ResultId = response.ResultId,
|
||||
TableSlug = response.TableSlug,
|
||||
TableName = response.TableName,
|
||||
SourceDocument = response.SourceDocument,
|
||||
RollBand = response.RollBand,
|
||||
GroupKey = response.GroupKey,
|
||||
GroupLabel = response.GroupLabel,
|
||||
ColumnKey = response.ColumnKey,
|
||||
ColumnLabel = response.ColumnLabel,
|
||||
ColumnRole = response.ColumnRole,
|
||||
RawCellText = response.RawCellText,
|
||||
DescriptionText = response.DescriptionText,
|
||||
RawAffixText = response.RawAffixText,
|
||||
ParseStatus = response.ParseStatus,
|
||||
ParsedJson = response.ParsedJson,
|
||||
Effects = response.Effects.Select(CriticalEffectEditorModel.FromItem).ToList(),
|
||||
Branches = response.Branches.Select(CriticalBranchEditorModel.FromItem).ToList()
|
||||
};
|
||||
|
||||
public CriticalCellUpdateRequest ToRequest() =>
|
||||
new(
|
||||
RawCellText,
|
||||
DescriptionText,
|
||||
RawAffixText,
|
||||
ParseStatus,
|
||||
ParsedJson,
|
||||
Effects.Select(effect => effect.ToItem()).ToList(),
|
||||
Branches
|
||||
.OrderBy(branch => branch.SortOrder)
|
||||
.Select((branch, index) =>
|
||||
{
|
||||
branch.SortOrder = index + 1;
|
||||
return branch.ToItem();
|
||||
})
|
||||
.ToList());
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using RolemasterDb.App.Features;
|
||||
|
||||
namespace RolemasterDb.App.Components.Shared;
|
||||
|
||||
public sealed class CriticalEffectEditorModel
|
||||
{
|
||||
public string EffectCode { get; set; } = string.Empty;
|
||||
public string? Target { get; set; }
|
||||
public int? ValueInteger { get; set; }
|
||||
public decimal? ValueDecimal { get; set; }
|
||||
public string? ValueExpression { get; set; }
|
||||
public int? DurationRounds { get; set; }
|
||||
public int? PerRound { get; set; }
|
||||
public int? Modifier { get; set; }
|
||||
public string? BodyPart { get; set; }
|
||||
public bool IsPermanent { get; set; }
|
||||
public string SourceType { get; set; } = "symbol";
|
||||
public string? SourceText { get; set; }
|
||||
|
||||
public static CriticalEffectEditorModel FromItem(CriticalEffectEditorItem item) =>
|
||||
new()
|
||||
{
|
||||
EffectCode = item.EffectCode,
|
||||
Target = item.Target,
|
||||
ValueInteger = item.ValueInteger,
|
||||
ValueDecimal = item.ValueDecimal,
|
||||
ValueExpression = item.ValueExpression,
|
||||
DurationRounds = item.DurationRounds,
|
||||
PerRound = item.PerRound,
|
||||
Modifier = item.Modifier,
|
||||
BodyPart = item.BodyPart,
|
||||
IsPermanent = item.IsPermanent,
|
||||
SourceType = item.SourceType,
|
||||
SourceText = item.SourceText
|
||||
};
|
||||
|
||||
public CriticalEffectEditorItem ToItem() =>
|
||||
new(
|
||||
EffectCode,
|
||||
Target,
|
||||
ValueInteger,
|
||||
ValueDecimal,
|
||||
ValueExpression,
|
||||
DurationRounds,
|
||||
PerRound,
|
||||
Modifier,
|
||||
BodyPart,
|
||||
IsPermanent,
|
||||
SourceType,
|
||||
SourceText);
|
||||
}
|
||||
Reference in New Issue
Block a user