feat(saves): 实例存档 / 截图的只读列表与打开文件夹(M235 STOREMVP)
用户诉求是 PCL 差距四项里的「存档截图 / 导入」,M230 审计确认三层全缺。塔裁决 U3
把这一版钉成「只读列表 + 打开文件夹」,不做缩略图墙(缩略图要引图片解码依赖,而
AGENTS §2 只允许 palette-extract 引 image 类依赖;截图/导入产物形态也没定型)。
core
- `game::saves`(新):只读列 `<run_dir>/saves`(世界目录)与 `<run_dir>/screenshots`
(图片文件)。只调 symlink_metadata / read_dir / metadata,不建目录、不写文件、不读
文件内容(`icon.png` 只判存在)。目录不存在 ⇒ 空列表不是错误;存在但不可用(符号
链接 / 不是目录 / 读不了)⇒ 具名错误。条目级异常逐条登记原因,不静默丢。
- `platform::opener`(新):`open_dir` 是**目录**打开器的唯一收敛点(explorer / open /
xdg-open),内部复用 `child::quiet`,cfg 分支只此一处。塔明确要求按 AGENTS §6 放
platform 而不是业务模块,并要一条可执行判据防退化
(`the_launcher_literals_live_here_only` 扫 `crates/core/src`:除本文件外不许出现
`"explorer"` / `"xdg-open"` 字面量)。
presenter
- `dto::saves`:SavesCommand(List / OpenFolder)、SavesListView、SavesFailureView
(结构 + OS 原文,不拼中文)、`SavesListView` 的计数助手。
- `host::saves`:走 `job::spawn`,UI 只收快照;运行目录仍是
`isolation::derive_game_dir` 的同一个答案(与启动 / mod 列表同源,**不写盘**)。
「打开文件夹」打开哪个目录由 host 定:`saves/` → `screenshots/` → 运行目录。
失败分三类(InstanceUnavailable / DirUnusable / OpenFailed),可重试。
- `state::saves`:快照整体替换、迟到结果丢弃、失败保留绑定(重试才有意义)。
- 接线:CoreEvent::Saves / AppCommand::Saves / HostTask::Saves。
app-iced
- `pages::instances::saves`:详情页 SAVES 卡片(MODS 之后、启动参数之前;**不套第二层
滚动条**,整列那一条是唯一入口)。世界行带 UTC 时间与「有图标」,截图行带大小与时间;
第一行固定画「这一版只读」(U3 边界让用户看得见)。读盘失败画一条红字 + 「重试」;
打开文件夹失败**不画**重试(那个动作的重试就是再点一次按钮)。
- 接线:Message::{ReloadSaves, OpenSavesFolder}、`sync_saves`(与 `sync_mods` 同一判据)、
Model::saves、`saves_shot` 出图夹具(ULL_SAVES_FIXTURE=1)。
i18n
- `InstancesSaves*` 20 条中英成对,catalog 与金表在**同一条锚点**后追加、逐字节一致。
证据
- core 只读判据:`game::saves::tests` 在真盘建世界/截图,调用前后各拍一次
(相对路径, 是否目录, 字节数, mtime)指纹并逐条比对;另有用例钉「目录缺失不建目录」。
- presenter:`host::saves::tests` 真盘端到端(custom 档位派生运行目录 → 快照)。
- GUI:`--software-renderer` 1180×760 浅/深各一张 + 1180×1280 两张「整块卡片可见」
(1180×760 下 SAVES 卡片位于折叠线附近,只能看到卡头;第二对用 ULL_WINDOW_SIZE
把窗口拉高,口径与 M98 的矩阵一致),落 `DevDocs/artifacts/M235/`。
没做的(U3 边界内,见 handoff):缩略图墙、导入、删除/重命名、`level.dat` 的
LevelName 解析(世界名暂时就是目录名)。
This commit is contained in:
1 parent
3789cb7e61
commit
f217d83680
36 files changed
+2702
-17
No files matched your search
@@ -51,6 +51,8 @@ mod detail;
|
||||
mod launch_args;
|
||||
// M224: 详情页的 MODS 区(本地 mod 列表 + 启停开关)
|
||||
mod mods;
|
||||
// M235: 详情页的 SAVES 区(存档 / 截图的只读列表 + 打开文件夹)
|
||||
mod saves;
|
||||
mod state;
|
||||
mod text;
|
||||
|
||||
@@ -61,6 +63,17 @@ mod tests;
|
||||
pub(crate) use data::{DEMO, NOTE, SELECTED};
|
||||
pub(crate) use demo::{demo, demo_form};
|
||||
pub use mods::{from_state as mods_from_state, Mods};
|
||||
pub use saves::{from_state as saves_from_state, Saves};
|
||||
|
||||
/// 存档区失败的一句话(app 层就地画红字用;取词入口仍是 [`Texts`])。
|
||||
///
|
||||
/// 本版**不弹 Toast**:只读列表的失败离用户的眼睛很近(就在卡片里),再弹一条是刷屏。
|
||||
pub fn saves_failure_line(
|
||||
failure: &unlucky_presenter::dto::SavesFailureView,
|
||||
catalog: unlucky_i18n::Catalog,
|
||||
) -> String {
|
||||
saves::failure_line(failure, Texts::of(catalog))
|
||||
}
|
||||
|
||||
/// 启停失败/读目录失败的一句话(app 层弹 Toast 用;取词入口仍是 [`Texts`])。
|
||||
pub fn mods_failure_line(
|
||||
@@ -142,16 +155,30 @@ pub fn view<'a>(
|
||||
metrics,
|
||||
list_column(ui, block, forced, texts),
|
||||
match &block.detail {
|
||||
Some(detail) => {
|
||||
detail::detail_column(ui, metrics, detail, form, &model.mods, forced, texts)
|
||||
}
|
||||
Some(detail) => detail::detail_column(
|
||||
ui,
|
||||
metrics,
|
||||
detail,
|
||||
form,
|
||||
&model.mods,
|
||||
&model.saves,
|
||||
forced,
|
||||
texts,
|
||||
),
|
||||
None => detail::detail_empty(ui, texts),
|
||||
},
|
||||
),
|
||||
_ => match &block.detail {
|
||||
Some(detail) => {
|
||||
detail::narrow_detail(ui, metrics, detail, form, &model.mods, forced, texts)
|
||||
}
|
||||
Some(detail) => detail::narrow_detail(
|
||||
ui,
|
||||
metrics,
|
||||
detail,
|
||||
form,
|
||||
&model.mods,
|
||||
&model.saves,
|
||||
forced,
|
||||
texts,
|
||||
),
|
||||
None => frame::body(metrics, detail::detail_empty(ui, texts)),
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user