feat: advance legacy UI behavior parity

This commit is contained in:
2026-07-18 10:18:29 +09:00
parent d7ec571343
commit 1302b1b92f
71 changed files with 11244 additions and 1153 deletions

View File

@@ -66,7 +66,10 @@ public sealed record LegacyCutViewRow(
bool IsSelected,
bool IsFocused = false);
public sealed record LegacyDialogState(string Message);
public sealed record LegacyDialogState(
string Message,
string Caption = "MmoneyCoder",
bool IsConfirmation = false);
public sealed record LegacyOperatorCommandResult(
long Sequence,
@@ -196,6 +199,8 @@ public sealed class LegacyOperatorController
private string _statusMessage = string.Empty;
private LegacyOperatorStatusKind _statusKind = LegacyOperatorStatusKind.Neutral;
private LegacyDialogState? _dialog;
private PendingNativeConfirmation? _pendingNativeConfirmation;
private bool _closeOperatorCatalogAfterDialog;
private LegacyOperatorTabId _activeTab = LegacyOperatorTabId.Overseas;
private LegacyMovingAverageSelection _movingAverages = new(true, true);
private LegacyThemeWorkflowSnapshot? _themeSnapshot;
@@ -231,7 +236,7 @@ public sealed class LegacyOperatorController
_timeProvider = timeProvider ?? TimeProvider.System;
_industryWorkflow = industryWorkflow;
_themeWorkflow = themeWorkflow;
_themeSnapshot = themeWorkflow?.CreateInitial();
_themeSnapshot = themeWorkflow?.CreateInitial(CurrentThemeNxtSession());
_expertWorkflow = expertWorkflow;
_tradingHaltWorkflow = tradingHaltWorkflow;
_comparisonWorkflow = comparisonWorkflow;
@@ -640,7 +645,7 @@ public sealed class LegacyOperatorController
var loaded = await _themeWorkflow.SearchAsync(
_themeSnapshot,
string.Empty,
MMoneyCoderSharp.Data.ThemeSession.PreMarket,
CurrentThemeNxtSession(),
cancellationToken).ConfigureAwait(false);
if (!IsCurrentTabActivation(tabId, activationRequest))
{
@@ -791,12 +796,6 @@ public sealed class LegacyOperatorController
await _operatorCatalogWorkflow.OpenAsync(
LegacyOperatorCatalogEntity.Theme,
cancellationToken).ConfigureAwait(false);
if (_operatorCatalogWorkflow.Current.CanBeginCreate)
{
await _operatorCatalogWorkflow.BeginCreateThemeAsync(
MMoneyCoderSharp.Data.ThemeMarket.Krx,
cancellationToken).ConfigureAwait(false);
}
ApplyOperatorCatalogStatus();
Touch();
@@ -824,18 +823,32 @@ public sealed class LegacyOperatorController
return CreateSnapshot();
}
public async Task<LegacyOperatorSnapshot> DeleteSelectedThemeCatalogAsync(
public Task<LegacyOperatorSnapshot> DeleteSelectedThemeCatalogAsync(
CancellationToken cancellationToken = default)
{
await EditSelectedThemeCatalogAsync(cancellationToken).ConfigureAwait(false);
if (_operatorCatalogWorkflow?.Current.CanDelete == true)
cancellationToken.ThrowIfCancellationRequested();
if (_operatorCatalogWorkflow is null)
{
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
ApplyOperatorCatalogStatus();
Touch();
return Task.FromResult(ReportError("ThemeA 관리 기능이 구성되지 않았습니다."));
}
return CreateSnapshot();
var identity = _themeSnapshot?.SelectedIdentity;
if (identity is null)
{
return Task.FromResult(ReportError("삭제할 테마를 먼저 선택하세요."));
}
if (_dialog is not null)
{
return Task.FromResult(CreateSnapshot());
}
_pendingNativeConfirmation = PendingNativeConfirmation.ForTheme(identity);
_dialog = new LegacyDialogState(
"선택한 테마를 삭제하겠습니까?",
IsConfirmation: true);
Touch();
return Task.FromResult(CreateSnapshot());
}
public async Task<LegacyOperatorSnapshot> BeginExpertCatalogFromUc6Async(
@@ -884,18 +897,35 @@ public sealed class LegacyOperatorController
return CreateSnapshot();
}
public async Task<LegacyOperatorSnapshot> DeleteSelectedExpertCatalogAsync(
public Task<LegacyOperatorSnapshot> DeleteSelectedExpertCatalogAsync(
CancellationToken cancellationToken = default)
{
await EditSelectedExpertCatalogAsync(cancellationToken).ConfigureAwait(false);
if (_operatorCatalogWorkflow?.Current.CanDelete == true)
cancellationToken.ThrowIfCancellationRequested();
if (_operatorCatalogWorkflow is null)
{
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
ApplyOperatorCatalogStatus();
Touch();
return Task.FromResult(ReportError("EList 관리 기능이 구성되지 않았습니다."));
}
return CreateSnapshot();
var selected = _expertWorkflow?.Current.SelectedExpert;
if (selected is null)
{
return Task.FromResult(ReportError("삭제할 전문가를 먼저 선택하세요."));
}
if (_dialog is not null)
{
return Task.FromResult(CreateSnapshot());
}
_pendingNativeConfirmation = PendingNativeConfirmation.ForExpert(
new MMoneyCoderSharp.Data.ExpertSelectionIdentity(
selected.ExpertCode,
selected.ExpertName));
_dialog = new LegacyDialogState(
"선택한 전문가를 삭제하겠습니까?",
IsConfirmation: true);
Touch();
return Task.FromResult(CreateSnapshot());
}
public LegacyOperatorSnapshot CloseOperatorCatalog()
@@ -910,6 +940,43 @@ public sealed class LegacyOperatorController
return CreateSnapshot();
}
public async Task<LegacyOperatorSnapshot> CloseOperatorCatalogAsync(
CancellationToken cancellationToken = default)
{
if (_operatorCatalogWorkflow is null)
{
return CreateSnapshot();
}
var closing = _operatorCatalogWorkflow.Current;
_operatorCatalogWorkflow.Close();
_statusMessage = string.Empty;
_statusKind = LegacyOperatorStatusKind.Neutral;
if (closing.IsOpen &&
closing.Entity == LegacyOperatorCatalogEntity.Expert &&
closing.Mode == LegacyOperatorCatalogMode.Create &&
_expertWorkflow is not null)
{
try
{
await _expertWorkflow.SearchAsync(string.Empty, cancellationToken)
.ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
_statusMessage = "전문가 목록을 새로 읽지 못했습니다. 다시 조회하세요.";
_statusKind = LegacyOperatorStatusKind.Warning;
}
}
Touch();
return CreateSnapshot();
}
public async Task<LegacyOperatorSnapshot> SearchOperatorCatalogAsync(
string query,
MMoneyCoderSharp.Data.ThemeSession nxtSession,
@@ -1058,7 +1125,7 @@ public sealed class LegacyOperatorController
public LegacyOperatorSnapshot SetOperatorCatalogDraftBuyAmount(
string rowId,
decimal buyAmount)
decimal? buyAmount)
{
if (_operatorCatalogWorkflow is null)
{
@@ -1107,6 +1174,61 @@ public sealed class LegacyOperatorController
return CreateSnapshot();
}
public LegacyOperatorSnapshot SelectOperatorCatalogDraftRow(string rowId)
{
if (_operatorCatalogWorkflow is null)
{
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
}
_operatorCatalogWorkflow.SelectDraftRow(rowId);
ApplyOperatorCatalogStatus();
Touch();
return CreateSnapshot();
}
public LegacyOperatorSnapshot RemoveActiveOperatorCatalogDraftRow()
{
if (_operatorCatalogWorkflow is null)
{
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
}
_operatorCatalogWorkflow.RemoveActiveDraftRow();
ApplyOperatorCatalogStatus();
Touch();
return CreateSnapshot();
}
public LegacyOperatorSnapshot ClearOperatorCatalogDraftRows()
{
if (_operatorCatalogWorkflow is null)
{
return ReportError("ThemeA 관리 기능이 구성되지 않았습니다.");
}
var catalog = _operatorCatalogWorkflow.Current;
if (!catalog.IsOpen || catalog.Entity != LegacyOperatorCatalogEntity.Theme ||
catalog.Mode is not (LegacyOperatorCatalogMode.Create or
LegacyOperatorCatalogMode.Edit))
{
return ReportError("ThemeA 편집 목록이 열려 있지 않습니다.");
}
if (catalog.DraftRows.Count == 0 || _dialog is not null)
{
return CreateSnapshot();
}
_pendingNativeConfirmation = PendingNativeConfirmation.ForThemeDraftClear(
catalog.Revision);
_dialog = new LegacyDialogState(
"모두 삭제하겠습니까?",
IsConfirmation: true);
Touch();
return CreateSnapshot();
}
public async Task<LegacyOperatorSnapshot> CheckOperatorCatalogThemeNameAsync(
string editorName,
CancellationToken cancellationToken = default)
@@ -1120,6 +1242,12 @@ public sealed class LegacyOperatorController
editorName,
cancellationToken).ConfigureAwait(false);
ApplyOperatorCatalogStatus();
var availability = _operatorCatalogWorkflow.Current.ThemeNameAvailability;
if (availability is LegacyThemeNameAvailability.Available or
LegacyThemeNameAvailability.Duplicate)
{
_dialog = new LegacyDialogState(_operatorCatalogWorkflow.Current.StatusMessage);
}
Touch();
return CreateSnapshot();
}
@@ -1133,23 +1261,95 @@ public sealed class LegacyOperatorController
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
}
var before = _operatorCatalogWorkflow.Current;
var selectedExpertId = before.Entity == LegacyOperatorCatalogEntity.Expert &&
before.Mode == LegacyOperatorCatalogMode.Edit
? _expertWorkflow?.Current.SelectedExpert?.SelectionId
: null;
if (before.Entity == LegacyOperatorCatalogEntity.Expert &&
before.Mode == LegacyOperatorCatalogMode.Create &&
string.IsNullOrWhiteSpace(editorName))
{
_dialog = new LegacyDialogState("이름을 입력하세요");
Touch();
return CreateSnapshot();
}
await _operatorCatalogWorkflow.SaveAsync(editorName, cancellationToken)
.ConfigureAwait(false);
ApplyOperatorCatalogStatus();
Touch();
return CreateSnapshot();
}
var after = _operatorCatalogWorkflow.Current;
public async Task<LegacyOperatorSnapshot> DeleteOperatorCatalogAsync(
CancellationToken cancellationToken = default)
{
if (_operatorCatalogWorkflow is null)
if (before.Entity == LegacyOperatorCatalogEntity.Theme)
{
return ReportError("ThemeA/EList 관리 기능이 구성되지 않았습니다.");
if (after.Mode == LegacyOperatorCatalogMode.List)
{
_operatorCatalogWorkflow.Close();
_statusMessage = string.Empty;
_statusKind = LegacyOperatorStatusKind.Neutral;
}
else if (string.Equals(
after.StatusMessage,
LegacyOperatorCatalogWorkflowController.ThemeNameDuplicateMessage,
StringComparison.Ordinal))
{
_dialog = new LegacyDialogState(
LegacyOperatorCatalogWorkflowController.ThemeNameDuplicateMessage);
}
}
else if (after.Mode == LegacyOperatorCatalogMode.List)
{
var refreshFailed = false;
if (before.Mode == LegacyOperatorCatalogMode.Create)
{
_operatorCatalogWorkflow.Close();
try
{
if (_expertWorkflow is not null)
{
await _expertWorkflow.SearchAsync(string.Empty, cancellationToken)
.ConfigureAwait(false);
}
}
catch
{
// The mutation receipt is already known-committed. A UC6 list refresh
// failure must not be reclassified as an unknown DB-write outcome.
_statusMessage = "전문가 목록을 새로 읽지 못했습니다. 다시 조회하세요.";
_statusKind = LegacyOperatorStatusKind.Warning;
refreshFailed = true;
}
}
else if (before.Mode == LegacyOperatorCatalogMode.Edit)
{
try
{
if (_expertWorkflow is not null && selectedExpertId is not null)
{
await _expertWorkflow.SelectExpertAsync(
selectedExpertId,
cancellationToken)
.ConfigureAwait(false);
}
}
catch
{
// The expert transaction is known-committed. Preview refresh failure
// is a read failure and must never turn into an unknown write outcome.
refreshFailed = true;
_statusMessage = "저장된 추천 종목을 다시 읽지 못했습니다. 전문가를 다시 선택하세요.";
_statusKind = LegacyOperatorStatusKind.Warning;
}
_dialog = new LegacyDialogState("저장되었습니다");
_closeOperatorCatalogAfterDialog = true;
}
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
ApplyOperatorCatalogStatus();
if (!refreshFailed)
{
_statusMessage = string.Empty;
_statusKind = LegacyOperatorStatusKind.Neutral;
}
}
Touch();
return CreateSnapshot();
}
@@ -2085,6 +2285,32 @@ public sealed class LegacyOperatorController
return CreateSnapshot();
}
public LegacyOperatorSnapshot SetIndustryComparisonText(
LegacyIndustryComparisonSlot slot,
string text)
{
ArgumentNullException.ThrowIfNull(text);
if (!IsActiveIndustryReady())
{
return ReportError("업종 목록을 먼저 불러오세요.");
}
try
{
_industryWorkflow!.SetComparisonText(slot, text);
Touch();
return CreateSnapshot();
}
catch (ArgumentOutOfRangeException)
{
return ReportError("등록되지 않은 업종 비교 입력칸입니다.");
}
catch (LegacyIndustryWorkflowException exception)
{
return ReportError(exception.Message);
}
}
public LegacyOperatorSnapshot ActivateIndustryAction(string actionId)
{
ArgumentNullException.ThrowIfNull(actionId);
@@ -2117,46 +2343,13 @@ public sealed class LegacyOperatorController
return ReportError("업종 목록을 먼저 불러오세요.");
}
var snapshot = _industryWorkflow!.Current;
var sections = snapshot.Actions
.Select(static action => action.Section)
.Distinct(StringComparer.Ordinal)
.ToArray();
if (sectionIndex < 0 || sectionIndex >= sections.Length)
{
return ReportError("등록되지 않은 업종 컷 섹션입니다.");
}
try
{
var section = sections[sectionIndex];
// Materialize every direct child before mutating the playlist. This keeps
// the original parent double-click all-or-nothing when one child lacks a
// required industry selection.
var drafts = snapshot.Actions
.Where(action => string.Equals(
action.Section,
section,
StringComparison.Ordinal))
.Select(action => _industryWorkflow.MaterializePlaylistDraft(action.ActionId))
.ToArray();
foreach (var draft in drafts)
{
AppendIndustryRow(draft, snapshot.Industries.Count);
}
_statusMessage = $"{section} 항목 {drafts.Length}개를 추가했습니다.";
_statusKind = LegacyOperatorStatusKind.Information;
Touch();
return CreateSnapshot();
}
catch (LegacyIndustryWorkflowException exception)
{
_dialog = new LegacyDialogState(
$"섹션의 모든 항목을 검증하지 못해 아무것도 추가하지 않았습니다. {exception.Message}");
Touch();
return CreateSnapshot();
}
// MainForm constructs both industry UC1 instances with UC3 == null. The
// original root double-click path dereferences UC3 before its 22-child loop,
// then its outer catch silently consumes the exception. Preserve that
// observable no-op; only child double-clicks add industry playlist rows.
return sectionIndex == 0
? CreateSnapshot()
: ReportError("등록되지 않은 업종 컷 섹션입니다.");
}
public LegacyOperatorSnapshot SelectOverseasFixedIndex(string targetId)
@@ -2484,8 +2677,17 @@ public sealed class LegacyOperatorController
if (all)
{
await _comparisonWorkflow.DeleteAllPairsAsync(cancellationToken)
.ConfigureAwait(false);
if (_dialog is not null)
{
return CreateSnapshot();
}
_pendingNativeConfirmation = PendingNativeConfirmation.ForComparisonDeleteAll(
_comparisonWorkflow.Current.Revision);
_dialog = new LegacyDialogState(
"모두 삭제하겠습니까?",
"MmoneyCoder",
IsConfirmation: true);
}
else
{
@@ -2598,10 +2800,23 @@ public sealed class LegacyOperatorController
}
}
public async Task<LegacyOperatorSnapshot> SearchThemesAsync(
public Task<LegacyOperatorSnapshot> SearchThemesAsync(
string query,
CancellationToken cancellationToken = default) =>
SearchThemesCoreAsync(query, CurrentThemeNxtSession(), cancellationToken);
// Compatibility overload for native callers. UC4 does not allow a WebView
// payload to select the NXT session; TimeProvider remains authoritative.
public Task<LegacyOperatorSnapshot> SearchThemesAsync(
string query,
MMoneyCoderSharp.Data.ThemeSession ignoredNxtSession,
CancellationToken cancellationToken = default) =>
SearchThemesAsync(query, cancellationToken);
private async Task<LegacyOperatorSnapshot> SearchThemesCoreAsync(
string query,
MMoneyCoderSharp.Data.ThemeSession nxtSession,
CancellationToken cancellationToken = default)
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
if (!IsThemeActive() || _themeWorkflow is null || _themeSnapshot is null)
@@ -3422,11 +3637,63 @@ public sealed class LegacyOperatorController
return CreateSnapshot();
}
public async Task<LegacyOperatorSnapshot> ConfirmDialogAsync(
CancellationToken cancellationToken = default)
{
var pending = _pendingNativeConfirmation;
if (_dialog?.IsConfirmation != true || pending is null)
{
return CreateSnapshot();
}
// Consume the confirmation before crossing a database-write boundary. A
// timeout or unknown outcome must never leave a replayable Yes action.
_pendingNativeConfirmation = null;
_dialog = null;
return pending.Kind switch
{
PendingNativeConfirmationKind.DeleteTheme =>
await ExecuteConfirmedThemeDeleteAsync(
pending.ThemeIdentity!,
cancellationToken).ConfigureAwait(false),
PendingNativeConfirmationKind.DeleteExpert =>
await ExecuteConfirmedExpertDeleteAsync(
pending.ExpertIdentity!,
cancellationToken).ConfigureAwait(false),
PendingNativeConfirmationKind.ClearThemeDraft =>
ExecuteConfirmedThemeDraftClear(pending.CatalogRevision),
PendingNativeConfirmationKind.DeleteAllComparisonPairs =>
await ExecuteConfirmedComparisonDeleteAllAsync(
pending.ComparisonRevision,
cancellationToken)
.ConfigureAwait(false),
_ => throw new InvalidOperationException("Unknown native confirmation kind.")
};
}
public LegacyOperatorSnapshot CancelDialog()
{
if (_dialog?.IsConfirmation == true)
{
_pendingNativeConfirmation = null;
_dialog = null;
Touch();
}
return CreateSnapshot();
}
public LegacyOperatorSnapshot DismissDialog()
{
if (_dialog is not null)
if (_dialog is not null && !_dialog.IsConfirmation)
{
_dialog = null;
if (_closeOperatorCatalogAfterDialog)
{
_operatorCatalogWorkflow?.Close();
_closeOperatorCatalogAfterDialog = false;
}
ContinuePendingDrop();
Touch();
}
@@ -3434,6 +3701,121 @@ public sealed class LegacyOperatorController
return CreateSnapshot();
}
private async Task<LegacyOperatorSnapshot> ExecuteConfirmedThemeDeleteAsync(
MMoneyCoderSharp.Data.ThemeSelectionIdentity identity,
CancellationToken cancellationToken)
{
if (_operatorCatalogWorkflow is null || _themeWorkflow is null ||
_themeSnapshot?.SelectedIdentity != identity)
{
return ReportError("삭제 대상 테마가 변경되어 DB 삭제를 시작하지 않았습니다.");
}
await _operatorCatalogWorkflow.OpenThemeForEditAsync(identity, cancellationToken)
.ConfigureAwait(false);
if (!_operatorCatalogWorkflow.Current.CanDelete)
{
ApplyOperatorCatalogStatus();
_operatorCatalogWorkflow.Close();
Touch();
return CreateSnapshot();
}
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
if (_operatorCatalogWorkflow.Current.Mode == LegacyOperatorCatalogMode.List)
{
_themeSnapshot = _themeWorkflow.RemoveSelectedResult(_themeSnapshot);
_operatorCatalogWorkflow.Close();
_statusMessage = string.Empty;
_statusKind = LegacyOperatorStatusKind.Neutral;
}
else
{
ApplyOperatorCatalogStatus();
}
Touch();
return CreateSnapshot();
}
private async Task<LegacyOperatorSnapshot> ExecuteConfirmedExpertDeleteAsync(
MMoneyCoderSharp.Data.ExpertSelectionIdentity identity,
CancellationToken cancellationToken)
{
var selected = _expertWorkflow?.Current.SelectedExpert;
if (_operatorCatalogWorkflow is null || selected is null ||
!string.Equals(selected.ExpertCode, identity.ExpertCode, StringComparison.Ordinal) ||
!string.Equals(selected.ExpertName, identity.ExpertName, StringComparison.Ordinal))
{
return ReportError("삭제 대상 전문가가 변경되어 DB 삭제를 시작하지 않았습니다.");
}
await _operatorCatalogWorkflow.OpenExpertForEditAsync(identity, cancellationToken)
.ConfigureAwait(false);
if (!_operatorCatalogWorkflow.Current.CanDelete)
{
ApplyOperatorCatalogStatus();
_operatorCatalogWorkflow.Close();
Touch();
return CreateSnapshot();
}
await _operatorCatalogWorkflow.DeleteAsync(cancellationToken).ConfigureAwait(false);
if (_operatorCatalogWorkflow.Current.Mode == LegacyOperatorCatalogMode.List)
{
_expertWorkflow?.RemoveSelectedResultPreservingPreview();
_operatorCatalogWorkflow.Close();
_statusMessage = string.Empty;
_statusKind = LegacyOperatorStatusKind.Neutral;
}
else
{
ApplyOperatorCatalogStatus();
}
Touch();
return CreateSnapshot();
}
private LegacyOperatorSnapshot ExecuteConfirmedThemeDraftClear(long catalogRevision)
{
if (_operatorCatalogWorkflow is null)
{
return ReportError("ThemeA 관리 기능이 구성되지 않았습니다.");
}
var catalog = _operatorCatalogWorkflow.Current;
if (catalog.Revision != catalogRevision || !catalog.IsOpen ||
catalog.Entity != LegacyOperatorCatalogEntity.Theme ||
catalog.Mode is not (LegacyOperatorCatalogMode.Create or
LegacyOperatorCatalogMode.Edit))
{
return ReportError("ThemeA 편집 목록이 변경되어 전체 삭제를 실행하지 않았습니다.");
}
_operatorCatalogWorkflow.ClearThemeDraftRows();
ApplyOperatorCatalogStatus();
Touch();
return CreateSnapshot();
}
private async Task<LegacyOperatorSnapshot> ExecuteConfirmedComparisonDeleteAllAsync(
long comparisonRevision,
CancellationToken cancellationToken)
{
if (!IsComparisonActive() || _comparisonWorkflow is null ||
_comparisonWorkflow.Current.Revision != comparisonRevision)
{
return ReportError(
"삭제 대상 종목 비교 목록이 변경되어 전체 삭제를 실행하지 않았습니다.");
}
await _comparisonWorkflow.DeleteAllPairsAsync(cancellationToken)
.ConfigureAwait(false);
Touch();
return CreateSnapshot();
}
public LegacyOperatorSnapshot ReportError(string message)
{
_statusMessage = message ?? string.Empty;
@@ -4055,6 +4437,11 @@ public sealed class LegacyOperatorController
private bool IsThemeActive() => _activeTab == LegacyOperatorTabId.Theme;
private MMoneyCoderSharp.Data.ThemeSession CurrentThemeNxtSession() =>
_timeProvider.GetLocalNow().Hour <= 12
? MMoneyCoderSharp.Data.ThemeSession.PreMarket
: MMoneyCoderSharp.Data.ThemeSession.AfterMarket;
private void BeginTabActivation(LegacyOperatorTabId tabId)
{
if (tabId == LegacyOperatorTabId.KospiIndustry)
@@ -4077,6 +4464,12 @@ public sealed class LegacyOperatorController
else if (tabId == LegacyOperatorTabId.Theme)
{
_themeActivationListLoaded = false;
if (_themeWorkflow is not null && _themeSnapshot is not null)
{
_themeSnapshot = _themeWorkflow.ResetForTabActivation(
_themeSnapshot,
CurrentThemeNxtSession());
}
}
else if (tabId == LegacyOperatorTabId.OverseasStocks)
{
@@ -4382,15 +4775,14 @@ public sealed class LegacyOperatorController
private void AppendOverseasRow(LegacyOverseasPlaylistDraft draft)
{
var isCandle = string.Equals(draft.BuilderKey, "s8010", StringComparison.Ordinal);
AppendPlaylistRow(new LegacyOperatorPlaylistRow(
$"legacy-overseas-{++_playlistSequence:D8}",
draft.IsEnabled,
draft.Selection.GroupCode,
draft.Title,
isCandle ? "캔들그래프" : draft.Selection.GraphicType,
draft.Selection.Subtype,
draft.InitialPageText,
draft.VisibleFields.Market,
draft.VisibleFields.StockName,
draft.VisibleFields.GraphicType,
draft.VisibleFields.Subtype,
draft.VisibleFields.PageText,
draft.Selection.DataCode,
draft.Detail,
draft.FadeDuration,
@@ -4398,23 +4790,20 @@ public sealed class LegacyOperatorController
PlayoutSelection: new MBN_STOCK_WEBVIEW.Core.Playout.Scenes.LegacySceneSelection(
draft.Selection.GroupCode,
draft.Selection.Subject,
isCandle ? string.Empty : draft.Selection.GraphicType,
draft.Selection.GraphicType,
draft.Selection.Subtype,
draft.Selection.DataCode)));
}
private void AppendThemeRow(LegacyThemePlaylistDraft draft)
{
var separator = draft.Detail.IndexOf(" · ", StringComparison.Ordinal);
var graphicType = separator > 0 ? draft.Detail[..separator] : draft.Detail;
var subtype = separator > 0 ? draft.Detail[(separator + 3)..] : string.Empty;
AppendPlaylistRow(new LegacyOperatorPlaylistRow(
$"legacy-theme-{++_playlistSequence:D8}",
true,
draft.Selection.GroupCode == "PAGED_THEME" ? "테마" : "테마_NXT",
draft.Title,
graphicType,
subtype,
draft.PlaylistGraphicType,
draft.PlaylistSubtype,
draft.PagePlan.InitialPageText,
draft.Selection.DataCode,
draft.Detail,
@@ -4881,5 +5270,55 @@ public sealed class LegacyOperatorController
}
}
private enum PendingNativeConfirmationKind
{
DeleteTheme,
DeleteExpert,
ClearThemeDraft,
DeleteAllComparisonPairs
}
private sealed record PendingNativeConfirmation(
PendingNativeConfirmationKind Kind,
MMoneyCoderSharp.Data.ThemeSelectionIdentity? ThemeIdentity,
MMoneyCoderSharp.Data.ExpertSelectionIdentity? ExpertIdentity,
long CatalogRevision,
long ComparisonRevision)
{
internal static PendingNativeConfirmation ForTheme(
MMoneyCoderSharp.Data.ThemeSelectionIdentity identity) =>
new(
PendingNativeConfirmationKind.DeleteTheme,
identity,
null,
0,
0);
internal static PendingNativeConfirmation ForExpert(
MMoneyCoderSharp.Data.ExpertSelectionIdentity identity) =>
new(
PendingNativeConfirmationKind.DeleteExpert,
null,
identity,
0,
0);
internal static PendingNativeConfirmation ForThemeDraftClear(long revision) =>
new(
PendingNativeConfirmationKind.ClearThemeDraft,
null,
null,
revision,
0);
internal static PendingNativeConfirmation ForComparisonDeleteAll(long revision) =>
new(
PendingNativeConfirmationKind.DeleteAllComparisonPairs,
null,
null,
0,
revision);
}
private void Touch() => _revision++;
}