2738 lines
109 KiB
C#
2738 lines
109 KiB
C#
using System.Globalization;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using MBN_STOCK_WEBVIEW.LegacyApplication;
|
|
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
|
|
using MMoneyCoderSharp.Data;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.LegacyBridge;
|
|
|
|
public abstract record LegacyUiIntent;
|
|
|
|
public sealed record LegacyReadyIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchStocksIntent(string Text, LegacySearchTrigger Trigger)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectStockIntent(int ResultIndex) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyCutPointerDownIntent(
|
|
int PhysicalIndex,
|
|
bool Control,
|
|
bool Shift,
|
|
int X,
|
|
int Y,
|
|
long TimestampMilliseconds,
|
|
bool AllowAppend) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyCutKeyDownIntent(
|
|
LegacyCutKeyboardKey Key,
|
|
bool Control,
|
|
bool Shift) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyClearCutSelectionIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDropSelectedCutsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySetPlaylistEnabledIntent(string RowId, bool Enabled)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacySetAllPlaylistEnabledIntent(bool Enabled)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectPlaylistRowIntent(
|
|
string RowId,
|
|
bool Control,
|
|
bool Shift) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivatePlaylistRowIntent(string RowId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyMoveSelectedPlaylistRowsIntent(
|
|
LegacyPlaylistMoveDirection Direction) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyReorderPlaylistRowsIntent(
|
|
string SourceRowId,
|
|
string TargetRowId,
|
|
LegacyDragDropPosition Position) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteSelectedPlaylistRowsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteAllPlaylistRowsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyToggleActivePlaylistEnabledIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectPlaylistBoundaryIntent(bool Last) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectTabIntent(LegacyOperatorTabId TabId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySwapTabsIntent(
|
|
LegacyOperatorTabId Source,
|
|
LegacyOperatorTabId Target) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyHoverSwapTabsIntent(
|
|
LegacyOperatorTabId Source,
|
|
LegacyOperatorTabId Target,
|
|
int ExpectedSourceIndex,
|
|
int ExpectedTargetIndex) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySetMovingAveragesIntent(bool Ma5, bool Ma20) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateFixedActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateFixedSectionIntent(int SectionIndex) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectIndustryIntent(int Index, bool DoubleClick) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySetIndustryComparisonTextIntent(
|
|
LegacyIndustryComparisonSlot Slot,
|
|
string Value) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySwapIndustriesIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateIndustryActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateIndustrySectionIntent(int SectionIndex) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectOverseasFixedIndexIntent(string TargetId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchOverseasIndustriesIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchOverseasStocksIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectOverseasIndustryIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectOverseasStockIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateOverseasActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchComparisonDomesticIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchComparisonWorldIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectComparisonDomesticIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectComparisonWorldIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectComparisonFixedIntent(string TargetId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyChooseComparisonDomesticIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyChooseComparisonWorldIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyChooseComparisonFixedIntent(string TargetId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySwapComparisonTargetsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyClearComparisonTargetsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyAddComparisonPairIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectComparisonPairIntent(string RowId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyMoveComparisonPairIntent(LegacyComparisonPairMoveDirection Direction)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteSelectedComparisonPairIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteAllComparisonPairsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateComparisonActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateComparisonSectionIntent(int SectionIndex) : LegacyUiIntent;
|
|
|
|
// UC4 has no NXT-session control. The native controller chooses it from its
|
|
// local clock, so the WebView can supply only the search text.
|
|
public sealed record LegacySearchThemesIntent(string Query)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectThemeIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateThemeResultIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySetThemeSortIntent(string SortId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateThemeActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchExpertsIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectExpertIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateExpertActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchTradingHaltsIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectTradingHaltIntent(string SelectionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyToggleTradingHaltSortIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateTradingHaltActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyOpenOperatorCatalogIntent(LegacyOperatorCatalogEntity Entity)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacyBeginUc4ThemeCatalogIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyEditUc4SelectedThemeIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteUc4SelectedThemeIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyBeginUc6ExpertCatalogIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyEditUc6SelectedExpertIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteUc6SelectedExpertIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyCloseOperatorCatalogIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchOperatorCatalogIntent(string Query, ThemeSession NxtSession)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectOperatorCatalogResultIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyBeginCreateThemeCatalogIntent(ThemeMarket Market) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyBeginCreateExpertCatalogIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchOperatorCatalogStocksIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectOperatorCatalogStockIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyAddOperatorCatalogStockIntent(decimal? BuyAmount) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateOperatorCatalogStockIntent(
|
|
string ResultId,
|
|
decimal? BuyAmount) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyMoveOperatorCatalogDraftRowIntent(
|
|
string RowId,
|
|
LegacyOperatorCatalogMoveDirection Direction) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyReorderOperatorCatalogDraftRowIntent(
|
|
string SourceRowId,
|
|
string TargetRowId,
|
|
LegacyDragDropPosition Position) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyRemoveOperatorCatalogDraftRowIntent(string RowId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectOperatorCatalogDraftRowIntent(string RowId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyRemoveActiveOperatorCatalogDraftRowIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyClearOperatorCatalogDraftRowsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySetOperatorCatalogDraftBuyAmountIntent(
|
|
string RowId,
|
|
decimal? BuyAmount) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyCheckOperatorCatalogThemeNameIntent(string Name) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySaveOperatorCatalogIntent(string Name) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyConfirmNativeDialogIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyCancelNativeDialogIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyOpenManualFinancialIntent(ManualFinancialScreenKind Screen)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacyCloseManualFinancialIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchManualFinancialIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyFindManualFinancialIntent(
|
|
string Query,
|
|
LegacyManualFinancialFindDirection Direction,
|
|
long Generation) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyToggleManualFinancialNameSortIntent(long Generation) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyLoadManualFinancialIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateManualFinancialResultIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyBeginManualFinancialCreateIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchManualFinancialStocksIntent(string StockName) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectManualFinancialStockIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySaveManualFinancialIntent(ManualFinancialRecord Record)
|
|
: LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteManualFinancialIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteAllManualFinancialIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyActivateManualFinancialActionIntent(string ActionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyOpenManualListIntent(
|
|
LegacyManualListScreen Screen,
|
|
S5025ManualAudience Audience) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyCloseManualListIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyRefreshManualListIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySetManualNetSellCellIntent(
|
|
string RowId,
|
|
LegacyManualNetSellField Field,
|
|
string Value) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySaveManualNetSellIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyAddManualNetSellCutIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySearchManualViIntent(string Query) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectManualViResultIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyAddManualViResultIntent(string ResultId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyMoveManualViItemIntent(
|
|
string ItemId,
|
|
LegacyManualListMoveDirection Direction) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteManualViItemIntent(string ItemId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteAllManualViItemsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySaveManualViIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyAddManualViCutIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyConfirmManualListIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyImportManualListsIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyRefreshNamedPlaylistsIntent(string RequestId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySelectNamedPlaylistIntent(
|
|
string DefinitionId,
|
|
string RequestId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyLoadSelectedNamedPlaylistIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyLoadNamedPlaylistByIdIntent(string DefinitionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyCreateNamedPlaylistIntent(string Title, string RequestId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacySaveCurrentNamedPlaylistIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacySaveCurrentNamedPlaylistToIntent(string DefinitionId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDeleteSelectedNamedPlaylistIntent(string RequestId) : LegacyUiIntent;
|
|
|
|
public sealed record LegacyDismissDialogIntent : LegacyUiIntent;
|
|
|
|
public sealed record LegacyInteractionMetrics(
|
|
int CutDragWidthDips,
|
|
int CutDragHeightDips,
|
|
uint SourceDpi = 96,
|
|
double HostRasterizationScale = 1d)
|
|
{
|
|
public const uint CssPixelsPerInch = 96;
|
|
public const int FallbackCutDragWidth = 4;
|
|
public const int FallbackCutDragHeight = 4;
|
|
|
|
public static LegacyInteractionMetrics Default { get; } = new(
|
|
FallbackCutDragWidth,
|
|
FallbackCutDragHeight,
|
|
CssPixelsPerInch,
|
|
1d);
|
|
|
|
public static LegacyInteractionMetrics FromPixelsAtDpi(
|
|
int cutDragWidthPixels,
|
|
int cutDragHeightPixels,
|
|
uint sourceDpi,
|
|
double hostRasterizationScale = 1d) =>
|
|
sourceDpi == 0
|
|
? Default with
|
|
{
|
|
HostRasterizationScale = NormalizeHostScale(hostRasterizationScale)
|
|
}
|
|
: new(
|
|
ToDips(
|
|
cutDragWidthPixels,
|
|
sourceDpi,
|
|
FallbackCutDragWidth),
|
|
ToDips(
|
|
cutDragHeightPixels,
|
|
sourceDpi,
|
|
FallbackCutDragHeight),
|
|
sourceDpi,
|
|
NormalizeHostScale(hostRasterizationScale));
|
|
|
|
internal static LegacyInteractionMetrics Normalize(LegacyInteractionMetrics? metrics) =>
|
|
metrics is null
|
|
? Default
|
|
: new(
|
|
metrics.CutDragWidthDips > 0
|
|
? metrics.CutDragWidthDips
|
|
: FallbackCutDragWidth,
|
|
metrics.CutDragHeightDips > 0
|
|
? metrics.CutDragHeightDips
|
|
: FallbackCutDragHeight,
|
|
metrics.SourceDpi > 0 ? metrics.SourceDpi : CssPixelsPerInch,
|
|
NormalizeHostScale(metrics.HostRasterizationScale));
|
|
|
|
private static double NormalizeHostScale(double hostRasterizationScale) =>
|
|
double.IsFinite(hostRasterizationScale) && hostRasterizationScale > 0d
|
|
? hostRasterizationScale
|
|
: 1d;
|
|
|
|
private static int ToDips(int pixelsAtSourceDpi, uint sourceDpi, int fallback) =>
|
|
pixelsAtSourceDpi <= 0
|
|
? fallback
|
|
: Math.Max(
|
|
1,
|
|
(int)Math.Round(
|
|
pixelsAtSourceDpi * (double)CssPixelsPerInch / sourceDpi,
|
|
MidpointRounding.AwayFromZero));
|
|
}
|
|
|
|
public static class LegacyBridgeProtocol
|
|
{
|
|
public const string TrustedHost = "legacy-parity.mbn.local";
|
|
|
|
private static readonly JsonSerializerOptions SerializerOptions = CreateSerializerOptions();
|
|
|
|
public static bool IsTrustedSource(string? source)
|
|
{
|
|
if (!Uri.TryCreate(source, UriKind.Absolute, out var uri))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(uri.Host, TrustedHost, StringComparison.OrdinalIgnoreCase) &&
|
|
uri.IsDefaultPort;
|
|
}
|
|
|
|
public static bool TryParseIntent(
|
|
string json,
|
|
out LegacyUiIntent? intent,
|
|
out string? error)
|
|
{
|
|
intent = null;
|
|
error = null;
|
|
|
|
try
|
|
{
|
|
using var document = JsonDocument.Parse(json, new JsonDocumentOptions
|
|
{
|
|
AllowTrailingCommas = false,
|
|
CommentHandling = JsonCommentHandling.Disallow,
|
|
MaxDepth = 8
|
|
});
|
|
var root = document.RootElement;
|
|
if (root.ValueKind != JsonValueKind.Object ||
|
|
!HasOnlyProperties(root, "type", "payload") ||
|
|
!TryString(root, "type", 64, out var type) ||
|
|
!root.TryGetProperty("payload", out var payload) ||
|
|
payload.ValueKind != JsonValueKind.Object)
|
|
{
|
|
error = "올바르지 않은 UI 요청입니다.";
|
|
return false;
|
|
}
|
|
|
|
intent = type switch
|
|
{
|
|
"ready" => ParseReady(payload),
|
|
"search-stocks" => ParseSearch(payload),
|
|
"select-stock" => ParseStockSelection(payload),
|
|
"cut-pointer-down" => ParseCutPointer(payload),
|
|
"cut-key-down" => ParseCutKeyDown(payload),
|
|
"clear-cut-selection" => ParseClearCutSelection(payload),
|
|
"drop-selected-cuts" => ParseDrop(payload),
|
|
"set-playlist-enabled" => ParseEnabled(payload),
|
|
"set-all-playlist-enabled" => ParseAllEnabled(payload),
|
|
"select-playlist-row" => ParsePlaylistSelection(payload),
|
|
"activate-playlist-row" => ParsePlaylistActivation(payload),
|
|
"move-selected-playlist-rows" => ParsePlaylistMove(payload),
|
|
"reorder-playlist-rows" => ParsePlaylistReorder(payload),
|
|
"delete-selected-playlist-rows" => ParseDeleteSelected(payload),
|
|
"delete-all-playlist-rows" => ParseDeleteAll(payload),
|
|
"toggle-active-playlist-enabled" => ParseToggleActiveEnabled(payload),
|
|
"select-playlist-boundary" => ParsePlaylistBoundary(payload),
|
|
"select-tab" => ParseTabSelection(payload),
|
|
"swap-tabs" => ParseTabSwap(payload),
|
|
"hover-swap-tabs" => ParseTabHoverSwap(payload),
|
|
"set-moving-averages" => ParseMovingAverages(payload),
|
|
"activate-fixed-action" => ParseFixedAction(payload),
|
|
"activate-fixed-section" => ParseFixedSection(payload),
|
|
"select-industry" => ParseIndustrySelection(payload, false),
|
|
"double-click-industry" => ParseIndustrySelection(payload, true),
|
|
"set-industry-comparison-text" => ParseIndustryComparisonText(payload),
|
|
"swap-industries" => ParseSwapIndustries(payload),
|
|
"activate-industry-action" => ParseIndustryAction(payload),
|
|
"activate-industry-section" => ParseSectionIndex(
|
|
payload,
|
|
static index => new LegacyActivateIndustrySectionIntent(index)),
|
|
"select-overseas-fixed-index" => ParseOverseasFixedIndex(payload),
|
|
"search-overseas-industries" => ParseOverseasSearch(payload, false),
|
|
"search-overseas-stocks" => ParseOverseasSearch(payload, true),
|
|
"select-overseas-industry" => ParseOverseasSelection(payload, false),
|
|
"select-overseas-stock" => ParseOverseasSelection(payload, true),
|
|
"activate-overseas-action" => ParseOverseasAction(payload),
|
|
"search-comparison-domestic" => ParseComparisonSearch(payload, false),
|
|
"search-comparison-world" => ParseComparisonSearch(payload, true),
|
|
"select-comparison-domestic" => ParseComparisonSelection(payload, false),
|
|
"select-comparison-world" => ParseComparisonSelection(payload, true),
|
|
"select-comparison-fixed" => ParseComparisonFixedSelection(payload),
|
|
"choose-comparison-domestic" => ParseComparisonChoice(payload, false),
|
|
"choose-comparison-world" => ParseComparisonChoice(payload, true),
|
|
"choose-comparison-fixed" => ParseComparisonFixed(payload),
|
|
"swap-comparison-targets" => ParseComparisonSwap(payload),
|
|
"clear-comparison-targets" => ParseComparisonClear(payload),
|
|
"add-comparison-pair" => ParseComparisonAdd(payload),
|
|
"select-comparison-pair" => ParseComparisonPairSelection(payload),
|
|
"move-comparison-pair" => ParseComparisonPairMove(payload),
|
|
"delete-selected-comparison-pair" => ParseComparisonDeleteSelected(payload),
|
|
"delete-all-comparison-pairs" => ParseComparisonDeleteAll(payload),
|
|
"activate-comparison-action" => ParseComparisonAction(payload),
|
|
"activate-comparison-section" => ParseSectionIndex(
|
|
payload,
|
|
static index => new LegacyActivateComparisonSectionIntent(index)),
|
|
"search-themes" => ParseThemeSearch(payload),
|
|
"select-theme" => ParseThemeSelection(payload),
|
|
"activate-theme-result" => ParseThemeResultActivation(payload),
|
|
"set-theme-sort" => ParseThemeSort(payload),
|
|
"activate-theme-action" => ParseThemeAction(payload),
|
|
"search-experts" => ParseExpertSearch(payload),
|
|
"select-expert" => ParseExpertSelection(payload),
|
|
"activate-expert-action" => ParseExpertAction(payload),
|
|
"search-trading-halts" => ParseTradingHaltSearch(payload),
|
|
"select-trading-halt" => ParseTradingHaltSelection(payload),
|
|
"toggle-trading-halt-sort" => ParseTradingHaltSort(payload),
|
|
"activate-trading-halt-action" => ParseTradingHaltAction(payload),
|
|
"open-operator-catalog" => ParseOperatorCatalogOpen(payload),
|
|
"begin-uc4-theme-catalog" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyBeginUc4ThemeCatalogIntent()),
|
|
"edit-uc4-selected-theme" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyEditUc4SelectedThemeIntent()),
|
|
"delete-uc4-selected-theme" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyDeleteUc4SelectedThemeIntent()),
|
|
"begin-uc6-expert-catalog" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyBeginUc6ExpertCatalogIntent()),
|
|
"edit-uc6-selected-expert" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyEditUc6SelectedExpertIntent()),
|
|
"delete-uc6-selected-expert" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyDeleteUc6SelectedExpertIntent()),
|
|
"close-operator-catalog" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyCloseOperatorCatalogIntent()),
|
|
"search-operator-catalog" => ParseOperatorCatalogSearch(payload),
|
|
"select-operator-catalog-result" => ParseOperatorCatalogResult(payload),
|
|
"begin-create-theme-catalog" => ParseOperatorCatalogThemeCreate(payload),
|
|
"begin-create-expert-catalog" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyBeginCreateExpertCatalogIntent()),
|
|
"search-operator-catalog-stocks" => ParseOperatorCatalogStockSearch(payload),
|
|
"select-operator-catalog-stock" => ParseOperatorCatalogStockSelection(payload),
|
|
"add-operator-catalog-stock" => ParseOperatorCatalogStockAdd(payload),
|
|
"activate-operator-catalog-stock" =>
|
|
ParseOperatorCatalogStockActivation(payload),
|
|
"move-operator-catalog-draft-row" => ParseOperatorCatalogDraftMove(payload),
|
|
"reorder-operator-catalog-draft-row" =>
|
|
ParseOperatorCatalogDraftReorder(payload),
|
|
"remove-operator-catalog-draft-row" => ParseOperatorCatalogDraftRemove(payload),
|
|
"select-operator-catalog-draft-row" =>
|
|
ParseOperatorCatalogDraftSelection(payload),
|
|
"remove-active-operator-catalog-draft-row" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyRemoveActiveOperatorCatalogDraftRowIntent()),
|
|
"clear-operator-catalog-draft-rows" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyClearOperatorCatalogDraftRowsIntent()),
|
|
"set-operator-catalog-draft-buy-amount" =>
|
|
ParseOperatorCatalogDraftBuyAmount(payload),
|
|
"check-operator-catalog-theme-name" =>
|
|
ParseOperatorCatalogThemeNameCheck(payload),
|
|
"save-operator-catalog" => ParseOperatorCatalogSave(payload),
|
|
"confirm-native-dialog" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyConfirmNativeDialogIntent()),
|
|
"cancel-native-dialog" => ParseOperatorCatalogEmpty(
|
|
payload,
|
|
static () => new LegacyCancelNativeDialogIntent()),
|
|
"refresh-named-playlists" => ParseNamedPlaylistRefresh(payload),
|
|
"select-named-playlist" => ParseNamedPlaylistSelection(payload),
|
|
"load-named-playlist-by-id" => ParseNamedPlaylistLoadById(payload),
|
|
"load-selected-named-playlist" => ParseNamedPlaylistEmpty(
|
|
payload,
|
|
static () => new LegacyLoadSelectedNamedPlaylistIntent()),
|
|
"create-named-playlist" => ParseNamedPlaylistCreate(payload),
|
|
"save-current-named-playlist" => ParseNamedPlaylistEmpty(
|
|
payload,
|
|
static () => new LegacySaveCurrentNamedPlaylistIntent()),
|
|
"save-current-named-playlist-to" => ParseNamedPlaylistSaveTo(payload),
|
|
"delete-selected-named-playlist" =>
|
|
ParseNamedPlaylistDelete(payload),
|
|
"open-manual-financial" => ParseManualFinancialOpen(payload),
|
|
"close-manual-financial" => ParseManualFinancialEmpty(
|
|
payload,
|
|
static () => new LegacyCloseManualFinancialIntent()),
|
|
"search-manual-financial" => ParseManualFinancialSearch(payload),
|
|
"find-manual-financial" => ParseManualFinancialFind(payload),
|
|
"toggle-manual-financial-name-sort" =>
|
|
ParseManualFinancialNameSort(payload),
|
|
"load-manual-financial" => ParseManualFinancialLoad(payload),
|
|
"activate-manual-financial-result" =>
|
|
ParseManualFinancialResultActivation(payload),
|
|
"begin-manual-financial-create" => ParseManualFinancialEmpty(
|
|
payload,
|
|
static () => new LegacyBeginManualFinancialCreateIntent()),
|
|
"search-manual-financial-stocks" => ParseManualFinancialStockSearch(payload),
|
|
"select-manual-financial-stock" => ParseManualFinancialStockSelection(payload),
|
|
"save-manual-financial" => ParseManualFinancialSave(payload),
|
|
"delete-manual-financial" => ParseManualFinancialEmpty(
|
|
payload,
|
|
static () => new LegacyDeleteManualFinancialIntent()),
|
|
"delete-all-manual-financial" => ParseManualFinancialEmpty(
|
|
payload,
|
|
static () => new LegacyDeleteAllManualFinancialIntent()),
|
|
"activate-manual-financial-action" => ParseManualFinancialAction(payload),
|
|
"open-manual-list" => ParseManualListOpen(payload),
|
|
"close-manual-list" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacyCloseManualListIntent()),
|
|
"refresh-manual-list" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacyRefreshManualListIntent()),
|
|
"set-manual-net-sell-cell" => ParseManualNetSellCell(payload),
|
|
"save-manual-net-sell" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacySaveManualNetSellIntent()),
|
|
"add-manual-net-sell-cut" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacyAddManualNetSellCutIntent()),
|
|
"search-manual-vi" => ParseManualViSearch(payload),
|
|
"select-manual-vi-result" => ParseManualViSelection(payload),
|
|
"add-manual-vi-result" => ParseManualViResult(payload),
|
|
"move-manual-vi-item" => ParseManualViMove(payload),
|
|
"delete-manual-vi-item" => ParseManualViDelete(payload),
|
|
"delete-all-manual-vi-items" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacyDeleteAllManualViItemsIntent()),
|
|
"save-manual-vi" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacySaveManualViIntent()),
|
|
"add-manual-vi-cut" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacyAddManualViCutIntent()),
|
|
"confirm-manual-list" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacyConfirmManualListIntent()),
|
|
"import-manual-lists" => ParseManualListEmpty(
|
|
payload,
|
|
static () => new LegacyImportManualListsIntent()),
|
|
"dismiss-dialog" => ParseDismiss(payload),
|
|
_ => LegacyPlayoutUiIntentParser.Parse(type, payload)
|
|
};
|
|
|
|
if (intent is null)
|
|
{
|
|
error = "지원하지 않는 UI 요청입니다.";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
error = "UI 요청 JSON을 읽을 수 없습니다.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static string SerializeState(
|
|
LegacyOperatorSnapshot snapshot,
|
|
bool isBusy = false,
|
|
LegacyOperatorPlayoutSnapshot? playout = null,
|
|
LegacyInteractionMetrics? interactionMetrics = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(snapshot);
|
|
var normalizedInteractionMetrics = LegacyInteractionMetrics.Normalize(
|
|
interactionMetrics);
|
|
var payload = new
|
|
{
|
|
snapshot.Revision,
|
|
snapshot.SearchText,
|
|
snapshot.SearchResults,
|
|
snapshot.SelectedStockIndex,
|
|
snapshot.CutRows,
|
|
playlist = snapshot.Playlist.Select(row => new
|
|
{
|
|
row.RowId,
|
|
row.IsEnabled,
|
|
row.IsSelected,
|
|
row.IsActive,
|
|
row.MarketText,
|
|
row.StockName,
|
|
row.GraphicType,
|
|
row.Subtype,
|
|
row.PageText
|
|
}),
|
|
snapshot.Dialog,
|
|
snapshot.StatusMessage,
|
|
snapshot.StatusKind,
|
|
commandResult = snapshot.CommandResult is null ? null : new
|
|
{
|
|
snapshot.CommandResult.Sequence,
|
|
snapshot.CommandResult.Command,
|
|
snapshot.CommandResult.TargetId,
|
|
snapshot.CommandResult.Succeeded
|
|
},
|
|
tabs = snapshot.Tabs ?? [],
|
|
snapshot.MovingAverages,
|
|
snapshot.FixedCatalog,
|
|
snapshot.FixedSectionBatch,
|
|
industry = snapshot.Industry is null ? null : new
|
|
{
|
|
snapshot.Industry.Market,
|
|
industries = snapshot.Industry.Industries.Select((row, index) => new
|
|
{
|
|
index,
|
|
name = row.IndustryName
|
|
}),
|
|
snapshot.Industry.SelectedIndex,
|
|
selectedName = snapshot.Industry.SelectedIndustry?.IndustryName,
|
|
firstName = snapshot.Industry.FirstText,
|
|
secondName = snapshot.Industry.SecondText,
|
|
actions = snapshot.Industry.Actions.Select(action => new
|
|
{
|
|
actionId = action.ActionId,
|
|
action.Label,
|
|
action.Section,
|
|
action.Category
|
|
})
|
|
},
|
|
overseas = snapshot.Overseas is null ? null : new
|
|
{
|
|
snapshot.Overseas.Revision,
|
|
fixedIndexTargets = snapshot.Overseas.FixedIndexTargets.Select(target => new
|
|
{
|
|
target.TargetId,
|
|
target.DisplayName
|
|
}),
|
|
selectedFixedIndexId = snapshot.Overseas.SelectedFixedIndex?.TargetId,
|
|
selectedFixedIndexName = snapshot.Overseas.SelectedFixedIndex?.DisplayName,
|
|
industry = new
|
|
{
|
|
snapshot.Overseas.Industry.Status,
|
|
snapshot.Overseas.Industry.Query,
|
|
snapshot.Overseas.Industry.ResultCount,
|
|
snapshot.Overseas.Industry.IsTruncated,
|
|
snapshot.Overseas.Industry.SelectedId,
|
|
selectedName = snapshot.Overseas.Industry.Selected?.KoreanName,
|
|
snapshot.Overseas.Industry.Error,
|
|
results = snapshot.Overseas.Industry.Results.Select(row => new
|
|
{
|
|
row.SelectionId,
|
|
displayName = row.KoreanName
|
|
})
|
|
},
|
|
stock = new
|
|
{
|
|
snapshot.Overseas.Stock.Status,
|
|
snapshot.Overseas.Stock.Query,
|
|
snapshot.Overseas.Stock.ResultCount,
|
|
snapshot.Overseas.Stock.IsTruncated,
|
|
snapshot.Overseas.Stock.SelectedId,
|
|
selectedName = snapshot.Overseas.Stock.Selected?.InputName,
|
|
snapshot.Overseas.Stock.Error,
|
|
results = snapshot.Overseas.Stock.Results.Select(row => new
|
|
{
|
|
row.SelectionId,
|
|
displayName = row.InputName
|
|
})
|
|
},
|
|
actions = snapshot.Overseas.Actions.Select(action =>
|
|
{
|
|
var availability = GetOverseasActionAvailability(snapshot.Overseas, action);
|
|
return new
|
|
{
|
|
action.ActionId,
|
|
action.Label,
|
|
action.Source,
|
|
action.Kind,
|
|
action.PeriodDays,
|
|
availability.IsAvailable,
|
|
availability.Reason
|
|
};
|
|
})
|
|
},
|
|
comparison = snapshot.Comparison is null ? null : new
|
|
{
|
|
snapshot.Comparison.IsBusy,
|
|
domesticSearch = new
|
|
{
|
|
snapshot.Comparison.DomesticSearch.Query,
|
|
snapshot.Comparison.DomesticSearch.Status,
|
|
results = snapshot.Comparison.DomesticSearch.Results.Select(row => new
|
|
{
|
|
row.SelectionId,
|
|
row.DisplayName,
|
|
row.Metadata,
|
|
row.Kind
|
|
}),
|
|
snapshot.Comparison.DomesticSearch.SelectedResultId,
|
|
snapshot.Comparison.DomesticSearch.TotalRowCount,
|
|
snapshot.Comparison.DomesticSearch.ErrorMessage
|
|
},
|
|
worldSearch = new
|
|
{
|
|
snapshot.Comparison.WorldSearch.Query,
|
|
snapshot.Comparison.WorldSearch.Status,
|
|
results = snapshot.Comparison.WorldSearch.Results.Select(row => new
|
|
{
|
|
row.SelectionId,
|
|
row.DisplayName,
|
|
row.Metadata,
|
|
row.Kind
|
|
}),
|
|
snapshot.Comparison.WorldSearch.SelectedResultId,
|
|
snapshot.Comparison.WorldSearch.TotalRowCount,
|
|
snapshot.Comparison.WorldSearch.ErrorMessage
|
|
},
|
|
fixedTargets = snapshot.Comparison.FixedTargets.Select(row => new
|
|
{
|
|
targetId = row.SelectionId,
|
|
row.DisplayName,
|
|
row.Metadata,
|
|
row.Kind
|
|
}),
|
|
snapshot.Comparison.SelectedFixedTargetId,
|
|
firstTarget = snapshot.Comparison.FirstTarget is null ? null : new
|
|
{
|
|
snapshot.Comparison.FirstTarget.SelectionId,
|
|
snapshot.Comparison.FirstTarget.DisplayName,
|
|
snapshot.Comparison.FirstTarget.Metadata,
|
|
snapshot.Comparison.FirstTarget.Kind
|
|
},
|
|
secondTarget = snapshot.Comparison.SecondTarget is null ? null : new
|
|
{
|
|
snapshot.Comparison.SecondTarget.SelectionId,
|
|
snapshot.Comparison.SecondTarget.DisplayName,
|
|
snapshot.Comparison.SecondTarget.Metadata,
|
|
snapshot.Comparison.SecondTarget.Kind
|
|
},
|
|
savedPairs = snapshot.Comparison.SavedPairs.Select(row => new
|
|
{
|
|
row.RowId,
|
|
row.RowNumber,
|
|
row.DisplayLabel,
|
|
row.IsSelected
|
|
}),
|
|
snapshot.Comparison.SelectedPairId,
|
|
snapshot.Comparison.Actions,
|
|
snapshot.Comparison.StatusMessage,
|
|
snapshot.Comparison.StatusKind
|
|
},
|
|
theme = snapshot.Theme is null ? null : new
|
|
{
|
|
snapshot.Theme.Query,
|
|
snapshot.Theme.NxtSession,
|
|
snapshot.Theme.SortId,
|
|
searchResults = snapshot.Theme.SearchResults.Select(row => new
|
|
{
|
|
row.ResultId,
|
|
row.DisplayTitle,
|
|
row.Market,
|
|
row.Session
|
|
}),
|
|
snapshot.Theme.SearchIsTruncated,
|
|
snapshot.Theme.SelectedResultId,
|
|
snapshot.Theme.SelectedTheme,
|
|
previewItems = snapshot.Theme.PreviewItems.Select(row => new
|
|
{
|
|
row.InputIndex,
|
|
row.ItemName
|
|
}),
|
|
snapshot.Theme.PreviewIsTruncated,
|
|
sorts = LegacyThemeWorkflow.Sorts.Select(sort => new
|
|
{
|
|
sort.Id,
|
|
sort.Label
|
|
}),
|
|
actions = LegacyThemeWorkflow.Actions.Select(action =>
|
|
{
|
|
var state = snapshot.Theme.ActionStates.Single(item =>
|
|
string.Equals(item.ActionId, action.Id, StringComparison.Ordinal));
|
|
return new
|
|
{
|
|
actionId = action.Id,
|
|
action.Label,
|
|
state.IsAvailable,
|
|
state.UnavailableReason
|
|
};
|
|
})
|
|
},
|
|
expert = snapshot.Expert is null ? null : new
|
|
{
|
|
search = new
|
|
{
|
|
snapshot.Expert.Search.Query,
|
|
snapshot.Expert.Search.Status,
|
|
results = snapshot.Expert.Search.Results.Select(row => new
|
|
{
|
|
row.SelectionId,
|
|
row.ExpertName
|
|
}),
|
|
snapshot.Expert.Search.IsTruncated,
|
|
snapshot.Expert.Search.ErrorMessage
|
|
},
|
|
selectedExpert = snapshot.Expert.SelectedExpert is null ? null : new
|
|
{
|
|
snapshot.Expert.SelectedExpert.SelectionId,
|
|
snapshot.Expert.SelectedExpert.ExpertName
|
|
},
|
|
preview = new
|
|
{
|
|
snapshot.Expert.Preview.Status,
|
|
items = snapshot.Expert.Preview.Items.Select(row => new
|
|
{
|
|
row.PlayIndex,
|
|
row.StockName,
|
|
row.BuyAmount
|
|
}),
|
|
snapshot.Expert.Preview.IsTruncated,
|
|
snapshot.Expert.Preview.PageCount,
|
|
snapshot.Expert.Preview.MaximumPageCount,
|
|
snapshot.Expert.Preview.ErrorMessage
|
|
},
|
|
snapshot.Expert.Actions,
|
|
snapshot.Expert.StatusMessage,
|
|
snapshot.Expert.StatusKind
|
|
},
|
|
tradingHalt = snapshot.TradingHalt is null ? null : new
|
|
{
|
|
snapshot.TradingHalt.Query,
|
|
snapshot.TradingHalt.Status,
|
|
results = snapshot.TradingHalt.Results.Select(row => new
|
|
{
|
|
row.SelectionId,
|
|
row.Market,
|
|
row.DisplayName,
|
|
row.IsSelected
|
|
}),
|
|
selected = snapshot.TradingHalt.Selected is null ? null : new
|
|
{
|
|
snapshot.TradingHalt.Selected.SelectionId,
|
|
snapshot.TradingHalt.Selected.Market,
|
|
snapshot.TradingHalt.Selected.DisplayName
|
|
},
|
|
snapshot.TradingHalt.IsTruncated,
|
|
snapshot.TradingHalt.NameSort,
|
|
snapshot.TradingHalt.Ma5,
|
|
snapshot.TradingHalt.Ma20,
|
|
snapshot.TradingHalt.Actions,
|
|
snapshot.TradingHalt.ErrorMessage,
|
|
snapshot.TradingHalt.StatusMessage,
|
|
snapshot.TradingHalt.StatusKind
|
|
},
|
|
operatorCatalog = snapshot.OperatorCatalog is null ? null : new
|
|
{
|
|
snapshot.OperatorCatalog.Revision,
|
|
snapshot.OperatorCatalog.IsOpen,
|
|
snapshot.OperatorCatalog.Entity,
|
|
snapshot.OperatorCatalog.Mode,
|
|
snapshot.OperatorCatalog.Query,
|
|
snapshot.OperatorCatalog.NxtSession,
|
|
snapshot.OperatorCatalog.NewThemeMarket,
|
|
results = snapshot.OperatorCatalog.Results.Select(row => new
|
|
{
|
|
row.ResultId,
|
|
row.CodeLabel,
|
|
row.DisplayName,
|
|
row.Scope,
|
|
row.IsSelected
|
|
}),
|
|
snapshot.OperatorCatalog.ResultsAreTruncated,
|
|
snapshot.OperatorCatalog.SelectedResultId,
|
|
snapshot.OperatorCatalog.CodeLabel,
|
|
snapshot.OperatorCatalog.EditorName,
|
|
snapshot.OperatorCatalog.ThemeNameAvailability,
|
|
snapshot.OperatorCatalog.CheckedThemeName,
|
|
draftRows = snapshot.OperatorCatalog.DraftRows.Select(row => new
|
|
{
|
|
row.RowId,
|
|
row.DisplayName,
|
|
row.Market,
|
|
row.BuyAmount,
|
|
row.Position
|
|
}),
|
|
snapshot.OperatorCatalog.ActiveDraftRowId,
|
|
snapshot.OperatorCatalog.StockQuery,
|
|
stockResults = snapshot.OperatorCatalog.StockResults.Select(row => new
|
|
{
|
|
row.ResultId,
|
|
row.DisplayName,
|
|
row.Market,
|
|
row.IsCompatible,
|
|
row.IsSelected
|
|
}),
|
|
snapshot.OperatorCatalog.StockResultsAreTruncated,
|
|
snapshot.OperatorCatalog.SelectedStockResultId,
|
|
snapshot.OperatorCatalog.SchemaValidated,
|
|
snapshot.OperatorCatalog.WritesQuarantined,
|
|
snapshot.OperatorCatalog.CanBeginCreate,
|
|
snapshot.OperatorCatalog.CanCheckThemeName,
|
|
snapshot.OperatorCatalog.CanSave,
|
|
snapshot.OperatorCatalog.CanDelete,
|
|
snapshot.OperatorCatalog.CanAddStock,
|
|
snapshot.OperatorCatalog.StatusMessage,
|
|
snapshot.OperatorCatalog.StatusKind
|
|
},
|
|
manualFinancial = snapshot.ManualFinancial is null ? null : new
|
|
{
|
|
screen = ManualFinancialScreenId(snapshot.ManualFinancial.Screen),
|
|
snapshot.ManualFinancial.Definition.Label,
|
|
generation = snapshot.ManualFinancial.Revision,
|
|
snapshot.ManualFinancial.Query,
|
|
snapshot.ManualFinancial.FindQuery,
|
|
nameSortDirection = snapshot.ManualFinancial.NameSortDirection ==
|
|
LegacyManualFinancialNameSortDirection.Ascending
|
|
? "ascending"
|
|
: "descending",
|
|
rows = snapshot.ManualFinancial.Rows.Select(row => new
|
|
{
|
|
row.ResultId,
|
|
row.StockName
|
|
}),
|
|
snapshot.ManualFinancial.ListIsTruncated,
|
|
snapshot.ManualFinancial.SelectedRowId,
|
|
selectedRecord = ProjectManualFinancialRecord(
|
|
snapshot.ManualFinancial.SelectedRecord),
|
|
snapshot.ManualFinancial.IsDetailFresh,
|
|
snapshot.ManualFinancial.StockQuery,
|
|
stockCandidates = snapshot.ManualFinancial.StockCandidates.Select(row => new
|
|
{
|
|
row.ResultId,
|
|
row.Market,
|
|
row.Name
|
|
}),
|
|
snapshot.ManualFinancial.StockSearchIsTruncated,
|
|
snapshot.ManualFinancial.SelectedStockResultId,
|
|
verifiedStock = snapshot.ManualFinancial.VerifiedStock is null ? null : new
|
|
{
|
|
snapshot.ManualFinancial.VerifiedStock.Market,
|
|
snapshot.ManualFinancial.VerifiedStock.Name
|
|
},
|
|
snapshot.ManualFinancial.Status,
|
|
snapshot.ManualFinancial.WritesQuarantined,
|
|
snapshot.ManualFinancial.LastMessage,
|
|
snapshot.ManualFinancial.CanSave,
|
|
snapshot.ManualFinancial.CanDelete,
|
|
snapshot.ManualFinancial.CanMaterializePlaylist,
|
|
action = new
|
|
{
|
|
snapshot.ManualFinancial.Definition.ActionId,
|
|
snapshot.ManualFinancial.Definition.Label
|
|
}
|
|
},
|
|
manualLists = snapshot.ManualLists is null ? null : new
|
|
{
|
|
snapshot.ManualLists.IsOpen,
|
|
screen = snapshot.ManualLists.Screen switch
|
|
{
|
|
LegacyManualListScreen.NetSell => "net-sell",
|
|
LegacyManualListScreen.Vi => "vi",
|
|
_ => null
|
|
},
|
|
audience = snapshot.ManualLists.Audience switch
|
|
{
|
|
S5025ManualAudience.Individual => "individual",
|
|
S5025ManualAudience.Foreign => "foreign",
|
|
S5025ManualAudience.Institution => "institution",
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
},
|
|
snapshot.ManualLists.IsAvailable,
|
|
snapshot.ManualLists.AvailabilityCode,
|
|
snapshot.ManualLists.AvailabilityMessage,
|
|
snapshot.ManualLists.WritesQuarantined,
|
|
netRows = snapshot.ManualLists.NetRows.Select(row => new
|
|
{
|
|
row.RowId,
|
|
row.LeftName,
|
|
row.LeftAmount,
|
|
row.RightName,
|
|
row.RightAmount
|
|
}),
|
|
snapshot.ManualLists.NetRowsAreFresh,
|
|
viItems = snapshot.ManualLists.ViItems.Select(row => new
|
|
{
|
|
row.ItemId,
|
|
row.Index,
|
|
row.Name
|
|
}),
|
|
snapshot.ManualLists.ViPageCount,
|
|
snapshot.ManualLists.ViItemsAreFresh,
|
|
snapshot.ManualLists.SearchQuery,
|
|
searchResults = snapshot.ManualLists.SearchResults.Select(row => new
|
|
{
|
|
row.ResultId,
|
|
row.DisplayName,
|
|
row.MarketText
|
|
}),
|
|
snapshot.ManualLists.SelectedSearchResultId,
|
|
snapshot.ManualLists.StatusMessage,
|
|
snapshot.ManualLists.StatusKind,
|
|
snapshot.ManualLists.CanSave,
|
|
snapshot.ManualLists.CanMaterializePlaylist
|
|
},
|
|
namedPlaylist = snapshot.NamedPlaylist is null ? null : new
|
|
{
|
|
snapshot.NamedPlaylist.Revision,
|
|
snapshot.NamedPlaylist.ReadStatus,
|
|
definitions = snapshot.NamedPlaylist.Definitions.Select(definition => new
|
|
{
|
|
definition.DefinitionId,
|
|
definition.Title,
|
|
definition.IsSelected
|
|
}),
|
|
snapshot.NamedPlaylist.IsListTruncated,
|
|
snapshot.NamedPlaylist.SelectedDefinitionId,
|
|
snapshot.NamedPlaylist.SelectedTitle,
|
|
rowCount = snapshot.NamedPlaylist.Rows.Count,
|
|
snapshot.NamedPlaylist.ListFreshness,
|
|
snapshot.NamedPlaylist.DocumentFreshness,
|
|
snapshot.NamedPlaylist.IsWriteInProgress,
|
|
snapshot.NamedPlaylist.IsWriteQuarantined,
|
|
snapshot.NamedPlaylist.CanMutate,
|
|
snapshot.NamedPlaylist.CanCreate,
|
|
snapshot.NamedPlaylist.CanLoad,
|
|
snapshot.NamedPlaylist.CanSave,
|
|
snapshot.NamedPlaylist.CanDelete,
|
|
snapshot.NamedPlaylist.LastMutationOutcome,
|
|
snapshot.NamedPlaylist.StatusMessage,
|
|
snapshot.NamedPlaylist.StatusKind
|
|
},
|
|
playout = playout is null ? null : new
|
|
{
|
|
playout.Mode,
|
|
playout.ConnectionState,
|
|
playout.Phase,
|
|
playout.IsProcessRunning,
|
|
playout.IsConnected,
|
|
playout.IsCommandAvailable,
|
|
playout.LiveTakeInAllowed,
|
|
playout.IsPlayCompletionPending,
|
|
playout.IsTakeOutCompletionPending,
|
|
playout.OutcomeUnknown,
|
|
playout.PreparedCode,
|
|
playout.OnAirCode,
|
|
playout.CurrentCueIndexZeroBased,
|
|
playout.CurrentEntryId,
|
|
playout.PageIndexZeroBased,
|
|
playout.PageCount,
|
|
playout.PageSize,
|
|
playout.ItemCount,
|
|
playout.CurrentPageItemCount,
|
|
playout.IsLastPage,
|
|
playout.NextKind,
|
|
playout.IsBusy,
|
|
playout.RefreshActive,
|
|
playout.RefreshCompletedCount,
|
|
playout.RefreshMaximumCount,
|
|
playout.RefreshLimitReached,
|
|
playout.RefreshNextAtUtc,
|
|
playout.RefreshLastSuccessAtUtc,
|
|
playout.RefreshFaultCode,
|
|
playout.FadeDuration,
|
|
playout.BackgroundEnabled,
|
|
playout.BackgroundFileName,
|
|
playout.CanChangeBackground,
|
|
playout.Message
|
|
},
|
|
interactionMetrics = new
|
|
{
|
|
cutDragWidthDips = normalizedInteractionMetrics.CutDragWidthDips,
|
|
cutDragHeightDips = normalizedInteractionMetrics.CutDragHeightDips,
|
|
coordinateSpace = "host-dip",
|
|
sourceDpi = normalizedInteractionMetrics.SourceDpi,
|
|
hostRasterizationScale =
|
|
normalizedInteractionMetrics.HostRasterizationScale
|
|
},
|
|
isBusy
|
|
};
|
|
|
|
return JsonSerializer.Serialize(new { type = "state", payload }, SerializerOptions);
|
|
}
|
|
|
|
private static LegacyOverseasActionAvailability GetOverseasActionAvailability(
|
|
LegacyOverseasWorkflowSnapshot snapshot,
|
|
LegacyOverseasActionDefinition action) =>
|
|
action.Source switch
|
|
{
|
|
LegacyOverseasActionSource.FixedIndex when snapshot.SelectedFixedIndex is null =>
|
|
new(false, "해외지수를 선택하십시오."),
|
|
LegacyOverseasActionSource.Industry when snapshot.Industry.Selected is null =>
|
|
new(false, "해외업종을 선택하십시오."),
|
|
LegacyOverseasActionSource.Stock when snapshot.Stock.Selected is null =>
|
|
new(false, "해외종목을 선택하십시오."),
|
|
LegacyOverseasActionSource.Stock when snapshot.Stock.Selected!.NationCode.Length != 2 =>
|
|
new(false, "선택한 해외종목의 국가 코드는 두 글자여야 합니다."),
|
|
_ => new(true, null)
|
|
};
|
|
|
|
private static LegacyUiIntent? ParseReady(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyReadyIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseSearch(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "text", "trigger") ||
|
|
!TryString(
|
|
payload,
|
|
"text",
|
|
LegacyParityStockSearchService.LegacyTextBoxMaximumLength,
|
|
out var text) ||
|
|
!TryString(payload, "trigger", 16, out var triggerText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var trigger = triggerText switch
|
|
{
|
|
"button" => LegacySearchTrigger.Button,
|
|
"enter" => LegacySearchTrigger.Enter,
|
|
_ => (LegacySearchTrigger?)null
|
|
};
|
|
return trigger.HasValue ? new LegacySearchStocksIntent(text, trigger.Value) : null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseStockSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultIndex") &&
|
|
TryInt32(payload, "resultIndex", 0, int.MaxValue, out var resultIndex)
|
|
? new LegacySelectStockIntent(resultIndex)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseCutPointer(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(
|
|
payload,
|
|
"physicalIndex",
|
|
"control",
|
|
"shift",
|
|
"x",
|
|
"y",
|
|
"timestampMilliseconds",
|
|
"allowAppend") ||
|
|
!TryInt32(
|
|
payload,
|
|
"physicalIndex",
|
|
0,
|
|
LegacyRuntimeStockCutMenuLoader.MaximumRows - 1,
|
|
out var physicalIndex) ||
|
|
!TryBoolean(payload, "control", out var control) ||
|
|
!TryBoolean(payload, "shift", out var shift) ||
|
|
!TryInt32(payload, "x", 0, 16_384, out var x) ||
|
|
!TryInt32(payload, "y", 0, 16_384, out var y) ||
|
|
!TryInt64(payload, "timestampMilliseconds", 0, long.MaxValue, out var timestamp) ||
|
|
!TryBoolean(payload, "allowAppend", out var allowAppend))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new LegacyCutPointerDownIntent(
|
|
physicalIndex,
|
|
control,
|
|
shift,
|
|
x,
|
|
y,
|
|
timestamp,
|
|
allowAppend);
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseDrop(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyDropSelectedCutsIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseCutKeyDown(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "key", "control", "shift") ||
|
|
!TryString(payload, "key", 16, out var keyText) ||
|
|
!TryBoolean(payload, "control", out var control) ||
|
|
!TryBoolean(payload, "shift", out var shift))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var key = keyText switch
|
|
{
|
|
"arrow-up" => LegacyCutKeyboardKey.ArrowUp,
|
|
"arrow-down" => LegacyCutKeyboardKey.ArrowDown,
|
|
"home" => LegacyCutKeyboardKey.Home,
|
|
"end" => LegacyCutKeyboardKey.End,
|
|
"space" => LegacyCutKeyboardKey.Space,
|
|
_ => (LegacyCutKeyboardKey?)null
|
|
};
|
|
return key.HasValue
|
|
? new LegacyCutKeyDownIntent(key.Value, control, shift)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseClearCutSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyClearCutSelectionIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseEnabled(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "rowId", "enabled") ||
|
|
!TryString(payload, "rowId", 128, out var rowId) ||
|
|
!IsSafeIdentifier(rowId) ||
|
|
!TryBoolean(payload, "enabled", out var enabled))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new LegacySetPlaylistEnabledIntent(rowId, enabled);
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseAllEnabled(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "enabled") &&
|
|
TryBoolean(payload, "enabled", out var enabled)
|
|
? new LegacySetAllPlaylistEnabledIntent(enabled)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParsePlaylistSelection(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "rowId", "control", "shift") ||
|
|
!TryString(payload, "rowId", 128, out var rowId) ||
|
|
!IsSafeIdentifier(rowId) ||
|
|
!TryBoolean(payload, "control", out var control) ||
|
|
!TryBoolean(payload, "shift", out var shift))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new LegacySelectPlaylistRowIntent(rowId, control, shift);
|
|
}
|
|
|
|
private static LegacyUiIntent? ParsePlaylistActivation(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "rowId") &&
|
|
TryString(payload, "rowId", 128, out var rowId) &&
|
|
IsSafeIdentifier(rowId)
|
|
? new LegacyActivatePlaylistRowIntent(rowId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParsePlaylistMove(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "direction") ||
|
|
!TryString(payload, "direction", 8, out var directionText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var direction = directionText switch
|
|
{
|
|
"up" => LegacyPlaylistMoveDirection.Up,
|
|
"down" => LegacyPlaylistMoveDirection.Down,
|
|
_ => (LegacyPlaylistMoveDirection?)null
|
|
};
|
|
return direction.HasValue
|
|
? new LegacyMoveSelectedPlaylistRowsIntent(direction.Value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParsePlaylistReorder(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "sourceRowId", "targetRowId", "position") ||
|
|
!TryString(payload, "sourceRowId", 128, out var sourceRowId) ||
|
|
!TryString(payload, "targetRowId", 128, out var targetRowId) ||
|
|
!IsSafeIdentifier(sourceRowId) ||
|
|
!IsSafeIdentifier(targetRowId) ||
|
|
string.Equals(sourceRowId, targetRowId, StringComparison.Ordinal) ||
|
|
!TryString(payload, "position", 8, out var positionText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var position = ParseDragDropPosition(positionText);
|
|
return position.HasValue
|
|
? new LegacyReorderPlaylistRowsIntent(sourceRowId, targetRowId, position.Value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseDeleteSelected(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyDeleteSelectedPlaylistRowsIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseDeleteAll(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyDeleteAllPlaylistRowsIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseToggleActiveEnabled(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyToggleActivePlaylistEnabledIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParsePlaylistBoundary(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "boundary") ||
|
|
!TryString(payload, "boundary", 8, out var boundary))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return boundary switch
|
|
{
|
|
"first" => new LegacySelectPlaylistBoundaryIntent(false),
|
|
"last" => new LegacySelectPlaylistBoundaryIntent(true),
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseTabSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "tabId") &&
|
|
TryTabId(payload, "tabId", out var tabId)
|
|
? new LegacySelectTabIntent(tabId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseTabSwap(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "source", "target") &&
|
|
TryTabId(payload, "source", out var source) &&
|
|
TryTabId(payload, "target", out var target)
|
|
? new LegacySwapTabsIntent(source, target)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseTabHoverSwap(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(
|
|
payload,
|
|
"source",
|
|
"target",
|
|
"expectedSourceIndex",
|
|
"expectedTargetIndex") ||
|
|
!TryTabId(payload, "source", out var source) ||
|
|
!TryTabId(payload, "target", out var target) ||
|
|
source == target ||
|
|
!TryInt32(payload, "expectedSourceIndex", 0, 9, out var sourceIndex) ||
|
|
!TryInt32(payload, "expectedTargetIndex", 0, 9, out var targetIndex) ||
|
|
sourceIndex == targetIndex)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new LegacyHoverSwapTabsIntent(source, target, sourceIndex, targetIndex);
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseMovingAverages(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "ma5", "ma20") &&
|
|
TryBoolean(payload, "ma5", out var ma5) &&
|
|
TryBoolean(payload, "ma20", out var ma20)
|
|
? new LegacySetMovingAveragesIntent(ma5, ma20)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseFixedAction(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "actionId") &&
|
|
TryString(payload, "actionId", 64, out var actionId) &&
|
|
IsSafeIdentifier(actionId)
|
|
? new LegacyActivateFixedActionIntent(actionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseFixedSection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "sectionIndex") &&
|
|
TryInt32(payload, "sectionIndex", 0, 255, out var sectionIndex)
|
|
? new LegacyActivateFixedSectionIntent(sectionIndex)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseIndustrySelection(
|
|
JsonElement payload,
|
|
bool doubleClick) =>
|
|
HasOnlyProperties(payload, "index") &&
|
|
TryInt32(payload, "index", 0, 499, out var index)
|
|
? new LegacySelectIndustryIntent(index, doubleClick)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseIndustryComparisonText(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "slot", "value") ||
|
|
!TryString(payload, "slot", 16, out var slotText) ||
|
|
!TrySafeString(
|
|
payload,
|
|
"value",
|
|
LegacyIndustrySelectionWorkflow.MaximumComparisonTextLength,
|
|
allowEmpty: true,
|
|
allowUnderscore: true,
|
|
out var value))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var slot = slotText switch
|
|
{
|
|
"first" => LegacyIndustryComparisonSlot.First,
|
|
"second" => LegacyIndustryComparisonSlot.Second,
|
|
_ => (LegacyIndustryComparisonSlot?)null
|
|
};
|
|
return slot.HasValue
|
|
? new LegacySetIndustryComparisonTextIntent(slot.Value, value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseSwapIndustries(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacySwapIndustriesIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseIndustryAction(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "actionId") &&
|
|
TryString(payload, "actionId", 64, out var actionId) &&
|
|
IsSafeOpaqueActionId(actionId)
|
|
? new LegacyActivateIndustryActionIntent(actionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOverseasFixedIndex(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "targetId") &&
|
|
TryString(payload, "targetId", 64, out var targetId) &&
|
|
IsSafeIdentifier(targetId) &&
|
|
LegacyOverseasActionCatalog.FindFixedIndexTarget(targetId) is not null
|
|
? new LegacySelectOverseasFixedIndexIntent(targetId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOverseasSearch(
|
|
JsonElement payload,
|
|
bool stocks) =>
|
|
HasOnlyProperties(payload, "query") &&
|
|
TrySafeString(
|
|
payload,
|
|
"query",
|
|
LegacyOverseasSelectionWorkflow.MaximumQueryLength,
|
|
allowEmpty: true,
|
|
allowUnderscore: true,
|
|
out var query)
|
|
? stocks
|
|
? new LegacySearchOverseasStocksIntent(query)
|
|
: new LegacySearchOverseasIndustriesIntent(query)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOverseasSelection(
|
|
JsonElement payload,
|
|
bool stocks) =>
|
|
HasOnlyProperties(payload, "selectionId") &&
|
|
TryString(payload, "selectionId", 128, out var selectionId) &&
|
|
IsSafeIdentifier(selectionId)
|
|
? stocks
|
|
? new LegacySelectOverseasStockIntent(selectionId)
|
|
: new LegacySelectOverseasIndustryIntent(selectionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOverseasAction(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "actionId") &&
|
|
TryString(payload, "actionId", 64, out var actionId) &&
|
|
IsSafeIdentifier(actionId) &&
|
|
LegacyOverseasActionCatalog.FindAction(actionId) is not null
|
|
? new LegacyActivateOverseasActionIntent(actionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonSearch(
|
|
JsonElement payload,
|
|
bool world) =>
|
|
HasOnlyProperties(payload, "query") &&
|
|
TryString(payload, "query", 64, out var query)
|
|
? world
|
|
? new LegacySearchComparisonWorldIntent(query)
|
|
: new LegacySearchComparisonDomesticIntent(query)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonChoice(
|
|
JsonElement payload,
|
|
bool world) =>
|
|
HasOnlyProperties(payload, "selectionId") &&
|
|
TryString(payload, "selectionId", 128, out var selectionId) &&
|
|
IsSafeIdentifier(selectionId)
|
|
? world
|
|
? new LegacyChooseComparisonWorldIntent(selectionId)
|
|
: new LegacyChooseComparisonDomesticIntent(selectionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonSelection(
|
|
JsonElement payload,
|
|
bool world) =>
|
|
HasOnlyProperties(payload, "selectionId") &&
|
|
TryString(payload, "selectionId", 128, out var selectionId) &&
|
|
IsSafeIdentifier(selectionId)
|
|
? world
|
|
? new LegacySelectComparisonWorldIntent(selectionId)
|
|
: new LegacySelectComparisonDomesticIntent(selectionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonFixedSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "targetId") &&
|
|
TryString(payload, "targetId", 64, out var targetId) &&
|
|
IsSafeIdentifier(targetId)
|
|
? new LegacySelectComparisonFixedIntent(targetId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonFixed(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "targetId") &&
|
|
TryString(payload, "targetId", 64, out var targetId) &&
|
|
IsSafeIdentifier(targetId)
|
|
? new LegacyChooseComparisonFixedIntent(targetId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonSwap(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacySwapComparisonTargetsIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonClear(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyClearComparisonTargetsIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonAdd(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyAddComparisonPairIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonPairSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "rowId") &&
|
|
TryString(payload, "rowId", 128, out var rowId) &&
|
|
IsSafeIdentifier(rowId)
|
|
? new LegacySelectComparisonPairIntent(rowId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonPairMove(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "direction") ||
|
|
!TryString(payload, "direction", 8, out var direction))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return direction switch
|
|
{
|
|
"up" => new LegacyMoveComparisonPairIntent(
|
|
LegacyComparisonPairMoveDirection.Up),
|
|
"down" => new LegacyMoveComparisonPairIntent(
|
|
LegacyComparisonPairMoveDirection.Down),
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseComparisonDeleteSelected(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyDeleteSelectedComparisonPairIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonDeleteAll(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyDeleteAllComparisonPairsIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseComparisonAction(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "actionId") &&
|
|
TryString(payload, "actionId", 64, out var actionId) &&
|
|
IsSafeIdentifier(actionId)
|
|
? new LegacyActivateComparisonActionIntent(actionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseSectionIndex(
|
|
JsonElement payload,
|
|
Func<int, LegacyUiIntent> factory) =>
|
|
HasOnlyProperties(payload, "sectionIndex") &&
|
|
TryInt32(payload, "sectionIndex", 0, 63, out var sectionIndex)
|
|
? factory(sectionIndex)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseThemeSearch(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "query") ||
|
|
!TryString(
|
|
payload,
|
|
"query",
|
|
LegacyThemeSelectionService.MaximumQueryLength,
|
|
out var query))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new LegacySearchThemesIntent(query);
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseThemeSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 64, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacySelectThemeIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseThemeResultActivation(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 64, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacyActivateThemeResultIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseThemeSort(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "sortId") ||
|
|
!TryString(payload, "sortId", 24, out var sortId))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return sortId is "INPUT_ORDER" or "GAIN_DESC" or "GAIN_ASC"
|
|
? new LegacySetThemeSortIntent(sortId)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseThemeAction(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "actionId") &&
|
|
TryString(payload, "actionId", 64, out var actionId) &&
|
|
IsSafeIdentifier(actionId)
|
|
? new LegacyActivateThemeActionIntent(actionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseExpertSearch(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "query") &&
|
|
TryString(
|
|
payload,
|
|
"query",
|
|
LegacyExpertSelectionService.MaximumQueryLength,
|
|
out var query)
|
|
? new LegacySearchExpertsIntent(query)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseExpertSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "selectionId") &&
|
|
TryString(payload, "selectionId", 128, out var selectionId) &&
|
|
IsSafeIdentifier(selectionId)
|
|
? new LegacySelectExpertIntent(selectionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseExpertAction(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "actionId") &&
|
|
TryString(payload, "actionId", 64, out var actionId) &&
|
|
string.Equals(actionId, LegacyExpertWorkflowController.ActionId, StringComparison.Ordinal)
|
|
? new LegacyActivateExpertActionIntent(actionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseTradingHaltSearch(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "query") &&
|
|
TryString(
|
|
payload,
|
|
"query",
|
|
LegacyTradingHaltSelectionService.MaximumQueryLength,
|
|
out var query)
|
|
? new LegacySearchTradingHaltsIntent(query)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseTradingHaltSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "selectionId") &&
|
|
TryString(payload, "selectionId", 128, out var selectionId) &&
|
|
IsSafeIdentifier(selectionId)
|
|
? new LegacySelectTradingHaltIntent(selectionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseTradingHaltSort(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyToggleTradingHaltSortIntent() : null;
|
|
|
|
private static LegacyUiIntent? ParseTradingHaltAction(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "actionId") ||
|
|
!TryString(payload, "actionId", 64, out var actionId))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return actionId is LegacyTradingHaltWorkflowController.CurrentActionId or
|
|
LegacyTradingHaltWorkflowController.CandleActionId
|
|
? new LegacyActivateTradingHaltActionIntent(actionId)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogOpen(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "entity") ||
|
|
!TryString(payload, "entity", 16, out var entityText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var entity = entityText switch
|
|
{
|
|
"theme" => LegacyOperatorCatalogEntity.Theme,
|
|
"expert" => LegacyOperatorCatalogEntity.Expert,
|
|
_ => (LegacyOperatorCatalogEntity?)null
|
|
};
|
|
return entity.HasValue ? new LegacyOpenOperatorCatalogIntent(entity.Value) : null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogEmpty(
|
|
JsonElement payload,
|
|
Func<LegacyUiIntent> factory) =>
|
|
HasOnlyProperties(payload) ? factory() : null;
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogSearch(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "query", "nxtSession") ||
|
|
!TrySafeString(
|
|
payload,
|
|
"query",
|
|
64,
|
|
allowEmpty: true,
|
|
allowUnderscore: true,
|
|
out var query) ||
|
|
!TryString(payload, "nxtSession", 16, out var sessionText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var session = sessionText switch
|
|
{
|
|
"preMarket" => ThemeSession.PreMarket,
|
|
"afterMarket" => ThemeSession.AfterMarket,
|
|
_ => (ThemeSession?)null
|
|
};
|
|
return session.HasValue
|
|
? new LegacySearchOperatorCatalogIntent(query, session.Value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogResult(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 128, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacySelectOperatorCatalogResultIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogThemeCreate(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "market") ||
|
|
!TryString(payload, "market", 8, out var marketText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var market = marketText switch
|
|
{
|
|
"krx" => ThemeMarket.Krx,
|
|
"nxt" => ThemeMarket.Nxt,
|
|
_ => (ThemeMarket?)null
|
|
};
|
|
return market.HasValue
|
|
? new LegacyBeginCreateThemeCatalogIntent(market.Value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogStockSearch(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "query") &&
|
|
TrySafeString(
|
|
payload,
|
|
"query",
|
|
LegacyStockSearchService.MaximumQueryLength,
|
|
allowEmpty: false,
|
|
allowUnderscore: true,
|
|
out var query)
|
|
? new LegacySearchOperatorCatalogStocksIntent(query)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogStockSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 128, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacySelectOperatorCatalogStockIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogStockAdd(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "buyAmount") ||
|
|
!payload.TryGetProperty("buyAmount", out var amountElement))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (amountElement.ValueKind == JsonValueKind.Null)
|
|
{
|
|
return new LegacyAddOperatorCatalogStockIntent(null);
|
|
}
|
|
|
|
return amountElement.ValueKind == JsonValueKind.Number &&
|
|
amountElement.TryGetDecimal(out var amount) &&
|
|
decimal.Truncate(amount) == amount &&
|
|
amount is > 0 and <= 9_007_199_254_740_991m
|
|
? new LegacyAddOperatorCatalogStockIntent(amount)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogStockActivation(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "resultId", "buyAmount") ||
|
|
!TryString(payload, "resultId", 128, out var resultId) ||
|
|
!IsSafeIdentifier(resultId) ||
|
|
!payload.TryGetProperty("buyAmount", out var amountElement))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (amountElement.ValueKind == JsonValueKind.Null)
|
|
{
|
|
return new LegacyActivateOperatorCatalogStockIntent(resultId, null);
|
|
}
|
|
|
|
return amountElement.ValueKind == JsonValueKind.Number &&
|
|
amountElement.TryGetDecimal(out var amount) &&
|
|
decimal.Truncate(amount) == amount &&
|
|
amount is > 0 and <= 9_007_199_254_740_991m
|
|
? new LegacyActivateOperatorCatalogStockIntent(resultId, amount)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogDraftMove(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "rowId", "direction") ||
|
|
!TryString(payload, "rowId", 128, out var rowId) ||
|
|
!IsSafeIdentifier(rowId) ||
|
|
!TryString(payload, "direction", 8, out var directionText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var direction = directionText switch
|
|
{
|
|
"up" => LegacyOperatorCatalogMoveDirection.Up,
|
|
"down" => LegacyOperatorCatalogMoveDirection.Down,
|
|
_ => (LegacyOperatorCatalogMoveDirection?)null
|
|
};
|
|
return direction.HasValue
|
|
? new LegacyMoveOperatorCatalogDraftRowIntent(rowId, direction.Value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogDraftReorder(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "sourceRowId", "targetRowId", "position") ||
|
|
!TryString(payload, "sourceRowId", 128, out var sourceRowId) ||
|
|
!TryString(payload, "targetRowId", 128, out var targetRowId) ||
|
|
!IsSafeIdentifier(sourceRowId) ||
|
|
!IsSafeIdentifier(targetRowId) ||
|
|
string.Equals(sourceRowId, targetRowId, StringComparison.Ordinal) ||
|
|
!TryString(payload, "position", 8, out var positionText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var position = ParseDragDropPosition(positionText);
|
|
return position.HasValue
|
|
? new LegacyReorderOperatorCatalogDraftRowIntent(
|
|
sourceRowId,
|
|
targetRowId,
|
|
position.Value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogDraftRemove(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "rowId") &&
|
|
TryString(payload, "rowId", 128, out var rowId) &&
|
|
IsSafeIdentifier(rowId)
|
|
? new LegacyRemoveOperatorCatalogDraftRowIntent(rowId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogDraftSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "rowId") &&
|
|
TryString(payload, "rowId", 128, out var rowId) &&
|
|
IsSafeIdentifier(rowId)
|
|
? new LegacySelectOperatorCatalogDraftRowIntent(rowId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogDraftBuyAmount(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "rowId", "buyAmount") ||
|
|
!TryString(payload, "rowId", 128, out var rowId) ||
|
|
!IsSafeIdentifier(rowId) ||
|
|
!payload.TryGetProperty("buyAmount", out var amountElement))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (amountElement.ValueKind == JsonValueKind.Null)
|
|
{
|
|
return new LegacySetOperatorCatalogDraftBuyAmountIntent(rowId, null);
|
|
}
|
|
|
|
return amountElement.ValueKind == JsonValueKind.Number &&
|
|
amountElement.TryGetDecimal(out var amount) &&
|
|
decimal.Truncate(amount) == amount &&
|
|
amount is > 0 and <= 9_007_199_254_740_991m
|
|
? new LegacySetOperatorCatalogDraftBuyAmountIntent(rowId, amount)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogSave(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "name") &&
|
|
TrySafeString(
|
|
payload,
|
|
"name",
|
|
128,
|
|
allowEmpty: true,
|
|
allowUnderscore: true,
|
|
out var name) &&
|
|
string.Equals(name, name.Trim(), StringComparison.Ordinal)
|
|
? new LegacySaveOperatorCatalogIntent(name)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseOperatorCatalogThemeNameCheck(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "name") &&
|
|
TrySafeString(
|
|
payload,
|
|
"name",
|
|
128,
|
|
allowEmpty: false,
|
|
allowUnderscore: true,
|
|
out var name) &&
|
|
string.Equals(name, name.Trim(), StringComparison.Ordinal)
|
|
? new LegacyCheckOperatorCatalogThemeNameIntent(name)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseNamedPlaylistEmpty(
|
|
JsonElement payload,
|
|
Func<LegacyUiIntent> factory) =>
|
|
HasOnlyProperties(payload) ? factory() : null;
|
|
|
|
private static LegacyUiIntent? ParseNamedPlaylistSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "definitionId", "requestId") &&
|
|
TryString(payload, "definitionId", 128, out var definitionId) &&
|
|
IsSafeIdentifier(definitionId) &&
|
|
TryString(payload, "requestId", 128, out var requestId) &&
|
|
IsSafeIdentifier(requestId)
|
|
? new LegacySelectNamedPlaylistIntent(definitionId, requestId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseNamedPlaylistRefresh(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "requestId") &&
|
|
TryString(payload, "requestId", 128, out var requestId) &&
|
|
IsSafeIdentifier(requestId)
|
|
? new LegacyRefreshNamedPlaylistsIntent(requestId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseNamedPlaylistLoadById(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "definitionId") &&
|
|
TryString(payload, "definitionId", 128, out var definitionId) &&
|
|
IsSafeIdentifier(definitionId)
|
|
? new LegacyLoadNamedPlaylistByIdIntent(definitionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseNamedPlaylistSaveTo(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "definitionId") &&
|
|
TryString(payload, "definitionId", 128, out var definitionId) &&
|
|
IsSafeIdentifier(definitionId)
|
|
? new LegacySaveCurrentNamedPlaylistToIntent(definitionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseNamedPlaylistCreate(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "title", "requestId") &&
|
|
TrySafeString(
|
|
payload,
|
|
"title",
|
|
128,
|
|
allowEmpty: false,
|
|
allowUnderscore: true,
|
|
out var title) &&
|
|
TryString(payload, "requestId", 128, out var requestId) &&
|
|
IsSafeIdentifier(requestId) &&
|
|
string.Equals(title, title.Trim(), StringComparison.Ordinal)
|
|
? new LegacyCreateNamedPlaylistIntent(title, requestId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseNamedPlaylistDelete(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "requestId") &&
|
|
TryString(payload, "requestId", 128, out var requestId) &&
|
|
IsSafeIdentifier(requestId)
|
|
? new LegacyDeleteSelectedNamedPlaylistIntent(requestId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialOpen(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "screen") &&
|
|
TryString(payload, "screen", 32, out var screenText) &&
|
|
TryManualFinancialScreen(screenText, out var screen)
|
|
? new LegacyOpenManualFinancialIntent(screen)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialEmpty(
|
|
JsonElement payload,
|
|
Func<LegacyUiIntent> factory) =>
|
|
HasOnlyProperties(payload) ? factory() : null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialSearch(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "query") &&
|
|
TrySafeString(payload, "query", 64, allowEmpty: true, allowUnderscore: true, out var query)
|
|
? new LegacySearchManualFinancialIntent(query)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialFind(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "query", "direction", "generation") ||
|
|
!TrySafeString(
|
|
payload,
|
|
"query",
|
|
64,
|
|
allowEmpty: false,
|
|
allowUnderscore: true,
|
|
out var query) ||
|
|
!TryString(payload, "direction", 8, out var directionText) ||
|
|
!TryInt64(payload, "generation", 0, long.MaxValue, out var generation))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var direction = directionText switch
|
|
{
|
|
"up" => LegacyManualFinancialFindDirection.Up,
|
|
"down" => LegacyManualFinancialFindDirection.Down,
|
|
_ => (LegacyManualFinancialFindDirection?)null
|
|
};
|
|
return direction.HasValue
|
|
? new LegacyFindManualFinancialIntent(query, direction.Value, generation)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialNameSort(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "generation") &&
|
|
TryInt64(payload, "generation", 0, long.MaxValue, out var generation)
|
|
? new LegacyToggleManualFinancialNameSortIntent(generation)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialLoad(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 128, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacyLoadManualFinancialIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialResultActivation(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 128, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacyActivateManualFinancialResultIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialStockSearch(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "stockName") &&
|
|
TrySafeString(
|
|
payload,
|
|
"stockName",
|
|
64,
|
|
allowEmpty: false,
|
|
allowUnderscore: true,
|
|
out var stockName)
|
|
? new LegacySearchManualFinancialStocksIntent(stockName)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialStockSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 128, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacySelectManualFinancialStockIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialSave(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "record") ||
|
|
!payload.TryGetProperty("record", out var recordElement) ||
|
|
recordElement.ValueKind != JsonValueKind.Object)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var record = ParseManualFinancialRecord(recordElement);
|
|
return record is null ? null : new LegacySaveManualFinancialIntent(record);
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseManualFinancialAction(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "actionId") &&
|
|
TryString(payload, "actionId", 64, out var actionId) &&
|
|
LegacyManualFinancialWorkflow.Screens.Any(definition =>
|
|
string.Equals(definition.ActionId, actionId, StringComparison.Ordinal))
|
|
? new LegacyActivateManualFinancialActionIntent(actionId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualListOpen(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "screen") ||
|
|
!TryString(payload, "screen", 24, out var screen))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return screen switch
|
|
{
|
|
"individual" => new LegacyOpenManualListIntent(
|
|
LegacyManualListScreen.NetSell,
|
|
S5025ManualAudience.Individual),
|
|
"foreign" => new LegacyOpenManualListIntent(
|
|
LegacyManualListScreen.NetSell,
|
|
S5025ManualAudience.Foreign),
|
|
"institution" => new LegacyOpenManualListIntent(
|
|
LegacyManualListScreen.NetSell,
|
|
S5025ManualAudience.Institution),
|
|
"vi" => new LegacyOpenManualListIntent(
|
|
LegacyManualListScreen.Vi,
|
|
S5025ManualAudience.Individual),
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseManualListEmpty(
|
|
JsonElement payload,
|
|
Func<LegacyUiIntent> factory) =>
|
|
HasOnlyProperties(payload) ? factory() : null;
|
|
|
|
private static LegacyUiIntent? ParseManualNetSellCell(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "rowId", "field", "value") ||
|
|
!TryString(payload, "rowId", 64, out var rowId) ||
|
|
!IsSafeIdentifier(rowId) ||
|
|
!TryString(payload, "field", 24, out var fieldText) ||
|
|
!TrySafeString(
|
|
payload,
|
|
"value",
|
|
LegacyManualListsWorkflow.MaximumNetSellValueLength,
|
|
allowEmpty: true,
|
|
allowUnderscore: true,
|
|
out var value) ||
|
|
value.Contains('^'))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var field = fieldText switch
|
|
{
|
|
"leftName" => LegacyManualNetSellField.LeftName,
|
|
"leftAmount" => LegacyManualNetSellField.LeftAmount,
|
|
"rightName" => LegacyManualNetSellField.RightName,
|
|
"rightAmount" => LegacyManualNetSellField.RightAmount,
|
|
_ => (LegacyManualNetSellField?)null
|
|
};
|
|
return field.HasValue
|
|
? new LegacySetManualNetSellCellIntent(rowId, field.Value, value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseManualViSearch(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "query") &&
|
|
TrySafeString(
|
|
payload,
|
|
"query",
|
|
LegacyManualListsWorkflow.MaximumSearchLength,
|
|
allowEmpty: false,
|
|
allowUnderscore: true,
|
|
out var query)
|
|
? new LegacySearchManualViIntent(query)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualViResult(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 64, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacyAddManualViResultIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualViSelection(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "resultId") &&
|
|
TryString(payload, "resultId", 64, out var resultId) &&
|
|
IsSafeIdentifier(resultId)
|
|
? new LegacySelectManualViResultIntent(resultId)
|
|
: null;
|
|
|
|
private static LegacyUiIntent? ParseManualViMove(JsonElement payload)
|
|
{
|
|
if (!HasOnlyProperties(payload, "itemId", "direction") ||
|
|
!TryString(payload, "itemId", 64, out var itemId) ||
|
|
!IsSafeIdentifier(itemId) ||
|
|
!TryString(payload, "direction", 8, out var directionText))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var direction = directionText switch
|
|
{
|
|
"up" => LegacyManualListMoveDirection.Up,
|
|
"down" => LegacyManualListMoveDirection.Down,
|
|
_ => (LegacyManualListMoveDirection?)null
|
|
};
|
|
return direction.HasValue
|
|
? new LegacyMoveManualViItemIntent(itemId, direction.Value)
|
|
: null;
|
|
}
|
|
|
|
private static LegacyUiIntent? ParseManualViDelete(JsonElement payload) =>
|
|
HasOnlyProperties(payload, "itemId") &&
|
|
TryString(payload, "itemId", 64, out var itemId) &&
|
|
IsSafeIdentifier(itemId)
|
|
? new LegacyDeleteManualViItemIntent(itemId)
|
|
: null;
|
|
|
|
private static ManualFinancialRecord? ParseManualFinancialRecord(JsonElement element)
|
|
{
|
|
if (!element.TryGetProperty("screen", out var screenElement) ||
|
|
screenElement.ValueKind != JsonValueKind.String ||
|
|
!TryManualFinancialScreen(screenElement.GetString(), out var screen) ||
|
|
!TrySafeString(
|
|
element,
|
|
"stockName",
|
|
200,
|
|
allowEmpty: false,
|
|
allowUnderscore: true,
|
|
out var stockName))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var identity = new ManualFinancialIdentity(screen, stockName);
|
|
return screen switch
|
|
{
|
|
ManualFinancialScreenKind.RevenueComposition =>
|
|
ParseManualRevenue(element, identity),
|
|
ManualFinancialScreenKind.GrowthMetrics =>
|
|
ParseManualGrowth(element, identity),
|
|
ManualFinancialScreenKind.Sales =>
|
|
ParseManualQuarters(element, identity, operatingProfit: false),
|
|
ManualFinancialScreenKind.OperatingProfit =>
|
|
ParseManualQuarters(element, identity, operatingProfit: true),
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
private static ManualRevenueCompositionRecord? ParseManualRevenue(
|
|
JsonElement element,
|
|
ManualFinancialIdentity identity)
|
|
{
|
|
if (!HasOnlyProperties(element, "screen", "stockName", "baseDate", "slices") ||
|
|
!TrySafeString(
|
|
element,
|
|
"baseDate",
|
|
200,
|
|
allowEmpty: true,
|
|
allowUnderscore: true,
|
|
out var baseDate) ||
|
|
!element.TryGetProperty("slices", out var slicesElement) ||
|
|
slicesElement.ValueKind != JsonValueKind.Array ||
|
|
slicesElement.GetArrayLength() != 5)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var slices = new ManualRevenueSlice?[5];
|
|
var index = 0;
|
|
foreach (var item in slicesElement.EnumerateArray())
|
|
{
|
|
if (item.ValueKind == JsonValueKind.Null)
|
|
{
|
|
slices[index++] = null;
|
|
continue;
|
|
}
|
|
|
|
if (item.ValueKind != JsonValueKind.Object ||
|
|
!HasOnlyProperties(item, "label", "percentageText") ||
|
|
!TrySafeString(
|
|
item,
|
|
"label",
|
|
200,
|
|
allowEmpty: false,
|
|
allowUnderscore: false,
|
|
out var label) ||
|
|
!TrySafeString(
|
|
item,
|
|
"percentageText",
|
|
64,
|
|
allowEmpty: false,
|
|
allowUnderscore: false,
|
|
out var percentageText) ||
|
|
!TryManualDoubleText(percentageText, out var percentage))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
slices[index++] = new ManualRevenueSlice(label, percentageText, percentage);
|
|
}
|
|
|
|
return new ManualRevenueCompositionRecord(identity, baseDate, slices);
|
|
}
|
|
|
|
private static ManualGrowthMetricsRecord? ParseManualGrowth(
|
|
JsonElement element,
|
|
ManualFinancialIdentity identity)
|
|
{
|
|
if (!HasOnlyProperties(
|
|
element,
|
|
"screen",
|
|
"stockName",
|
|
"salesGrowth",
|
|
"operatingProfitGrowth",
|
|
"netAssetGrowth",
|
|
"netIncomeGrowth",
|
|
"periods") ||
|
|
!TryGrowthSeries(element, "salesGrowth", out var salesGrowth) ||
|
|
!TryGrowthSeries(
|
|
element,
|
|
"operatingProfitGrowth",
|
|
out var operatingProfitGrowth) ||
|
|
!TryGrowthSeries(element, "netAssetGrowth", out var netAssetGrowth) ||
|
|
!TryGrowthSeries(element, "netIncomeGrowth", out var netIncomeGrowth) ||
|
|
!TryStringArray(
|
|
element,
|
|
"periods",
|
|
4,
|
|
allowEmpty: true,
|
|
allowUnderscore: false,
|
|
out var periods))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new ManualGrowthMetricsRecord(
|
|
identity,
|
|
salesGrowth,
|
|
operatingProfitGrowth,
|
|
netAssetGrowth,
|
|
netIncomeGrowth,
|
|
new ManualGrowthPeriodLabels(periods[0], periods[1], periods[2], periods[3]));
|
|
}
|
|
|
|
private static ManualFinancialRecord? ParseManualQuarters(
|
|
JsonElement element,
|
|
ManualFinancialIdentity identity,
|
|
bool operatingProfit)
|
|
{
|
|
if (!HasOnlyProperties(element, "screen", "stockName", "quarters") ||
|
|
!element.TryGetProperty("quarters", out var quartersElement) ||
|
|
quartersElement.ValueKind != JsonValueKind.Array ||
|
|
quartersElement.GetArrayLength() != 6)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var quarters = new ManualQuarterValue[6];
|
|
var index = 0;
|
|
foreach (var item in quartersElement.EnumerateArray())
|
|
{
|
|
if (item.ValueKind != JsonValueKind.Object ||
|
|
!HasOnlyProperties(item, "quarter", "value") ||
|
|
!TrySafeString(
|
|
item,
|
|
"quarter",
|
|
200,
|
|
allowEmpty: false,
|
|
allowUnderscore: false,
|
|
out var quarter) ||
|
|
!item.TryGetProperty("value", out var valueElement) ||
|
|
valueElement.ValueKind != JsonValueKind.Number ||
|
|
!valueElement.TryGetInt32(out var value))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
quarters[index++] = new ManualQuarterValue(quarter, value);
|
|
}
|
|
|
|
return operatingProfit
|
|
? new ManualOperatingProfitRecord(identity, quarters)
|
|
: new ManualSalesRecord(identity, quarters);
|
|
}
|
|
|
|
private static bool TryGrowthSeries(
|
|
JsonElement element,
|
|
string propertyName,
|
|
out ManualGrowthSeriesValues values)
|
|
{
|
|
values = new ManualGrowthSeriesValues(null, null, null, null);
|
|
if (!element.TryGetProperty(propertyName, out var array) ||
|
|
array.ValueKind != JsonValueKind.Array || array.GetArrayLength() != 4)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var result = new double?[4];
|
|
var index = 0;
|
|
foreach (var item in array.EnumerateArray())
|
|
{
|
|
if (item.ValueKind == JsonValueKind.Null)
|
|
{
|
|
result[index++] = null;
|
|
continue;
|
|
}
|
|
|
|
if (item.ValueKind != JsonValueKind.Number ||
|
|
!item.TryGetDouble(out var number) || !double.IsFinite(number) ||
|
|
Math.Abs(number) > 1_000_000_000_000d)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
result[index++] = number;
|
|
}
|
|
|
|
values = new ManualGrowthSeriesValues(result[0], result[1], result[2], result[3]);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryStringArray(
|
|
JsonElement element,
|
|
string propertyName,
|
|
int expectedCount,
|
|
bool allowEmpty,
|
|
bool allowUnderscore,
|
|
out string[] values)
|
|
{
|
|
values = [];
|
|
if (!element.TryGetProperty(propertyName, out var array) ||
|
|
array.ValueKind != JsonValueKind.Array ||
|
|
array.GetArrayLength() != expectedCount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var result = new string[expectedCount];
|
|
var index = 0;
|
|
foreach (var item in array.EnumerateArray())
|
|
{
|
|
if (item.ValueKind != JsonValueKind.String ||
|
|
!IsSafeBridgeText(
|
|
item.GetString() ?? string.Empty,
|
|
200,
|
|
allowEmpty,
|
|
allowUnderscore))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
result[index++] = item.GetString()!;
|
|
}
|
|
|
|
values = result;
|
|
return true;
|
|
}
|
|
|
|
private static bool TryManualDoubleText(string value, out double number)
|
|
{
|
|
if (string.Equals(value, "-", StringComparison.Ordinal))
|
|
{
|
|
number = 0;
|
|
return true;
|
|
}
|
|
|
|
return double.TryParse(
|
|
value,
|
|
NumberStyles.Float,
|
|
CultureInfo.InvariantCulture,
|
|
out number) &&
|
|
double.IsFinite(number) && Math.Abs(number) <= 1_000_000_000_000d;
|
|
}
|
|
|
|
private static bool TryManualFinancialScreen(
|
|
string? value,
|
|
out ManualFinancialScreenKind screen)
|
|
{
|
|
var parsed = value switch
|
|
{
|
|
"revenue-composition" => ManualFinancialScreenKind.RevenueComposition,
|
|
"growth-metrics" => ManualFinancialScreenKind.GrowthMetrics,
|
|
"sales" => ManualFinancialScreenKind.Sales,
|
|
"operating-profit" => ManualFinancialScreenKind.OperatingProfit,
|
|
_ => (ManualFinancialScreenKind?)null
|
|
};
|
|
screen = parsed.GetValueOrDefault();
|
|
return parsed.HasValue;
|
|
}
|
|
|
|
private static string ManualFinancialScreenId(ManualFinancialScreenKind screen) => screen switch
|
|
{
|
|
ManualFinancialScreenKind.RevenueComposition => "revenue-composition",
|
|
ManualFinancialScreenKind.GrowthMetrics => "growth-metrics",
|
|
ManualFinancialScreenKind.Sales => "sales",
|
|
ManualFinancialScreenKind.OperatingProfit => "operating-profit",
|
|
_ => throw new ArgumentOutOfRangeException(nameof(screen))
|
|
};
|
|
|
|
private static object? ProjectManualFinancialRecord(ManualFinancialRecord? record) =>
|
|
record switch
|
|
{
|
|
ManualRevenueCompositionRecord revenue => new
|
|
{
|
|
screen = ManualFinancialScreenId(revenue.Identity.Screen),
|
|
stockName = revenue.Identity.StockName,
|
|
revenue.BaseDate,
|
|
slices = revenue.Slices.Select(slice => slice is null ? null : new
|
|
{
|
|
slice.Label,
|
|
slice.PercentageText
|
|
})
|
|
},
|
|
ManualGrowthMetricsRecord growth => new
|
|
{
|
|
screen = ManualFinancialScreenId(growth.Identity.Screen),
|
|
stockName = growth.Identity.StockName,
|
|
salesGrowth = growth.SalesGrowth.ToArray(),
|
|
operatingProfitGrowth = growth.OperatingProfitGrowth.ToArray(),
|
|
netAssetGrowth = growth.NetAssetGrowth.ToArray(),
|
|
netIncomeGrowth = growth.NetIncomeGrowth.ToArray(),
|
|
periods = growth.Periods.ToArray()
|
|
},
|
|
ManualSalesRecord sales => new
|
|
{
|
|
screen = ManualFinancialScreenId(sales.Identity.Screen),
|
|
stockName = sales.Identity.StockName,
|
|
sales.Quarters
|
|
},
|
|
ManualOperatingProfitRecord profit => new
|
|
{
|
|
screen = ManualFinancialScreenId(profit.Identity.Screen),
|
|
stockName = profit.Identity.StockName,
|
|
profit.Quarters
|
|
},
|
|
null => null,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(record))
|
|
};
|
|
|
|
private static bool TrySafeString(
|
|
JsonElement element,
|
|
string propertyName,
|
|
int maximumLength,
|
|
bool allowEmpty,
|
|
bool allowUnderscore,
|
|
out string value)
|
|
{
|
|
value = string.Empty;
|
|
return TryString(element, propertyName, maximumLength, out value) &&
|
|
IsSafeBridgeText(value, maximumLength, allowEmpty, allowUnderscore);
|
|
}
|
|
|
|
private static bool IsSafeBridgeText(
|
|
string value,
|
|
int maximumLength,
|
|
bool allowEmpty,
|
|
bool allowUnderscore) =>
|
|
value.Length <= maximumLength &&
|
|
(allowEmpty || value.Length > 0) &&
|
|
(allowUnderscore || !value.Contains('_')) &&
|
|
value.All(static character =>
|
|
!char.IsControl(character) && !char.IsSurrogate(character) &&
|
|
character != '\uFFFD');
|
|
|
|
private static LegacyUiIntent? ParseDismiss(JsonElement payload) =>
|
|
HasOnlyProperties(payload) ? new LegacyDismissDialogIntent() : null;
|
|
|
|
private static bool HasOnlyProperties(JsonElement element, params string[] names)
|
|
{
|
|
var expected = new HashSet<string>(names, StringComparer.Ordinal);
|
|
var count = 0;
|
|
foreach (var property in element.EnumerateObject())
|
|
{
|
|
count++;
|
|
if (!expected.Contains(property.Name))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return count == expected.Count;
|
|
}
|
|
|
|
private static bool TryString(
|
|
JsonElement element,
|
|
string propertyName,
|
|
int maximumLength,
|
|
out string value)
|
|
{
|
|
value = string.Empty;
|
|
return element.TryGetProperty(propertyName, out var property) &&
|
|
property.ValueKind == JsonValueKind.String &&
|
|
(value = property.GetString() ?? string.Empty).Length <= maximumLength;
|
|
}
|
|
|
|
private static bool TryBoolean(JsonElement element, string name, out bool value)
|
|
{
|
|
value = false;
|
|
if (!element.TryGetProperty(name, out var property) ||
|
|
(property.ValueKind != JsonValueKind.True && property.ValueKind != JsonValueKind.False))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
value = property.GetBoolean();
|
|
return true;
|
|
}
|
|
|
|
private static bool TryTabId(
|
|
JsonElement element,
|
|
string name,
|
|
out LegacyOperatorTabId tabId)
|
|
{
|
|
tabId = default;
|
|
if (!TryString(element, name, 32, out var value))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var parsed = value switch
|
|
{
|
|
"overseas" => LegacyOperatorTabId.Overseas,
|
|
"exchange" => LegacyOperatorTabId.Exchange,
|
|
"index" => LegacyOperatorTabId.Index,
|
|
"kospiIndustry" => LegacyOperatorTabId.KospiIndustry,
|
|
"kosdaqIndustry" => LegacyOperatorTabId.KosdaqIndustry,
|
|
"comparison" => LegacyOperatorTabId.Comparison,
|
|
"theme" => LegacyOperatorTabId.Theme,
|
|
"overseasStocks" => LegacyOperatorTabId.OverseasStocks,
|
|
"expert" => LegacyOperatorTabId.Expert,
|
|
"tradingHalt" => LegacyOperatorTabId.TradingHalt,
|
|
_ => (LegacyOperatorTabId?)null
|
|
};
|
|
if (!parsed.HasValue)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
tabId = parsed.Value;
|
|
return true;
|
|
}
|
|
|
|
private static bool TryInt32(
|
|
JsonElement element,
|
|
string name,
|
|
int minimum,
|
|
int maximum,
|
|
out int value)
|
|
{
|
|
value = 0;
|
|
return element.TryGetProperty(name, out var property) &&
|
|
property.ValueKind == JsonValueKind.Number &&
|
|
property.TryGetInt32(out value) &&
|
|
value >= minimum && value <= maximum;
|
|
}
|
|
|
|
private static LegacyDragDropPosition? ParseDragDropPosition(string value) => value switch
|
|
{
|
|
"before" => LegacyDragDropPosition.Before,
|
|
"after" => LegacyDragDropPosition.After,
|
|
_ => null
|
|
};
|
|
|
|
private static bool TryInt64(
|
|
JsonElement element,
|
|
string name,
|
|
long minimum,
|
|
long maximum,
|
|
out long value)
|
|
{
|
|
value = 0;
|
|
return element.TryGetProperty(name, out var property) &&
|
|
property.ValueKind == JsonValueKind.Number &&
|
|
property.TryGetInt64(out value) &&
|
|
value >= minimum && value <= maximum;
|
|
}
|
|
|
|
private static bool IsSafeIdentifier(string value)
|
|
{
|
|
if (value.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return value.All(character =>
|
|
char.IsAsciiLetterOrDigit(character) || character is '-' or '_');
|
|
}
|
|
|
|
private static bool IsSafeOpaqueActionId(string value) =>
|
|
value.Length is > 0 and <= 64 && value.All(character =>
|
|
char.IsLetterOrDigit(character) || character is '-' or '_');
|
|
|
|
private static JsonSerializerOptions CreateSerializerOptions()
|
|
{
|
|
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
|
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
|
|
return options;
|
|
}
|
|
}
|