导入入口(拖拽 + 路径输入 + 目标实例)与导出(格式选择 + 预检)的**界面层**,
数据面镜像 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 后按新形状写一次)。
100 lines
2.8 KiB
Rust
100 lines
2.8 KiB
Rust
//! 共用小件:带底色的容器、容忍域的标题、逐条列表。
|
|
//!
|
|
//! 三个都在两个以上地方用到,所以单独一个文件——不是为了行数好看,是因为
|
|
//! 「容忍域长什么样」只有一处定义,改颜色/图标才不用找第二个副本。
|
|
|
|
use iced::widget::{column, container, row};
|
|
use iced::{Alignment, Background, Border, Element, Length};
|
|
|
|
use unlucky_design::color::Role;
|
|
use unlucky_design::icons::IconKind;
|
|
use unlucky_design::shape::ShapeScale;
|
|
use unlucky_design::spacing::Space;
|
|
use unlucky_design::typography::TypeRole;
|
|
|
|
use unlucky_i18n::Catalog;
|
|
|
|
use crate::pages::frame;
|
|
use crate::pages::Message as PageMessage;
|
|
use crate::theme::Ui;
|
|
|
|
use super::super::model::Row;
|
|
|
|
/// 带底色的容器(探测块与致命块共用;颜色由调用方给)。
|
|
pub(super) fn marker_box<'a>(
|
|
ui: &'a Ui,
|
|
role: Role,
|
|
content: impl Into<Element<'a, PageMessage>>,
|
|
) -> Element<'a, PageMessage> {
|
|
let role = ui.color(role);
|
|
container(content.into())
|
|
.width(Length::Fill)
|
|
.padding(ui.space(Space::X3))
|
|
.style(move |_theme| container::Style {
|
|
background: Some(Background::Color(role)),
|
|
border: Border {
|
|
radius: ui.radius_dp(ShapeScale::Large),
|
|
..Border::default()
|
|
},
|
|
..container::Style::default()
|
|
})
|
|
.into()
|
|
}
|
|
|
|
pub(super) fn tolerable_title<'a>(
|
|
ui: &'a Ui,
|
|
catalog: Catalog,
|
|
title: &'a str,
|
|
) -> Element<'a, PageMessage> {
|
|
let _ = catalog;
|
|
row![
|
|
frame::glyph(IconKind::Warn, ui.color(Role::Tertiary), frame::ICON_PX),
|
|
frame::text_role(
|
|
ui,
|
|
TypeRole::LabelLarge,
|
|
true,
|
|
title,
|
|
ui.color(Role::Tertiary),
|
|
),
|
|
]
|
|
.spacing(ui.space(Space::X2))
|
|
.align_y(Alignment::Center)
|
|
.into()
|
|
}
|
|
|
|
pub(super) fn rows_block<'a>(
|
|
ui: &'a Ui,
|
|
catalog: Catalog,
|
|
rows: &'a [Row],
|
|
) -> Element<'a, PageMessage> {
|
|
let _ = catalog;
|
|
column(
|
|
rows.iter()
|
|
.map(|row| {
|
|
row![
|
|
frame::text_role(
|
|
ui,
|
|
TypeRole::BodySmall,
|
|
false,
|
|
row.path.as_str(),
|
|
ui.color(Role::OnSurface),
|
|
),
|
|
frame::text_role(
|
|
ui,
|
|
TypeRole::BodySmall,
|
|
false,
|
|
row.note.as_str(),
|
|
ui.color(Role::OnSurfaceVariant),
|
|
),
|
|
]
|
|
.spacing(ui.space(Space::X2))
|
|
.width(Length::Fill)
|
|
.into()
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
.spacing(ui.space(Space::X1))
|
|
.width(Length::Fill)
|
|
.into()
|
|
}
|