feat(packui): M113 第一批 —— 内容库页的导入/导出区(界面 + DTO + i18n + 拖拽落点)

导入入口(拖拽 + 路径输入 + 目标实例)与导出(格式选择 + 预检)的**界面层**,
数据面镜像 M111/M112 的管线报告(presenter DTO),进度整块复用下载页的安装面板。

## 落了什么

- pages/content/packui*:入口 / 进度块 / 结果块 / 导出预检块(每文件 ≤400)
  - 拖拽区(hover 变主色描边)+ 路径输入框 + 目标实例 chips
  - 进度:一行「步骤 n/5 · 解包覆盖内容」+ **下载页那块安装面板**(不另画进度条)
  - 结果:三态(Success/Partial/Failed)+ 缺文件逐条(Warn/Tertiary)+ 冲突逐条
    (成因 + 本次处置 + 可展开三方 SHA-1)+ 致命块(Error/ErrorContainer)
  - 导出:mrpack / CF 二选一 + 包名/版本/路径 + 预检「将内联 N 个文件 · 无法导出 M 项」
    + 逐条无法导出项 + 已知损失 + 确认写盘
- presenter/src/dto/pack{,/format,/import,/conflict,/export}.rs:镜像 core 报告的 DTO
  (三态、五阶段、三分类、十一条原因,全部 from_slug 与 core 稳定码逐字对齐)
- i18n:81 条 Pack* 键(content_entries! 尾部追加;golden.rs 同序同文)
- app/update.rs:FileDropped / FileHovered / FilesHoveredLeft → 内容库页(本版唯一的选文件通道)
- M109 的四个文件各一处插入:mod 声明 + Pack(..) 变体 + pack 字段 + 正列插入点

## 边界(如实)

- 没有系统文件选择框:依赖树里无对话框依赖,加它要动根 Cargo.toml + 许可门禁 +
  Linux 后端选型(影响单文件产物)。入口是拖拽 + 路径输入,原因写在界面上。
- 冲突没有逐文件决策:core 的 ConflictPolicy 是整次导入一个全局档,ConflictFile 是事后账。
  本批给「逐文件看 + 整体翻档(以包为准重新导入)」,文案明写「两者都留(改名)暂不支持」。
  默认一律 KeepUser——绝不默认覆盖用户改过的文件。
- 本批按钮半通:拖拽/路径/格式/实例/展开都真生效;「点导入真的开跑」「预检/确认写盘」
  要等第二批的 AppCommand 与事件通道(M109 rebase 到新 main 后按新形状写一次)。
This commit is contained in:
AskaEth committed 2026-11-05 04:06:37 +08:00
1 parent 0367073d32
commit fbeff94e55
31 files changed
+3646 -9

No files matched your search

+154
View File
@@ -83,6 +83,160 @@ macro_rules! content_entries {
"Installing from CurseForge is not available yet",
"CurseForge 来源的安装尚未开放"
),
// --- M113: pack import / export(PACKUI)---
PackTitle => ("Modpack import & export", "整合包导入 / 导出"),
PackImportTitle => ("Import a modpack", "导入整合包"),
PackDropHint => (
"Drop a .mrpack, .zip or a MultiMC/Prism instance folder here",
"把 .mrpack / .zip 或 MultiMC·Prism 实例目录拖到这里"
),
PackDropActive => ("Release to probe the pack", "松手开始探测"),
PackNoDialog => (
"This build has no system file picker: drag the file in, or paste its path below (a picker needs a dialog dependency that is not in the tree).",
"本版没有系统文件选择框:请把文件拖进来,或在下面粘贴路径(文件框需要引入对话框依赖,当前依赖树里没有)。"
),
PackPathLabel => ("Pack path", "整合包路径"),
PackPathPlaceholder => ("D:\\packs\\Better MC.mrpack", "D:\\packs\\Better MC.mrpack"),
PackImportAction => ("Import", "导入"),
PackCancel => ("Cancel import", "取消导入"),
PackDismiss => ("Dismiss", "关闭"),
PackTargetLabel => ("Install into", "装到实例"),
PackTargetNew => ("New instance", "新建实例"),
PackInstanceName => ("Instance name", "实例名"),
PackStageLine => ("Step {index}/{total} · {stage}", "步骤 {index}/{total} · {stage}"),
PackStageProbe => ("Reading the manifest", "读取清单"),
PackStageExtract => ("Unpacking overrides", "解包覆盖内容"),
PackStageFetch => ("Fetching missing files", "补下载文件"),
PackStageCommit => ("Writing files", "落地写盘"),
PackStageRegister => ("Registering the instance", "登记实例"),
PackProbeTitle => (
"This pack is almost a known format",
"这个包差一点就是已知格式"
),
PackProbeLooksLike => (
"Looks like {format}, but missing: {missing}",
"看起来像 {format},但缺:{missing}"
),
PackProbeUnknown => (
"No known format claims this pack",
"没有任何已知格式认领这个包"
),
PackProbeSaw => ("Found in the pack: {saw}", "包里看到:{saw}"),
PackProbeNothing => ("nothing recognisable", "没有可识别的东西"),
PackStatusSuccess => ("Imported", "已导入"),
PackStatusPartial => ("Imported with gaps", "导入完成,但有缺口"),
PackStatusFailed => ("Import failed — rolled back", "导入失败 —— 已整包回滚"),
PackReportWritten => ("{count} files written · {bytes}", "{count} 个文件已写盘 · {bytes}"),
PackReportMissing => (
"{count} files could not be fetched (tolerated)",
"有 {count} 个文件没搞到(可容忍)"
),
PackReportConflicts => (
"{count} files you had changed (yours kept by default)",
"{count} 个文件你改过(默认保留你的)"
),
PackReportRejectedNote => ("not supported by the client", "客户端不支持"),
PackOverwriteAction => ("Re-import, the pack wins", "以包为准重新导入"),
PackOverwriteNote => (
"Keeping both (renamed) is not supported yet: the import pipeline has one global conflict policy and no per-file decision channel.",
"「两者都留(改名)」暂不支持:导入管线只有一套全局冲突策略,没有逐文件决策通道。"
),
PackMissingOffline => ("offline import", "离线导入"),
PackMissingNoSource => (
"no download source (CurseForge file API not wired)",
"拿不到下载地址(CF 文件端点未接通)"
),
PackMissingFetchFailed => ("download failed: {detail}", "下载失败:{detail}"),
PackFatalTitle => (
"Rolled back — nothing was left behind",
"已回滚 —— 目标目录零残留"
),
PackFatalHint => ("What to try next", "下一步可以试"),
PackConflictUserModified => ("you changed it", "你改过"),
PackConflictUserDeleted => ("you deleted it", "你删过"),
PackConflictPreExisting => ("was already there", "本来就在"),
PackChoiceKeepUser => ("kept yours", "保留我的"),
PackChoiceOverwrite => ("pack version used", "用包里的"),
PackConflictHashes => (
"pack {pack} · base {base} · disk {disk}",
"包内 {pack} · 基线 {base} · 磁盘 {disk}"
),
PackConflictDetail => ("Hashes", "三方哈希"),
PackFormatModrinth => ("Modrinth .mrpack", "Modrinth .mrpack"),
PackFormatCurseforge => ("CurseForge manifest", "CurseForge 清单"),
PackFormatMultimc => ("MultiMC / Prism instance", "MultiMC·Prism 实例"),
PackExportTitle => ("Export this instance", "导出这个实例"),
PackExportOpen => ("Export…", "导出…"),
PackExportFormat => ("Format", "格式"),
PackExportName => ("Pack name", "包名"),
PackExportVersion => ("Version", "版本"),
PackExportOutput => ("Save to (empty = suggested)", "保存到(留空 = 自动)"),
PackExportPrecheck => ("Dry run", "预检"),
PackExportConfirm => ("Write the pack", "确认写盘"),
PackExportAbort => ("Back", "返回"),
PackExportMultimcNote => (
"MultiMC/Prism can only be imported, not exported (by design).",
"MultiMC·Prism 只能导入、不能导出(有意省略)。"
),
PackExportDone => ("Wrote {path} · {bytes}", "已写出 {path} · {bytes}"),
PackExportFailed => ("Export failed: {reason}", "导出失败:{reason}"),
PackExportPending => ("Scanning the instance…", "正在扫描实例…"),
PackPreviewHead => (
"Will inline {inline} files · {skipped} items cannot be exported",
"将内联 {inline} 个文件 · 无法导出 {skipped} 项"
),
PackPreviewRemote => (
"{count} files can be referenced remotely",
"{count} 个文件可走远程引用"
),
PackPreviewInlineBytes => (
"{bytes} copied into overrides/",
"{bytes} 会原样拷进 overrides/"
),
PackPreviewSkippedTitle => (
"Not in the pack (listed one by one)",
"没进包(逐条列出)"
),
PackPreviewNotes => ("Known losses", "已知损失"),
PackPreviewTarget => ("Will be written to {path}", "将写到 {path}"),
PackPreviewMore => ("…and {count} more files", "…还有 {count} 个文件"),
PackPreviewComplete => ("Nothing is left out", "没有遗漏任何文件"),
PackPreviewIncomplete => (
"Some files will not be in the pack",
"有文件不会进包"
),
PackReasonModrinthSha1 => ("Modrinth match (SHA-1)", "Modrinth 命中(SHA-1)"),
PackReasonCurseFingerprint => (
"CurseForge match (fingerprint)",
"CurseForge 命中(文件指纹)"
),
PackReasonNoRemoteMatch => (
"unknown to both catalogs, inlined",
"两个目录都不认识,已内联"
),
PackReasonCurseOnly => (
"only CurseForge knows it, this format cannot use it, inlined",
"只有 CF 认识,目标格式用不上,已内联"
),
PackReasonModrinthOnly => (
"only Modrinth knows it, the CF manifest cannot use it, inlined",
"只有 Modrinth 认识,CF 清单用不上,已内联"
),
PackReasonMissingSha512 => ("match has no sha512, inlined", "命中缺 sha512,已内联"),
PackReasonMissingCurseIds => (
"match has no projectID/fileID, inlined",
"命中缺 projectID/fileID,已内联"
),
PackReasonDisabled => (
"disabled file, this format cannot express it (.disabled kept)",
"禁用文件,目标格式表达不了(保留 .disabled)"
),
PackReasonSymlink => ("symbolic link, skipped", "符号链接,已跳过"),
PackReasonUnsafePath => (
"path fails the pack path rules, skipped",
"路径过不了包内规整,已跳过"
),
PackReasonUnreadable => ("cannot read it, skipped", "读不出来,已跳过"),
} }
};
}
+81
View File
@@ -219,4 +219,85 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[
("ContentModpackNotReady", "Installing modpacks is not available yet", "整合包安装尚未开放"),
("ContentSourceUnavailable", "CurseForge needs an API key: add one in Settings", "CurseForge 需要在设置里配置密钥才能搜索"),
("ContentSourceNotWired", "Installing from CurseForge is not available yet", "CurseForge 来源的安装尚未开放"),
("PackTitle", "Modpack import & export", "整合包导入 / 导出"),
("PackImportTitle", "Import a modpack", "导入整合包"),
("PackDropHint", "Drop a .mrpack, .zip or a MultiMC/Prism instance folder here", "把 .mrpack / .zip 或 MultiMC·Prism 实例目录拖到这里"),
("PackDropActive", "Release to probe the pack", "松手开始探测"),
("PackNoDialog", "This build has no system file picker: drag the file in, or paste its path below (a picker needs a dialog dependency that is not in the tree).", "本版没有系统文件选择框:请把文件拖进来,或在下面粘贴路径(文件框需要引入对话框依赖,当前依赖树里没有)。"),
("PackPathLabel", "Pack path", "整合包路径"),
("PackPathPlaceholder", "D:\\packs\\Better MC.mrpack", "D:\\packs\\Better MC.mrpack"),
("PackImportAction", "Import", "导入"),
("PackCancel", "Cancel import", "取消导入"),
("PackDismiss", "Dismiss", "关闭"),
("PackTargetLabel", "Install into", "装到实例"),
("PackTargetNew", "New instance", "新建实例"),
("PackInstanceName", "Instance name", "实例名"),
("PackStageLine", "Step {index}/{total} · {stage}", "步骤 {index}/{total} · {stage}"),
("PackStageProbe", "Reading the manifest", "读取清单"),
("PackStageExtract", "Unpacking overrides", "解包覆盖内容"),
("PackStageFetch", "Fetching missing files", "补下载文件"),
("PackStageCommit", "Writing files", "落地写盘"),
("PackStageRegister", "Registering the instance", "登记实例"),
("PackProbeTitle", "This pack is almost a known format", "这个包差一点就是已知格式"),
("PackProbeLooksLike", "Looks like {format}, but missing: {missing}", "看起来像 {format},但缺:{missing}"),
("PackProbeUnknown", "No known format claims this pack", "没有任何已知格式认领这个包"),
("PackProbeSaw", "Found in the pack: {saw}", "包里看到:{saw}"),
("PackProbeNothing", "nothing recognisable", "没有可识别的东西"),
("PackStatusSuccess", "Imported", "已导入"),
("PackStatusPartial", "Imported with gaps", "导入完成,但有缺口"),
("PackStatusFailed", "Import failed — rolled back", "导入失败 —— 已整包回滚"),
("PackReportWritten", "{count} files written · {bytes}", "{count} 个文件已写盘 · {bytes}"),
("PackReportMissing", "{count} files could not be fetched (tolerated)", "有 {count} 个文件没搞到(可容忍)"),
("PackReportConflicts", "{count} files you had changed (yours kept by default)", "{count} 个文件你改过(默认保留你的)"),
("PackReportRejectedNote", "not supported by the client", "客户端不支持"),
("PackOverwriteAction", "Re-import, the pack wins", "以包为准重新导入"),
("PackOverwriteNote", "Keeping both (renamed) is not supported yet: the import pipeline has one global conflict policy and no per-file decision channel.", "「两者都留(改名)」暂不支持:导入管线只有一套全局冲突策略,没有逐文件决策通道。"),
("PackMissingOffline", "offline import", "离线导入"),
("PackMissingNoSource", "no download source (CurseForge file API not wired)", "拿不到下载地址(CF 文件端点未接通)"),
("PackMissingFetchFailed", "download failed: {detail}", "下载失败:{detail}"),
("PackFatalTitle", "Rolled back — nothing was left behind", "已回滚 —— 目标目录零残留"),
("PackFatalHint", "What to try next", "下一步可以试"),
("PackConflictUserModified", "you changed it", "你改过"),
("PackConflictUserDeleted", "you deleted it", "你删过"),
("PackConflictPreExisting", "was already there", "本来就在"),
("PackChoiceKeepUser", "kept yours", "保留我的"),
("PackChoiceOverwrite", "pack version used", "用包里的"),
("PackConflictHashes", "pack {pack} · base {base} · disk {disk}", "包内 {pack} · 基线 {base} · 磁盘 {disk}"),
("PackConflictDetail", "Hashes", "三方哈希"),
("PackFormatModrinth", "Modrinth .mrpack", "Modrinth .mrpack"),
("PackFormatCurseforge", "CurseForge manifest", "CurseForge 清单"),
("PackFormatMultimc", "MultiMC / Prism instance", "MultiMC·Prism 实例"),
("PackExportTitle", "Export this instance", "导出这个实例"),
("PackExportOpen", "Export…", "导出…"),
("PackExportFormat", "Format", "格式"),
("PackExportName", "Pack name", "包名"),
("PackExportVersion", "Version", "版本"),
("PackExportOutput", "Save to (empty = suggested)", "保存到(留空 = 自动)"),
("PackExportPrecheck", "Dry run", "预检"),
("PackExportConfirm", "Write the pack", "确认写盘"),
("PackExportAbort", "Back", "返回"),
("PackExportMultimcNote", "MultiMC/Prism can only be imported, not exported (by design).", "MultiMC·Prism 只能导入、不能导出(有意省略)。"),
("PackExportDone", "Wrote {path} · {bytes}", "已写出 {path} · {bytes}"),
("PackExportFailed", "Export failed: {reason}", "导出失败:{reason}"),
("PackExportPending", "Scanning the instance…", "正在扫描实例…"),
("PackPreviewHead", "Will inline {inline} files · {skipped} items cannot be exported", "将内联 {inline} 个文件 · 无法导出 {skipped} 项"),
("PackPreviewRemote", "{count} files can be referenced remotely", "{count} 个文件可走远程引用"),
("PackPreviewInlineBytes", "{bytes} copied into overrides/", "{bytes} 会原样拷进 overrides/"),
("PackPreviewSkippedTitle", "Not in the pack (listed one by one)", "没进包(逐条列出)"),
("PackPreviewNotes", "Known losses", "已知损失"),
("PackPreviewTarget", "Will be written to {path}", "将写到 {path}"),
("PackPreviewMore", "…and {count} more files", "…还有 {count} 个文件"),
("PackPreviewComplete", "Nothing is left out", "没有遗漏任何文件"),
("PackPreviewIncomplete", "Some files will not be in the pack", "有文件不会进包"),
("PackReasonModrinthSha1", "Modrinth match (SHA-1)", "Modrinth 命中(SHA-1)"),
("PackReasonCurseFingerprint", "CurseForge match (fingerprint)", "CurseForge 命中(文件指纹)"),
("PackReasonNoRemoteMatch", "unknown to both catalogs, inlined", "两个目录都不认识,已内联"),
("PackReasonCurseOnly", "only CurseForge knows it, this format cannot use it, inlined", "只有 CF 认识,目标格式用不上,已内联"),
("PackReasonModrinthOnly", "only Modrinth knows it, the CF manifest cannot use it, inlined", "只有 Modrinth 认识,CF 清单用不上,已内联"),
("PackReasonMissingSha512", "match has no sha512, inlined", "命中缺 sha512,已内联"),
("PackReasonMissingCurseIds", "match has no projectID/fileID, inlined", "命中缺 projectID/fileID,已内联"),
("PackReasonDisabled", "disabled file, this format cannot express it (.disabled kept)", "禁用文件,目标格式表达不了(保留 .disabled)"),
("PackReasonSymlink", "symbolic link, skipped", "符号链接,已跳过"),
("PackReasonUnsafePath", "path fails the pack path rules, skipped", "路径过不了包内规整,已跳过"),
("PackReasonUnreadable", "cannot read it, skipped", "读不出来,已跳过"),
];