Share critical cell parsing across app and importer
This commit is contained in:
@@ -60,12 +60,22 @@
|
||||
"rawAffixText": "+8H - 2S",
|
||||
"parseStatus": "verified",
|
||||
"parsedJson": "{}",
|
||||
"validationMessages": [],
|
||||
"effects": [],
|
||||
"branches": []
|
||||
}</pre>
|
||||
<p class="panel-copy">Use this to retrieve the full editable result graph for one critical-table cell, including nested branches and normalized effects.</p>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2 class="panel-title">Cell re-parse</h2>
|
||||
<p class="panel-copy"><code>POST /api/tables/critical/{slug}/cells/{resultId}/reparse</code></p>
|
||||
<pre class="code-block">{
|
||||
"rawCellText": "Strike to thigh. +8H\nWith greaves: blow glances aside."
|
||||
}</pre>
|
||||
<p class="panel-copy">Re-runs the shared single-cell parser and returns a refreshed editor payload without saving changes.</p>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2 class="panel-title">Cell editor save</h2>
|
||||
<p class="panel-copy"><code>PUT /api/tables/critical/{slug}/cells/{resultId}</code></p>
|
||||
|
||||
@@ -207,10 +207,13 @@
|
||||
<CriticalCellEditorDialog
|
||||
Model="editorModel"
|
||||
IsLoading="isEditorLoading"
|
||||
IsReparsing="isEditorReparsing"
|
||||
IsSaving="isEditorSaving"
|
||||
LoadErrorMessage="@editorLoadError"
|
||||
ReparseErrorMessage="@editorReparseError"
|
||||
SaveErrorMessage="@editorSaveError"
|
||||
OnClose="CloseCellEditorAsync"
|
||||
OnReparse="ReparseCellEditorAsync"
|
||||
OnSave="SaveCellEditorAsync" />
|
||||
}
|
||||
|
||||
@@ -227,8 +230,10 @@
|
||||
private bool IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0;
|
||||
private bool isEditorOpen;
|
||||
private bool isEditorLoading;
|
||||
private bool isEditorReparsing;
|
||||
private bool isEditorSaving;
|
||||
private string? editorLoadError;
|
||||
private string? editorReparseError;
|
||||
private string? editorSaveError;
|
||||
private int? editingResultId;
|
||||
private CriticalCellEditorModel? editorModel;
|
||||
@@ -328,9 +333,11 @@
|
||||
}
|
||||
|
||||
editorLoadError = null;
|
||||
editorReparseError = null;
|
||||
editorSaveError = null;
|
||||
editorModel = null;
|
||||
editingResultId = resultId;
|
||||
isEditorReparsing = false;
|
||||
isEditorSaving = false;
|
||||
isEditorLoading = true;
|
||||
isEditorOpen = true;
|
||||
@@ -362,14 +369,47 @@
|
||||
{
|
||||
isEditorOpen = false;
|
||||
isEditorLoading = false;
|
||||
isEditorReparsing = false;
|
||||
isEditorSaving = false;
|
||||
editorLoadError = null;
|
||||
editorReparseError = null;
|
||||
editorSaveError = null;
|
||||
editingResultId = null;
|
||||
editorModel = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task ReparseCellEditorAsync()
|
||||
{
|
||||
if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isEditorReparsing = true;
|
||||
editorReparseError = null;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await LookupService.ReparseCriticalCellAsync(selectedTableSlug, editingResultId.Value, editorModel.RawCellText);
|
||||
if (response is null)
|
||||
{
|
||||
editorReparseError = "The selected cell could not be re-parsed.";
|
||||
return;
|
||||
}
|
||||
|
||||
editorModel = CriticalCellEditorModel.FromResponse(response);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
editorReparseError = exception.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
isEditorReparsing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveCellEditorAsync()
|
||||
{
|
||||
if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using RolemasterDb.App.Features;
|
||||
|
||||
namespace RolemasterDb.App.Components.Shared;
|
||||
@@ -35,11 +36,32 @@ public sealed class CriticalBranchEditorModel
|
||||
BranchKind,
|
||||
ConditionKey,
|
||||
ConditionText,
|
||||
ConditionJson,
|
||||
RawText,
|
||||
"{}",
|
||||
BuildRawText(),
|
||||
DescriptionText,
|
||||
RawAffixText,
|
||||
ParsedJson,
|
||||
SerializeParsedEffects(Effects),
|
||||
SortOrder,
|
||||
Effects.Select(effect => effect.ToItem()).ToList());
|
||||
|
||||
private string BuildRawText()
|
||||
{
|
||||
var condition = ConditionText.Trim();
|
||||
var description = DescriptionText.Trim();
|
||||
var firstLine = string.IsNullOrWhiteSpace(description)
|
||||
? $"{condition}:"
|
||||
: $"{condition}: {description}";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(RawAffixText))
|
||||
{
|
||||
return firstLine;
|
||||
}
|
||||
|
||||
return $"{firstLine}{Environment.NewLine}{RawAffixText.Trim()}";
|
||||
}
|
||||
|
||||
private static string SerializeParsedEffects(IReadOnlyList<CriticalEffectEditorModel> effects) =>
|
||||
effects.Count == 0
|
||||
? "{}"
|
||||
: JsonSerializer.Serialize(new { effects = effects.Select(effect => effect.ToItem()).ToList() });
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@
|
||||
{
|
||||
<EditForm Model="Model" OnSubmit="HandleSubmitAsync" class="critical-editor-form">
|
||||
<div class="critical-editor-body">
|
||||
@if (!string.IsNullOrWhiteSpace(ReparseErrorMessage))
|
||||
{
|
||||
<p class="error-text critical-editor-error">@ReparseErrorMessage</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(SaveErrorMessage))
|
||||
{
|
||||
<p class="error-text critical-editor-error">@SaveErrorMessage</p>
|
||||
@@ -79,11 +84,23 @@
|
||||
<h4>Raw Text</h4>
|
||||
<p class="muted">Update the source text, then adjust the visible card fields below.</p>
|
||||
</div>
|
||||
<button type="button" class="btn-ritual" @onclick="OnReparse" disabled="@IsSaving || IsReparsing">
|
||||
@(IsReparsing ? "Re-Parsing..." : "Re-Parse Raw Text")
|
||||
</button>
|
||||
</div>
|
||||
<div class="field-shell">
|
||||
<label>Raw Cell Text</label>
|
||||
<InputTextArea class="input-shell critical-editor-textarea tall" @bind-Value="Model.RawCellText" />
|
||||
</div>
|
||||
@if (Model.ValidationMessages.Count > 0)
|
||||
{
|
||||
<div class="critical-editor-validation-list">
|
||||
@foreach (var message in Model.ValidationMessages)
|
||||
{
|
||||
<p class="critical-editor-validation-item">@message</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="field-shell">
|
||||
<label>Result Text Override</label>
|
||||
<InputTextArea class="input-shell critical-editor-textarea compact" @bind-Value="Model.DescriptionText" />
|
||||
@@ -231,18 +248,27 @@
|
||||
[Parameter]
|
||||
public bool IsLoading { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool IsReparsing { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool IsSaving { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? LoadErrorMessage { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? ReparseErrorMessage { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? SaveErrorMessage { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public EventCallback OnClose { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public EventCallback OnReparse { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public EventCallback OnSave { get; set; }
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using RolemasterDb.App.Features;
|
||||
|
||||
namespace RolemasterDb.App.Components.Shared;
|
||||
@@ -19,6 +20,7 @@ public sealed class CriticalCellEditorModel
|
||||
public string? RawAffixText { get; set; }
|
||||
public string ParseStatus { get; set; } = string.Empty;
|
||||
public string ParsedJson { get; set; } = "{}";
|
||||
public List<string> ValidationMessages { get; set; } = [];
|
||||
public List<CriticalEffectEditorModel> Effects { get; set; } = [];
|
||||
public List<CriticalBranchEditorModel> Branches { get; set; } = [];
|
||||
|
||||
@@ -40,6 +42,7 @@ public sealed class CriticalCellEditorModel
|
||||
RawAffixText = response.RawAffixText,
|
||||
ParseStatus = response.ParseStatus,
|
||||
ParsedJson = response.ParsedJson,
|
||||
ValidationMessages = response.ValidationMessages.ToList(),
|
||||
Effects = response.Effects.Select(CriticalEffectEditorModel.FromItem).ToList(),
|
||||
Branches = response.Branches.Select(CriticalBranchEditorModel.FromItem).ToList()
|
||||
};
|
||||
@@ -49,8 +52,8 @@ public sealed class CriticalCellEditorModel
|
||||
RawCellText,
|
||||
DescriptionText,
|
||||
RawAffixText,
|
||||
ParseStatus,
|
||||
ParsedJson,
|
||||
ResolveParseStatus(Effects, Branches),
|
||||
SerializeParsedEffects(Effects),
|
||||
Effects.Select(effect => effect.ToItem()).ToList(),
|
||||
Branches
|
||||
.OrderBy(branch => branch.SortOrder)
|
||||
@@ -60,4 +63,16 @@ public sealed class CriticalCellEditorModel
|
||||
return branch.ToItem();
|
||||
})
|
||||
.ToList());
|
||||
|
||||
private static string ResolveParseStatus(
|
||||
IReadOnlyList<CriticalEffectEditorModel> effects,
|
||||
IReadOnlyList<CriticalBranchEditorModel> branches) =>
|
||||
effects.Count > 0 || branches.Any(branch => branch.Effects.Count > 0)
|
||||
? "partial"
|
||||
: "raw";
|
||||
|
||||
private static string SerializeParsedEffects(IReadOnlyList<CriticalEffectEditorModel> effects) =>
|
||||
effects.Count == 0
|
||||
? "{}"
|
||||
: JsonSerializer.Serialize(new { effects = effects.Select(effect => effect.ToItem()).ToList() });
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ namespace RolemasterDb.App.Domain;
|
||||
|
||||
public static class CriticalEffectCodes
|
||||
{
|
||||
public const string DirectHits = "direct_hits";
|
||||
public const string MustParryRounds = "must_parry_rounds";
|
||||
public const string NoParryRounds = "no_parry_rounds";
|
||||
public const string StunnedRounds = "stunned_rounds";
|
||||
public const string BleedPerRound = "bleed_per_round";
|
||||
public const string FoePenalty = "foe_penalty";
|
||||
public const string AttackerBonusNextRound = "attacker_bonus_next_round";
|
||||
public const string PowerPointModifier = "power_point_modifier";
|
||||
public const string DirectHits = RolemasterDb.CriticalParsing.CriticalEffectCodes.DirectHits;
|
||||
public const string MustParryRounds = RolemasterDb.CriticalParsing.CriticalEffectCodes.MustParryRounds;
|
||||
public const string NoParryRounds = RolemasterDb.CriticalParsing.CriticalEffectCodes.NoParryRounds;
|
||||
public const string StunnedRounds = RolemasterDb.CriticalParsing.CriticalEffectCodes.StunnedRounds;
|
||||
public const string BleedPerRound = RolemasterDb.CriticalParsing.CriticalEffectCodes.BleedPerRound;
|
||||
public const string FoePenalty = RolemasterDb.CriticalParsing.CriticalEffectCodes.FoePenalty;
|
||||
public const string AttackerBonusNextRound = RolemasterDb.CriticalParsing.CriticalEffectCodes.AttackerBonusNextRound;
|
||||
public const string PowerPointModifier = RolemasterDb.CriticalParsing.CriticalEffectCodes.PowerPointModifier;
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@ public sealed record CriticalCellEditorResponse(
|
||||
string? RawAffixText,
|
||||
string ParseStatus,
|
||||
string ParsedJson,
|
||||
IReadOnlyList<string> ValidationMessages,
|
||||
IReadOnlyList<CriticalEffectEditorItem> Effects,
|
||||
IReadOnlyList<CriticalBranchEditorItem> Branches);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace RolemasterDb.App.Features;
|
||||
|
||||
public sealed record CriticalCellReparseRequest(
|
||||
string RawCellText);
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RolemasterDb.App.Data;
|
||||
using RolemasterDb.App.Domain;
|
||||
using SharedParsing = RolemasterDb.CriticalParsing;
|
||||
|
||||
namespace RolemasterDb.App.Features;
|
||||
|
||||
@@ -286,6 +288,36 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
return result is null ? null : CreateCellEditorResponse(result);
|
||||
}
|
||||
|
||||
public async Task<CriticalCellEditorResponse?> ReparseCriticalCellAsync(
|
||||
string slug,
|
||||
int resultId,
|
||||
string rawCellText,
|
||||
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)
|
||||
.SingleOrDefaultAsync(
|
||||
item => item.Id == resultId && item.CriticalTable.Slug == normalizedSlug,
|
||||
cancellationToken);
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var affixLegend = await BuildSharedAffixLegendAsync(dbContext, result.CriticalTableId, cancellationToken);
|
||||
var content = SharedParsing.CriticalCellTextParser.Parse(rawCellText, affixLegend);
|
||||
return CreateCellEditorResponse(result, content);
|
||||
}
|
||||
|
||||
public async Task<CriticalCellEditorResponse?> UpdateCriticalCellAsync(
|
||||
string slug,
|
||||
int resultId,
|
||||
@@ -402,6 +434,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
result.RawAffixText,
|
||||
result.ParseStatus,
|
||||
result.ParsedJson,
|
||||
[],
|
||||
result.Effects
|
||||
.OrderBy(effect => effect.Id)
|
||||
.Select(CreateEffectEditorItem)
|
||||
@@ -411,6 +444,34 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
.Select(CreateBranchEditorItem)
|
||||
.ToList());
|
||||
|
||||
private static CriticalCellEditorResponse CreateCellEditorResponse(
|
||||
CriticalResult result,
|
||||
SharedParsing.CriticalCellParseContent content) =>
|
||||
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,
|
||||
content.RawCellText,
|
||||
content.DescriptionText,
|
||||
content.RawAffixText,
|
||||
ResolveParseStatus(content.Effects, content.Branches),
|
||||
SerializeParsedEffects(content.Effects),
|
||||
content.ValidationErrors.ToList(),
|
||||
content.Effects
|
||||
.Select(CreateEffectEditorItem)
|
||||
.ToList(),
|
||||
content.Branches
|
||||
.OrderBy(branch => branch.SortOrder)
|
||||
.Select(CreateBranchEditorItem)
|
||||
.ToList());
|
||||
|
||||
private static CriticalBranchLookupResponse CreateBranchLookupResponse(CriticalBranch branch) =>
|
||||
new(
|
||||
branch.BranchKind,
|
||||
@@ -441,6 +502,21 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
.Select(CreateEffectEditorItem)
|
||||
.ToList());
|
||||
|
||||
private static CriticalBranchEditorItem CreateBranchEditorItem(SharedParsing.ParsedCriticalBranch branch) =>
|
||||
new(
|
||||
branch.BranchKind,
|
||||
branch.ConditionKey,
|
||||
branch.ConditionText,
|
||||
"{}",
|
||||
branch.RawText,
|
||||
branch.DescriptionText,
|
||||
branch.RawAffixText,
|
||||
SerializeParsedEffects(branch.Effects),
|
||||
branch.SortOrder,
|
||||
branch.Effects
|
||||
.Select(CreateEffectEditorItem)
|
||||
.ToList());
|
||||
|
||||
private static CriticalEffectEditorItem CreateEffectEditorItem(CriticalEffect effect) =>
|
||||
new(
|
||||
effect.EffectCode,
|
||||
@@ -456,6 +532,21 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
effect.SourceType,
|
||||
effect.SourceText);
|
||||
|
||||
private static CriticalEffectEditorItem CreateEffectEditorItem(SharedParsing.ParsedCriticalEffect effect) =>
|
||||
new(
|
||||
effect.EffectCode,
|
||||
effect.Target,
|
||||
effect.ValueInteger,
|
||||
null,
|
||||
effect.ValueExpression,
|
||||
effect.DurationRounds,
|
||||
effect.PerRound,
|
||||
effect.Modifier,
|
||||
effect.BodyPart,
|
||||
effect.IsPermanent,
|
||||
effect.SourceType,
|
||||
effect.SourceText);
|
||||
|
||||
private static void ReplaceBaseEffects(
|
||||
RolemasterDbContext dbContext,
|
||||
CriticalResult result,
|
||||
@@ -520,10 +611,120 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
Modifier = effect.Modifier,
|
||||
BodyPart = NormalizeOptionalText(effect.BodyPart),
|
||||
IsPermanent = effect.IsPermanent,
|
||||
SourceType = effect.SourceType.Trim(),
|
||||
SourceType = string.IsNullOrWhiteSpace(effect.SourceType) ? "manual" : effect.SourceType.Trim(),
|
||||
SourceText = NormalizeOptionalText(effect.SourceText)
|
||||
};
|
||||
|
||||
private static string ResolveParseStatus(
|
||||
IReadOnlyList<SharedParsing.ParsedCriticalEffect> effects,
|
||||
IReadOnlyList<SharedParsing.ParsedCriticalBranch> branches) =>
|
||||
effects.Count > 0 || branches.Any(branch => branch.Effects.Count > 0)
|
||||
? "partial"
|
||||
: "raw";
|
||||
|
||||
private static string SerializeParsedEffects(IReadOnlyList<SharedParsing.ParsedCriticalEffect> effects) =>
|
||||
effects.Count == 0
|
||||
? "{}"
|
||||
: JsonSerializer.Serialize(new
|
||||
{
|
||||
effects = effects.Select(effect => new
|
||||
{
|
||||
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 async Task<SharedParsing.AffixLegend> BuildSharedAffixLegendAsync(
|
||||
RolemasterDbContext dbContext,
|
||||
int tableId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var effectRows = await dbContext.CriticalEffects
|
||||
.AsNoTracking()
|
||||
.Where(item =>
|
||||
(item.CriticalResult != null && item.CriticalResult.CriticalTableId == tableId) ||
|
||||
(item.CriticalBranch != null && item.CriticalBranch.CriticalResult.CriticalTableId == tableId))
|
||||
.Select(item => new { item.EffectCode, item.SourceType, item.SourceText })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var symbolEffects = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
var supportsFoePenalty = false;
|
||||
var supportsAttackerBonus = false;
|
||||
var supportsPowerPointModifier = false;
|
||||
|
||||
foreach (var effectRow in effectRows)
|
||||
{
|
||||
supportsFoePenalty |= string.Equals(effectRow.EffectCode, CriticalEffectCodes.FoePenalty, StringComparison.Ordinal);
|
||||
supportsAttackerBonus |= string.Equals(effectRow.EffectCode, CriticalEffectCodes.AttackerBonusNextRound, StringComparison.Ordinal);
|
||||
supportsPowerPointModifier |= string.Equals(effectRow.EffectCode, CriticalEffectCodes.PowerPointModifier, StringComparison.Ordinal);
|
||||
|
||||
if (!string.Equals(effectRow.SourceType, "symbol", StringComparison.OrdinalIgnoreCase) ||
|
||||
!IsLegendSymbolEffectCode(effectRow.EffectCode) ||
|
||||
!TryExtractLegendSymbol(effectRow.SourceText, out var symbol))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
symbolEffects.TryAdd(symbol, effectRow.EffectCode);
|
||||
}
|
||||
|
||||
return new SharedParsing.AffixLegend(
|
||||
symbolEffects,
|
||||
supportsPowerPointModifier ? ["P"] : [],
|
||||
supportsFoePenalty,
|
||||
supportsAttackerBonus,
|
||||
supportsPowerPointModifier);
|
||||
}
|
||||
|
||||
private static bool IsLegendSymbolEffectCode(string effectCode) =>
|
||||
effectCode is CriticalEffectCodes.MustParryRounds
|
||||
or CriticalEffectCodes.NoParryRounds
|
||||
or CriticalEffectCodes.StunnedRounds
|
||||
or CriticalEffectCodes.BleedPerRound;
|
||||
|
||||
private static bool TryExtractLegendSymbol(string? sourceText, out string symbol)
|
||||
{
|
||||
symbol = string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(sourceText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var candidate = new string(sourceText
|
||||
.Where(character =>
|
||||
!char.IsWhiteSpace(character) &&
|
||||
!char.IsDigit(character) &&
|
||||
character is not ('+' or '-' or '–' or '(' or ')' or '/') &&
|
||||
!char.IsLetter(character))
|
||||
.ToArray());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(candidate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var distinctSymbols = candidate
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (distinctSymbols.Count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
symbol = distinctSymbols[0].ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string? NormalizeOptionalText(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ api.MapGet("/tables/critical/{slug}/cells/{resultId:int}", async (string slug, i
|
||||
var result = await lookupService.GetCriticalCellEditorAsync(slug, resultId, cancellationToken);
|
||||
return result is null ? Results.NotFound() : Results.Ok(result);
|
||||
});
|
||||
api.MapPost("/tables/critical/{slug}/cells/{resultId:int}/reparse", async (string slug, int resultId, CriticalCellReparseRequest request, LookupService lookupService, CancellationToken cancellationToken) =>
|
||||
{
|
||||
var result = await lookupService.ReparseCriticalCellAsync(slug, resultId, request.RawCellText, cancellationToken);
|
||||
return result is null ? Results.NotFound() : Results.Ok(result);
|
||||
});
|
||||
api.MapPut("/tables/critical/{slug}/cells/{resultId:int}", async (string slug, int resultId, CriticalCellUpdateRequest request, LookupService lookupService, CancellationToken cancellationToken) =>
|
||||
{
|
||||
var result = await lookupService.UpdateCriticalCellAsync(slug, resultId, request, cancellationToken);
|
||||
|
||||
@@ -16,4 +16,8 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RolemasterDb.CriticalParsing\RolemasterDb.CriticalParsing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -830,6 +830,19 @@ textarea {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.critical-editor-validation-list {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.critical-editor-validation-item {
|
||||
margin: 0;
|
||||
padding: 0.65rem 0.8rem;
|
||||
border-radius: 12px;
|
||||
background: rgba(184, 121, 59, 0.12);
|
||||
color: #6b4c29;
|
||||
}
|
||||
|
||||
.critical-editor-effect-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user