1use std::fmt::Write as _;
2use zng_ext_l10n::l10n;
3use zng_ext_setup::{
4 SetupError, SetupErrorState,
5 task::{SetupTaskError, TaskTypeId},
6};
7use zng_wgt::{ICONS, Wgt, WidgetFn, is_inited, prelude::*, visibility, wgt_fn};
8use zng_wgt_container::Container;
9use zng_wgt_fill::background;
10use zng_wgt_filter::opacity;
11use zng_wgt_input::{CursorIcon, cursor};
12use zng_wgt_size_offset::{size, y};
13use zng_wgt_stack::{Stack, StackDirection};
14use zng_wgt_text::icon::ico_color;
15use zng_wgt_text::{self as text, Text};
16use zng_wgt_wizard::{Page, PageArgs};
17
18use crate::{APP_NAME_VAR, INSTALLED_VERSION_VAR, SETUP_OP_VAR, SetupOp, page::TaskInfo};
19
20pub struct FailedPage {
22 pub error: SetupError,
24
25 pub task_infos: Vec<TaskInfo>,
36
37 pub task_info_fn: WidgetFn<FailedTaskInfoFnArgs>,
39}
40impl FailedPage {
41 pub fn new(error: SetupError) -> Self {
43 Self {
44 error,
45 task_infos: vec![],
46 task_info_fn: WidgetFn::new(default_task_info_fn),
47 }
48 }
49
50 pub fn build(self) -> Page {
52 let title = SETUP_OP_VAR.flat_map(|op| match op {
53 SetupOp::Install => l10n!("failed/title.install", "Install Failed"),
54 SetupOp::Update => l10n!("failed/title.update", "Update Failed"),
55 SetupOp::Repair => l10n!("failed/title.repair", "Repair Failed"),
56 SetupOp::Uninstall => l10n!("failed/title.uninstall", "Uninstall Failed"),
57 });
58
59 let info = match &self.error.state {
60 SetupErrorState::Canceled | SetupErrorState::PartialPrepareInstall => {
61 l10n!("failed/info.clean", "Operation failed, all changes undone.")
62 }
63 SetupErrorState::PartialInstall { .. } | SetupErrorState::PartialUninstall => {
64 l10n!("failed/info.corrupted", "Operation failed, installation corrupted.")
65 }
66 };
67
68 let message = match &self.error.state {
69 SetupErrorState::Canceled | SetupErrorState::PartialPrepareInstall => SETUP_OP_VAR.flat_map(|op| match op {
70 SetupOp::Install => l10n!(
71 "failed/message.install-clean",
72 "Install operation failed, {$app} was not installed.",
73 app = APP_NAME_VAR
74 ),
75 SetupOp::Update => l10n!(
76 "failed/message.update-clean",
77 "Update operation failed, {$app} was not updated. Current installed version {$current_version} remains and can still be used.",
78 app = APP_NAME_VAR,
79 current_version = INSTALLED_VERSION_VAR,
80 ),
81 SetupOp::Repair => l10n!(
82 "failed/message.repair-clean",
83 "Repair operation failed, {$app} installation was not repaired.",
84 app = APP_NAME_VAR,
85 ),
86 SetupOp::Uninstall => l10n!("failed/message.uninstall-clean", "Uninstall operation failed, {$app} remains installed.", app = APP_NAME_VAR),
87 }),
88 SetupErrorState::PartialInstall { .. }|
89 SetupErrorState::PartialUninstall => SETUP_OP_VAR.flat_map(|op| match op {
90 SetupOp::Install => l10n!("failed/message.install-corrupted", "Install operation failed, {$app} was only partially installed.", app = APP_NAME_VAR),
91 SetupOp::Update => l10n!("failed/message.update-corrupted", "Update operation failed, {$app} was only partially updated.", app = APP_NAME_VAR),
92 SetupOp::Repair => l10n!("failed/message.repair-corrupted", "Repair operation failed, {$app} was only partially repaired.", app = APP_NAME_VAR),
93 SetupOp::Uninstall => l10n!("failed/message.uninstall-corrupted", "Uninstall operation failed, {$app} was only partially uninstalled.", app = APP_NAME_VAR),
94 }),
95 };
96
97 let mut pg = Page::new(
98 title,
99 info,
100 wgt_fn!(|_| build(
101 message.clone(),
102 self.error.clone(),
103 self.task_infos.clone(),
104 self.task_info_fn.clone()
105 )),
106 );
107 pg.side = WidgetFn::nil();
108 pg.footer = wgt_fn!(|a: PageArgs| {
109 let id = a.wizard_id();
110 zng_wgt_wizard::default_page_footer_cancel(id)
111 });
112 pg
113 }
114}
115
116fn build(message: Var<Txt>, error: SetupError, task_infos: Vec<TaskInfo>, task_info_fn: WidgetFn<FailedTaskInfoFnArgs>) -> UiNode {
117 let SetupError {
118 op_error,
119 task_errors,
120 state,
121 ..
122 } = error;
123 let op_error = match op_error {
124 Some(e) => e.to_txt(),
125 None => "".into(),
126 };
127 Container! {
128 text::rich_text = true;
129 text::txt_selectable = true;
130 cursor = CursorIcon::Text;
131 child_spacing = 10;
132 child_top = Stack! {
133 direction = StackDirection::top_to_bottom();
134 spacing = 10;
135 children = ui_vec![
136 Text!(message),
137 Text! {
138 visibility = !op_error.is_empty();
139 txt = op_error;
140 font_color = error_font_color();
141 }
142 ];
143
144 #[easing(600.ms())]
145 opacity = 0.pct();
146 when #is_inited {
147 opacity = 100.pct();
148 }
149 };
150 child = Stack! {
151 direction = StackDirection::top_to_bottom();
152 spacing = 5;
153 children = list_presenter_from_iter(
154 task_infos,
155 wgt_fn!(|a| task_info_fn(FailedTaskInfoFnArgs::new(a, &task_errors, &state))),
156 );
157
158 #[easing(300.ms())]
159 y = -15;
160 when #is_inited {
161 y = 0;
162 }
163 };
164 }
165}
166
167#[derive(Clone)]
169#[non_exhaustive]
170pub struct FailedTaskInfoFnArgs {
171 pub info: TaskInfo,
173
174 pub errors: Vec<SetupTaskError>,
176
177 pub clean: bool,
183}
184impl FailedTaskInfoFnArgs {
185 fn new(info: TaskInfo, task_errors: &[((usize, TaskTypeId, Txt), SetupTaskError)], state: &SetupErrorState) -> Self {
186 Self {
187 errors: task_errors
188 .iter()
189 .filter_map(|((_, id, name), e)| {
190 if id == &info.id.0 && name == &info.id.1 {
191 Some(e.clone())
192 } else {
193 None
194 }
195 })
196 .collect(),
197 clean: matches!(state, SetupErrorState::Canceled | SetupErrorState::PartialPrepareInstall),
198 info,
199 }
200 }
201}
202
203pub fn default_task_info_fn(args: FailedTaskInfoFnArgs) -> UiNode {
204 let mut r = String::new();
205 let mut sep = "";
206 for e in &args.errors {
207 write!(&mut r, "{sep}{e}").unwrap();
208 sep = "\n";
209 }
210 let errors = Txt::from(r);
211 Container! {
212 child_start = Wgt! {
213 background = ICONS.req(if args.clean { "circle" } else { "error" });
214 y = 5;
215 size = 20;
216
217 when !args.clean {
218 ico_color = light_dark(colors::RED.darken(50.pct()), colors::RED.lighten(50.pct()));
219 }
220 };
221 child_bottom = Text! {
222 txt = errors;
223 font_color = error_font_color();
224 };
225 child = Text! {
226 txt = args.info.info.0;
227 font_size = 1.1.em();
228 };
229 child_spacing = (0, 5);
230 }
231}
232
233fn error_font_color() -> LightDark {
234 light_dark(colors::RED.darken(70.pct()), colors::RED.lighten(70.pct()))
235}