From 60aa0d330a15dccd8308467cda75b70cb6268351 Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Sat, 21 Mar 2026 10:47:49 +0100 Subject: [PATCH] Split quick parse layout from input control --- .../Shared/CriticalCellCurationDialog.razor | 33 +-- .../Shared/CriticalCellEditorDialog.razor | 143 +++++++++++- .../Shared/CriticalCellQuickParseEditor.razor | 207 +----------------- src/RolemasterDb.App/wwwroot/app.css | 24 +- 4 files changed, 180 insertions(+), 227 deletions(-) diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor index ed3bce9..d626710 100644 --- a/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellCurationDialog.razor @@ -41,9 +41,9 @@ else if (Model is null) {
- @if (!string.IsNullOrWhiteSpace(ErrorMessage)) + @if (!string.IsNullOrWhiteSpace(GetVisibleErrorMessage())) { -

@ErrorMessage

+

@GetVisibleErrorMessage()

} else { @@ -60,22 +60,15 @@ }
-
+
@if (IsQuickParseMode) { + IsDisabled="@(IsSaving || IsReparsing)" + TextAreaCssClass="input-shell critical-editor-textarea critical-curation-quick-parse-textarea" + OnReparse="OnReparse" /> } else { @@ -197,6 +190,7 @@ private IJSObjectReference? jsModule; private bool isBackdropPointerDown; + private CriticalCellQuickParseEditor? quickParseEditor; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -292,6 +286,17 @@ private async Task HandleReparseClickAsync(MouseEventArgs _) { + if (quickParseEditor is not null) + { + await quickParseEditor.ReparseAsync(); + return; + } + await OnReparse.InvokeAsync(); } + + private string? GetVisibleErrorMessage() => + IsQuickParseMode && !string.IsNullOrWhiteSpace(QuickParseErrorMessage) + ? QuickParseErrorMessage + : ErrorMessage; } diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor index 4b1c80c..bcd132c 100644 --- a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor @@ -57,19 +57,90 @@ {
+ @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)) + { + @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.

+
+ @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.

+ } +
+ + +
+
@@ -287,8 +358,20 @@ [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; + private CriticalCellQuickParseEditor? quickParseEditor; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -362,6 +445,17 @@ await OnSave.InvokeAsync(); } + private async Task HandleQuickParseReparseClickAsync(MouseEventArgs _) + { + if (quickParseEditor is not null) + { + await quickParseEditor.ReparseAsync(); + return; + } + + await OnReparse.InvokeAsync(); + } + private void AddBaseEffect() { if (Model is null) @@ -516,6 +610,39 @@ return segments.Count == 0 ? "Generated compare" : string.Join(" ยท ", segments); } + private void MarkDescriptionOverridden() + { + if (Model is not null) + { + Model.IsDescriptionOverridden = true; + } + } + + 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) diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellQuickParseEditor.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellQuickParseEditor.razor index 7ecc267..e3c909f 100644 --- a/src/RolemasterDb.App/Components/Shared/CriticalCellQuickParseEditor.razor +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellQuickParseEditor.razor @@ -1,221 +1,28 @@ @using Microsoft.AspNetCore.Components.Web -@using RolemasterDb.App.Domain -@using RolemasterDb.App.Features -
- @if (!string.IsNullOrWhiteSpace(ErrorMessage)) - { -

@ErrorMessage

- } - - @if (ShowSectionHeader) - { -
-
-

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.

- } -
-
- } -
- - @if (ShowExampleHint) - { -

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 ShowSectionHeader { get; set; } = true; - - [Parameter] - public bool ShowSourcePreview { get; set; } = true; - - [Parameter] - public bool ShowExampleHint { 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; } + public string TextAreaCssClass { get; set; } = "input-shell critical-editor-textarea tall"; [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(); - } + public Task ReparseAsync() => 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 bfc36fb..c799ea3 100644 --- a/src/RolemasterDb.App/wwwroot/app.css +++ b/src/RolemasterDb.App/wwwroot/app.css @@ -1419,6 +1419,12 @@ textarea { padding: 0.75rem; } +.critical-curation-preview-card.is-quick-parse { + padding: 0; + display: flex; + overflow: hidden; +} + .critical-curation-preview-button { width: 100%; min-height: 100%; @@ -1444,12 +1450,20 @@ textarea { cursor: default; } -.critical-curation-quick-parse-section { +.critical-curation-quick-parse-textarea { + display: block; + flex: 1 1 auto; + width: 100%; + height: 100%; + min-height: 100%; margin: 0; -} - -.critical-curation-quick-parse-section .critical-editor-quick-parse-grid { - grid-template-columns: minmax(0, 1.45fr) minmax(260px, 1fr); + padding: 0.75rem; + border: none; + border-radius: inherit; + background: transparent; + box-sizing: border-box; + box-shadow: none; + resize: none; } .critical-curation-source-card {