feat: restore legacy drag events and Tornado launch integration

This commit is contained in:
2026-07-15 15:35:25 +09:00
parent 6e0c275fdd
commit 06a16e5741
32 changed files with 3096 additions and 101 deletions

View File

@@ -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)
{