Implement queue-first curation workflow

This commit is contained in:
2026-04-12 00:45:50 +02:00
parent 752593fa62
commit 7843073d13
15 changed files with 1295 additions and 471 deletions

View File

@@ -2,6 +2,7 @@
@rendermode InteractiveServer
@using System
@using System.Linq
@using RolemasterDb.App.Frontend.AppState
@inject NavigationManager NavigationManager
@inject LookupService LookupService
@inject RolemasterDb.App.Frontend.AppState.PinnedTablesState PinnedTablesState
@@ -87,26 +88,6 @@
}
</section>
@if (isCurationOpen)
{
<CriticalCellCurationDialog
@key="curationModel"
Model="curationModel"
IsLoading="isCurationLoading"
IsSaving="isCurationSaving"
IsReparsing="isCurationReparsing"
IsQuickParseMode="isCurationQuickParseMode"
ErrorMessage="@curationError"
QuickParseErrorMessage="@curationQuickParseError"
LegendEntries="@(tableDetail?.Legend ?? Array.Empty<CriticalTableLegendEntry>())"
OnClose="CloseCellCurationAsync"
OnMarkCurated="MarkCellCuratedAsync"
OnEdit="OpenEditorFromCurationAsync"
OnEnterQuickParse="EnterCurationQuickParseMode"
OnCancelQuickParse="CancelCurationQuickParseMode"
OnReparse="ReparseCurationCellAsync"/>
}
@if (isEditorOpen)
{
<CriticalCellEditorDialog
@@ -141,15 +122,6 @@
private int? editingResultId;
private CriticalCellEditorModel? editorModel;
private CriticalCellEditorModel? editorComparisonBaselineModel;
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 string referenceMode = TablesReferenceMode.Reference;
private string selectedGroupKey = string.Empty;
private string selectedColumnKey = string.Empty;
@@ -282,228 +254,6 @@
}
}
private async Task OpenCellCurationAsync(int resultId)
{
if (string.IsNullOrWhiteSpace(selectedTableSlug))
{
return;
}
isCurationSaving = false;
isCurationReparsing = false;
isCurationQuickParseMode = false;
isCurationOpen = true;
await LoadCurationCellAsync(resultId, "The selected cell could not be loaded for curation.");
}
private async Task CloseCellCurationAsync()
{
isCurationOpen = false;
isCurationLoading = false;
isCurationSaving = false;
isCurationReparsing = false;
isCurationQuickParseMode = false;
curationError = null;
curationQuickParseError = null;
curatingResultId = null;
curationModel = null;
await InvokeAsync(StateHasChanged);
}
private async Task MarkCellCuratedAsync()
{
if (curationModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || curatingResultId is null)
{
return;
}
isCurationSaving = true;
curationError = null;
try
{
curationModel.IsCurated = true;
var response = await LookupService.UpdateCriticalCellAsync(selectedTableSlug, curatingResultId.Value, curationModel.ToRequest());
if (response is null)
{
curationError = "The selected cell could not be marked curated.";
return;
}
await LoadTableDetailAsync();
var nextResultId = FindNextUncuratedResultId(curatingResultId.Value);
if (nextResultId is null)
{
await CloseCellCurationAsync();
return;
}
await LoadCurationCellAsync(nextResultId.Value, "The next cell could not be loaded for curation.");
}
catch (Exception exception)
{
curationError = exception.Message;
}
finally
{
isCurationSaving = false;
}
}
private async Task OpenEditorFromCurationAsync()
{
if (curatingResultId is null)
{
return;
}
var resultId = curatingResultId.Value;
await CloseCellCurationAsync();
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
{
var response = await LookupService.GetCriticalCellEditorAsync(selectedTableSlug, resultId);
if (response is null)
{
curationError = loadFailureMessage;
curationModel = null;
return;
}
curationModel = CriticalCellEditorModel.FromResponse(response);
}
catch (Exception exception)
{
curationError = exception.Message;
curationModel = null;
}
finally
{
isCurationLoading = false;
}
}
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)
{
return null;
}
var orderedCells = tableDetail.Cells.OrderBy(cell => GetGroupSortOrder(cell.GroupKey)).ThenBy(cell => GetColumnSortOrder(cell.ColumnKey)).ThenBy(cell => GetRollBandSortOrder(cell.RollBand)).ToList();
var currentIndex = orderedCells.FindIndex(cell => cell.ResultId == currentResultId);
for (var index = currentIndex + 1; index < orderedCells.Count; index++)
{
if (!orderedCells[index].IsCurated)
{
return orderedCells[index].ResultId;
}
}
return null;
}
private int GetGroupSortOrder(string? groupKey)
{
if (tableDetail is null || string.IsNullOrWhiteSpace(groupKey))
{
return 0;
}
return tableDetail.Groups.FirstOrDefault(group => string.Equals(group.Key, groupKey, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue;
}
private int GetColumnSortOrder(string columnKey)
{
if (tableDetail is null)
{
return int.MaxValue;
}
return tableDetail.Columns.FirstOrDefault(column => string.Equals(column.Key, columnKey, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue;
}
private int GetRollBandSortOrder(string rollBandLabel)
{
if (tableDetail is null)
{
return int.MaxValue;
}
return tableDetail.RollBands.FirstOrDefault(rollBand => string.Equals(rollBand.Label, rollBandLabel, StringComparison.OrdinalIgnoreCase))?.SortOrder ?? int.MaxValue;
}
private async Task CloseCellEditorAsync()
{
isEditorOpen = false;
@@ -639,8 +389,18 @@
private Task OpenSelectedCellEditorAsync() =>
selectedCell is null ? Task.CompletedTask : OpenCellEditorAsync(selectedCell.ResultId);
private Task OpenSelectedCellCurationAsync() =>
selectedCell is null ? Task.CompletedTask : OpenCellCurationAsync(selectedCell.ResultId);
private async Task OpenSelectedCellCurationAsync()
{
if (SelectedCellDetail is not { } cellDetail || string.IsNullOrWhiteSpace(selectedTableSlug))
{
return;
}
var snapshot = new TableContextSnapshot(TableSlug: selectedTableSlug, GroupKey: cellDetail.GroupKey, ColumnKey: cellDetail.ColumnKey, RollBand: cellDetail.RollBand, ResultId: cellDetail.ResultId, Mode: TableContextMode.Curation, QueueScope: CurationQueueScopes.SelectedTable);
await TableContextState.PersistAsync("curation", snapshot);
NavigationManager.NavigateTo(TableContextState.BuildUri("/curation", snapshot));
}
private Task ToggleLegend()
{