diff --git a/docs/critical_tables_db_model.md b/docs/critical_tables_db_model.md index a7eee57..f1c3a2c 100644 --- a/docs/critical_tables_db_model.md +++ b/docs/critical_tables_db_model.md @@ -290,9 +290,13 @@ Because the import path depends on OCR, PDF XML extraction, and heuristics, the Current curation flow: 1. Browse a table on the `/tables` page. -2. Hover a populated cell to identify editable entries. -3. Open the popup editor for that cell. -4. Edit the entire `critical_result` graph: +2. Hover a populated cell to identify editable or curation-ready entries. +3. Open the curation popup for a cell that still needs review. +4. Either: + - mark the preview as curated, + - click the compact result card to make a quick parse adjustment inline, or + - jump into the full popup editor. +5. In the full editor, edit the entire `critical_result` graph: - base raw cell text - curated prose / description - raw affix text @@ -301,7 +305,7 @@ Current curation flow: - parsed JSON - nested `critical_branch` rows - nested `critical_effect` rows for both the base result and branches -5. Save the result back through the API. +6. Save the result back through the API. The corresponding API endpoints are: diff --git a/src/RolemasterDb.App/Components/Pages/Tables.razor b/src/RolemasterDb.App/Components/Pages/Tables.razor index d4fd2f8..4de9b3e 100644 --- a/src/RolemasterDb.App/Components/Pages/Tables.razor +++ b/src/RolemasterDb.App/Components/Pages/Tables.razor @@ -280,11 +280,17 @@ Model="curationModel" IsLoading="isCurationLoading" IsSaving="isCurationSaving" + IsReparsing="isCurationReparsing" + IsQuickParseMode="isCurationQuickParseMode" ErrorMessage="@curationError" + QuickParseErrorMessage="@curationQuickParseError" LegendEntries="@(tableDetail?.Legend ?? Array.Empty())" OnClose="CloseCellCurationAsync" OnMarkCurated="MarkCellCuratedAsync" - OnEdit="OpenEditorFromCurationAsync" /> + OnEdit="OpenEditorFromCurationAsync" + OnEnterQuickParse="EnterCurationQuickParseMode" + OnCancelQuickParse="CancelCurationQuickParseMode" + OnReparse="ReparseCurationCellAsync" /> } @if (isEditorOpen) @@ -328,7 +334,10 @@ private bool isCurationOpen; private bool isCurationLoading; private bool isCurationSaving; + private bool isCurationReparsing; + private bool isCurationQuickParseMode; private string? curationError; + private string? curationQuickParseError; private int? curatingResultId; private CriticalCellEditorModel? curationModel; private bool isTableMenuOpen; @@ -487,6 +496,8 @@ } isCurationSaving = false; + isCurationReparsing = false; + isCurationQuickParseMode = false; isCurationOpen = true; await LoadCurationCellAsync(resultId, "The selected cell could not be loaded for curation."); } @@ -496,7 +507,10 @@ isCurationOpen = false; isCurationLoading = false; isCurationSaving = false; + isCurationReparsing = false; + isCurationQuickParseMode = false; curationError = null; + curationQuickParseError = null; curatingResultId = null; curationModel = null; await InvokeAsync(StateHasChanged); @@ -555,12 +569,39 @@ await OpenCellEditorAsync(resultId); } + private Task EnterCurationQuickParseMode() + { + if (curationModel is null) + { + return Task.CompletedTask; + } + + curationQuickParseError = null; + isCurationQuickParseMode = true; + return Task.CompletedTask; + } + + private Task CancelCurationQuickParseMode() + { + if (isCurationReparsing) + { + return Task.CompletedTask; + } + + curationQuickParseError = null; + isCurationQuickParseMode = false; + return Task.CompletedTask; + } + private async Task LoadCurationCellAsync(int resultId, string loadFailureMessage) { curationError = null; + curationQuickParseError = null; curationModel = null; curatingResultId = resultId; isCurationLoading = true; + isCurationQuickParseMode = false; + isCurationReparsing = false; try { @@ -585,6 +626,39 @@ } } + private async Task ReparseCurationCellAsync() + { + if (curationModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || curatingResultId is null) + { + return; + } + + isCurationReparsing = true; + curationQuickParseError = null; + + try + { + var response = await ReparseCriticalCellAsync(curationModel, curatingResultId.Value); + if (response is null) + { + curationQuickParseError = "The selected cell could not be re-parsed."; + return; + } + + curationModel = CriticalCellEditorModel.FromResponse(response); + isCurationQuickParseMode = false; + await InvokeAsync(StateHasChanged); + } + catch (Exception exception) + { + curationQuickParseError = exception.Message; + } + finally + { + isCurationReparsing = false; + } + } + private int? FindNextUncuratedResultId(int currentResultId) { if (tableDetail?.Cells is null || tableDetail.Cells.Count == 0) @@ -674,7 +748,7 @@ try { var comparisonBaseline = editorModel.Clone(); - var response = await LookupService.ReparseCriticalCellAsync(selectedTableSlug, editingResultId.Value, editorModel.ToRequest()); + var response = await ReparseCriticalCellAsync(editorModel, editingResultId.Value); if (response is null) { editorReparseError = "The selected cell could not be re-parsed."; @@ -695,6 +769,9 @@ } } + private Task ReparseCriticalCellAsync(CriticalCellEditorModel model, int resultId) => + LookupService.ReparseCriticalCellAsync(selectedTableSlug, resultId, model.ToRequest()); + private async Task SaveCellEditorAsync() { if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null) diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor index fc39284..84d6d6f 100644 --- a/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor @@ -1,5 +1,6 @@ @using System.Collections.Generic @using System.Linq +@using Microsoft.AspNetCore.Components.Web @using Microsoft.JSInterop @using RolemasterDb.App.Features @implements IAsyncDisposable @@ -37,63 +38,108 @@

Loading curation preview...

} - else if (!string.IsNullOrWhiteSpace(ErrorMessage)) + else if (Model is null) {
-

@ErrorMessage

+ @if (!string.IsNullOrWhiteSpace(ErrorMessage)) + { +

@ErrorMessage

+ } + else + { +

No curation preview is available.

+ }
} - else if (Model is not null) + else { -
-
-
- -
- -
- @if (!string.IsNullOrWhiteSpace(Model.SourceImageUrl)) - { - @BuildSourceImageAltText(Model) - } - else - { -
-

No source image is available for this cell yet.

-
- } -
-
- - @{ - var usedLegendEntries = GetUsedLegendEntries(Model, LegendEntries); +
+ @if (!string.IsNullOrWhiteSpace(ErrorMessage)) + { +

@ErrorMessage

} - @if (usedLegendEntries.Count > 0) + @if (IsQuickParseMode) { -
- @foreach (var entry in usedLegendEntries) - { -
- @entry.Symbol - @entry.Label -
- } + + } + else + { +
+
+ +
+ +
+ @if (!string.IsNullOrWhiteSpace(Model.SourceImageUrl)) + { + @CriticalCellPresentation.BuildSourceImageAltText(Model) + } + else + { +
+

No source image is available for this cell yet.

+
+ } +
+ + @if (GetUsedLegendEntries(Model, LegendEntries) is { Count: > 0 } usedLegendEntries) + { +
+ @foreach (var entry in usedLegendEntries) + { +
+ @entry.Symbol + @entry.Label +
+ } +
+ } }
- - + @if (IsQuickParseMode) + { + + + } + else + { + + + }
}
@@ -109,9 +155,18 @@ [Parameter] public bool IsSaving { get; set; } + [Parameter] + public bool IsReparsing { get; set; } + + [Parameter] + public bool IsQuickParseMode { get; set; } + [Parameter] public string? ErrorMessage { get; set; } + [Parameter] + public string? QuickParseErrorMessage { get; set; } + [Parameter] public IReadOnlyList? LegendEntries { get; set; } @@ -124,6 +179,15 @@ [Parameter, EditorRequired] public EventCallback OnEdit { get; set; } + [Parameter, EditorRequired] + public EventCallback OnEnterQuickParse { get; set; } + + [Parameter, EditorRequired] + public EventCallback OnCancelQuickParse { get; set; } + + [Parameter, EditorRequired] + public EventCallback OnReparse { get; set; } + private IJSObjectReference? jsModule; private bool isBackdropPointerDown; @@ -194,71 +258,11 @@ isBackdropPointerDown = false; } - private static string BuildSourceImageAltText(CriticalCellEditorModel model) - { - var segments = new List - { - 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 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 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 GetUsedLegendEntries( CriticalCellEditorModel model, IReadOnlyList? legendEntries) @@ -278,4 +282,9 @@ .Where(entry => usedEffectCodes.Contains(entry.EffectCode)) .ToList(); } + + private async Task HandleReparseClickAsync(MouseEventArgs _) + { + await OnReparse.InvokeAsync(); + } } diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor index ce93bbf..4b1c80c 100644 --- a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor @@ -57,89 +57,19 @@ {
- @if (!string.IsNullOrWhiteSpace(ReparseErrorMessage)) - { -

@ReparseErrorMessage

- } - @if (!string.IsNullOrWhiteSpace(SaveErrorMessage)) {

@SaveErrorMessage

} -
-
-
-

Quick Parse Input

-

First line is the result prose. Later lines are base affixes or condition: ... lines with comma-separated shorthand.

-
- -
-
-
- - -
- -
-
- - @if (Model.SourcePageNumber is not null) - { - Page @Model.SourcePageNumber - } -
- -
- @if (!string.IsNullOrWhiteSpace(Model.SourceImageUrl)) - { - @BuildSourceImageAltText(Model) - } - else - { -

No source image is available for this cell yet.

- } -
-
-
-

Example: Foe brings his guard up, frightened by your display. then +5, 1mp or w/o shield: glancing blow, +15, 3s, 3np.

-
- @foreach (var entry in QuickParseLegendEntries) - { -
- @entry.Token - = - -
- } -
- @if (Model.ValidationMessages.Count > 0) - { -

@GetParserNoteSummary(Model.ValidationMessages.Count)

- } - @if (HasComparisonDifferences(Model, ComparisonBaseline)) - { -

Fresh generation differs from the current edited card. Review Generated Compare before saving if you want to keep the overrides.

- } -
- - -
-
+
@@ -267,8 +197,8 @@ Title="Current Edited Card" Caption="@GetCurrentComparisonCaption(ComparisonBaseline)" Description="@GetComparisonSourceModel(Model, ComparisonBaseline).DescriptionText" - Effects="@BuildPreviewEffects(GetComparisonSourceModel(Model, ComparisonBaseline))" - Branches="@BuildPreviewBranches(GetComparisonSourceModel(Model, ComparisonBaseline))" /> + Effects="@CriticalCellPresentation.BuildPreviewEffects(GetComparisonSourceModel(Model, ComparisonBaseline))" + Branches="@CriticalCellPresentation.BuildPreviewBranches(GetComparisonSourceModel(Model, ComparisonBaseline))" /> + Effects="@CriticalCellPresentation.BuildPreviewEffects(Model)" + Branches="@CriticalCellPresentation.BuildPreviewBranches(Model)" /> }
@@ -357,17 +287,6 @@ [Parameter, EditorRequired] public EventCallback OnSave { get; set; } - private static readonly IReadOnlyList<(string Token, IReadOnlyList Effects)> QuickParseLegendEntries = - [ - ("+15", [CreateQuickLegendEffect(CriticalEffectCodes.DirectHits, valueInteger: 15)]), - ("3s", [CreateQuickLegendEffect(CriticalEffectCodes.StunnedRounds, durationRounds: 3)]), - ("1mp", [CreateQuickLegendEffect(CriticalEffectCodes.MustParryRounds, durationRounds: 1)]), - ("3np", [CreateQuickLegendEffect(CriticalEffectCodes.NoParryRounds, durationRounds: 3)]), - ("1hpr", [CreateQuickLegendEffect(CriticalEffectCodes.BleedPerRound, perRound: 1)]), - ("-20", [CreateQuickLegendEffect(CriticalEffectCodes.FoePenalty, modifier: -20)]), - ("+20b", [CreateQuickLegendEffect(CriticalEffectCodes.AttackerBonusNextRound, modifier: 20)]), - ("+2d10-3pp", [CreateQuickLegendEffect(CriticalEffectCodes.PowerPointModifier, valueExpression: "2d10-3")]) - ]; private IJSObjectReference? jsModule; private bool isBackdropPointerDown; @@ -443,21 +362,6 @@ await OnSave.InvokeAsync(); } - private async Task HandleReparseClickAsync(MouseEventArgs _) - { - await OnReparse.InvokeAsync(); - } - - private void HandleQuickParseInputChanged(ChangeEventArgs args) - { - if (Model is null) - { - return; - } - - Model.QuickParseInput = args.Value?.ToString() ?? string.Empty; - } - private void AddBaseEffect() { if (Model is null) @@ -583,14 +487,6 @@ effect.IsOverridden = true; } - private void MarkDescriptionOverridden() - { - if (Model is not null) - { - Model.IsDescriptionOverridden = true; - } - } - private static void MarkBranchOverridden(CriticalBranchEditorModel branch) { branch.IsOverridden = true; @@ -620,31 +516,6 @@ return segments.Count == 0 ? "Generated compare" : string.Join(" ยท ", segments); } - private static string GetParserNoteSummary(int noteCount) => - noteCount == 1 - ? "1 parser note is available under Advanced Review." - : $"{noteCount} parser notes are available under Advanced Review."; - - private static CriticalEffectLookupResponse CreateQuickLegendEffect( - string effectCode, - int? valueInteger = null, - string? valueExpression = null, - int? durationRounds = null, - int? perRound = null, - int? modifier = null) => - new( - effectCode, - "foe", - valueInteger, - valueExpression, - durationRounds, - perRound, - modifier, - null, - false, - "legend", - null); - private static IReadOnlyList GetComparisonSummaryItems(CriticalCellEditorModel model, CriticalCellEditorModel? comparisonBaseline) { if (model.GeneratedState is null) @@ -653,8 +524,8 @@ } var comparisonSourceModel = GetComparisonSourceModel(model, comparisonBaseline); - var comparisonSourceEffects = BuildPreviewEffects(comparisonSourceModel); - var comparisonSourceBranches = BuildPreviewBranches(comparisonSourceModel); + var comparisonSourceEffects = CriticalCellPresentation.BuildPreviewEffects(comparisonSourceModel); + var comparisonSourceBranches = CriticalCellPresentation.BuildPreviewBranches(comparisonSourceModel); var items = new List(); if (CriticalCellComparisonEvaluator.DescriptionDiffers(comparisonSourceModel.DescriptionText, model.GeneratedState.DescriptionText)) @@ -700,8 +571,8 @@ var comparisonSourceModel = GetComparisonSourceModel(model, comparisonBaseline); return CriticalCellComparisonEvaluator.GetDifferenceCount( comparisonSourceModel.DescriptionText, - BuildPreviewEffects(comparisonSourceModel), - BuildPreviewBranches(comparisonSourceModel), + CriticalCellPresentation.BuildPreviewEffects(comparisonSourceModel), + CriticalCellPresentation.BuildPreviewBranches(comparisonSourceModel), model.GeneratedState); } @@ -718,34 +589,6 @@ ? "This is the result that will be saved." : "This is the edited card before the last re-parse."; - private static string BuildSourceImageAltText(CriticalCellEditorModel model) - { - var segments = new List - { - 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 IReadOnlyList BuildPreviewEffects(CriticalCellEditorModel model) => - model.Effects - .Select(CreatePreviewEffect) - .ToList(); - - private static IReadOnlyList BuildPreviewBranches(CriticalCellEditorModel model) => - model.Branches - .OrderBy(branch => branch.SortOrder) - .Select(CreatePreviewBranch) - .ToList(); - private static IReadOnlyList BuildSingleBadgeEffect(CriticalEffectEditorModel effect) => [CreateBadgeEffect(effect)]; @@ -763,21 +606,6 @@ 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 { diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellPresentation.cs b/src/RolemasterDb.App/Components/Shared/CriticalCellPresentation.cs new file mode 100644 index 0000000..8e4e5d1 --- /dev/null +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellPresentation.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using RolemasterDb.App.Features; + +namespace RolemasterDb.App.Components.Shared; + +public static class CriticalCellPresentation +{ + public static string BuildSourceImageAltText(CriticalCellEditorModel model) + { + var segments = new List + { + model.TableName, + $"roll band {model.RollBand}", + $"column {model.ColumnLabel}" + }; + + if (!string.IsNullOrWhiteSpace(model.GroupLabel)) + { + segments.Add($"variant {model.GroupLabel}"); + } + + return string.Join(", ", segments); + } + + public static IReadOnlyList BuildPreviewEffects(CriticalCellEditorModel model) => + model.Effects + .Select(CreatePreviewEffect) + .ToList(); + + public static IReadOnlyList BuildPreviewBranches(CriticalCellEditorModel model) => + model.Branches + .OrderBy(branch => branch.SortOrder) + .Select(CreatePreviewBranch) + .ToList(); + + private static CriticalEffectLookupResponse CreatePreviewEffect(CriticalEffectEditorModel effect) => + new( + effect.EffectCode, + effect.Target, + effect.ValueInteger, + effect.ValueExpression, + effect.DurationRounds, + effect.PerRound, + effect.Modifier, + effect.BodyPart, + effect.IsPermanent, + effect.SourceType, + effect.SourceText); + + 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); +} diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellQuickParseEditor.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellQuickParseEditor.razor new file mode 100644 index 0000000..2c1dc14 --- /dev/null +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellQuickParseEditor.razor @@ -0,0 +1,209 @@ +@using Microsoft.AspNetCore.Components.Web +@using RolemasterDb.App.Domain +@using RolemasterDb.App.Features + +
+ @if (!string.IsNullOrWhiteSpace(ErrorMessage)) + { +

@ErrorMessage

+ } + +
+
+

Quick Parse Input

+

First line is the result prose. Later lines are base affixes or condition: ... lines with comma-separated shorthand.

+
+ @if (ShowActionButton) + { + + } +
+ +
+
+ + +
+ + @if (ShowSourcePreview) + { +
+
+ + @if (Model.SourcePageNumber is not null) + { + Page @Model.SourcePageNumber + } +
+ +
+ @if (!string.IsNullOrWhiteSpace(Model.SourceImageUrl)) + { + @CriticalCellPresentation.BuildSourceImageAltText(Model) + } + else + { +

No source image is available for this cell yet.

+ } +
+
+ } +
+ +

Example: Foe brings his guard up, frightened by your display. then +5, 1mp or w/o shield: glancing blow, +15, 3s, 3np.

+ + @if (ShowLegend) + { +
+ @foreach (var entry in QuickParseLegendEntries) + { +
+ @entry.Token + = + +
+ } +
+ } + + @if (ShowValidationSummary && Model.ValidationMessages.Count > 0) + { +

@GetParserNoteSummary(Model.ValidationMessages.Count)

+ } + + @if (!string.IsNullOrWhiteSpace(ComparisonHintText)) + { +

@ComparisonHintText

+ } + + @if (ShowDescriptionEditor) + { +
+ + +
+ } +
+ +@code { + [Parameter, EditorRequired] + public CriticalCellEditorModel Model { get; set; } = default!; + + [Parameter] + public bool IsBusy { get; set; } + + [Parameter] + public bool IsDisabled { get; set; } + + [Parameter] + public bool ShowActionButton { get; set; } = true; + + [Parameter] + public bool ShowSourcePreview { get; set; } = true; + + [Parameter] + public bool ShowLegend { get; set; } = true; + + [Parameter] + public bool ShowValidationSummary { get; set; } = true; + + [Parameter] + public bool ShowDescriptionEditor { get; set; } + + [Parameter] + public string SectionClass { get; set; } = "critical-editor-section"; + + [Parameter] + public string TextAreaSizeCssClass { get; set; } = "tall"; + + [Parameter] + public string? GridCssClass { get; set; } + + [Parameter] + public string ActionButtonText { get; set; } = "Generate From Quick Input"; + + [Parameter] + public string BusyActionButtonText { get; set; } = "Generating..."; + + [Parameter] + public string? ComparisonHintText { get; set; } + + [Parameter] + public string? ErrorMessage { get; set; } + + [Parameter] + public EventCallback OnReparse { get; set; } + + private static readonly IReadOnlyList<(string Token, IReadOnlyList Effects)> QuickParseLegendEntries = + [ + ("+15", [CreateQuickLegendEffect(CriticalEffectCodes.DirectHits, valueInteger: 15)]), + ("3s", [CreateQuickLegendEffect(CriticalEffectCodes.StunnedRounds, durationRounds: 3)]), + ("1mp", [CreateQuickLegendEffect(CriticalEffectCodes.MustParryRounds, durationRounds: 1)]), + ("3np", [CreateQuickLegendEffect(CriticalEffectCodes.NoParryRounds, durationRounds: 3)]), + ("1hpr", [CreateQuickLegendEffect(CriticalEffectCodes.BleedPerRound, perRound: 1)]), + ("-20", [CreateQuickLegendEffect(CriticalEffectCodes.FoePenalty, modifier: -20)]), + ("+20b", [CreateQuickLegendEffect(CriticalEffectCodes.AttackerBonusNextRound, modifier: 20)]), + ("+2d10-3pp", [CreateQuickLegendEffect(CriticalEffectCodes.PowerPointModifier, valueExpression: "2d10-3")]) + ]; + + private async Task HandleReparseClickAsync(MouseEventArgs _) + { + await OnReparse.InvokeAsync(); + } + + private void HandleQuickParseInputChanged(ChangeEventArgs args) + { + Model.QuickParseInput = args.Value?.ToString() ?? string.Empty; + } + + private void MarkDescriptionOverridden() + { + Model.IsDescriptionOverridden = true; + } + + private string GetQuickParseGridCssClass() => + string.IsNullOrWhiteSpace(GridCssClass) + ? "critical-editor-quick-parse-grid" + : $"critical-editor-quick-parse-grid {GridCssClass}"; + + private string GetTextAreaCssClass() => $"input-shell critical-editor-textarea {TextAreaSizeCssClass}"; + + private static string GetParserNoteSummary(int noteCount) => + noteCount == 1 + ? "1 parser note is available under Advanced Review." + : $"{noteCount} parser notes are available under Advanced Review."; + + private static CriticalEffectLookupResponse CreateQuickLegendEffect( + string effectCode, + int? valueInteger = null, + string? valueExpression = null, + int? durationRounds = null, + int? perRound = null, + int? modifier = null) => + new( + effectCode, + "foe", + valueInteger, + valueExpression, + durationRounds, + perRound, + modifier, + null, + false, + "legend", + null); +} diff --git a/src/RolemasterDb.App/wwwroot/app.css b/src/RolemasterDb.App/wwwroot/app.css index 2b96622..bfc36fb 100644 --- a/src/RolemasterDb.App/wwwroot/app.css +++ b/src/RolemasterDb.App/wwwroot/app.css @@ -1376,10 +1376,14 @@ textarea { .critical-curation-body { flex: 0 1 auto; align-items: stretch; - overflow: hidden; + overflow: auto; padding: 0.85rem 1rem 1rem; } +.critical-curation-body.is-quick-parse { + overflow: auto; +} + .critical-curation-grid { display: grid; gap: 0.85rem; @@ -1415,6 +1419,39 @@ textarea { padding: 0.75rem; } +.critical-curation-preview-button { + width: 100%; + min-height: 100%; + display: block; + padding: 0; + border: none; + background: transparent; + text-align: left; + border-radius: 12px; + cursor: pointer; + transition: transform 140ms ease, box-shadow 140ms ease, background-color 140ms ease; +} + +.critical-curation-preview-button:hover:not(:disabled), +.critical-curation-preview-button:focus-visible { + background: rgba(247, 239, 225, 0.65); + box-shadow: 0 0 0 1px rgba(127, 96, 55, 0.16); + transform: translateY(-1px); + outline: none; +} + +.critical-curation-preview-button:disabled { + cursor: default; +} + +.critical-curation-quick-parse-section { + margin: 0; +} + +.critical-curation-quick-parse-section .critical-editor-quick-parse-grid { + grid-template-columns: minmax(0, 1.45fr) minmax(260px, 1fr); +} + .critical-curation-source-card { display: flex; align-items: center;