feat(state): 迁移 itemNotifications(复用 ItemNotificationData)
This commit is contained in:
1 parent
bb544643b5
commit
6f68afa678
8 files changed
+251
-21
No files matched your search
@@ -143,7 +143,7 @@
|
|||||||
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
|
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
|
||||||
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
|
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
|
||||||
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
|
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
|
||||||
- 已迁移切片 **86/89**:
|
- 已迁移切片 **87/89**:
|
||||||
- `inviteState` → `aD` / `pD.er5`(`InviteReducer.kt`)
|
- `inviteState` → `aD` / `pD.er5`(`InviteReducer.kt`)
|
||||||
- `loginByPassword` → `gD` / `MD.cr6`(`LoginReducer.kt`,含 `SetLogin`/`SetPassword` 把 status 打回 `NONE` 的原语义)
|
- `loginByPassword` → `gD` / `MD.cr6`(`LoginReducer.kt`,含 `SetLogin`/`SetPassword` 把 status 打回 `NONE` 的原语义)
|
||||||
- `twitchState` → `g9` / `y9.gta`(`TwitchReducer.kt`;`SetAccount` 是 Twitch 账号,不是 UID)
|
- `twitchState` → `g9` / `y9.gta`(`TwitchReducer.kt`;`SetAccount` 是 Twitch 账号,不是 UID)
|
||||||
@@ -435,12 +435,16 @@
|
|||||||
`IgnoreUserMessages`/`ShowUserMessages` 增删屏蔽名单(`_C` 重载时清空);
|
`IgnoreUserMessages`/`ShowUserMessages` 增删屏蔽名单(`_C` 重载时清空);
|
||||||
`rN`(用户资料刷新)依赖消息内的用户对象,离线重建按 no-op;`SendMessageToServer` 的时间戳留给中间件。
|
`rN`(用户资料刷新)依赖消息内的用户对象,离线重建按 no-op;`SendMessageToServer` 的时间戳留给中间件。
|
||||||
消息复用生成物 `ChatMessage`。
|
消息复用生成物 `ChatMessage`。
|
||||||
|
- `itemNotifications` → `_1` / `A1().dso`(`ItemNotificationsReducer.kt`):1 字段,6 个分支。
|
||||||
|
`AddItemNotification(s)` 加入(构造 `ItemNotificationData`);`RemoveItemNotification` 先按有效期过滤,
|
||||||
|
再删掉匹配谓词的通知;`UpdateItemNotification` 对匹配项应用 handler;
|
||||||
|
`S1` 只做持久化(no-op);`_C` 重载时只保留有效期内的通知。
|
||||||
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
|
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
|
||||||
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
|
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
|
||||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 85 个有 reducer 的切片,
|
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 86 个有 reducer 的切片,
|
||||||
`launchParams` 与其余 **3 个字段原样透传**;
|
`launchParams` 与其余 **2 个字段原样透传**;
|
||||||
迁移进度用 `RootReducer.Coverage`(`已迁移 86/89`)与 `RootWiring.Unmigrated` 暴露。
|
迁移进度用 `RootReducer.Coverage`(`已迁移 87/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **953 项全绿**,含 89 槽位完整性、85 个归约切片一次归约互不串片、未迁移字段透传,
|
- 自检:`TankiOffline/tools/RunStateTest.ps1` **964 项全绿**,含 89 槽位完整性、86 个归约切片一次归约互不串片、未迁移字段透传,
|
||||||
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
|
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
|
||||||
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
|
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
|
||||||
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
|
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generatedAt": "2026-10-24T14:08:05.684Z",
|
"generatedAt": "2026-10-24T14:12:51.294Z",
|
||||||
"module": "module_10032.js",
|
"module": "module_10032.js",
|
||||||
"RootState": {
|
"RootState": {
|
||||||
"Class": "z6",
|
"Class": "z6",
|
||||||
@@ -92,7 +92,8 @@
|
|||||||
"challenges",
|
"challenges",
|
||||||
"clansStorage",
|
"clansStorage",
|
||||||
"clan",
|
"clan",
|
||||||
"chatState"
|
"chatState",
|
||||||
|
"itemNotifications"
|
||||||
],
|
],
|
||||||
"AnonymousActions": [
|
"AnonymousActions": [
|
||||||
{
|
{
|
||||||
@@ -1636,6 +1637,12 @@
|
|||||||
"Name": "SetChatConfig",
|
"Name": "SetChatConfig",
|
||||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||||
"Effect": "config(原名 Configure,与匹配大厅同名类冲突,改名 SetChatConfig)"
|
"Effect": "config(原名 Configure,与匹配大厅同名类冲突,改名 SetChatConfig)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Local": "S1",
|
||||||
|
"Name": "PersistItemNotifications",
|
||||||
|
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||||
|
"Effect": "通知列表变化后写回存储(纯 reducer 里 no-op)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Markers": [
|
"Markers": [
|
||||||
@@ -3218,15 +3225,15 @@
|
|||||||
"Reducer": "A1().dso",
|
"Reducer": "A1().dso",
|
||||||
"Delegate": "A1().dso(t, n.xpx_1)",
|
"Delegate": "A1().dso(t, n.xpx_1)",
|
||||||
"Actions": [
|
"Actions": [
|
||||||
"?S1",
|
|
||||||
"AddItemNotification",
|
"AddItemNotification",
|
||||||
"AddItemNotifications",
|
"AddItemNotifications",
|
||||||
|
"PersistItemNotifications",
|
||||||
"RemoveItemNotification",
|
"RemoveItemNotification",
|
||||||
"ResetSettingsFromStorage",
|
"ResetSettingsFromStorage",
|
||||||
"UpdateItemNotification"
|
"UpdateItemNotification"
|
||||||
],
|
],
|
||||||
"Default": "new _1()",
|
"Default": "new _1()",
|
||||||
"Migrated": false
|
"Migrated": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Field": "contextTutorial",
|
"Field": "contextTutorial",
|
||||||
|
|||||||
@@ -368,6 +368,8 @@ const AnonymousActionNames = new Map([
|
|||||||
['sN', 'MarkFirstChannelMessagesLoaded'],
|
['sN', 'MarkFirstChannelMessagesLoaded'],
|
||||||
// YP 的 toString 名 Configure 与匹配大厅同名类冲突,改名避免字段错配。
|
// YP 的 toString 名 Configure 与匹配大厅同名类冲突,改名避免字段错配。
|
||||||
['YP', 'SetChatConfig'],
|
['YP', 'SetChatConfig'],
|
||||||
|
// itemNotifications 切片(_1 / I1.dso):S1 没有 toString。
|
||||||
|
['S1', 'PersistItemNotifications'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
|
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
|
||||||
@@ -858,6 +860,7 @@ const Migrated = {
|
|||||||
clansStorage: { Type: 'ClanStorageStateSlice', Reduce: 'ReduceClanStorage' },
|
clansStorage: { Type: 'ClanStorageStateSlice', Reduce: 'ReduceClanStorage' },
|
||||||
clan: { Type: 'ClanStateSlice', Reduce: 'ReduceClan' },
|
clan: { Type: 'ClanStateSlice', Reduce: 'ReduceClan' },
|
||||||
chatState: { Type: 'ChatStateSlice', Reduce: 'ReduceChat' },
|
chatState: { Type: 'ChatStateSlice', Reduce: 'ReduceChat' },
|
||||||
|
itemNotifications: { Type: 'ItemNotificationsStateSlice', Reduce: 'ReduceItemNotifications' },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
|
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
|
||||||
@@ -1306,6 +1309,7 @@ const AppSettingsAnonymousEffects = {
|
|||||||
qN: 'isNeedScroll=true',
|
qN: 'isNeedScroll=true',
|
||||||
sN: 'isFirstChannelMessagesLoad=false',
|
sN: 'isFirstChannelMessagesLoad=false',
|
||||||
YP: 'config(原名 Configure,与匹配大厅同名类冲突,改名 SetChatConfig)',
|
YP: 'config(原名 Configure,与匹配大厅同名类冲突,改名 SetChatConfig)',
|
||||||
|
S1: '通知列表变化后写回存储(纯 reducer 里 no-op)',
|
||||||
};
|
};
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(P, 'names', 'root_wiring.json'),
|
path.join(P, 'names', 'root_wiring.json'),
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
// ItemNotificationsReducer.kt —— 物品「新」标记切片(对应 module_10032 的 _1 状态 + I1.dso)
|
||||||
|
//
|
||||||
|
// 证据链:
|
||||||
|
// 状态类 _1:1 个字段,toString 名 "ItemNotificationsState(notifications=…)"。
|
||||||
|
// 默认:notifications = emptySet()。
|
||||||
|
// 根委派:`A1().dso(t, n.xpx_1)`。
|
||||||
|
// reducer:
|
||||||
|
// q1(AddItemNotifications) → 批量加入(逐条去重)
|
||||||
|
// y1(AddItemNotification) → 用 6 个字段构造 `ItemNotificationData` 后加入
|
||||||
|
// $1(RemoveItemNotification) → 先按有效期过滤,再删掉匹配谓词的通知
|
||||||
|
// (section/itemId/type/parentId/excludeType;ssn_1 = 跳过有效期检查)
|
||||||
|
// j1(UpdateItemNotification) → 保留不匹配项,对匹配项应用 updateHandler
|
||||||
|
// S1(无 toString) → 只做持久化标记(纯 reducer 里 no-op)
|
||||||
|
// _C(ResetSettingsFromStorage)→ 从存储重载并过滤过期(Kotlin 侧按「只保留有效期内的」收口)
|
||||||
|
//
|
||||||
|
// 有效期判定 `msm`:`(startTime == null || startTime <= now) && (endTime == null || endTime > now)`。
|
||||||
|
// 通知模型直接复用生成物 `ItemNotificationData`(section / itemId / type / parentId / startTime / endTime)。
|
||||||
|
package App.State.Reducers
|
||||||
|
|
||||||
|
import App.State.AddItemNotification
|
||||||
|
import App.State.AddItemNotifications
|
||||||
|
import App.State.ItemNotificationData
|
||||||
|
import App.State.RemoveItemNotification
|
||||||
|
import App.State.UpdateItemNotification
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ 匿名动作 */
|
||||||
|
|
||||||
|
/** 原 `S1`:通知列表变化后写回存储(纯 reducer 里 no-op)。 */
|
||||||
|
object PersistItemNotifications
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ 状态 */
|
||||||
|
|
||||||
|
data class ItemNotificationsStateSlice(
|
||||||
|
val Notifications: Set<Any?> = emptySet(),
|
||||||
|
)
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ reducer */
|
||||||
|
|
||||||
|
private fun Now(): Double = (js("Date.now()") as Number).toDouble()
|
||||||
|
|
||||||
|
private fun TimeOf(Value: Any?): Double? = (Value as? Number)?.toDouble()
|
||||||
|
|
||||||
|
/** 原 `msm`:有效期判定。 */
|
||||||
|
private fun InPeriod(Item: ItemNotificationData): Boolean {
|
||||||
|
val Now = Now()
|
||||||
|
val Start = TimeOf(Item.startTime)
|
||||||
|
val End = TimeOf(Item.endTime)
|
||||||
|
return (Start == null || Start <= Now) && (End == null || End > Now)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 原 `tsm`:删除 / 更新谓词(忽略最后一个 `skipPeriodCheck` 参数)。 */
|
||||||
|
private fun Matches(
|
||||||
|
Item: ItemNotificationData,
|
||||||
|
Section: Any?,
|
||||||
|
ItemId: Any?,
|
||||||
|
Type: Any?,
|
||||||
|
ParentId: Any?,
|
||||||
|
ExcludeType: Any?,
|
||||||
|
): Boolean {
|
||||||
|
val WantedId = (ItemId as? Number)?.toLong() ?: ItemId
|
||||||
|
return (Section == null || Item.section == Section) &&
|
||||||
|
(ItemId == null || Item.itemId == WantedId) &&
|
||||||
|
(Type == null || Item.type == Type) &&
|
||||||
|
(ParentId == null || Item.parentId == ParentId) &&
|
||||||
|
(ExcludeType == null || Item.type != ExcludeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AsItem(Value: Any?): ItemNotificationData? = Value as? ItemNotificationData
|
||||||
|
|
||||||
|
fun ReduceItemNotifications(
|
||||||
|
State0: ItemNotificationsStateSlice,
|
||||||
|
Action: Any,
|
||||||
|
): ItemNotificationsStateSlice = when (Action) {
|
||||||
|
is AddItemNotifications ->
|
||||||
|
State0.copy(Notifications = State0.Notifications + Action.data)
|
||||||
|
is AddItemNotification -> State0.copy(
|
||||||
|
Notifications = State0.Notifications + ItemNotificationData(
|
||||||
|
section = Action.section,
|
||||||
|
itemId = Action.itemId,
|
||||||
|
type = Action.type,
|
||||||
|
parentId = Action.parentId,
|
||||||
|
startTime = Action.startTime,
|
||||||
|
endTime = Action.endTime,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
is RemoveItemNotification -> State0.copy(
|
||||||
|
Notifications = State0.Notifications.filterNot {
|
||||||
|
val Item = AsItem(it) ?: return@filterNot false
|
||||||
|
val Skipped = Action.skipPeriodCheck == true
|
||||||
|
(Skipped || InPeriod(Item)) &&
|
||||||
|
Matches(
|
||||||
|
Item,
|
||||||
|
Action.section,
|
||||||
|
Action.itemId,
|
||||||
|
Action.type,
|
||||||
|
Action.parentId,
|
||||||
|
Action.excludeType,
|
||||||
|
)
|
||||||
|
}.toSet(),
|
||||||
|
)
|
||||||
|
is UpdateItemNotification -> State0.copy(
|
||||||
|
Notifications = State0.Notifications.map {
|
||||||
|
val Item = AsItem(it) ?: return@map it
|
||||||
|
if (!Matches(
|
||||||
|
Item,
|
||||||
|
Action.section,
|
||||||
|
Action.itemId,
|
||||||
|
Action.type,
|
||||||
|
Action.parentId,
|
||||||
|
Action.excludeType,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val Transform = Action.updateHandler as? Function1<ItemNotificationData, ItemNotificationData>
|
||||||
|
Transform?.invoke(Item) ?: it
|
||||||
|
}
|
||||||
|
}.toSet(),
|
||||||
|
)
|
||||||
|
is PersistItemNotifications -> State0
|
||||||
|
is ResetSettingsFromStorage -> State0.copy(
|
||||||
|
Notifications = State0.Notifications.filter { AsItem(it)?.let(::InPeriod) ?: true }.toSet(),
|
||||||
|
)
|
||||||
|
else -> State0
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
|
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
|
||||||
//
|
//
|
||||||
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
|
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
|
||||||
// 这里采用同一形状。已迁移的 86 个切片里,85 个真正调用 reducer,launchParams 只做类型收口:
|
// 这里采用同一形状。已迁移的 87 个切片里,86 个真正调用 reducer,launchParams 只做类型收口:
|
||||||
//
|
//
|
||||||
// inviteState → ReduceInvite(aD / pD.er5)
|
// inviteState → ReduceInvite(aD / pD.er5)
|
||||||
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
|
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
|
||||||
@@ -88,9 +88,10 @@
|
|||||||
// clansStorage → ReduceClanStorage(lO / me.vr1,根委派 `me.vr1(t, n.rpv_1)`)
|
// clansStorage → ReduceClanStorage(lO / me.vr1,根委派 `me.vr1(t, n.rpv_1)`)
|
||||||
// clan → ReduceClan(jN / we.hr0,根委派 `we.hr0(t, n.qpv_1)`)
|
// clan → ReduceClan(jN / we.hr0,根委派 `we.hr0(t, n.qpv_1)`)
|
||||||
// chatState → ReduceChat(LP / $r.wqv,根委派 `$r.wqv(t, n.opv_1)`)
|
// chatState → ReduceChat(LP / $r.wqv,根委派 `$r.wqv(t, n.opv_1)`)
|
||||||
|
// itemNotifications → ReduceItemNotifications(_1 / A1().dso,根委派 `A1().dso(t, n.xpx_1)`)
|
||||||
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
|
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
|
||||||
//
|
//
|
||||||
// 其余 3 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
// 其余 2 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||||
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
|
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
|
||||||
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
|
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
|
||||||
package App.State
|
package App.State
|
||||||
@@ -132,6 +133,7 @@ import App.State.Reducers.ReduceGraphicsSettings
|
|||||||
import App.State.Reducers.ReduceHome
|
import App.State.Reducers.ReduceHome
|
||||||
import App.State.Reducers.ReduceIcons
|
import App.State.Reducers.ReduceIcons
|
||||||
import App.State.Reducers.ReduceInvite
|
import App.State.Reducers.ReduceInvite
|
||||||
|
import App.State.Reducers.ReduceItemNotifications
|
||||||
import App.State.Reducers.ReduceKeyboardSettings
|
import App.State.Reducers.ReduceKeyboardSettings
|
||||||
import App.State.Reducers.ReduceLanguage
|
import App.State.Reducers.ReduceLanguage
|
||||||
import App.State.Reducers.ReduceLocalBattleUser
|
import App.State.Reducers.ReduceLocalBattleUser
|
||||||
@@ -279,6 +281,7 @@ object RootReducer {
|
|||||||
ClansStorage = ReduceClanStorage(State0.ClansStorage, Action),
|
ClansStorage = ReduceClanStorage(State0.ClansStorage, Action),
|
||||||
Clan = ReduceClan(State0.Clan, Action),
|
Clan = ReduceClan(State0.Clan, Action),
|
||||||
ChatState = ReduceChat(State0.ChatState, Action),
|
ChatState = ReduceChat(State0.ChatState, Action),
|
||||||
|
ItemNotifications = ReduceItemNotifications(State0.ItemNotifications, Action),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import App.State.Reducers.GraphicsSettingsStateSlice
|
|||||||
import App.State.Reducers.HomeStateSlice
|
import App.State.Reducers.HomeStateSlice
|
||||||
import App.State.Reducers.IconsStateSlice
|
import App.State.Reducers.IconsStateSlice
|
||||||
import App.State.Reducers.InviteStateSlice
|
import App.State.Reducers.InviteStateSlice
|
||||||
|
import App.State.Reducers.ItemNotificationsStateSlice
|
||||||
import App.State.Reducers.KeyboardSettingsStateSlice
|
import App.State.Reducers.KeyboardSettingsStateSlice
|
||||||
import App.State.Reducers.LobbyLoaderStateSlice
|
import App.State.Reducers.LobbyLoaderStateSlice
|
||||||
import App.State.Reducers.LocalBattleUserStateSlice
|
import App.State.Reducers.LocalBattleUserStateSlice
|
||||||
@@ -248,8 +249,8 @@ data class RootState(
|
|||||||
val TwitchState: TwitchStateSlice = TwitchStateSlice(),
|
val TwitchState: TwitchStateSlice = TwitchStateSlice(),
|
||||||
/** 原 icons(oH / vs.kru)。已迁移。 */
|
/** 原 icons(oH / vs.kru)。已迁移。 */
|
||||||
val Icons: IconsStateSlice = IconsStateSlice(),
|
val Icons: IconsStateSlice = IconsStateSlice(),
|
||||||
/** 原 itemNotifications(_1 / A1().dso)。未迁移:原样透传。 */
|
/** 原 itemNotifications(_1 / A1().dso)。已迁移。 */
|
||||||
val ItemNotifications: Any? = null,
|
val ItemNotifications: ItemNotificationsStateSlice = ItemNotificationsStateSlice(),
|
||||||
/** 原 contextTutorial(Mst / Jst().tth)。已迁移。 */
|
/** 原 contextTutorial(Mst / Jst().tth)。已迁移。 */
|
||||||
val ContextTutorial: ContextTutorialStateSlice = ContextTutorialStateSlice(),
|
val ContextTutorial: ContextTutorialStateSlice = ContextTutorialStateSlice(),
|
||||||
/** 原 suppliesTutorial(ust / Uw.uth)。已迁移。 */
|
/** 原 suppliesTutorial(ust / Uw.uth)。已迁移。 */
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ object RootWiring {
|
|||||||
SliceWiring("rewardedVideosState", "upx_1", "JQ", "xh.zsl", "xh.zsl(t, n.upx_1)", listOf("SetDisplayState")),
|
SliceWiring("rewardedVideosState", "upx_1", "JQ", "xh.zsl", "xh.zsl(t, n.upx_1)", listOf("SetDisplayState")),
|
||||||
SliceWiring("twitchState", "vpx_1", "g9", "uf.gta", "uf.gta(t, n.vpx_1)", listOf("SetAccount", "SetDaysForNextLinking", "SetTwitchEnabled")),
|
SliceWiring("twitchState", "vpx_1", "g9", "uf.gta", "uf.gta(t, n.vpx_1)", listOf("SetAccount", "SetDaysForNextLinking", "SetTwitchEnabled")),
|
||||||
SliceWiring("icons", "wpx_1", "oH", "vs.kru", "vs.kru(t, n.wpx_1)", listOf("SetIcons")),
|
SliceWiring("icons", "wpx_1", "oH", "vs.kru", "vs.kru(t, n.wpx_1)", listOf("SetIcons")),
|
||||||
SliceWiring("itemNotifications", "xpx_1", "_1", "A1().dso", "A1().dso(t, n.xpx_1)", listOf("?S1", "AddItemNotification", "AddItemNotifications", "RemoveItemNotification", "ResetSettingsFromStorage", "UpdateItemNotification")),
|
SliceWiring("itemNotifications", "xpx_1", "_1", "A1().dso", "A1().dso(t, n.xpx_1)", listOf("AddItemNotification", "AddItemNotifications", "PersistItemNotifications", "RemoveItemNotification", "ResetSettingsFromStorage", "UpdateItemNotification")),
|
||||||
SliceWiring("contextTutorial", "ypx_1", "Mst", "Jst().tth", "Jst().tth(t, n.ypx_1)", listOf("LoadCurrentElement", "MarkElementAsViewed", "SetLoadedElements", "SetMissionToElement", "SetUnViewedElements")),
|
SliceWiring("contextTutorial", "ypx_1", "Mst", "Jst().tth", "Jst().tth(t, n.ypx_1)", listOf("LoadCurrentElement", "MarkElementAsViewed", "SetLoadedElements", "SetMissionToElement", "SetUnViewedElements")),
|
||||||
SliceWiring("suppliesTutorial", "zpx_1", "ust", "Uw.uth", "Uw.uth(t, n.zpx_1)", listOf("ActivatedInCurrentBattle", "SetLoadedElement")),
|
SliceWiring("suppliesTutorial", "zpx_1", "ust", "Uw.uth", "Uw.uth(t, n.zpx_1)", listOf("ActivatedInCurrentBattle", "SetLoadedElement")),
|
||||||
SliceWiring("questCategories", "apy_1", "zZ", "th.lsh", "th.lsh(t, n.apy_1)", listOf("RemoveQuestCategory", "RequestQuestCategoriesStatistic", "SetQuestCategories", "SetQuestCategoriesStatistic")),
|
SliceWiring("questCategories", "apy_1", "zZ", "th.lsh", "th.lsh(t, n.apy_1)", listOf("RemoveQuestCategory", "RequestQuestCategoriesStatistic", "SetQuestCategories", "SetQuestCategoriesStatistic")),
|
||||||
@@ -106,7 +106,7 @@ object RootWiring {
|
|||||||
)
|
)
|
||||||
|
|
||||||
/** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */
|
/** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */
|
||||||
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "accountSettings", "proBattle", "language", "announcement", "launchParams", "news", "notifications", "quests", "lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl", "rulesUpdated", "notificationByCountry", "availabilityByRank", "icons", "questCategories", "mainQuest", "securitySettings", "referralEvent", "tipsState", "rankUpDialogState", "battleResult", "captchaState", "modalScreen", "droppableGold", "eventGoods", "creatorTagState", "rewardedVideosState", "passwordChangeState", "system", "contextTutorial", "battleTutorial", "spectator", "twoStepVerificationState", "antiAddiction", "moderation", "migrationState", "register", "userReportsState", "friends", "suppliesState", "clanUserInfo", "battleInviteState", "partner", "userRenameState", "battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial", "clanChatState", "battleSearchState", "battleStatistics", "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings", "graphicsSettings", "controlSettings", "battleSettings", "contractState", "tankBuilderState", "battleMessage", "maps", "battleChat", "remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage", "clan", "chatState")
|
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "accountSettings", "proBattle", "language", "announcement", "launchParams", "news", "notifications", "quests", "lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl", "rulesUpdated", "notificationByCountry", "availabilityByRank", "icons", "questCategories", "mainQuest", "securitySettings", "referralEvent", "tipsState", "rankUpDialogState", "battleResult", "captchaState", "modalScreen", "droppableGold", "eventGoods", "creatorTagState", "rewardedVideosState", "passwordChangeState", "system", "contextTutorial", "battleTutorial", "spectator", "twoStepVerificationState", "antiAddiction", "moderation", "migrationState", "register", "userReportsState", "friends", "suppliesState", "clanUserInfo", "battleInviteState", "partner", "userRenameState", "battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial", "clanChatState", "battleSearchState", "battleStatistics", "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings", "graphicsSettings", "controlSettings", "battleSettings", "contractState", "tankBuilderState", "battleMessage", "maps", "battleChat", "remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage", "clan", "chatState", "itemNotifications")
|
||||||
|
|
||||||
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
|
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
|
||||||
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
|
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
// System / ContextTutorial / BattleTutorial / Spectator / TwoStepVerification / AntiAddiction /
|
// System / ContextTutorial / BattleTutorial / Spectator / TwoStepVerification / AntiAddiction /
|
||||||
// Moderation / MigrationState / Register / UserReports /
|
// Moderation / MigrationState / Register / UserReports /
|
||||||
// Friends / Supplies / ClanUserInfo / BattleInvite / Partner / UserRename /
|
// Friends / Supplies / ClanUserInfo / BattleInvite / Partner / UserRename /
|
||||||
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking / BattlesSelect / BattleUsers / Challenges / ClansStorage / Clan / ChatState;
|
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking / BattlesSelect / BattleUsers / Challenges / ClansStorage / Clan / ChatState / ItemNotifications;
|
||||||
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
|
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
|
||||||
//
|
//
|
||||||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||||||
@@ -904,6 +904,14 @@ import App.State.Reducers.RequireHideChatKeyboard
|
|||||||
import App.State.Reducers.ResetChat
|
import App.State.Reducers.ResetChat
|
||||||
import App.State.Reducers.SetChatConfig
|
import App.State.Reducers.SetChatConfig
|
||||||
import App.State.Reducers.ShowChatKeyboard
|
import App.State.Reducers.ShowChatKeyboard
|
||||||
|
import App.State.AddItemNotification
|
||||||
|
import App.State.AddItemNotifications
|
||||||
|
import App.State.ItemNotificationData
|
||||||
|
import App.State.RemoveItemNotification
|
||||||
|
import App.State.UpdateItemNotification
|
||||||
|
import App.State.Reducers.ItemNotificationsStateSlice
|
||||||
|
import App.State.Reducers.PersistItemNotifications
|
||||||
|
import App.State.Reducers.ReduceItemNotifications
|
||||||
import App.State.Reducers.UserRankBounds
|
import App.State.Reducers.UserRankBounds
|
||||||
import App.State.Reducers.UserStateSlice
|
import App.State.Reducers.UserStateSlice
|
||||||
import App.State.Reducers.ClearHullData
|
import App.State.Reducers.ClearHullData
|
||||||
@@ -1331,7 +1339,7 @@ object StateCheck {
|
|||||||
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
|
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⑭ 根 reducer:89 个槽位的委派表 + 86 个已迁移切片(85 归约 + launchParams 透传)
|
// ⑭ 根 reducer:89 个槽位的委派表 + 87 个已迁移切片(86 归约 + launchParams 透传)
|
||||||
run {
|
run {
|
||||||
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
||||||
Check(
|
Check(
|
||||||
@@ -1395,7 +1403,7 @@ object StateCheck {
|
|||||||
LocalBattleW.Reducer == "(inline@localBattleUserState)"
|
LocalBattleW.Reducer == "(inline@localBattleUserState)"
|
||||||
)
|
)
|
||||||
Check(
|
Check(
|
||||||
"根:迁移表 = 86 个已迁移字段,其余 3 个待迁",
|
"根:迁移表 = 87 个已迁移字段,其余 2 个待迁",
|
||||||
RootReducer.Migrated ==
|
RootReducer.Migrated ==
|
||||||
setOf(
|
setOf(
|
||||||
"inviteState", "loginByPassword", "twitchState", "home",
|
"inviteState", "loginByPassword", "twitchState", "home",
|
||||||
@@ -1419,9 +1427,9 @@ object StateCheck {
|
|||||||
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
|
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
|
||||||
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
|
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
|
||||||
"tankBuilderState", "battleMessage", "maps", "battleChat",
|
"tankBuilderState", "battleMessage", "maps", "battleChat",
|
||||||
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage", "clan", "chatState",
|
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage", "clan", "chatState", "itemNotifications",
|
||||||
) &&
|
) &&
|
||||||
RootWiring.Unmigrated.size == 3
|
RootWiring.Unmigrated.size == 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
|
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
|
||||||
@@ -1477,7 +1485,7 @@ object StateCheck {
|
|||||||
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
|
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
|
||||||
)
|
)
|
||||||
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
|
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
|
||||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 86/89")
|
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 87/89")
|
||||||
|
|
||||||
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
||||||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||||||
@@ -7346,6 +7354,83 @@ object StateCheck {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 97 切片 reducer:ItemNotifications(_1 / A1().dso,1 字段;6 个动作)
|
||||||
|
run {
|
||||||
|
fun Item(Id: Long, End: Any? = null): ItemNotificationData = ItemNotificationData(
|
||||||
|
section = "shop", itemId = Id, type = "new", parentId = null,
|
||||||
|
startTime = null, endTime = End,
|
||||||
|
)
|
||||||
|
|
||||||
|
val S0 = ItemNotificationsStateSlice()
|
||||||
|
Check("切片 ItemNotifications:默认空集合", S0.Notifications.isEmpty())
|
||||||
|
val S1 = ReduceItemNotifications(S0, AddItemNotification("shop", 1L, "new", null, null, null))
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:AddItemNotification 构造并入集合",
|
||||||
|
S1.Notifications.size == 1 &&
|
||||||
|
(S1.Notifications.first() as ItemNotificationData).itemId == 1L
|
||||||
|
)
|
||||||
|
val S2 = ReduceItemNotifications(S1, AddItemNotifications(listOf(Item(2L), Item(3L))))
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:AddItemNotifications 批量加入",
|
||||||
|
(S2.Notifications.map { (it as ItemNotificationData).itemId }).toSet() ==
|
||||||
|
setOf(1L, 2L, 3L)
|
||||||
|
)
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:RemoveItemNotification 按 section+itemId 删",
|
||||||
|
ReduceItemNotifications(
|
||||||
|
S2,
|
||||||
|
RemoveItemNotification("shop", 2L, null, null, null, true),
|
||||||
|
).Notifications.size == 2
|
||||||
|
)
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:excludeType 命中时保留",
|
||||||
|
ReduceItemNotifications(
|
||||||
|
S2,
|
||||||
|
RemoveItemNotification("shop", null, null, null, "new", true),
|
||||||
|
).Notifications.size == 3
|
||||||
|
)
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:UpdateItemNotification 对匹配项应用 handler",
|
||||||
|
ReduceItemNotifications(
|
||||||
|
S2,
|
||||||
|
UpdateItemNotification("shop", 1L, null, null, null, { Item: ItemNotificationData ->
|
||||||
|
Item.copy(section = "updated")
|
||||||
|
}),
|
||||||
|
).let { Updated ->
|
||||||
|
(Updated.Notifications.first { (it as ItemNotificationData).itemId == 1L }
|
||||||
|
as ItemNotificationData).section == "updated"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:PersistItemNotifications 是 no-op",
|
||||||
|
ReduceItemNotifications(S2, PersistItemNotifications) == S2
|
||||||
|
)
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:过期项在重载时被过滤",
|
||||||
|
ReduceItemNotifications(
|
||||||
|
ReduceItemNotifications(S0, AddItemNotification("shop", 9L, "new", null, null, 1L)),
|
||||||
|
ResetSettingsFromStorage,
|
||||||
|
).Notifications.isEmpty()
|
||||||
|
)
|
||||||
|
Check(
|
||||||
|
"切片 ItemNotifications:无关动作不改状态",
|
||||||
|
ReduceItemNotifications(S2, AddFrags(1)) == S2
|
||||||
|
)
|
||||||
|
|
||||||
|
val Root0 = RootState()
|
||||||
|
val Root1 = ReduceRoot(Root0, AddItemNotification("shop", 1L, "new", null, null, null))
|
||||||
|
Check(
|
||||||
|
"根:AddItemNotification 只动 itemNotifications",
|
||||||
|
Root1.ItemNotifications.Notifications.size == 1 &&
|
||||||
|
Root1.ChatState == Root0.ChatState
|
||||||
|
)
|
||||||
|
val W = RootWiring.ByField("itemNotifications")!!
|
||||||
|
Check(
|
||||||
|
"根:itemNotifications 委派到 _1 / A1().dso 且 6 个动作",
|
||||||
|
W.SliceClass == "_1" && W.Reducer == "A1().dso" && W.Actions.size == 6
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val Failed = Results.filter { !it.second }
|
val Failed = Results.filter { !it.second }
|
||||||
println("TankiOffline 状态层自检:${Results.size} 项")
|
println("TankiOffline 状态层自检:${Results.size} 项")
|
||||||
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)
|
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)
|
||||||
|
|||||||
Reference in new issue
Block a user