feat: restore legacy drag events and Tornado launch integration
This commit is contained in:
@@ -23,6 +23,12 @@ public enum LegacyPlaylistMoveDirection
|
||||
Down
|
||||
}
|
||||
|
||||
public enum LegacyDragDropPosition
|
||||
{
|
||||
Before,
|
||||
After
|
||||
}
|
||||
|
||||
public enum LegacyOperatorTabId
|
||||
{
|
||||
Overseas,
|
||||
@@ -549,6 +555,29 @@ public sealed class LegacyOperatorController
|
||||
return await SelectTabAsync(source, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<LegacyOperatorSnapshot> SwapTabsAtExpectedPositionsAsync(
|
||||
LegacyOperatorTabId source,
|
||||
LegacyOperatorTabId target,
|
||||
int expectedSourceIndex,
|
||||
int expectedTargetIndex,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (source == target ||
|
||||
expectedSourceIndex < 0 || expectedSourceIndex >= _tabOrder.Count ||
|
||||
expectedTargetIndex < 0 || expectedTargetIndex >= _tabOrder.Count ||
|
||||
expectedSourceIndex == expectedTargetIndex ||
|
||||
_tabOrder[expectedSourceIndex] != source ||
|
||||
_tabOrder[expectedTargetIndex] != target)
|
||||
{
|
||||
return Task.FromResult(CreateSnapshot());
|
||||
}
|
||||
|
||||
// The expected positions make a repeated dragover message idempotent. Once
|
||||
// the first message swaps the two tabs, an identical delivery no longer
|
||||
// matches the C#-owned order and therefore cannot swap them back.
|
||||
return SwapTabsAsync(source, target, cancellationToken);
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot SetMovingAverages(bool ma5, bool ma20)
|
||||
{
|
||||
var next = new LegacyMovingAverageSelection(ma5, ma20);
|
||||
@@ -831,6 +860,31 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot ReorderOperatorCatalogDraftRow(
|
||||
string sourceRowId,
|
||||
string targetRowId,
|
||||
LegacyDragDropPosition position)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
|
||||
}
|
||||
|
||||
var beforeRevision = _operatorCatalogWorkflow.Current.Revision;
|
||||
var reordered = _operatorCatalogWorkflow.ReorderDraftRow(
|
||||
sourceRowId,
|
||||
targetRowId,
|
||||
position);
|
||||
if (reordered.Revision == beforeRevision)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot SetOperatorCatalogDraftBuyAmount(
|
||||
string rowId,
|
||||
decimal buyAmount)
|
||||
@@ -882,6 +936,23 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> CheckOperatorCatalogThemeNameAsync(
|
||||
string editorName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_operatorCatalogWorkflow is null)
|
||||
{
|
||||
return ReportError("ThemeA 관리 기능이 구성되지 않았습니다.");
|
||||
}
|
||||
|
||||
await _operatorCatalogWorkflow.CheckThemeNameAvailabilityAsync(
|
||||
editorName,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
ApplyOperatorCatalogStatus();
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> SaveOperatorCatalogAsync(
|
||||
string editorName,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -2769,6 +2840,82 @@ public sealed class LegacyOperatorController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorSnapshot ReorderPlaylistRows(
|
||||
string sourceRowId,
|
||||
string targetRowId,
|
||||
LegacyDragDropPosition position)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sourceRowId);
|
||||
ArgumentNullException.ThrowIfNull(targetRowId);
|
||||
|
||||
var sourceIndex = FindPlaylistIndex(sourceRowId);
|
||||
var targetIndex = FindPlaylistIndex(targetRowId);
|
||||
if (sourceIndex < 0 || targetIndex < 0 || sourceIndex == targetIndex)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
var sourceWasSelected = _selectedPlaylistRowIds.Contains(sourceRowId);
|
||||
var movingIds = sourceWasSelected
|
||||
? new HashSet<string>(_selectedPlaylistRowIds, StringComparer.Ordinal)
|
||||
: new HashSet<string>([sourceRowId], StringComparer.Ordinal);
|
||||
if (movingIds.Contains(targetRowId))
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
var movingRows = _playlist
|
||||
.Where(row => movingIds.Contains(row.RowId))
|
||||
.ToArray();
|
||||
var remainingRows = _playlist
|
||||
.Where(row => !movingIds.Contains(row.RowId))
|
||||
.ToList();
|
||||
var remainingTargetIndex = remainingRows.FindIndex(row =>
|
||||
string.Equals(row.RowId, targetRowId, StringComparison.Ordinal));
|
||||
if (movingRows.Length == 0 || remainingTargetIndex < 0)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
var insertionIndex = position switch
|
||||
{
|
||||
LegacyDragDropPosition.Before => remainingTargetIndex,
|
||||
LegacyDragDropPosition.After => remainingTargetIndex + 1,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(position), position, null)
|
||||
};
|
||||
remainingRows.InsertRange(insertionIndex, movingRows);
|
||||
|
||||
var orderChanged = !_playlist
|
||||
.Select(static row => row.RowId)
|
||||
.SequenceEqual(
|
||||
remainingRows.Select(static row => row.RowId),
|
||||
StringComparer.Ordinal);
|
||||
var selectionChanged = !sourceWasSelected ||
|
||||
!string.Equals(_activePlaylistRowId, sourceRowId, StringComparison.Ordinal);
|
||||
if (!orderChanged && !selectionChanged)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
if (orderChanged)
|
||||
{
|
||||
_playlist.Clear();
|
||||
_playlist.AddRange(remainingRows);
|
||||
}
|
||||
|
||||
if (!sourceWasSelected)
|
||||
{
|
||||
_selectedPlaylistRowIds.Clear();
|
||||
_selectedPlaylistRowIds.Add(sourceRowId);
|
||||
}
|
||||
|
||||
_activePlaylistRowId = sourceRowId;
|
||||
_playlistCursorIndex = FindPlaylistIndex(sourceRowId);
|
||||
_playlistPriorCursorIndex = _playlistCursorIndex;
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorSnapshot> RefreshNamedPlaylistsAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,14 @@ public enum LegacyOperatorCatalogStatusKind
|
||||
Error
|
||||
}
|
||||
|
||||
public enum LegacyThemeNameAvailability
|
||||
{
|
||||
NotChecked,
|
||||
Available,
|
||||
Duplicate,
|
||||
Indeterminate
|
||||
}
|
||||
|
||||
public enum LegacyOperatorCatalogMoveDirection
|
||||
{
|
||||
Up,
|
||||
@@ -65,6 +73,8 @@ public sealed record LegacyOperatorCatalogWorkflowSnapshot(
|
||||
string? SelectedResultId,
|
||||
string CodeLabel,
|
||||
string EditorName,
|
||||
LegacyThemeNameAvailability ThemeNameAvailability,
|
||||
string CheckedThemeName,
|
||||
IReadOnlyList<LegacyOperatorCatalogDraftRowView> DraftRows,
|
||||
string StockQuery,
|
||||
IReadOnlyList<LegacyOperatorCatalogStockResultView> StockResults,
|
||||
@@ -73,6 +83,7 @@ public sealed record LegacyOperatorCatalogWorkflowSnapshot(
|
||||
bool SchemaValidated,
|
||||
bool WritesQuarantined,
|
||||
bool CanBeginCreate,
|
||||
bool CanCheckThemeName,
|
||||
bool CanSave,
|
||||
bool CanDelete,
|
||||
bool CanAddStock,
|
||||
@@ -224,6 +235,9 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
private string _stockQuery = string.Empty;
|
||||
private string _codeLabel = string.Empty;
|
||||
private string _editorName = string.Empty;
|
||||
private LegacyThemeNameAvailability _themeNameAvailability =
|
||||
LegacyThemeNameAvailability.NotChecked;
|
||||
private string _checkedThemeName = string.Empty;
|
||||
private string? _selectedResultId;
|
||||
private string? _selectedStockResultId;
|
||||
private ThemeSelectionIdentity? _expectedThemeIdentity;
|
||||
@@ -504,6 +518,7 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
|
||||
_selectedResultId = resultId;
|
||||
_mode = LegacyOperatorCatalogMode.Edit;
|
||||
ResetThemeNameAvailability();
|
||||
ResetStockSearch();
|
||||
SetStatus("편집할 항목을 불러왔습니다.", LegacyOperatorCatalogStatusKind.Information);
|
||||
}
|
||||
@@ -545,6 +560,7 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
_expectedThemeIdentity = null;
|
||||
_expectedExpertIdentity = null;
|
||||
_draftRows.Clear();
|
||||
ResetThemeNameAvailability();
|
||||
ResetStockSearch();
|
||||
SetStatus("새 ThemeA 항목을 입력하세요.", LegacyOperatorCatalogStatusKind.Information);
|
||||
}
|
||||
@@ -562,6 +578,53 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorCatalogWorkflowSnapshot> CheckThemeNameAvailabilityAsync(
|
||||
string editorName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
EnsureOpenEntity(LegacyOperatorCatalogEntity.Theme);
|
||||
EnsureEditing();
|
||||
_editorName = ValidateRequiredText(editorName, 128);
|
||||
_checkedThemeName = _editorName;
|
||||
_themeNameAvailability = LegacyThemeNameAvailability.Indeterminate;
|
||||
|
||||
try
|
||||
{
|
||||
var duplicate = await _themePersistenceService.ThemeNameExistsAsync(
|
||||
_newThemeMarket,
|
||||
_editorName,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (duplicate)
|
||||
{
|
||||
_themeNameAvailability = LegacyThemeNameAvailability.Duplicate;
|
||||
SetStatus(
|
||||
"이미 사용중인 테마명입니다.",
|
||||
LegacyOperatorCatalogStatusKind.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
_themeNameAvailability = LegacyThemeNameAvailability.Available;
|
||||
SetStatus(
|
||||
"사용 가능한 테마명입니다.",
|
||||
LegacyOperatorCatalogStatusKind.Information);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
SetStatus(
|
||||
"테마명 중복 여부를 확인하지 못했습니다. DB 연결을 확인하세요.",
|
||||
LegacyOperatorCatalogStatusKind.Error);
|
||||
}
|
||||
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public async Task<LegacyOperatorCatalogWorkflowSnapshot> BeginCreateExpertAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -730,6 +793,51 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorCatalogWorkflowSnapshot ReorderDraftRow(
|
||||
string sourceRowId,
|
||||
string targetRowId,
|
||||
LegacyDragDropPosition position)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sourceRowId);
|
||||
ArgumentNullException.ThrowIfNull(targetRowId);
|
||||
EnsureEditing();
|
||||
|
||||
var sourceIndex = _draftRows.FindIndex(row =>
|
||||
string.Equals(row.RowId, sourceRowId, StringComparison.Ordinal));
|
||||
var targetIndex = _draftRows.FindIndex(row =>
|
||||
string.Equals(row.RowId, targetRowId, StringComparison.Ordinal));
|
||||
if (sourceIndex < 0 || targetIndex < 0 || sourceIndex == targetIndex)
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
var candidate = _draftRows.ToList();
|
||||
var movingRow = candidate[sourceIndex];
|
||||
candidate.RemoveAt(sourceIndex);
|
||||
var remainingTargetIndex = candidate.FindIndex(row =>
|
||||
string.Equals(row.RowId, targetRowId, StringComparison.Ordinal));
|
||||
var insertionIndex = position switch
|
||||
{
|
||||
LegacyDragDropPosition.Before => remainingTargetIndex,
|
||||
LegacyDragDropPosition.After => remainingTargetIndex + 1,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(position), position, null)
|
||||
};
|
||||
candidate.Insert(insertionIndex, movingRow);
|
||||
|
||||
if (_draftRows.Select(static row => row.RowId).SequenceEqual(
|
||||
candidate.Select(static row => row.RowId),
|
||||
StringComparer.Ordinal))
|
||||
{
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
_draftRows.Clear();
|
||||
_draftRows.AddRange(candidate);
|
||||
SetStatus("표시 순서를 변경했습니다.", LegacyOperatorCatalogStatusKind.Information);
|
||||
Touch();
|
||||
return CreateSnapshot();
|
||||
}
|
||||
|
||||
public LegacyOperatorCatalogWorkflowSnapshot SetDraftBuyAmount(
|
||||
string rowId,
|
||||
decimal buyAmount)
|
||||
@@ -1103,6 +1211,8 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
_selectedResultId,
|
||||
_codeLabel,
|
||||
_editorName,
|
||||
_themeNameAvailability,
|
||||
_checkedThemeName,
|
||||
Array.AsReadOnly(draftViews),
|
||||
_stockQuery,
|
||||
Array.AsReadOnly(stockViews),
|
||||
@@ -1111,6 +1221,9 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
_schemaValidated,
|
||||
quarantined,
|
||||
canMutate && _mode == LegacyOperatorCatalogMode.List,
|
||||
_schemaValidated && _isOpen &&
|
||||
_entity == LegacyOperatorCatalogEntity.Theme &&
|
||||
_mode is LegacyOperatorCatalogMode.Create or LegacyOperatorCatalogMode.Edit,
|
||||
canMutate &&
|
||||
(_mode is LegacyOperatorCatalogMode.Create or LegacyOperatorCatalogMode.Edit),
|
||||
canMutate && _mode == LegacyOperatorCatalogMode.Edit,
|
||||
@@ -1149,9 +1262,16 @@ public sealed class LegacyOperatorCatalogWorkflowController
|
||||
_codeLabel = string.Empty;
|
||||
_editorName = string.Empty;
|
||||
_draftRows.Clear();
|
||||
ResetThemeNameAvailability();
|
||||
ResetStockSearch();
|
||||
}
|
||||
|
||||
private void ResetThemeNameAvailability()
|
||||
{
|
||||
_themeNameAvailability = LegacyThemeNameAvailability.NotChecked;
|
||||
_checkedThemeName = string.Empty;
|
||||
}
|
||||
|
||||
private void ResetStockSearch()
|
||||
{
|
||||
_stockQuery = string.Empty;
|
||||
|
||||
Reference in New Issue
Block a user