Add critical cell reparse comparison review
This commit is contained in:
@@ -73,4 +73,22 @@ public sealed class CriticalBranchEditorModel
|
||||
effects.Count == 0
|
||||
? "{}"
|
||||
: JsonSerializer.Serialize(new { effects = effects.Select(effect => effect.ToItem()).ToList() });
|
||||
|
||||
public CriticalBranchEditorModel Clone() =>
|
||||
new()
|
||||
{
|
||||
BranchKind = BranchKind,
|
||||
ConditionKey = ConditionKey,
|
||||
ConditionText = ConditionText,
|
||||
ConditionJson = ConditionJson,
|
||||
RawText = RawText,
|
||||
DescriptionText = DescriptionText,
|
||||
RawAffixText = RawAffixText,
|
||||
ParsedJson = ParsedJson,
|
||||
SortOrder = SortOrder,
|
||||
OriginKey = OriginKey,
|
||||
IsOverridden = IsOverridden,
|
||||
AreEffectsOverridden = AreEffectsOverridden,
|
||||
Effects = Effects.Select(effect => effect.Clone()).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
{
|
||||
<p class="muted critical-editor-advanced-hint">@GetParserNoteSummary(Model.ValidationMessages.Count)</p>
|
||||
}
|
||||
@if (HasComparisonDifferences(Model, ComparisonBaseline))
|
||||
{
|
||||
<p class="muted critical-editor-advanced-hint">Fresh parsing differs from the current edited card. Review Generated Compare before saving if you want to keep the overrides.</p>
|
||||
}
|
||||
<div class="field-shell">
|
||||
<label>Result Text</label>
|
||||
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="Model.DescriptionText" @bind-Value:after="MarkDescriptionOverridden" />
|
||||
@@ -176,11 +180,55 @@
|
||||
<section class="critical-editor-section critical-editor-diagnostics-section">
|
||||
<details class="critical-editor-advanced">
|
||||
<summary class="critical-editor-advanced-summary">
|
||||
<span>Advanced Diagnostics</span>
|
||||
<span class="critical-editor-advanced-meta">@GetAdvancedSummary(Model)</span>
|
||||
<span>Advanced Review & Diagnostics</span>
|
||||
<span class="critical-editor-advanced-meta">@GetAdvancedSummary(Model, ComparisonBaseline)</span>
|
||||
</summary>
|
||||
|
||||
<div class="critical-editor-advanced-body">
|
||||
@if (Model.GeneratedState is not null)
|
||||
{
|
||||
<div class="critical-editor-card nested">
|
||||
<div class="critical-editor-card-header">
|
||||
<div>
|
||||
<strong>Generated Compare</strong>
|
||||
<p class="muted critical-editor-inline-copy">Compare the current edited card against the fresh parser output from the raw text.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chip-row">
|
||||
@foreach (var item in GetComparisonSummaryItems(Model, ComparisonBaseline))
|
||||
{
|
||||
<span class="chip">@item</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="critical-editor-compare-grid">
|
||||
<CriticalResultPreviewCard
|
||||
Title="Current Edited Card"
|
||||
Caption="@GetCurrentComparisonCaption(ComparisonBaseline)"
|
||||
Description="@GetComparisonSourceModel(Model, ComparisonBaseline).DescriptionText"
|
||||
Effects="@BuildPreviewEffects(GetComparisonSourceModel(Model, ComparisonBaseline))"
|
||||
Branches="@BuildPreviewBranches(GetComparisonSourceModel(Model, ComparisonBaseline))" />
|
||||
<CriticalResultPreviewCard
|
||||
Title="Generated From Raw Text"
|
||||
Caption="This is the fresh parser output before override preservation."
|
||||
Description="@Model.GeneratedState.DescriptionText"
|
||||
Effects="@Model.GeneratedState.Effects"
|
||||
Branches="@Model.GeneratedState.Branches"
|
||||
Notes="@Model.GeneratedState.ValidationMessages" />
|
||||
@if (ComparisonBaseline is not null)
|
||||
{
|
||||
<CriticalResultPreviewCard
|
||||
Title="Current After Re-Parse"
|
||||
Caption="This is the merged editor state after keeping the existing overrides."
|
||||
Description="@Model.DescriptionText"
|
||||
Effects="@BuildPreviewEffects(Model)"
|
||||
Branches="@BuildPreviewBranches(Model)" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="critical-editor-card nested">
|
||||
<div class="critical-editor-card-header">
|
||||
<div>
|
||||
@@ -277,6 +325,9 @@
|
||||
[Parameter, EditorRequired]
|
||||
public CriticalCellEditorModel? Model { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public CriticalCellEditorModel? ComparisonBaseline { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool IsLoading { get; set; }
|
||||
|
||||
@@ -462,10 +513,23 @@
|
||||
effect.IsOverridden = true;
|
||||
}
|
||||
|
||||
private static string GetAdvancedSummary(CriticalCellEditorModel model)
|
||||
private static string GetAdvancedSummary(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline)
|
||||
{
|
||||
var differenceCount = GetComparisonDifferenceCount(model, comparisonBaseline);
|
||||
var noteCount = model.ValidationMessages.Count;
|
||||
return noteCount == 0 ? "Parser metadata and save payload" : $"{noteCount} parser note{(noteCount == 1 ? string.Empty : "s")}";
|
||||
var segments = new List<string>();
|
||||
|
||||
if (differenceCount > 0)
|
||||
{
|
||||
segments.Add($"{differenceCount} review change{(differenceCount == 1 ? string.Empty : "s")}");
|
||||
}
|
||||
|
||||
if (noteCount > 0)
|
||||
{
|
||||
segments.Add($"{noteCount} parser note{(noteCount == 1 ? string.Empty : "s")}");
|
||||
}
|
||||
|
||||
return segments.Count == 0 ? "Generated compare and diagnostics" : string.Join(" · ", segments);
|
||||
}
|
||||
|
||||
private static string GetParserNoteSummary(int noteCount) =>
|
||||
@@ -494,6 +558,135 @@
|
||||
private static string BuildCurrentSavePayloadJson(CriticalCellEditorModel model) =>
|
||||
JsonSerializer.Serialize(model.ToRequest(), DiagnosticJsonOptions);
|
||||
|
||||
private static IReadOnlyList<string> GetComparisonSummaryItems(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline)
|
||||
{
|
||||
if (model.GeneratedState is null)
|
||||
{
|
||||
return ["Generated comparison is unavailable."];
|
||||
}
|
||||
|
||||
var items = new List<string>();
|
||||
|
||||
if (DescriptionDiffers(model, comparisonBaseline))
|
||||
{
|
||||
items.Add("Result text differs");
|
||||
}
|
||||
|
||||
if (EffectsDiffer(model, comparisonBaseline))
|
||||
{
|
||||
items.Add("Base effects differ");
|
||||
}
|
||||
|
||||
if (BranchesDiffer(model, comparisonBaseline))
|
||||
{
|
||||
items.Add("Conditions differ");
|
||||
}
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
items.Add("Current card matches the fresh parse");
|
||||
}
|
||||
|
||||
if (model.GeneratedState.ValidationMessages.Count > 0)
|
||||
{
|
||||
items.Add($"{model.GeneratedState.ValidationMessages.Count} parser note{(model.GeneratedState.ValidationMessages.Count == 1 ? string.Empty : "s")}");
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static int GetComparisonDifferenceCount(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline)
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
if (DescriptionDiffers(model, comparisonBaseline))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (EffectsDiffer(model, comparisonBaseline))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (BranchesDiffer(model, comparisonBaseline))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static bool HasComparisonDifferences(CriticalCellEditorModel? model, CriticalCellEditorModel? comparisonBaseline) =>
|
||||
model is not null && GetComparisonDifferenceCount(model, comparisonBaseline) > 0;
|
||||
|
||||
private static bool DescriptionDiffers(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline) =>
|
||||
model.GeneratedState is not null &&
|
||||
!string.Equals(
|
||||
NormalizeDisplayText(GetComparisonSourceModel(model, comparisonBaseline).DescriptionText),
|
||||
NormalizeDisplayText(model.GeneratedState.DescriptionText),
|
||||
StringComparison.Ordinal);
|
||||
|
||||
private static bool EffectsDiffer(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline) =>
|
||||
model.GeneratedState is not null &&
|
||||
SerializeComparisonValue(BuildPreviewEffects(GetComparisonSourceModel(model, comparisonBaseline)).Select(ProjectEffectForComparison).ToList()) !=
|
||||
SerializeComparisonValue(model.GeneratedState.Effects.Select(ProjectEffectForComparison).ToList());
|
||||
|
||||
private static bool BranchesDiffer(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline) =>
|
||||
model.GeneratedState is not null &&
|
||||
SerializeComparisonValue(BuildPreviewBranches(GetComparisonSourceModel(model, comparisonBaseline)).Select(ProjectBranchForComparison).ToList()) !=
|
||||
SerializeComparisonValue(model.GeneratedState.Branches.Select(ProjectBranchForComparison).ToList());
|
||||
|
||||
private static string NormalizeDisplayText(string? value) =>
|
||||
value?.Trim() ?? string.Empty;
|
||||
|
||||
private static string SerializeComparisonValue<TValue>(TValue value) =>
|
||||
JsonSerializer.Serialize(value, DiagnosticJsonOptions);
|
||||
|
||||
private static object ProjectEffectForComparison(CriticalEffectLookupResponse effect) => new
|
||||
{
|
||||
effect.EffectCode,
|
||||
effect.Target,
|
||||
effect.ValueInteger,
|
||||
effect.ValueExpression,
|
||||
effect.DurationRounds,
|
||||
effect.PerRound,
|
||||
effect.Modifier,
|
||||
effect.BodyPart,
|
||||
effect.IsPermanent,
|
||||
SourceText = NormalizeDisplayText(effect.SourceText)
|
||||
};
|
||||
|
||||
private static object ProjectBranchForComparison(CriticalBranchLookupResponse branch) => new
|
||||
{
|
||||
Condition = NormalizeDisplayText(branch.ConditionText),
|
||||
Description = NormalizeDisplayText(branch.Description),
|
||||
Effects = branch.Effects
|
||||
.Select(ProjectEffectForComparison)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
private static CriticalCellEditorModel GetComparisonSourceModel(
|
||||
CriticalCellEditorModel model,
|
||||
CriticalCellEditorModel? comparisonBaseline) =>
|
||||
comparisonBaseline ?? model;
|
||||
|
||||
private static string GetCurrentComparisonCaption(CriticalCellEditorModel? comparisonBaseline) =>
|
||||
comparisonBaseline is null
|
||||
? "This is the result that will be saved."
|
||||
: "This is the edited card before the last re-parse.";
|
||||
|
||||
private static IReadOnlyList<CriticalEffectLookupResponse> BuildPreviewEffects(CriticalCellEditorModel model) =>
|
||||
model.Effects
|
||||
.Select(CreatePreviewEffect)
|
||||
.ToList();
|
||||
|
||||
private static IReadOnlyList<CriticalBranchLookupResponse> BuildPreviewBranches(CriticalCellEditorModel model) =>
|
||||
model.Branches
|
||||
.OrderBy(branch => branch.SortOrder)
|
||||
.Select(CreatePreviewBranch)
|
||||
.ToList();
|
||||
|
||||
private static List<(string Scope, string EffectLabel, string SourceType, string? SourceText)> GetEffectMetadataRows(CriticalCellEditorModel model)
|
||||
{
|
||||
var rows = new List<(string Scope, string EffectLabel, string SourceType, string? SourceText)>();
|
||||
@@ -541,6 +734,22 @@
|
||||
effect.IsPermanent,
|
||||
effect.SourceType,
|
||||
effect.SourceText);
|
||||
|
||||
private static CriticalEffectLookupResponse CreatePreviewEffect(CriticalEffectEditorModel effect) =>
|
||||
CreateBadgeEffect(effect);
|
||||
|
||||
private static CriticalBranchLookupResponse CreatePreviewBranch(CriticalBranchEditorModel branch) =>
|
||||
new(
|
||||
branch.BranchKind,
|
||||
branch.ConditionKey,
|
||||
branch.ConditionText,
|
||||
branch.DescriptionText,
|
||||
branch.RawAffixText,
|
||||
branch.Effects
|
||||
.Select(CreatePreviewEffect)
|
||||
.ToList(),
|
||||
branch.RawText,
|
||||
branch.SortOrder);
|
||||
}
|
||||
|
||||
@functions {
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed class CriticalCellEditorModel
|
||||
public List<string> ValidationMessages { get; set; } = [];
|
||||
public List<CriticalEffectEditorModel> Effects { get; set; } = [];
|
||||
public List<CriticalBranchEditorModel> Branches { get; set; } = [];
|
||||
public CriticalCellComparisonState? GeneratedState { get; set; }
|
||||
|
||||
public static CriticalCellEditorModel FromResponse(CriticalCellEditorResponse response) =>
|
||||
new()
|
||||
@@ -52,7 +53,8 @@ public sealed class CriticalCellEditorModel
|
||||
AreBranchesOverridden = response.AreBranchesOverridden,
|
||||
ValidationMessages = response.ValidationMessages.ToList(),
|
||||
Effects = response.Effects.Select(CriticalEffectEditorModel.FromItem).ToList(),
|
||||
Branches = response.Branches.Select(CriticalBranchEditorModel.FromItem).ToList()
|
||||
Branches = response.Branches.Select(CriticalBranchEditorModel.FromItem).ToList(),
|
||||
GeneratedState = response.GeneratedState
|
||||
};
|
||||
|
||||
public CriticalCellUpdateRequest ToRequest()
|
||||
@@ -83,6 +85,34 @@ public sealed class CriticalCellEditorModel
|
||||
};
|
||||
}
|
||||
|
||||
public CriticalCellEditorModel Clone() =>
|
||||
new()
|
||||
{
|
||||
ResultId = ResultId,
|
||||
TableSlug = TableSlug,
|
||||
TableName = TableName,
|
||||
SourceDocument = SourceDocument,
|
||||
RollBand = RollBand,
|
||||
GroupKey = GroupKey,
|
||||
GroupLabel = GroupLabel,
|
||||
ColumnKey = ColumnKey,
|
||||
ColumnLabel = ColumnLabel,
|
||||
ColumnRole = ColumnRole,
|
||||
RawCellText = RawCellText,
|
||||
DescriptionText = DescriptionText,
|
||||
RawAffixText = RawAffixText,
|
||||
ParseStatus = ParseStatus,
|
||||
ParsedJson = ParsedJson,
|
||||
IsDescriptionOverridden = IsDescriptionOverridden,
|
||||
IsRawAffixTextOverridden = IsRawAffixTextOverridden,
|
||||
AreEffectsOverridden = AreEffectsOverridden,
|
||||
AreBranchesOverridden = AreBranchesOverridden,
|
||||
ValidationMessages = ValidationMessages.ToList(),
|
||||
Effects = Effects.Select(effect => effect.Clone()).ToList(),
|
||||
Branches = Branches.Select(branch => branch.Clone()).ToList(),
|
||||
GeneratedState = GeneratedState
|
||||
};
|
||||
|
||||
private static string ResolveParseStatus(
|
||||
IReadOnlyList<CriticalEffectEditorModel> effects,
|
||||
IReadOnlyList<CriticalBranchEditorModel> branches) =>
|
||||
|
||||
@@ -54,4 +54,23 @@ public sealed class CriticalEffectEditorModel
|
||||
SourceText,
|
||||
OriginKey,
|
||||
IsOverridden);
|
||||
|
||||
public CriticalEffectEditorModel Clone() =>
|
||||
new()
|
||||
{
|
||||
EffectCode = EffectCode,
|
||||
Target = Target,
|
||||
ValueInteger = ValueInteger,
|
||||
ValueDecimal = ValueDecimal,
|
||||
ValueExpression = ValueExpression,
|
||||
DurationRounds = DurationRounds,
|
||||
PerRound = PerRound,
|
||||
Modifier = Modifier,
|
||||
BodyPart = BodyPart,
|
||||
IsPermanent = IsPermanent,
|
||||
SourceType = SourceType,
|
||||
SourceText = SourceText,
|
||||
OriginKey = OriginKey,
|
||||
IsOverridden = IsOverridden
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
@using System
|
||||
@using System.Collections.Generic
|
||||
@using RolemasterDb.App.Features
|
||||
|
||||
<div class="critical-editor-compare-card">
|
||||
<div class="critical-editor-compare-card-header">
|
||||
<div>
|
||||
<strong>@Title</strong>
|
||||
@if (!string.IsNullOrWhiteSpace(Caption))
|
||||
{
|
||||
<p class="muted critical-editor-inline-copy">@Caption</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ((Effects?.Count ?? 0) == 0 && (Branches?.Count ?? 0) == 0 && string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
<p class="muted">No visible result is available for this card.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<CompactCriticalCell
|
||||
Description="@Description"
|
||||
Effects="Effects"
|
||||
Branches="Branches" />
|
||||
}
|
||||
|
||||
@if ((Notes?.Count ?? 0) > 0)
|
||||
{
|
||||
<div class="critical-editor-validation-list">
|
||||
@foreach (var note in Notes!)
|
||||
{
|
||||
<p class="critical-editor-validation-item">@note</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string? Caption { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<CriticalEffectLookupResponse>? Effects { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<CriticalBranchLookupResponse>? Branches { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<string>? Notes { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user