fix(packui): M113 第一批修正 —— 一次只摊开一件事、有界滚动、阶段词不再打架

D30 出图实测暴露的四个问题,都在本批范围内修掉:

1. **动作按钮被顶出屏幕**:内容库页正列没有整页滚动(只有结果列表自己滚),入口 + 报告 +
   导出三块同时摊开时,「确认写盘 / 关闭 / 以包为准重新导入」落在 900px 以下。
   改成一态一块(导出面板 > 进度 > 结果 > 入口);拖拽是窗口级事件,收起入口不丢功能。
2. **阶段词打架**:复用面板自带的阶段词是安装语义(Building the version),与
   「Step 2/5 · Unpacking overrides」并排时两种说法。现在面板阶段词留空、由步骤行说真话
   —— 仍然只有一份进度条 / 计数 / 取消按钮,没有第二套进度 UI。
3. **列表过长**:缺文件 / 冲突 / 预检样例与已知损失都套上「超过 n 条才滚」的有界容器
   (packui/view/parts.rs 的 bounded),少量条目时不浪费空间。
4. **英文单复数**:计数行改成「标签:值」(Files written: 2、cannot export: 1)。
   PackPathPlaceholder / PackFormatModrinth 的中文与英文雷同,被
   chinese_catalog_is_actually_chinese 抓到,一并改掉。

另:app/update.rs 加完拖拽分支后 408 行超线 → 两个钩子改成返回 Task、三个分支各一行,
现在 397 行。新增致命态出图夹具(ULL_CONTENT_PACK=failed),用来证明「容忍的缺文件」与
「致命回滚」在视觉上可区分。

门禁:cargo check ✓ / clippy -D warnings ✓ / cargo test -p unlucky-app-iced(467+6)✓ /
cargo test -p unlucky-i18n(24)✓ / rustfmt --check ✓ / wc -l 全表 ≤400 ✓
This commit is contained in:
AskaEth committed 2026-11-05 04:06:38 +08:00
1 parent fbeff94e55
commit 2129d8cee6
10 files changed
+161 -83

No files matched your search

@@ -8,7 +8,7 @@
//!
//! | 变量 | 取值 | 作用 |
//! |---|---|---|
//! | `ULL_CONTENT_PACK` | `importing` / `partial` / `conflict` / `export` | 选导入导出区的哪一态(不设 = 只有入口) |
//! | `ULL_CONTENT_PACK` | `importing` / `partial` / `conflict` / `failed` / `export` | 选导入导出区的哪一态(不设 = 只有入口) |
//!
//! 夹具**不直接造视图模型**,而是造 presenter 侧的快照(`ImportState` / `ExportState`),
//! 再走产品路径同一个 [`Pack::of`]。这样截图里看到的每一行字都是产品路径会画的那一行,
@@ -19,9 +19,9 @@ use std::path::PathBuf;
use unlucky_i18n::Lang;
use unlucky_presenter::dto::{
ConflictChoiceView, ConflictFileView, ConflictReasonView, ExportClassView, ExportFileView,
ExportPreviewView, ExportReasonView, ExportState, FileActionView, ImportProgressView,
ImportReportView, ImportStageView, ImportState, ImportStatusView, InstanceId, LoaderKind,
MissingFileView, MissingReasonView, PackFormatView,
ExportPreviewView, ExportReasonView, ExportState, FileActionView, ImportErrorView,
ImportProgressView, ImportReportView, ImportStageView, ImportState, ImportStatusView,
InstanceId, LoaderKind, MissingFileView, MissingReasonView, PackFormatView,
};
use crate::pages::content::Target;
@@ -73,6 +73,19 @@ pub(super) fn pack_in(state: &str, lang: Lang) -> (Pack, PackPageState) {
import.report = Some(report(ImportStatusView::Partial, true));
page.expanded = Some("config/sodium-options.json".to_string());
}
// 致命态:整包回滚 + 容忍域仍在(展示两类失败的视觉分野)
"failed" => {
let mut fatal = report(ImportStatusView::Failed, false);
fatal.error = Some(ImportErrorView {
code: "pack_traversal".to_string(),
label: "Path traversal".to_string(),
message: "overrides/../../etc/passwd".to_string(),
hint: Some("Re-download the pack from its source".to_string()),
});
fatal.files.clear();
fatal.bytes_written = 0;
import.report = Some(fatal);
}
"export" => {
page.export_open = true;
page.export_name = "Survival 1.20.1".to_string();
@@ -122,11 +135,7 @@ fn report(status: ImportStatusView, conflicts: bool) -> ImportReportView {
target: PathBuf::from("C:/ULL/instances/survival/.minecraft"),
files: vec![
file("mods/sodium.jar", FileActionView::Installed, 812_000),
file(
"mods/lithium.jar",
FileActionView::SkippedIdentical,
640_000,
),
file("mods/lithium.jar", FileActionView::Installed, 640_000),
file("config/xaeroplus.cfg", FileActionView::KeptUser, 4_200),
],
conflicts: Vec::new(),
@@ -151,7 +160,7 @@ fn report(status: ImportStatusView, conflicts: bool) -> ImportReportView {
},
],
rejected: vec!["org.lwjgl3:3.3.1".to_string()],
warnings: vec!["包内 `server-overrides/` 未解压(客户端导入)".to_string()],
warnings: vec!["`server-overrides/` was not unpacked (client import)".to_string()],
error: None,
registered: Some("Survival".to_string()),
bytes_written: 3_400_000,