1use std::mem;
2
3use zng_ext_l10n::l10n;
4use zng_wgt::{
5 enabled, margin,
6 prelude::{task::parking_lot::Mutex, *},
7};
8use zng_wgt_container::Container;
9use zng_wgt_markdown::Markdown;
10use zng_wgt_scroll::{Scroll, ScrollMode};
11use zng_wgt_stack::{Stack, StackDirection};
12use zng_wgt_text::Text;
13use zng_wgt_toggle::{CheckStyle, Toggle};
14use zng_wgt_wizard::{CAN_FINISH_VAR, FINISH_CMD, Page, PageArgs, default_page_footer_finish};
15
16use crate::{APP_NAME_VAR, APP_VERSION_VAR, INSTALLED_VERSION_VAR, SETUP_OP_VAR, SetupOp};
17
18pub struct FinishPage {
20 pub title: Var<Txt>,
24
25 pub msg: Var<Txt>,
27
28 pub msg_extra: Var<Txt>,
30
31 pub actions: Vec<FinishAction>,
33}
34impl Default for FinishPage {
35 fn default() -> Self {
36 Self {
37 title: SETUP_OP_VAR.flat_map(|op| match op {
38 SetupOp::Install => l10n!("finish/title.install", "{$app} Installed", app = APP_NAME_VAR),
39 SetupOp::Update => l10n!("finish/title.update", "{$app} Updated", app = APP_NAME_VAR),
40 SetupOp::Repair => l10n!("finish/title.repair", "{$app} Repaired", app = APP_NAME_VAR),
41 SetupOp::Uninstall => l10n!("finish/title.uninstall", "{$app} Uninstalled", app = APP_NAME_VAR),
42 }),
43 msg: SETUP_OP_VAR.flat_map(|op| match op {
44 SetupOp::Install => l10n!(
45 "finish/message.install",
46 "Wizard has installed {$app} {$version} on your computer.",
47 app = APP_NAME_VAR,
48 version = APP_VERSION_VAR,
49 ),
50 SetupOp::Update => l10n!(
51 "finish/message.update",
52 "Wizard has updated {$app} from {$current_version} to {$new_version} on your computer.",
53 app = APP_NAME_VAR,
54 current_version = INSTALLED_VERSION_VAR,
55 new_version = APP_VERSION_VAR,
56 ),
57 SetupOp::Repair => l10n!(
58 "finish/message.repair",
59 "Wizard has repaired {$app} {$version} installation on your computer.",
60 app = APP_NAME_VAR,
61 version = APP_VERSION_VAR,
62 ),
63 SetupOp::Uninstall => l10n!(
64 "finish/message.uninstall",
65 "Wizard has uninstalled {$app} {$version} from your your computer.",
66 app = APP_NAME_VAR,
67 version = APP_VERSION_VAR,
68 ),
69 }),
70 msg_extra: const_var("".into()),
71 actions: vec![],
72 }
73 }
74}
75impl FinishPage {
76 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn build(self) -> Page {
83 let Self {
84 title,
85 msg,
86 msg_extra,
87 actions,
88 } = self;
89
90 let msg = expr_var! {
91 formatx!("## {}\n\n{}\n\n{}", #{title}, #{msg}, #{msg_extra})
92 };
93
94 let (action_infos, actions): (Vec<_>, Vec<_>) = actions.into_iter().map(|a| ((a.selected, a.info), a.action)).unzip();
95 let actions = Mutex::new(actions);
96
97 let content = wgt_fn!(|args: PageArgs| {
98 if !args.is_last() && !args.is_custom() {
99 tracing::warn!("finish page is not last");
100 }
101
102 let actions = mem::take(&mut *actions.lock());
104 if !actions.is_empty() {
105 let selected: Vec<_> = action_infos.iter().map(|(s, _)| s.clone()).collect();
106 let handle = FINISH_CMD.scoped(args.wizard_id()).on_pre_event(
107 false,
108 true,
109 false,
110 hn_once!(|_| {
111 for (action, selected) in actions.into_iter().zip(selected) {
112 if selected.get()
113 && let Err(e) = action()
114 {
115 tracing::error!("finish action failed, {e}");
116 }
117 }
118 }),
119 );
120 CAN_FINISH_VAR.set_bind(handle.enabled()).perm();
122 WIDGET.push_any_handle(handle);
123 }
124
125 Container! {
126 child = Scroll! {
127 child_align = Align::FILL_TOP;
128 padding = 20;
129 child = Markdown! {
130 txt = msg.clone();
131 };
132 mode = ScrollMode::VERTICAL;
133 };
134 child_bottom = Stack! {
135 margin = (0, 20, 20, 20);
136 direction = StackDirection::top_to_bottom();
137 spacing = 5;
138 children =
139 action_infos
140 .iter()
141 .map(|(checked, txt)| {
142 Toggle! {
143 checked = checked.clone();
144 child = Text!(txt.clone());
145 style_fn = CheckStyle!();
146 enabled = checked.capabilities().can_modify();
147 }
148 })
149 .collect::<UiVec>(),
150 ;
151 };
152 }
153 });
154
155 let mut pg = Page::new("", "", content);
156 pg.content_fill = true;
157 pg.header = WidgetFn::nil();
158 pg.footer = wgt_fn!(|a: PageArgs| default_page_footer_finish(a.wizard_id()));
159 pg
160 }
161}
162
163#[non_exhaustive]
165pub struct FinishAction {
166 pub info: Var<Txt>,
168 pub selected: Var<bool>,
172 pub action: Box<dyn FnOnce() -> std::io::Result<()> + Send + 'static>,
177}
178impl FinishAction {
179 pub fn new(info: impl IntoVar<Txt>, enabled_default: bool, action: impl FnOnce() -> std::io::Result<()> + Send + 'static) -> Self {
181 Self {
182 info: info.into_var(),
183 selected: var(enabled_default),
184 action: Box::new(action),
185 }
186 }
187
188 pub fn required(mut self) -> Self {
190 self.selected = const_var(true);
191 self
192 }
193
194 pub fn run_app(mut run_app_cmd: std::process::Command) -> Self {
196 Self::new(l10n!("finish/action-run.info", "Run {$app}", app = APP_NAME_VAR), true, move || {
197 run_app_cmd.spawn()?;
198 Ok(())
199 })
200 }
201}