Share critical cell parsing across app and importer

This commit is contained in:
2026-03-15 02:10:17 +01:00
parent c5800d6878
commit 641e33f811
27 changed files with 1207 additions and 19 deletions

View File

@@ -60,12 +60,22 @@
"rawAffixText": "+8H - 2S",
"parseStatus": "verified",
"parsedJson": "{}",
"validationMessages": [],
"effects": [],
"branches": []
}</pre>
<p class="panel-copy">Use this to retrieve the full editable result graph for one critical-table cell, including nested branches and normalized effects.</p>
</section>
<section class="panel">
<h2 class="panel-title">Cell re-parse</h2>
<p class="panel-copy"><code>POST /api/tables/critical/{slug}/cells/{resultId}/reparse</code></p>
<pre class="code-block">{
"rawCellText": "Strike to thigh. +8H\nWith greaves: blow glances aside."
}</pre>
<p class="panel-copy">Re-runs the shared single-cell parser and returns a refreshed editor payload without saving changes.</p>
</section>
<section class="panel">
<h2 class="panel-title">Cell editor save</h2>
<p class="panel-copy"><code>PUT /api/tables/critical/{slug}/cells/{resultId}</code></p>

View File

@@ -207,10 +207,13 @@
<CriticalCellEditorDialog
Model="editorModel"
IsLoading="isEditorLoading"
IsReparsing="isEditorReparsing"
IsSaving="isEditorSaving"
LoadErrorMessage="@editorLoadError"
ReparseErrorMessage="@editorReparseError"
SaveErrorMessage="@editorSaveError"
OnClose="CloseCellEditorAsync"
OnReparse="ReparseCellEditorAsync"
OnSave="SaveCellEditorAsync" />
}
@@ -227,8 +230,10 @@
private bool IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0;
private bool isEditorOpen;
private bool isEditorLoading;
private bool isEditorReparsing;
private bool isEditorSaving;
private string? editorLoadError;
private string? editorReparseError;
private string? editorSaveError;
private int? editingResultId;
private CriticalCellEditorModel? editorModel;
@@ -328,9 +333,11 @@
}
editorLoadError = null;
editorReparseError = null;
editorSaveError = null;
editorModel = null;
editingResultId = resultId;
isEditorReparsing = false;
isEditorSaving = false;
isEditorLoading = true;
isEditorOpen = true;
@@ -362,14 +369,47 @@
{
isEditorOpen = false;
isEditorLoading = false;
isEditorReparsing = false;
isEditorSaving = false;
editorLoadError = null;
editorReparseError = null;
editorSaveError = null;
editingResultId = null;
editorModel = null;
await InvokeAsync(StateHasChanged);
}
private async Task ReparseCellEditorAsync()
{
if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null)
{
return;
}
isEditorReparsing = true;
editorReparseError = null;
try
{
var response = await LookupService.ReparseCriticalCellAsync(selectedTableSlug, editingResultId.Value, editorModel.RawCellText);
if (response is null)
{
editorReparseError = "The selected cell could not be re-parsed.";
return;
}
editorModel = CriticalCellEditorModel.FromResponse(response);
}
catch (Exception exception)
{
editorReparseError = exception.Message;
}
finally
{
isEditorReparsing = false;
}
}
private async Task SaveCellEditorAsync()
{
if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null)

View File

@@ -1,3 +1,4 @@
using System.Text.Json;
using RolemasterDb.App.Features;
namespace RolemasterDb.App.Components.Shared;
@@ -35,11 +36,32 @@ public sealed class CriticalBranchEditorModel
BranchKind,
ConditionKey,
ConditionText,
ConditionJson,
RawText,
"{}",
BuildRawText(),
DescriptionText,
RawAffixText,
ParsedJson,
SerializeParsedEffects(Effects),
SortOrder,
Effects.Select(effect => effect.ToItem()).ToList());
private string BuildRawText()
{
var condition = ConditionText.Trim();
var description = DescriptionText.Trim();
var firstLine = string.IsNullOrWhiteSpace(description)
? $"{condition}:"
: $"{condition}: {description}";
if (string.IsNullOrWhiteSpace(RawAffixText))
{
return firstLine;
}
return $"{firstLine}{Environment.NewLine}{RawAffixText.Trim()}";
}
private static string SerializeParsedEffects(IReadOnlyList<CriticalEffectEditorModel> effects) =>
effects.Count == 0
? "{}"
: JsonSerializer.Serialize(new { effects = effects.Select(effect => effect.ToItem()).ToList() });
}

View File

@@ -45,6 +45,11 @@
{
<EditForm Model="Model" OnSubmit="HandleSubmitAsync" class="critical-editor-form">
<div class="critical-editor-body">
@if (!string.IsNullOrWhiteSpace(ReparseErrorMessage))
{
<p class="error-text critical-editor-error">@ReparseErrorMessage</p>
}
@if (!string.IsNullOrWhiteSpace(SaveErrorMessage))
{
<p class="error-text critical-editor-error">@SaveErrorMessage</p>
@@ -79,11 +84,23 @@
<h4>Raw Text</h4>
<p class="muted">Update the source text, then adjust the visible card fields below.</p>
</div>
<button type="button" class="btn-ritual" @onclick="OnReparse" disabled="@IsSaving || IsReparsing">
@(IsReparsing ? "Re-Parsing..." : "Re-Parse Raw Text")
</button>
</div>
<div class="field-shell">
<label>Raw Cell Text</label>
<InputTextArea class="input-shell critical-editor-textarea tall" @bind-Value="Model.RawCellText" />
</div>
@if (Model.ValidationMessages.Count > 0)
{
<div class="critical-editor-validation-list">
@foreach (var message in Model.ValidationMessages)
{
<p class="critical-editor-validation-item">@message</p>
}
</div>
}
<div class="field-shell">
<label>Result Text Override</label>
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="Model.DescriptionText" />
@@ -231,18 +248,27 @@
[Parameter]
public bool IsLoading { get; set; }
[Parameter]
public bool IsReparsing { get; set; }
[Parameter]
public bool IsSaving { get; set; }
[Parameter]
public string? LoadErrorMessage { get; set; }
[Parameter]
public string? ReparseErrorMessage { get; set; }
[Parameter]
public string? SaveErrorMessage { get; set; }
[Parameter, EditorRequired]
public EventCallback OnClose { get; set; }
[Parameter, EditorRequired]
public EventCallback OnReparse { get; set; }
[Parameter, EditorRequired]
public EventCallback OnSave { get; set; }

View File

@@ -1,3 +1,4 @@
using System.Text.Json;
using RolemasterDb.App.Features;
namespace RolemasterDb.App.Components.Shared;
@@ -19,6 +20,7 @@ public sealed class CriticalCellEditorModel
public string? RawAffixText { get; set; }
public string ParseStatus { get; set; } = string.Empty;
public string ParsedJson { get; set; } = "{}";
public List<string> ValidationMessages { get; set; } = [];
public List<CriticalEffectEditorModel> Effects { get; set; } = [];
public List<CriticalBranchEditorModel> Branches { get; set; } = [];
@@ -40,6 +42,7 @@ public sealed class CriticalCellEditorModel
RawAffixText = response.RawAffixText,
ParseStatus = response.ParseStatus,
ParsedJson = response.ParsedJson,
ValidationMessages = response.ValidationMessages.ToList(),
Effects = response.Effects.Select(CriticalEffectEditorModel.FromItem).ToList(),
Branches = response.Branches.Select(CriticalBranchEditorModel.FromItem).ToList()
};
@@ -49,8 +52,8 @@ public sealed class CriticalCellEditorModel
RawCellText,
DescriptionText,
RawAffixText,
ParseStatus,
ParsedJson,
ResolveParseStatus(Effects, Branches),
SerializeParsedEffects(Effects),
Effects.Select(effect => effect.ToItem()).ToList(),
Branches
.OrderBy(branch => branch.SortOrder)
@@ -60,4 +63,16 @@ public sealed class CriticalCellEditorModel
return branch.ToItem();
})
.ToList());
private static string ResolveParseStatus(
IReadOnlyList<CriticalEffectEditorModel> effects,
IReadOnlyList<CriticalBranchEditorModel> branches) =>
effects.Count > 0 || branches.Any(branch => branch.Effects.Count > 0)
? "partial"
: "raw";
private static string SerializeParsedEffects(IReadOnlyList<CriticalEffectEditorModel> effects) =>
effects.Count == 0
? "{}"
: JsonSerializer.Serialize(new { effects = effects.Select(effect => effect.ToItem()).ToList() });
}