feat: complete legacy UI and playout parity migration

This commit is contained in:
2026-07-15 13:11:38 +09:00
parent f14800656b
commit 6e0c275fdd
108 changed files with 43738 additions and 560 deletions

View File

@@ -14,6 +14,7 @@ public sealed partial class MainWindow : Window
private readonly CancellationTokenSource _lifetimeCancellation = new();
private readonly SemaphoreSlim _intentGate = new(1, 1);
private readonly LegacyOperatorController _controller;
private readonly LegacyIndustrySelectionWorkflow _industryWorkflow;
private DatabaseRuntime? _databaseRuntime;
private bool _closing;
private bool _intentBusy;
@@ -22,29 +23,144 @@ public sealed partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();
EnsureCloseConfirmationAttached();
ILegacyStockLookup stockLookup;
IIndustrySelectionService industrySelectionService;
IThemeSelectionService themeSelectionService;
IExpertSelectionService expertSelectionService;
ITradingHaltSelectionService tradingHaltSelectionService;
IOverseasIndustryIndexSearchService overseasIndustrySearchService;
IWorldStockSearchService worldStockSearchService;
IManualFinancialScreenService manualFinancialService;
IStockSearchService manualFinancialStockSearchService;
INamedPlaylistPersistenceService namedPlaylistPersistenceService;
IThemeCatalogPersistenceService themeCatalogPersistenceService;
IExpertCatalogPersistenceService expertCatalogPersistenceService;
IStockMasterIdentityValidationService stockMasterIdentityValidationService;
IOperatorCatalogSchemaValidationService operatorCatalogSchemaValidationService;
string? initializationError = null;
try
{
_databaseRuntime = DatabaseRuntime.CreateDefault();
DataQueryExecutor.Configure(_databaseRuntime.Executor);
stockLookup = new LegacyParityStockSearchService(_databaseRuntime.Executor);
industrySelectionService = new LegacyIndustrySelectionService(
_databaseRuntime.Executor);
themeSelectionService = new LegacyThemeSelectionService(
_databaseRuntime.Executor);
expertSelectionService = new LegacyExpertSelectionService(
_databaseRuntime.Executor);
tradingHaltSelectionService = new LegacyTradingHaltSelectionService(
_databaseRuntime.Executor);
overseasIndustrySearchService = new LegacyOverseasIndustryIndexSearchService(
_databaseRuntime.Executor);
worldStockSearchService = new LegacyWorldStockSearchService(
_databaseRuntime.Executor);
manualFinancialService = new LegacyManualFinancialScreenService(
_databaseRuntime.Executor,
new OracleManualFinancialMutationExecutor(
_databaseRuntime.ConnectionFactory,
_databaseRuntime.Options.Resilience));
manualFinancialStockSearchService = new LegacyStockSearchService(
_databaseRuntime.Executor);
namedPlaylistPersistenceService = new LegacyNamedPlaylistPersistenceService(
_databaseRuntime.Executor,
new OracleNamedPlaylistMutationExecutor(
_databaseRuntime.ConnectionFactory,
_databaseRuntime.Options.Resilience));
var operatorCatalogMutationExecutor = new OperatorCatalogMutationExecutor(
_databaseRuntime.ConnectionFactory,
_databaseRuntime.Options.Resilience);
themeCatalogPersistenceService = new LegacyThemeCatalogPersistenceService(
_databaseRuntime.Executor,
operatorCatalogMutationExecutor);
expertCatalogPersistenceService = new LegacyExpertCatalogPersistenceService(
_databaseRuntime.Executor,
operatorCatalogMutationExecutor);
stockMasterIdentityValidationService =
new LegacyStockMasterIdentityValidationService(_databaseRuntime.Executor);
operatorCatalogSchemaValidationService =
new LegacyOperatorCatalogSchemaValidationService(_databaseRuntime.Executor);
}
catch
{
initializationError =
"데이터베이스가 설정되지 않았습니다. 로컬 설정을 확인하세요.";
stockLookup = new UnavailableStockLookup(initializationError);
industrySelectionService = new UnavailableIndustrySelectionService(
initializationError);
themeSelectionService = new UnavailableThemeSelectionService(
initializationError);
expertSelectionService = new UnavailableExpertSelectionService(
initializationError);
tradingHaltSelectionService = new UnavailableTradingHaltSelectionService(
initializationError);
overseasIndustrySearchService = new UnavailableOverseasIndustryIndexSearchService(
initializationError);
worldStockSearchService = new UnavailableWorldStockSearchService(
initializationError);
manualFinancialService = new UnavailableManualFinancialScreenService(
initializationError);
manualFinancialStockSearchService = new UnavailableStockSearchService(
initializationError);
namedPlaylistPersistenceService = new UnavailableNamedPlaylistPersistenceService(
initializationError);
themeCatalogPersistenceService = new UnavailableThemeCatalogPersistenceService(
initializationError);
expertCatalogPersistenceService = new UnavailableExpertCatalogPersistenceService(
initializationError);
stockMasterIdentityValidationService =
new UnavailableStockMasterIdentityValidationService(initializationError);
operatorCatalogSchemaValidationService =
new UnavailableOperatorCatalogSchemaValidationService(initializationError);
DataQueryExecutor.Reset();
}
_controller = new LegacyOperatorController(stockLookup);
_industryWorkflow = new LegacyIndustrySelectionWorkflow(industrySelectionService);
var overseasWorkflow = new LegacyOverseasSelectionWorkflow(
overseasIndustrySearchService,
worldStockSearchService);
var comparisonStore = new LegacyComparisonPairFileStore(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MBN_STOCK_WEBVIEW",
"Data",
"종목비교.dat"),
Path.Combine(AppContext.BaseDirectory, "Data", "종목비교.dat"));
var manualListsWorkflow = CreateManualListsWorkflow(stockLookup);
_controller = new LegacyOperatorController(
stockLookup,
industryWorkflow: _industryWorkflow,
themeWorkflow: new LegacyThemeWorkflow(themeSelectionService),
expertWorkflow: new LegacyExpertWorkflowController(expertSelectionService),
tradingHaltWorkflow: new LegacyTradingHaltWorkflowController(
tradingHaltSelectionService),
comparisonWorkflow: new LegacyComparisonWorkflowController(
new LegacyComparisonDataService(stockLookup, worldStockSearchService),
comparisonStore),
manualFinancialWorkflow: new LegacyManualFinancialWorkflow(
manualFinancialService,
manualFinancialStockSearchService),
namedPlaylistWorkflow: new LegacyNamedPlaylistWorkflowController(
namedPlaylistPersistenceService),
overseasWorkflow: overseasWorkflow,
manualListsWorkflow: manualListsWorkflow,
operatorCatalogWorkflow: new LegacyOperatorCatalogWorkflowController(
themeSelectionService,
expertSelectionService,
themeCatalogPersistenceService,
expertCatalogPersistenceService,
manualFinancialStockSearchService,
stockMasterIdentityValidationService,
operatorCatalogSchemaValidationService));
if (initializationError is not null)
{
_controller.ReportError(initializationError);
}
InitializePlayoutRuntime();
Root.Loaded += OnRootLoaded;
Closed += OnClosed;
}
@@ -59,10 +175,15 @@ public sealed partial class MainWindow : Window
ConfigureWindow();
await InitializeWebViewAsync();
if (!_closing)
{
await ConnectPlayoutAsync(_lifetimeCancellation.Token);
}
}
private void ConfigureWindow()
{
EnsureCloseConfirmationAttached();
var windowHandle = WindowNative.GetWindowHandle(this);
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle);
var appWindow = AppWindow.GetFromWindowId(windowId);
@@ -208,15 +329,94 @@ public sealed partial class MainWindow : Window
return;
}
if (intent is LegacySearchStocksIntent searchIntent &&
!(searchIntent.Trigger == LegacySearchTrigger.Button &&
searchIntent.Text.Length == 0))
if (IsOperatorMutationIntent(intent) && IsOperatorMutationQuarantined())
{
PostState(_controller.ReportError(
"이전 저장 결과가 불명확하여 이번 실행의 추가 변경이 차단되었습니다. DB 또는 로컬 파일을 읽기 전용으로 대조한 뒤 앱을 다시 시작하세요."));
return;
}
if (_controller.Current.FixedSectionBatch is not null &&
IsOperatorMutationIntent(intent) &&
!IsFixedSectionBatchMutationIntent(intent))
{
PostState(_controller.ReportError(
"고정 컷 섹션의 수동 입력 중에는 현재 입력 단계만 변경할 수 있습니다. 완료하거나 취소한 뒤 다른 편성 작업을 실행하세요."));
return;
}
if (IsOperatorMutationIntent(intent) && !CanAcceptOperatorMutation())
{
PostState(_controller.ReportError(
"PREPARE 또는 송출 중에는 플레이리스트와 운영 데이터를 변경할 수 없습니다. TAKE OUT 후 다시 시도하세요."));
return;
}
if ((intent is LegacySearchStocksIntent searchIntent &&
!(searchIntent.Trigger == LegacySearchTrigger.Button &&
searchIntent.Text.Length == 0)) ||
intent is LegacySelectTabIntent or
LegacySearchThemesIntent or LegacySelectThemeIntent or
LegacyActivateThemeResultIntent or
LegacySearchExpertsIntent or LegacySelectExpertIntent or
LegacySearchTradingHaltsIntent or
LegacySearchComparisonDomesticIntent or
LegacySearchComparisonWorldIntent or
LegacySearchOverseasIndustriesIntent or
LegacySearchOverseasStocksIntent or
LegacyOpenOperatorCatalogIntent or
LegacyBeginUc4ThemeCatalogIntent or
LegacyEditUc4SelectedThemeIntent or
LegacyDeleteUc4SelectedThemeIntent or
LegacyBeginUc6ExpertCatalogIntent or
LegacyEditUc6SelectedExpertIntent or
LegacyDeleteUc6SelectedExpertIntent or
LegacySearchOperatorCatalogIntent or
LegacySelectOperatorCatalogResultIntent or
LegacyBeginCreateThemeCatalogIntent or
LegacyBeginCreateExpertCatalogIntent or
LegacySearchOperatorCatalogStocksIntent or
LegacyActivateOperatorCatalogStockIntent or
LegacySetOperatorCatalogDraftBuyAmountIntent or
LegacySaveOperatorCatalogIntent or
LegacyDeleteOperatorCatalogIntent or
LegacyAddComparisonPairIntent or
LegacyMoveComparisonPairIntent or
LegacyDeleteSelectedComparisonPairIntent or
LegacyDeleteAllComparisonPairsIntent or
LegacyOpenManualFinancialIntent or
LegacySearchManualFinancialIntent or
LegacyFindManualFinancialIntent or
LegacyLoadManualFinancialIntent or
LegacyActivateManualFinancialResultIntent or
LegacySearchManualFinancialStocksIntent or
LegacySaveManualFinancialIntent or
LegacyDeleteManualFinancialIntent or
LegacyDeleteAllManualFinancialIntent or
LegacyOpenManualListIntent or
LegacyRefreshManualListIntent or
LegacySaveManualNetSellIntent or
LegacySearchManualViIntent or
LegacySaveManualViIntent or
LegacyImportManualListsIntent or
LegacyRefreshNamedPlaylistsIntent or
LegacyLoadSelectedNamedPlaylistIntent or
LegacyLoadNamedPlaylistByIdIntent or
LegacyCreateNamedPlaylistIntent or
LegacySaveCurrentNamedPlaylistIntent or
LegacySaveCurrentNamedPlaylistToIntent or
LegacyDeleteSelectedNamedPlaylistIntent or
LegacyExecutePlayoutIntent or
LegacySetFadeDurationIntent or
LegacyToggleBackgroundIntent or
LegacyChooseBackgroundIntent)
{
_intentBusy = true;
PostState(_controller.Current);
}
var state = await HandleIntentAsync(intent, _lifetimeCancellation.Token);
ObserveOperatorMutationQuarantine(state);
if (!_closing)
{
_intentBusy = false;
@@ -272,6 +472,318 @@ public sealed partial class MainWindow : Window
LegacyDropSelectedCutsIntent => _controller.DropSelectedCutsOnPlaylist(),
LegacySetPlaylistEnabledIntent enabled =>
_controller.SetPlaylistRowEnabled(enabled.RowId, enabled.Enabled),
LegacySetAllPlaylistEnabledIntent enabled =>
_controller.SetAllPlaylistRowsEnabled(enabled.Enabled),
LegacySelectPlaylistRowIntent selection =>
_controller.SelectPlaylistRow(
selection.RowId,
selection.Control,
selection.Shift),
LegacyMoveSelectedPlaylistRowsIntent move =>
_controller.MoveSelectedPlaylistRows(move.Direction),
LegacyDeleteSelectedPlaylistRowsIntent =>
_controller.DeleteSelectedPlaylistRows(),
LegacyDeleteAllPlaylistRowsIntent =>
_controller.DeleteAllPlaylistRows(),
LegacyToggleActivePlaylistEnabledIntent =>
_controller.ToggleActivePlaylistRowEnabled(),
LegacySelectPlaylistBoundaryIntent boundary =>
_controller.SelectPlaylistBoundary(boundary.Last),
LegacySelectTabIntent tab =>
await _controller.SelectTabAsync(tab.TabId, cancellationToken),
LegacySwapTabsIntent tabs =>
await _controller.SwapTabsAsync(
tabs.Source,
tabs.Target,
cancellationToken),
LegacySetMovingAveragesIntent movingAverages =>
_controller.SetMovingAverages(movingAverages.Ma5, movingAverages.Ma20),
LegacyActivateFixedActionIntent action =>
await _controller.ActivateFixedActionAsync(
action.ActionId,
cancellationToken),
LegacyActivateFixedSectionIntent section =>
await _controller.ActivateFixedSection(
section.SectionIndex,
cancellationToken),
LegacySelectIndustryIntent industry =>
_controller.SelectIndustry(industry.Index, industry.DoubleClick),
LegacySwapIndustriesIntent =>
_controller.SwapIndustries(),
LegacyActivateIndustryActionIntent action =>
_controller.ActivateIndustryAction(action.ActionId),
LegacyActivateIndustrySectionIntent section =>
_controller.ActivateIndustrySection(section.SectionIndex),
LegacySelectOverseasFixedIndexIntent selection =>
_controller.SelectOverseasFixedIndex(selection.TargetId),
LegacySearchOverseasIndustriesIntent search =>
await _controller.SearchOverseasIndustriesAsync(
search.Query,
cancellationToken),
LegacySearchOverseasStocksIntent search =>
await _controller.SearchOverseasStocksAsync(
search.Query,
cancellationToken),
LegacySelectOverseasIndustryIntent selection =>
_controller.SelectOverseasIndustry(selection.SelectionId),
LegacySelectOverseasStockIntent selection =>
_controller.SelectOverseasStock(selection.SelectionId),
LegacyActivateOverseasActionIntent action =>
_controller.ActivateOverseasAction(action.ActionId),
LegacySearchComparisonDomesticIntent search =>
await _controller.SearchComparisonDomesticAsync(
search.Query,
cancellationToken),
LegacySearchComparisonWorldIntent search =>
await _controller.SearchComparisonWorldAsync(
search.Query,
cancellationToken),
LegacySelectComparisonDomesticIntent selection =>
_controller.SelectComparisonDomesticResult(selection.SelectionId),
LegacySelectComparisonWorldIntent selection =>
_controller.SelectComparisonWorldResult(selection.SelectionId),
LegacySelectComparisonFixedIntent selection =>
_controller.SelectComparisonFixedTarget(selection.TargetId),
LegacyChooseComparisonDomesticIntent selection =>
_controller.ChooseComparisonDomesticResult(selection.SelectionId),
LegacyChooseComparisonWorldIntent selection =>
_controller.ChooseComparisonWorldResult(selection.SelectionId),
LegacyChooseComparisonFixedIntent selection =>
_controller.ChooseComparisonFixedTarget(selection.TargetId),
LegacySwapComparisonTargetsIntent =>
_controller.SwapComparisonTargets(),
LegacyClearComparisonTargetsIntent =>
_controller.ClearComparisonTargets(),
LegacyAddComparisonPairIntent =>
await _controller.AddComparisonPairAsync(cancellationToken),
LegacySelectComparisonPairIntent selection =>
_controller.SelectComparisonPair(selection.RowId),
LegacyMoveComparisonPairIntent move =>
await _controller.MoveComparisonPairAsync(
move.Direction,
cancellationToken),
LegacyDeleteSelectedComparisonPairIntent =>
await _controller.DeleteComparisonPairAsync(
all: false,
cancellationToken),
LegacyDeleteAllComparisonPairsIntent =>
await _controller.DeleteComparisonPairAsync(
all: true,
cancellationToken),
LegacyActivateComparisonActionIntent action =>
_controller.ActivateComparisonAction(action.ActionId),
LegacyActivateComparisonSectionIntent section =>
_controller.ActivateComparisonSection(section.SectionIndex),
LegacySearchThemesIntent search =>
await _controller.SearchThemesAsync(
search.Query,
search.NxtSession,
cancellationToken),
LegacySelectThemeIntent selection =>
await _controller.SelectThemeAsync(
selection.ResultId,
cancellationToken),
LegacyActivateThemeResultIntent activation =>
await _controller.ActivateThemeResultAsync(
activation.ResultId,
cancellationToken),
LegacySetThemeSortIntent sort =>
_controller.SetThemeSort(sort.SortId),
LegacyActivateThemeActionIntent action =>
_controller.ActivateThemeAction(action.ActionId),
LegacySearchExpertsIntent search =>
await _controller.SearchExpertsAsync(search.Query, cancellationToken),
LegacySelectExpertIntent selection =>
await _controller.SelectExpertAsync(
selection.SelectionId,
cancellationToken),
LegacyActivateExpertActionIntent action =>
_controller.ActivateExpertAction(action.ActionId),
LegacySearchTradingHaltsIntent search =>
await _controller.SearchTradingHaltsAsync(
search.Query,
cancellationToken),
LegacySelectTradingHaltIntent selection =>
_controller.SelectTradingHalt(selection.SelectionId),
LegacyToggleTradingHaltSortIntent =>
_controller.ToggleTradingHaltSort(),
LegacyActivateTradingHaltActionIntent action =>
_controller.ActivateTradingHaltAction(action.ActionId),
LegacyOpenOperatorCatalogIntent catalog =>
await _controller.OpenOperatorCatalogAsync(
catalog.Entity,
cancellationToken),
LegacyBeginUc4ThemeCatalogIntent =>
await _controller.BeginThemeCatalogFromUc4Async(cancellationToken),
LegacyEditUc4SelectedThemeIntent =>
await _controller.EditSelectedThemeCatalogAsync(cancellationToken),
LegacyDeleteUc4SelectedThemeIntent =>
await _controller.DeleteSelectedThemeCatalogAsync(cancellationToken),
LegacyBeginUc6ExpertCatalogIntent =>
await _controller.BeginExpertCatalogFromUc6Async(cancellationToken),
LegacyEditUc6SelectedExpertIntent =>
await _controller.EditSelectedExpertCatalogAsync(cancellationToken),
LegacyDeleteUc6SelectedExpertIntent =>
await _controller.DeleteSelectedExpertCatalogAsync(cancellationToken),
LegacyCloseOperatorCatalogIntent =>
_controller.CloseOperatorCatalog(),
LegacySearchOperatorCatalogIntent search =>
await _controller.SearchOperatorCatalogAsync(
search.Query,
search.NxtSession,
cancellationToken),
LegacySelectOperatorCatalogResultIntent selection =>
await _controller.SelectOperatorCatalogResultAsync(
selection.ResultId,
cancellationToken),
LegacyBeginCreateThemeCatalogIntent create =>
await _controller.BeginCreateThemeCatalogAsync(
create.Market,
cancellationToken),
LegacyBeginCreateExpertCatalogIntent =>
await _controller.BeginCreateExpertCatalogAsync(cancellationToken),
LegacySearchOperatorCatalogStocksIntent search =>
await _controller.SearchOperatorCatalogStocksAsync(
search.Query,
cancellationToken),
LegacySelectOperatorCatalogStockIntent selection =>
_controller.SelectOperatorCatalogStock(selection.ResultId),
LegacyAddOperatorCatalogStockIntent add =>
_controller.AddOperatorCatalogStock(add.BuyAmount),
LegacyActivateOperatorCatalogStockIntent activation =>
_controller.ActivateOperatorCatalogStock(
activation.ResultId,
activation.BuyAmount),
LegacyMoveOperatorCatalogDraftRowIntent move =>
_controller.MoveOperatorCatalogDraftRow(move.RowId, move.Direction),
LegacyRemoveOperatorCatalogDraftRowIntent remove =>
_controller.RemoveOperatorCatalogDraftRow(remove.RowId),
LegacySetOperatorCatalogDraftBuyAmountIntent amount =>
_controller.SetOperatorCatalogDraftBuyAmount(
amount.RowId,
amount.BuyAmount),
LegacySaveOperatorCatalogIntent save =>
await _controller.SaveOperatorCatalogAsync(
save.Name,
cancellationToken),
LegacyDeleteOperatorCatalogIntent =>
await _controller.DeleteOperatorCatalogAsync(cancellationToken),
LegacyRefreshNamedPlaylistsIntent =>
await _controller.RefreshNamedPlaylistsAsync(cancellationToken),
LegacySelectNamedPlaylistIntent selection =>
_controller.SelectNamedPlaylist(selection.DefinitionId),
LegacyLoadSelectedNamedPlaylistIntent =>
await _controller.LoadSelectedNamedPlaylistAsync(cancellationToken),
LegacyLoadNamedPlaylistByIdIntent load =>
await _controller.LoadNamedPlaylistByIdAsync(
load.DefinitionId,
cancellationToken),
LegacyCreateNamedPlaylistIntent create =>
await _controller.CreateNamedPlaylistAsync(
create.Title,
cancellationToken),
LegacySaveCurrentNamedPlaylistIntent =>
await _controller.SaveCurrentNamedPlaylistAsync(cancellationToken),
LegacySaveCurrentNamedPlaylistToIntent save =>
await _controller.SaveCurrentNamedPlaylistToAsync(
save.DefinitionId,
cancellationToken),
LegacyDeleteSelectedNamedPlaylistIntent =>
await _controller.DeleteSelectedNamedPlaylistAsync(cancellationToken),
LegacyOpenManualFinancialIntent manual =>
await _controller.OpenManualFinancialAsync(
manual.Screen,
cancellationToken),
LegacyCloseManualFinancialIntent =>
_controller.CloseManualFinancial(),
LegacySearchManualFinancialIntent search =>
await _controller.SearchManualFinancialAsync(
search.Query,
cancellationToken),
LegacyFindManualFinancialIntent find =>
await _controller.FindManualFinancialAsync(
find.Query,
find.Direction,
find.Generation,
cancellationToken),
LegacyToggleManualFinancialNameSortIntent sort =>
_controller.ToggleManualFinancialNameSort(sort.Generation),
LegacyLoadManualFinancialIntent load =>
await _controller.LoadManualFinancialAsync(
load.ResultId,
cancellationToken),
LegacyActivateManualFinancialResultIntent activation =>
await _controller.ActivateManualFinancialResultAsync(
activation.ResultId,
cancellationToken),
LegacyBeginManualFinancialCreateIntent =>
_controller.BeginManualFinancialCreate(),
LegacySearchManualFinancialStocksIntent search =>
await _controller.SearchManualFinancialStocksAsync(
search.StockName,
cancellationToken),
LegacySelectManualFinancialStockIntent selection =>
_controller.SelectManualFinancialStock(selection.ResultId),
LegacySaveManualFinancialIntent save =>
await _controller.SaveManualFinancialAsync(
save.Record,
cancellationToken),
LegacyDeleteManualFinancialIntent =>
await _controller.DeleteManualFinancialAsync(
all: false,
cancellationToken),
LegacyDeleteAllManualFinancialIntent =>
await _controller.DeleteManualFinancialAsync(
all: true,
cancellationToken),
LegacyActivateManualFinancialActionIntent action =>
_controller.ActivateManualFinancialAction(action.ActionId),
LegacyOpenManualListIntent manual =>
manual.Screen == LegacyManualListScreen.NetSell
? await _controller.OpenManualNetSellAsync(
manual.Audience,
cancellationToken)
: await _controller.OpenManualViAsync(cancellationToken),
LegacyCloseManualListIntent =>
_controller.CloseManualLists(),
LegacyRefreshManualListIntent =>
await _controller.RefreshManualListsAsync(cancellationToken),
LegacySetManualNetSellCellIntent cell =>
_controller.SetManualNetSellCell(cell.RowId, cell.Field, cell.Value),
LegacySaveManualNetSellIntent =>
await _controller.SaveManualNetSellAsync(cancellationToken),
LegacyAddManualNetSellCutIntent =>
_controller.AddManualNetSellPlaylistRow(),
LegacySearchManualViIntent search =>
await _controller.SearchManualViAsync(search.Query, cancellationToken),
LegacySelectManualViResultIntent result =>
_controller.SelectManualViSearchResult(result.ResultId),
LegacyAddManualViResultIntent result =>
_controller.AddManualViSearchResult(result.ResultId),
LegacyMoveManualViItemIntent move =>
_controller.MoveManualViItem(move.ItemId, move.Direction),
LegacyDeleteManualViItemIntent item =>
_controller.DeleteManualViItem(item.ItemId),
LegacyDeleteAllManualViItemsIntent =>
_controller.DeleteAllManualViItems(),
LegacySaveManualViIntent =>
await _controller.SaveManualViAsync(cancellationToken),
LegacyAddManualViCutIntent =>
_controller.AddManualViPlaylistRow(),
LegacyConfirmManualListIntent =>
await _controller.ConfirmManualListsAsync(cancellationToken),
LegacyImportManualListsIntent =>
await _controller.ImportLegacyManualListsAsync(cancellationToken),
LegacyExecutePlayoutIntent playout =>
await ExecuteOperatorPlayoutAsync(
playout.Command,
cancellationToken),
LegacySetFadeDurationIntent fade =>
await SetFadeDurationAsync(fade.Duration, cancellationToken),
LegacyToggleBackgroundIntent =>
await ToggleBackgroundAsync(cancellationToken),
LegacyChooseBackgroundIntent =>
await ChooseBackgroundAsync(cancellationToken),
LegacyDismissDialogIntent => _controller.DismissDialog(),
_ => _controller.ReportError("지원하지 않는 화면 요청입니다.")
};
@@ -282,12 +794,17 @@ public sealed partial class MainWindow : Window
if (!_closing && _webViewReady && Browser.CoreWebView2 is not null)
{
Browser.CoreWebView2.PostWebMessageAsJson(
LegacyBridgeProtocol.SerializeState(snapshot, _intentBusy));
LegacyBridgeProtocol.SerializeState(
snapshot,
_intentBusy,
CreatePlayoutSnapshot()));
}
}
private void OnProcessFailed(object? sender, CoreWebView2ProcessFailedEventArgs args)
{
ObserveOperatorMutationQuarantine(
_controller.InvalidateOperatorCatalogBrowserCorrelation());
if (!_closing)
{
ShowError("WebView2 프로세스가 중단되었습니다.", args.ProcessFailedKind.ToString());
@@ -314,9 +831,14 @@ public sealed partial class MainWindow : Window
}
_closing = true;
ObserveOperatorMutationQuarantine(
_controller.InvalidateOperatorCatalogBrowserCorrelation());
Root.Loaded -= OnRootLoaded;
Closed -= OnClosed;
DetachCloseConfirmation();
_lifetimeCancellation.Cancel();
ShutdownPlayoutRuntime();
ShutdownManualListsRuntime();
DataQueryExecutor.Reset();
if (Browser.CoreWebView2 is not null)
@@ -335,6 +857,166 @@ public sealed partial class MainWindow : Window
// cancellation and release the gate without racing disposed objects.
}
private sealed class UnavailableThemeCatalogPersistenceService
: IThemeCatalogPersistenceService
{
private readonly string _message;
public UnavailableThemeCatalogPersistenceService(string message)
{
_message = message;
}
public bool CanMutate => false;
public Task<string> SuggestNextCodeAsync(
ThemeMarket market,
CancellationToken cancellationToken = default) =>
Task.FromException<string>(new InvalidOperationException(_message));
public Task<OperatorCatalogMutationReceipt> CreateAsync(
ThemeCatalogDefinition definition,
CancellationToken cancellationToken = default) =>
Task.FromException<OperatorCatalogMutationReceipt>(
new InvalidOperationException(_message));
public Task<OperatorCatalogMutationReceipt> ReplaceAsync(
ThemeSelectionIdentity expectedIdentity,
string newTitle,
IReadOnlyList<ThemeCatalogItem> items,
CancellationToken cancellationToken = default) =>
Task.FromException<OperatorCatalogMutationReceipt>(
new InvalidOperationException(_message));
public Task<OperatorCatalogMutationReceipt> DeleteAsync(
ThemeSelectionIdentity expectedIdentity,
CancellationToken cancellationToken = default) =>
Task.FromException<OperatorCatalogMutationReceipt>(
new InvalidOperationException(_message));
}
private sealed class UnavailableExpertCatalogPersistenceService
: IExpertCatalogPersistenceService
{
private readonly string _message;
public UnavailableExpertCatalogPersistenceService(string message)
{
_message = message;
}
public bool CanMutate => false;
public Task<string> SuggestNextCodeAsync(
CancellationToken cancellationToken = default) =>
Task.FromException<string>(new InvalidOperationException(_message));
public Task<OperatorCatalogMutationReceipt> CreateAsync(
ExpertCatalogDefinition definition,
CancellationToken cancellationToken = default) =>
Task.FromException<OperatorCatalogMutationReceipt>(
new InvalidOperationException(_message));
public Task<OperatorCatalogMutationReceipt> ReplaceAsync(
ExpertSelectionIdentity expectedIdentity,
string newName,
IReadOnlyList<ExpertCatalogRecommendation> recommendations,
CancellationToken cancellationToken = default) =>
Task.FromException<OperatorCatalogMutationReceipt>(
new InvalidOperationException(_message));
public Task<OperatorCatalogMutationReceipt> DeleteAsync(
ExpertSelectionIdentity expectedIdentity,
CancellationToken cancellationToken = default) =>
Task.FromException<OperatorCatalogMutationReceipt>(
new InvalidOperationException(_message));
}
private sealed class UnavailableStockMasterIdentityValidationService
: IStockMasterIdentityValidationService
{
private readonly string _message;
public UnavailableStockMasterIdentityValidationService(string message)
{
_message = message;
}
public Task<IReadOnlyList<StockMasterIdentity>> ValidateThemeItemsAsync(
IReadOnlyList<ThemeCatalogItem> items,
CancellationToken cancellationToken = default) =>
Task.FromException<IReadOnlyList<StockMasterIdentity>>(
new InvalidOperationException(_message));
public Task<IReadOnlyList<StockMasterIdentity>> ValidateExpertRecommendationsAsync(
IReadOnlyList<ExpertCatalogRecommendation> recommendations,
CancellationToken cancellationToken = default) =>
Task.FromException<IReadOnlyList<StockMasterIdentity>>(
new InvalidOperationException(_message));
}
private sealed class UnavailableOperatorCatalogSchemaValidationService
: IOperatorCatalogSchemaValidationService
{
private readonly string _message;
public UnavailableOperatorCatalogSchemaValidationService(string message)
{
_message = message;
}
public Task<OperatorCatalogSchemaValidationResult> ValidateAsync(
CancellationToken cancellationToken = default) =>
Task.FromException<OperatorCatalogSchemaValidationResult>(
new InvalidOperationException(_message));
}
private sealed class UnavailableNamedPlaylistPersistenceService
: INamedPlaylistPersistenceService
{
private readonly string _message;
public UnavailableNamedPlaylistPersistenceService(string message)
{
_message = message;
}
public bool CanMutate => false;
public Task<NamedPlaylistListResult> ListAsync(
int maximumResults = LegacyNamedPlaylistPersistenceService.DefaultMaximumDefinitions,
CancellationToken cancellationToken = default) =>
Task.FromException<NamedPlaylistListResult>(
new InvalidOperationException(_message));
public Task<string> SuggestNextProgramCodeAsync(
CancellationToken cancellationToken = default) =>
Task.FromException<string>(new InvalidOperationException(_message));
public Task<NamedPlaylistDocument> LoadAsync(
string programCode,
CancellationToken cancellationToken = default) =>
Task.FromException<NamedPlaylistDocument>(
new InvalidOperationException(_message));
public Task CreateDefinitionAsync(
string programCode,
string title,
CancellationToken cancellationToken = default) =>
Task.FromException(new InvalidOperationException(_message));
public Task ReplaceItemsAsync(
string programCode,
IReadOnlyList<NamedPlaylistStoredItem> items,
CancellationToken cancellationToken = default) =>
Task.FromException(new InvalidOperationException(_message));
public Task DeleteAsync(
string programCode,
CancellationToken cancellationToken = default) =>
Task.FromException(new InvalidOperationException(_message));
}
private sealed class UnavailableStockLookup : ILegacyStockLookup
{
private readonly string _message;
@@ -350,4 +1032,185 @@ public sealed partial class MainWindow : Window
Task.FromException<LegacyStockSearchData>(
new InvalidOperationException(_message));
}
private sealed class UnavailableIndustrySelectionService : IIndustrySelectionService
{
private readonly string _message;
public UnavailableIndustrySelectionService(string message)
{
_message = message;
}
public Task<IReadOnlyList<IndustrySelection>> GetAsync(
IndustryMarket market,
CancellationToken cancellationToken = default) =>
Task.FromException<IReadOnlyList<IndustrySelection>>(
new InvalidOperationException(_message));
}
private sealed class UnavailableThemeSelectionService : IThemeSelectionService
{
private readonly string _message;
public UnavailableThemeSelectionService(string message)
{
_message = message;
}
public Task<ThemeSearchResult> SearchAsync(
string query,
ThemeSession nxtSession,
int maximumResults = LegacyThemeSelectionService.DefaultMaximumResults,
CancellationToken cancellationToken = default) =>
Task.FromException<ThemeSearchResult>(new InvalidOperationException(_message));
public Task<ThemePreviewResult> PreviewAsync(
ThemeSelectionIdentity identity,
int maximumItems = LegacyThemeSelectionService.DefaultMaximumPreviewItems,
CancellationToken cancellationToken = default) =>
Task.FromException<ThemePreviewResult>(new InvalidOperationException(_message));
}
private sealed class UnavailableExpertSelectionService : IExpertSelectionService
{
private readonly string _message;
public UnavailableExpertSelectionService(string message)
{
_message = message;
}
public Task<ExpertSearchResult> SearchAsync(
string query = "",
int maximumResults = LegacyExpertSelectionService.DefaultMaximumResults,
CancellationToken cancellationToken = default) =>
Task.FromException<ExpertSearchResult>(new InvalidOperationException(_message));
public Task<ExpertPreviewResult> PreviewAsync(
ExpertSelectionIdentity identity,
int maximumItems = LegacyExpertSelectionService.DefaultMaximumPreviewItems,
CancellationToken cancellationToken = default) =>
Task.FromException<ExpertPreviewResult>(new InvalidOperationException(_message));
}
private sealed class UnavailableTradingHaltSelectionService : ITradingHaltSelectionService
{
private readonly string _message;
public UnavailableTradingHaltSelectionService(string message)
{
_message = message;
}
public Task<TradingHaltSelectionResult> SearchAsync(
string query = "",
int maximumResults = LegacyTradingHaltSelectionService.DefaultMaximumResults,
CancellationToken cancellationToken = default) =>
Task.FromException<TradingHaltSelectionResult>(
new InvalidOperationException(_message));
}
private sealed class UnavailableManualFinancialScreenService
: IManualFinancialScreenService
{
private readonly string _message;
public UnavailableManualFinancialScreenService(string message)
{
_message = message;
}
public bool CanMutate => false;
public Task<ManualFinancialSearchResult> SearchAsync(
ManualFinancialScreenKind screen,
string query = "",
int maximumResults = LegacyManualFinancialScreenService.DefaultMaximumResults,
CancellationToken cancellationToken = default) =>
Task.FromException<ManualFinancialSearchResult>(
new InvalidOperationException(_message));
public Task<ManualFinancialSnapshot> GetAsync(
ManualFinancialIdentity identity,
CancellationToken cancellationToken = default) =>
Task.FromException<ManualFinancialSnapshot>(
new InvalidOperationException(_message));
public Task<ManualFinancialMutationReceipt> CreateAsync(
ManualFinancialRecord record,
CancellationToken cancellationToken = default) =>
Task.FromException<ManualFinancialMutationReceipt>(
new InvalidOperationException(_message));
public Task<ManualFinancialMutationReceipt> UpdateAsync(
ManualFinancialSnapshot expected,
ManualFinancialRecord replacement,
CancellationToken cancellationToken = default) =>
Task.FromException<ManualFinancialMutationReceipt>(
new InvalidOperationException(_message));
public Task<ManualFinancialMutationReceipt> DeleteAsync(
ManualFinancialSnapshot expected,
CancellationToken cancellationToken = default) =>
Task.FromException<ManualFinancialMutationReceipt>(
new InvalidOperationException(_message));
public Task<ManualFinancialMutationReceipt> DeleteAllAsync(
ManualFinancialScreenKind screen,
CancellationToken cancellationToken = default) =>
Task.FromException<ManualFinancialMutationReceipt>(
new InvalidOperationException(_message));
}
private sealed class UnavailableStockSearchService : IStockSearchService
{
private readonly string _message;
public UnavailableStockSearchService(string message)
{
_message = message;
}
public Task<StockSearchResult> SearchAsync(
string query,
int maximumResults = LegacyStockSearchService.DefaultMaximumResults,
CancellationToken cancellationToken = default) =>
Task.FromException<StockSearchResult>(new InvalidOperationException(_message));
}
private sealed class UnavailableWorldStockSearchService : IWorldStockSearchService
{
private readonly string _message;
public UnavailableWorldStockSearchService(string message)
{
_message = message;
}
public Task<WorldStockSearchResult> SearchAsync(
string query,
int maximumResults = LegacyWorldStockSearchService.DefaultMaximumResults,
CancellationToken cancellationToken = default) =>
Task.FromException<WorldStockSearchResult>(
new InvalidOperationException(_message));
}
private sealed class UnavailableOverseasIndustryIndexSearchService
: IOverseasIndustryIndexSearchService
{
private readonly string _message;
public UnavailableOverseasIndustryIndexSearchService(string message)
{
_message = message;
}
public Task<OverseasIndustryIndexSearchResult> SearchAsync(
string query,
int maximumResults = LegacyOverseasIndustryIndexSearchService.DefaultMaximumResults,
CancellationToken cancellationToken = default) =>
Task.FromException<OverseasIndustryIndexSearchResult>(
new InvalidOperationException(_message));
}
}