Skip to main content

zng_wgt_setup/page/
failed.rs

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
20/// Setup operation failed page.
21pub struct FailedPage {
22    /// The error.
23    pub error: SetupError,
24
25    /// Display info about setup task components of the operation that failed.
26    ///
27    /// This task list is displayed on the same order as defined, as a *check list* with
28    /// individual progress indicator.
29    ///
30    /// If this list is empty or a task is not present only the overall operation error is shown.
31    ///
32    /// This is usually a clone of [`StatusPage::task_infos`].
33    ///
34    /// [`StatusPage::task_infos`]: crate::page::StatusPage::task_infos
35    pub task_infos: Vec<TaskInfo>,
36
37    /// Widget that generates the status item for each item in `task_infos`.
38    pub task_info_fn: WidgetFn<FailedTaskInfoFnArgs>,
39}
40impl FailedPage {
41    /// New from error, without task infos.
42    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    /// Build page.
51    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/// Arguments for generating a task status UI.
168#[derive(Clone)]
169#[non_exhaustive]
170pub struct FailedTaskInfoFnArgs {
171    /// Task info.
172    pub info: TaskInfo,
173
174    /// Task errors.
175    pub errors: Vec<SetupTaskError>,
176
177    /// If all task changes where undone or task never committed changes.
178    ///
179    /// If this is `true` and `errors` is empty the task never started.
180    ///
181    /// If this is `false` and `errors` is empty the task completed successfully.
182    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}