diff --git a/docs/player_gm_ux_redesign_plan.md b/docs/player_gm_ux_redesign_plan.md index 7ee27e4..d906041 100644 --- a/docs/player_gm_ux_redesign_plan.md +++ b/docs/player_gm_ux_redesign_plan.md @@ -533,6 +533,10 @@ Acceptance criteria: ### Phase 3: Redesign the cell editor +Status: + +- implemented in the web app on March 15, 2026 + Scope: - add live result preview diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor index 109069a..3ef634b 100644 --- a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor +++ b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor @@ -1,22 +1,29 @@ +@using System +@using System.Collections.Generic +@using System.Linq +@using RolemasterDb.App.Domain +@using RolemasterDb.App.Features +
- Manual Curation @if (Model is not null) { -

Edit @Model.TableName

+

Edit Result Card

- Roll band @Model.RollBand, column @Model.ColumnLabel + @Model.TableName + · Roll band @Model.RollBand + · Severity @Model.ColumnLabel @if (!string.IsNullOrWhiteSpace(Model.GroupLabel)) { - , group @Model.GroupLabel + · Variant @Model.GroupLabel }

} else { -

Critical Cell Editor

+

Edit Result Card

}
@@ -44,49 +51,80 @@ }
-

Base Cell

-
- - -
-
- - -
-
- - -
-
-
- - +
+
+

Result Preview

+

This is the card the table browser will show.

-
- - +
+
+
+ Severity: @Model.ColumnLabel + Roll band: @Model.RollBand + @if (!string.IsNullOrWhiteSpace(Model.GroupLabel)) + { + Variant: @Model.GroupLabel + }
+
-

Base Effects

+
+

Raw Text

+

Update the source text, then adjust the visible card fields below.

+
+
+
+ + +
+
+ + +
+
+ +
+
+
+

Base Effects

+

These chips appear on the main result.

+
@if (Model.Effects.Count == 0) { -

No normalized base effects for this cell.

+

No base effects on this result yet.

} else { +
+ @for (var index = 0; index < Model.Effects.Count; index++) + { + var effect = Model.Effects[index]; + var effectIndex = index; +
+
+ + @GetEffectLabel(effect) +
+ +
+ } +
+ @for (var index = 0; index < Model.Effects.Count; index++) { var effect = Model.Effects[index]; -
+
- Effect @(index + 1) - + @GetEffectLabel(effect)
@EffectFields(effect)
@@ -96,12 +134,15 @@
-

Branches

- +
+

Conditions

+

Use condition cards for alternate outcomes.

+
+
@if (Model.Branches.Count == 0) { -

No branch records on this cell.

+

No alternate condition cards on this result yet.

} else { @@ -110,68 +151,56 @@ var branch = Model.Branches[index];
- Branch @(index + 1) +
+ @GetBranchTitle(branch, index) +

Shown when this condition applies.

+
-
-
- - -
-
- - -
-
- - -
-
- +
- - -
-
- - -
-
- - -
-
-
- - -
-
- - -
+ +
-
Branch Effects
+
+
Condition Effects
+

These chips only appear when the condition is met.

+
@if (branch.Effects.Count == 0) { -

No normalized branch effects.

+

No effects on this condition card yet.

} else { +
+ @for (var effectIndex = 0; effectIndex < branch.Effects.Count; effectIndex++) + { + var effect = branch.Effects[effectIndex]; + var localEffectIndex = effectIndex; +
+
+ + @GetEffectLabel(effect) +
+ +
+ } +
+ @for (var effectIndex = 0; effectIndex < branch.Effects.Count; effectIndex++) { var effect = branch.Effects[effectIndex];
- Branch Effect @(effectIndex + 1) - + @GetEffectLabel(effect)
@EffectFields(effect)
@@ -229,7 +258,7 @@ private void AddBaseEffect() { - Model?.Effects.Add(new CriticalEffectEditorModel()); + Model?.Effects.Add(CreateDefaultEffectModel()); } private void RemoveBaseEffect(int index) @@ -251,6 +280,7 @@ Model.Branches.Add(new CriticalBranchEditorModel { + ConditionText = $"Condition {Model.Branches.Count + 1}", SortOrder = Model.Branches.Count + 1 }); } @@ -267,7 +297,7 @@ private static void AddBranchEffect(CriticalBranchEditorModel branch) { - branch.Effects.Add(new CriticalEffectEditorModel()); + branch.Effects.Add(CreateDefaultEffectModel()); } private static void RemoveBranchEffect(CriticalBranchEditorModel branch, int index) @@ -279,55 +309,167 @@ branch.Effects.RemoveAt(index); } + + private static CriticalEffectEditorModel CreateDefaultEffectModel() => + new() + { + EffectCode = CriticalEffectCodes.DirectHits, + SourceType = "symbol" + }; + + private static string GetBranchTitle(CriticalBranchEditorModel branch, int index) => + string.IsNullOrWhiteSpace(branch.ConditionText) + ? $"Condition {index + 1}" + : branch.ConditionText; + + private static string GetEffectLabel(CriticalEffectEditorModel effect) + { + if (AffixDisplayMap.TryGet(effect.EffectCode, out var info)) + { + return info.Label; + } + + return string.IsNullOrWhiteSpace(effect.EffectCode) ? "Custom Effect" : effect.EffectCode; + } + + private static IEnumerable> GetEffectOptions(string? currentCode) + { + if (!string.IsNullOrWhiteSpace(currentCode) && !AffixDisplayMap.Entries.ContainsKey(currentCode)) + { + yield return new KeyValuePair(currentCode, currentCode); + } + + foreach (var entry in AffixDisplayMap.Entries.OrderBy(item => item.Value.Label)) + { + yield return new KeyValuePair(entry.Key, entry.Value.Label); + } + } + + private static void HandleEffectCodeChanged(CriticalEffectEditorModel effect, string? newCode) + { + var normalizedCode = newCode?.Trim() ?? string.Empty; + if (string.Equals(effect.EffectCode, normalizedCode, StringComparison.Ordinal)) + { + return; + } + + effect.EffectCode = normalizedCode; + effect.Target = null; + effect.ValueInteger = null; + effect.ValueDecimal = null; + effect.ValueExpression = null; + effect.DurationRounds = null; + effect.PerRound = null; + effect.Modifier = null; + effect.BodyPart = null; + effect.IsPermanent = false; + effect.SourceText = null; + effect.SourceType = AffixDisplayMap.TryGet(effect.EffectCode, out _) ? "symbol" : "manual"; + } + + private static IReadOnlyList BuildPreviewEffects(IEnumerable effects) => + effects.Select(CreatePreviewEffect).ToList(); + + private static IReadOnlyList BuildSinglePreviewEffect(CriticalEffectEditorModel effect) => + [CreatePreviewEffect(effect)]; + + private static IReadOnlyList BuildPreviewBranches(IEnumerable branches) => + branches + .OrderBy(branch => branch.SortOrder) + .Select(branch => new CriticalBranchLookupResponse( + branch.BranchKind, + branch.ConditionKey, + branch.ConditionText, + branch.DescriptionText, + branch.RawAffixText, + BuildPreviewEffects(branch.Effects), + branch.RawText, + branch.SortOrder)) + .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); } @functions { - private RenderFragment EffectFields(CriticalEffectEditorModel effect) => @
-
+ private RenderFragment EffectFields(CriticalEffectEditorModel effect) => @
+
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - + +
+ @switch (effect.EffectCode) + { + case CriticalEffectCodes.DirectHits: +
+ + +
+ break; + case CriticalEffectCodes.StunnedRounds: + case CriticalEffectCodes.MustParryRounds: + case CriticalEffectCodes.NoParryRounds: +
+ + +
+ break; + case CriticalEffectCodes.BleedPerRound: +
+ + +
+ break; + case CriticalEffectCodes.FoePenalty: + case CriticalEffectCodes.AttackerBonusNextRound: +
+ + +
+ break; + case CriticalEffectCodes.PowerPointModifier: +
+ + +
+ break; + default: +
+ + +
+ break; + } + @if (!string.IsNullOrWhiteSpace(effect.BodyPart)) + { +
+ + +
+ } + @if (!string.IsNullOrWhiteSpace(effect.Target)) + { +
+ + +
+ }