feat(mods): 本地 Mod 列表与启停(M224 MODTOGGLE)
三层一次做完:core 读写目录 → presenter DTO/host → 实例详情页 MODS 区。 r1 复审后 rebase 到 main 71144df(M223 已合入),并修掉四条文档疵。 core(crates/core/src/addon/mods/) - suffix:`.disabled` / `.old` 的**唯一判据**(四态枚举 + 展示名 + 双向改名目标名), 收敛原先两份同构实现——`export/scan.rs::is_disabled_path` 与 `import/plan/paths.rs::suffix_variant` 改为委派/再导出,签名与行为不变。 - list:单根语义(入口是 derive_game_dir 派生出的运行目录)列 `<run>/mods/`,不按扩展名过滤, 符号链接/目录/怪名字逐条登记原因;`mods/` 本身是符号链接时拒(不跟随)。 - toggle:启停 = 在 `mods/` 里改名。五条守卫(裸文件名 / 目录可用 / 普通文件 / 不覆盖 / 不做 copy+delete 兜底)+ 幂等;`.disabled` 只插在 `.old` 之前。 presenter - dto/mods:ModsCommand / ModListView / ModFailureView(不拼文案,只给结构 + OS 原文)。 - host/mods:后台线程列目录与启停,运行目录走与启动同一份 D20 派生、不写任何设置; 游戏在跑时 host 侧直接挡。 - state/mods:快照整体替换、迟到结果丢弃、在飞标记;`AppState::mods`。 app-iced(实例详情页) - MODS 区(`pages/instances/mods.rs`):名字 + 大小 + `.old` 徽标 + Switch;运行中/在飞只读。 - 进入详情页即后台列一次(不走 3 s 观察窗,避免打红 M192 的「空闲不烧帧」守卫)。 - MODS 卡排在启动参数**之前**(详情列是滚动区,排在后面 1180×1000 下永远看不见)。 - i18n:19 条中英文案(`catalog/instances.rs` + 金表)。 rebase 冲突解法(三个文件,全是"两侧同段新增",都保留): - `i18n/catalog/instances.rs` + `i18n/catalog/golden.rs`:M223 的 Adopt* 段在前、M224 的 InstancesMods* 段在后(= `Key::ALL` 顺序 = 金表顺序)。 - `presenter/src/state.rs`:`adopt` 与 `mods` 字段/初始化/apply_core 分支都留;`tiles()` 归 `state/tiles.rs`(本分支搬的),`apply_agent`/`agent_rail_visible` 归 `state/agent.rs` (M223 搬的)——两处搬移不重叠,state.rs 里都不再出现。 r1 四条文档疵: 1. `pages/instances/mods.rs` 模块文档:挂载位置写反("启动参数卡之后"→实际之前),并补上为什么; 2. `state/mods.rs`:引用不存在的 `CoreEvent::ModsChanged/ModsFailed` → 改成 `CoreEvent::Mods` + 文字说明 Ok 落快照 / Err 落具名失败; 3. `app/state/mods_shot.rs`:文档说"目录串取真实派生结果"但代码留空 → 改成"故意留空"; 4. `app/state/refresh.rs::note_mods_failure`:文档说"连点两次看见两次红字"与去重机制不符 → 改成按 `(实例, 文件, 大类)` 三元组去重(防刷屏),换文件/换实例/换大类仍会再弹。 顺带(r1 已确认的 main 红):`pages/download/options/view.rs:88-89` 两条 `clippy::doc_lazy_continuation` 只缩进修掉,文字未动。
This commit is contained in:
1 parent
71144dfca8
commit
a0f61d8bb5
40 files changed
+2762
-91
No files matched your search
@@ -45,9 +45,12 @@ mod accounts;
|
||||
// M223:AI 栏的事件投递与可见性(纯搬移,守 400 行硬线)
|
||||
mod agent;
|
||||
mod live;
|
||||
// M224: 本地 Mod 列表与启停的视图态(状态落地 + 在飞标记)
|
||||
mod mods;
|
||||
mod tiles;
|
||||
|
||||
pub use live::MAX_LIVE_LINES;
|
||||
pub use mods::ModsState;
|
||||
pub use tiles::{grid_positions, TileCell};
|
||||
|
||||
/// 一份加载器版本清单查询的结果(`Ok` = 最新在前;`Err` = 人读原因)。
|
||||
@@ -136,6 +139,8 @@ pub struct AppState {
|
||||
pub pack: PackState,
|
||||
/// 导入已有版本(M223):扫描 / 预检 / 失败 / 完成(**只装事件态**,表单在页面本地态)
|
||||
pub adopt: AdoptState,
|
||||
/// 本地 Mod(M224):当前实例的快照 / 失败 / 在飞启停
|
||||
pub mods: ModsState,
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
@@ -182,6 +187,7 @@ impl AppState {
|
||||
content: ContentState::default(),
|
||||
pack: PackState::default(),
|
||||
adopt: AdoptState::default(),
|
||||
mods: ModsState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,27 +312,13 @@ impl AppState {
|
||||
CoreEvent::ContentFailed(failure) => self.apply_content_failure(*failure),
|
||||
// M162:详情/版本列表(判断逻辑在 `state/content.rs`,这里只有一行投递)
|
||||
CoreEvent::ContentDetail { project, result } => {
|
||||
self.apply_content_detail(&project, *result);
|
||||
self.apply_content_detail(&project, *result)
|
||||
}
|
||||
// M224:本地 Mod(落地判据在 `state/mods.rs`,含迟到丢弃与在飞标记)
|
||||
CoreEvent::Mods(result) => self.mods.apply(*result),
|
||||
}
|
||||
}
|
||||
|
||||
/// 按搜索词过滤后的磁贴网格(顺序稳定 = 实例列表顺序)。
|
||||
pub fn tiles(&self) -> Vec<TileCell<'_>> {
|
||||
let query = self.instance_query.trim().to_lowercase();
|
||||
let filtered: Vec<&InstanceSummary> = self
|
||||
.instances
|
||||
.iter()
|
||||
.filter(|instance| query.is_empty() || instance.name.to_lowercase().contains(&query))
|
||||
.collect();
|
||||
let positions = grid_positions(filtered.len(), self.prefs.tile_size.columns());
|
||||
filtered
|
||||
.into_iter()
|
||||
.zip(positions)
|
||||
.map(|(instance, (row, col))| TileCell { instance, row, col })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 运行中的实例(实例页顶部迷你状态条)。
|
||||
pub fn running(&self) -> Vec<&InstanceSummary> {
|
||||
self.instances
|
||||
|
||||
Reference in new issue
Block a user