feat: advance legacy UI behavior parity

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

View File

@@ -82,6 +82,10 @@ public sealed record LegacyActivateFixedSectionIntent(int SectionIndex) : Legacy
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;
@@ -135,7 +139,9 @@ public sealed record LegacyActivateComparisonActionIntent(string ActionId) : Leg
public sealed record LegacyActivateComparisonSectionIntent(int SectionIndex) : LegacyUiIntent;
public sealed record LegacySearchThemesIntent(string Query, ThemeSession NxtSession)
// 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;
@@ -207,15 +213,23 @@ public sealed record LegacyReorderOperatorCatalogDraftRowIntent(
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;
decimal? BuyAmount) : LegacyUiIntent;
public sealed record LegacyCheckOperatorCatalogThemeNameIntent(string Name) : LegacyUiIntent;
public sealed record LegacySaveOperatorCatalogIntent(string Name) : LegacyUiIntent;
public sealed record LegacyDeleteOperatorCatalogIntent : LegacyUiIntent;
public sealed record LegacyConfirmNativeDialogIntent : LegacyUiIntent;
public sealed record LegacyCancelNativeDialogIntent : LegacyUiIntent;
public sealed record LegacyOpenManualFinancialIntent(ManualFinancialScreenKind Screen)
: LegacyUiIntent;
@@ -447,6 +461,7 @@ public static class LegacyBridgeProtocol
"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(
@@ -526,14 +541,25 @@ public static class LegacyBridgeProtocol
"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),
"delete-operator-catalog" => ParseOperatorCatalogEmpty(
"confirm-native-dialog" => ParseOperatorCatalogEmpty(
payload,
static () => new LegacyDeleteOperatorCatalogIntent()),
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),
@@ -676,8 +702,8 @@ public static class LegacyBridgeProtocol
}),
snapshot.Industry.SelectedIndex,
selectedName = snapshot.Industry.SelectedIndustry?.IndustryName,
firstName = snapshot.Industry.FirstIndustry?.IndustryName,
secondName = snapshot.Industry.SecondIndustry?.IndustryName,
firstName = snapshot.Industry.FirstText,
secondName = snapshot.Industry.SecondText,
actions = snapshot.Industry.Actions.Select(action => new
{
actionId = action.ActionId,
@@ -723,27 +749,22 @@ public static class LegacyBridgeProtocol
results = snapshot.Overseas.Stock.Results.Select(row => new
{
row.SelectionId,
displayName = row.InputName,
row.NationCode
displayName = row.InputName
})
},
actions = snapshot.Overseas.Actions.Select(action => new
actions = snapshot.Overseas.Actions.Select(action =>
{
action.ActionId,
action.Label,
action.Source,
action.Kind,
action.PeriodDays,
isAvailable = action.Source switch
var availability = GetOverseasActionAvailability(snapshot.Overseas, action);
return new
{
LegacyOverseasActionSource.FixedIndex =>
snapshot.Overseas.SelectedFixedIndex is not null,
LegacyOverseasActionSource.Industry =>
snapshot.Overseas.Industry.Selected is not null,
LegacyOverseasActionSource.Stock =>
snapshot.Overseas.Stock.Selected is not null,
_ => false
}
action.ActionId,
action.Label,
action.Source,
action.Kind,
action.PeriodDays,
availability.IsAvailable,
availability.Reason
};
})
},
comparison = snapshot.Comparison is null ? null : new
@@ -946,6 +967,7 @@ public static class LegacyBridgeProtocol
row.BuyAmount,
row.Position
}),
snapshot.OperatorCatalog.ActiveDraftRowId,
snapshot.OperatorCatalog.StockQuery,
stockResults = snapshot.OperatorCatalog.StockResults.Select(row => new
{
@@ -1119,6 +1141,9 @@ public static class LegacyBridgeProtocol
playout.RefreshCompletedCount,
playout.RefreshMaximumCount,
playout.RefreshLimitReached,
playout.RefreshNextAtUtc,
playout.RefreshLastSuccessAtUtc,
playout.RefreshFaultCode,
playout.FadeDuration,
playout.BackgroundEnabled,
playout.BackgroundFileName,
@@ -1140,6 +1165,22 @@ public static class LegacyBridgeProtocol
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;
@@ -1400,6 +1441,32 @@ public static class LegacyBridgeProtocol
? 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;
@@ -1556,26 +1623,17 @@ public static class LegacyBridgeProtocol
private static LegacyUiIntent? ParseThemeSearch(JsonElement payload)
{
if (!HasOnlyProperties(payload, "query", "nxtSession") ||
if (!HasOnlyProperties(payload, "query") ||
!TryString(
payload,
"query",
LegacyThemeSelectionService.MaximumQueryLength,
out var query) ||
!TryString(payload, "nxtSession", 16, out var sessionText))
out var query))
{
return null;
}
var session = sessionText switch
{
"preMarket" => ThemeSession.PreMarket,
"afterMarket" => ThemeSession.AfterMarket,
_ => (ThemeSession?)null
};
return session.HasValue
? new LegacySearchThemesIntent(query, session.Value)
: null;
return new LegacySearchThemesIntent(query);
}
private static LegacyUiIntent? ParseThemeSelection(JsonElement payload) =>
@@ -1857,17 +1915,35 @@ public static class LegacyBridgeProtocol
? new LegacyRemoveOperatorCatalogDraftRowIntent(rowId)
: null;
private static LegacyUiIntent? ParseOperatorCatalogDraftBuyAmount(JsonElement payload) =>
HasOnlyProperties(payload, "rowId", "buyAmount") &&
private static LegacyUiIntent? ParseOperatorCatalogDraftSelection(JsonElement payload) =>
HasOnlyProperties(payload, "rowId") &&
TryString(payload, "rowId", 128, out var rowId) &&
IsSafeIdentifier(rowId) &&
payload.TryGetProperty("buyAmount", out var amountElement) &&
amountElement.ValueKind == JsonValueKind.Number &&
amountElement.TryGetDecimal(out var amount) &&
decimal.Truncate(amount) == amount &&
amount is > 0 and <= 9_007_199_254_740_991m
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") &&
@@ -1875,7 +1951,7 @@ public static class LegacyBridgeProtocol
payload,
"name",
128,
allowEmpty: false,
allowEmpty: true,
allowUnderscore: true,
out var name) &&
string.Equals(name, name.Trim(), StringComparison.Ordinal)