Add manual critical table cell editor

This commit is contained in:
2026-03-14 15:09:16 +01:00
parent 4e518244a2
commit 6e28ad975f
16 changed files with 1105 additions and 27 deletions

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace RolemasterDb.App.Features;
public sealed record CriticalBranchEditorItem(
string BranchKind,
string? ConditionKey,
string ConditionText,
string ConditionJson,
string RawText,
string DescriptionText,
string? RawAffixText,
string ParsedJson,
int SortOrder,
IReadOnlyList<CriticalEffectEditorItem> Effects);

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace RolemasterDb.App.Features;
public sealed record CriticalCellEditorResponse(
int ResultId,
string TableSlug,
string TableName,
string SourceDocument,
string RollBand,
string? GroupKey,
string? GroupLabel,
string ColumnKey,
string ColumnLabel,
string ColumnRole,
string RawCellText,
string DescriptionText,
string? RawAffixText,
string ParseStatus,
string ParsedJson,
IReadOnlyList<CriticalEffectEditorItem> Effects,
IReadOnlyList<CriticalBranchEditorItem> Branches);

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace RolemasterDb.App.Features;
public sealed record CriticalCellUpdateRequest(
string RawCellText,
string DescriptionText,
string? RawAffixText,
string ParseStatus,
string ParsedJson,
IReadOnlyList<CriticalEffectEditorItem> Effects,
IReadOnlyList<CriticalBranchEditorItem> Branches);

View File

@@ -0,0 +1,15 @@
namespace RolemasterDb.App.Features;
public sealed record CriticalEffectEditorItem(
string EffectCode,
string? Target,
int? ValueInteger,
decimal? ValueDecimal,
string? ValueExpression,
int? DurationRounds,
int? PerRound,
int? Modifier,
string? BodyPart,
bool IsPermanent,
string SourceType,
string? SourceText);

View File

@@ -96,6 +96,7 @@ public sealed record AttackLookupResponse(
CriticalLookupResponse? AutoCritical);
public sealed record CriticalTableCellDetail(
int ResultId,
string RollBand,
string ColumnKey,
string ColumnLabel,

View File

@@ -231,6 +231,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
.ThenBy(result => result.CriticalGroup?.SortOrder ?? 0)
.ThenBy(result => result.CriticalColumn.SortOrder)
.Select(result => new CriticalTableCellDetail(
result.Id,
result.CriticalRollBand.Label,
result.CriticalColumn.ColumnKey,
result.CriticalColumn.Label,
@@ -262,6 +263,70 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
cells,
legend);
}
public async Task<CriticalCellEditorResponse?> GetCriticalCellEditorAsync(string slug, int resultId, CancellationToken cancellationToken = default)
{
await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
var normalizedSlug = NormalizeSlug(slug);
var result = await dbContext.CriticalResults
.AsNoTracking()
.AsSplitQuery()
.Include(item => item.CriticalTable)
.Include(item => item.CriticalColumn)
.Include(item => item.CriticalGroup)
.Include(item => item.CriticalRollBand)
.Include(item => item.Effects)
.Include(item => item.Branches)
.ThenInclude(branch => branch.Effects)
.SingleOrDefaultAsync(
item => item.Id == resultId && item.CriticalTable.Slug == normalizedSlug,
cancellationToken);
return result is null ? null : CreateCellEditorResponse(result);
}
public async Task<CriticalCellEditorResponse?> UpdateCriticalCellAsync(
string slug,
int resultId,
CriticalCellUpdateRequest request,
CancellationToken cancellationToken = default)
{
await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
var normalizedSlug = NormalizeSlug(slug);
var result = await dbContext.CriticalResults
.AsSplitQuery()
.Include(item => item.CriticalTable)
.Include(item => item.CriticalColumn)
.Include(item => item.CriticalGroup)
.Include(item => item.CriticalRollBand)
.Include(item => item.Effects)
.Include(item => item.Branches)
.ThenInclude(branch => branch.Effects)
.SingleOrDefaultAsync(
item => item.Id == resultId && item.CriticalTable.Slug == normalizedSlug,
cancellationToken);
if (result is null)
{
return null;
}
result.RawCellText = request.RawCellText.Trim();
result.DescriptionText = request.DescriptionText.Trim();
result.RawAffixText = NormalizeOptionalText(request.RawAffixText);
result.ParseStatus = request.ParseStatus.Trim();
result.ParsedJson = string.IsNullOrWhiteSpace(request.ParsedJson) ? "{}" : request.ParsedJson.Trim();
ReplaceBaseEffects(dbContext, result, request.Effects);
ReplaceBranches(dbContext, result, request.Branches);
await dbContext.SaveChangesAsync(cancellationToken);
return CreateCellEditorResponse(result);
}
private static IReadOnlyList<CriticalTableLegendEntry> BuildLegend(IReadOnlyList<CriticalTableCellDetail> cells)
{
var seenCodes = new HashSet<string>(StringComparer.Ordinal);
@@ -320,6 +385,32 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
effect.SourceType,
effect.SourceText);
private static CriticalCellEditorResponse CreateCellEditorResponse(CriticalResult result) =>
new(
result.Id,
result.CriticalTable.Slug,
result.CriticalTable.DisplayName,
result.CriticalTable.SourceDocument,
result.CriticalRollBand.Label,
result.CriticalGroup?.GroupKey,
result.CriticalGroup?.Label,
result.CriticalColumn.ColumnKey,
result.CriticalColumn.Label,
result.CriticalColumn.Role,
result.RawCellText,
result.DescriptionText,
result.RawAffixText,
result.ParseStatus,
result.ParsedJson,
result.Effects
.OrderBy(effect => effect.Id)
.Select(CreateEffectEditorItem)
.ToList(),
result.Branches
.OrderBy(branch => branch.SortOrder)
.Select(CreateBranchEditorItem)
.ToList());
private static CriticalBranchLookupResponse CreateBranchLookupResponse(CriticalBranch branch) =>
new(
branch.BranchKind,
@@ -334,6 +425,108 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
branch.RawText,
branch.SortOrder);
private static CriticalBranchEditorItem CreateBranchEditorItem(CriticalBranch branch) =>
new(
branch.BranchKind,
branch.ConditionKey,
branch.ConditionText,
branch.ConditionJson,
branch.RawText,
branch.DescriptionText,
branch.RawAffixText,
branch.ParsedJson,
branch.SortOrder,
(branch.Effects ?? Enumerable.Empty<CriticalEffect>())
.OrderBy(effect => effect.Id)
.Select(CreateEffectEditorItem)
.ToList());
private static CriticalEffectEditorItem CreateEffectEditorItem(CriticalEffect effect) =>
new(
effect.EffectCode,
effect.Target,
effect.ValueInteger,
effect.ValueDecimal,
effect.ValueExpression,
effect.DurationRounds,
effect.PerRound,
effect.Modifier,
effect.BodyPart,
effect.IsPermanent,
effect.SourceType,
effect.SourceText);
private static void ReplaceBaseEffects(
RolemasterDbContext dbContext,
CriticalResult result,
IReadOnlyList<CriticalEffectEditorItem>? effects)
{
dbContext.CriticalEffects.RemoveRange(result.Effects);
result.Effects.Clear();
foreach (var effect in effects ?? Array.Empty<CriticalEffectEditorItem>())
{
result.Effects.Add(CreateEffectEntity(effect));
}
}
private static void ReplaceBranches(
RolemasterDbContext dbContext,
CriticalResult result,
IReadOnlyList<CriticalBranchEditorItem>? branches)
{
foreach (var branch in result.Branches)
{
dbContext.CriticalEffects.RemoveRange(branch.Effects);
}
dbContext.CriticalBranches.RemoveRange(result.Branches);
result.Branches.Clear();
foreach (var branch in branches ?? Array.Empty<CriticalBranchEditorItem>())
{
var branchEntity = new CriticalBranch
{
BranchKind = branch.BranchKind.Trim(),
ConditionKey = NormalizeOptionalText(branch.ConditionKey),
ConditionText = branch.ConditionText.Trim(),
ConditionJson = string.IsNullOrWhiteSpace(branch.ConditionJson) ? "{}" : branch.ConditionJson.Trim(),
RawText = branch.RawText.Trim(),
DescriptionText = branch.DescriptionText.Trim(),
RawAffixText = NormalizeOptionalText(branch.RawAffixText),
ParsedJson = string.IsNullOrWhiteSpace(branch.ParsedJson) ? "{}" : branch.ParsedJson.Trim(),
SortOrder = branch.SortOrder
};
foreach (var effect in branch.Effects ?? Array.Empty<CriticalEffectEditorItem>())
{
branchEntity.Effects.Add(CreateEffectEntity(effect));
}
result.Branches.Add(branchEntity);
}
}
private static CriticalEffect CreateEffectEntity(CriticalEffectEditorItem effect) =>
new()
{
EffectCode = effect.EffectCode.Trim(),
Target = NormalizeOptionalText(effect.Target),
ValueInteger = effect.ValueInteger,
ValueDecimal = effect.ValueDecimal,
ValueExpression = NormalizeOptionalText(effect.ValueExpression),
DurationRounds = effect.DurationRounds,
PerRound = effect.PerRound,
Modifier = effect.Modifier,
BodyPart = NormalizeOptionalText(effect.BodyPart),
IsPermanent = effect.IsPermanent,
SourceType = effect.SourceType.Trim(),
SourceText = NormalizeOptionalText(effect.SourceText)
};
private static string? NormalizeOptionalText(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static string NormalizeSlug(string value) =>
value.Trim().Replace(' ', '_').ToLowerInvariant();
}