refactor(setup): clean up setup wizard UX
- Hide raw filesystem paths from all status messages; use launcher display names instead (e.g. "Downloading Battle.net installer…") - Simplify installer source label: show "✓ Official installer detected" badge when URL is auto-filled - Replace separate "Download / Prepare" + "Run installer" buttons with a single context-aware action button whose label tracks the stage: Download → / Downloading… / Install → / Installing… - Remove auto-download on launcher confirm; show confirmation prompt "Ready to download the official X installer. Press Download → to begin." - Soften the exe-not-found error message; remove raw path and config editing advice — direct users to the log instead - Rename "Launch now" → "Open launcher" for clarity - Show "✓ X installed successfully!" success banner above Close/Open buttons when install completes and the exe is present Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+85
-46
@@ -3,7 +3,7 @@ use anyhow::Result;
|
||||
use iced::widget::{
|
||||
button, column, container, pick_list, progress_bar, row, scrollable, text, text_input, Column,
|
||||
};
|
||||
use iced::{Element, Length, Subscription, Task, Theme};
|
||||
use iced::{Color, Element, Length, Subscription, Task, Theme};
|
||||
use std::ffi::OsString;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -99,8 +99,8 @@ impl State {
|
||||
|
||||
fn new_install(config: Config, launcher: Launcher) -> Self {
|
||||
let status = format!(
|
||||
"Paste an installer URL or a local .exe path. It will install into {}.",
|
||||
launcher.prefix_dir.display()
|
||||
"Paste an installer URL or a local .exe path for {}.",
|
||||
launcher.display
|
||||
);
|
||||
let template_options = config::presets()
|
||||
.iter()
|
||||
@@ -171,30 +171,21 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
preset.prefix_dir = PathBuf::from(&prefix);
|
||||
|
||||
// If we know the official installer URL, pre-fill source and
|
||||
// kick off the download automatically so the user doesn't have
|
||||
// to find or paste anything.
|
||||
// let the user confirm before downloading.
|
||||
if let Some(url) = preset.installer_url.clone() {
|
||||
state.source = url.clone();
|
||||
state.stage = Stage::Busy;
|
||||
state.source = url;
|
||||
state.stage = Stage::Idle;
|
||||
state.status = format!(
|
||||
"Found official installer — downloading to {}…",
|
||||
preset.prefix_dir.display()
|
||||
"Ready to download the official {} installer. Press Download → to begin.",
|
||||
preset.display
|
||||
);
|
||||
if let Ok(mut p) = state.download.lock() {
|
||||
*p = DownloadProgress::default();
|
||||
}
|
||||
let name = preset.name.clone();
|
||||
let progress = state.download.clone();
|
||||
state.launcher = Some(preset);
|
||||
return Task::perform(
|
||||
async_blocking(move || download_blocking(&url, &name, progress)),
|
||||
Message::PrepareDone,
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
state.status = format!(
|
||||
"Paste an installer URL or a local .exe path. It will install into {}.",
|
||||
preset.prefix_dir.display()
|
||||
"Paste an installer URL or a local .exe path for {}.",
|
||||
preset.display
|
||||
);
|
||||
state.launcher = Some(preset);
|
||||
state.stage = Stage::Idle;
|
||||
@@ -228,7 +219,12 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
if path.is_file() {
|
||||
state.installer = Some(path.clone());
|
||||
state.stage = Stage::Ready;
|
||||
state.status = format!("Ready: {}", path.display());
|
||||
let display = state
|
||||
.launcher
|
||||
.as_ref()
|
||||
.map(|l| l.display.as_str())
|
||||
.unwrap_or("installer");
|
||||
state.status = format!("Local installer ready for {}. Press Install → to continue.", display);
|
||||
return Task::none();
|
||||
}
|
||||
if !src.starts_with("http://") && !src.starts_with("https://") {
|
||||
@@ -236,7 +232,12 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
return Task::none();
|
||||
}
|
||||
state.stage = Stage::Busy;
|
||||
state.status = format!("Downloading {src} …");
|
||||
let display_name = state
|
||||
.launcher
|
||||
.as_ref()
|
||||
.map(|l| l.display.clone())
|
||||
.unwrap_or_else(|| "installer".to_string());
|
||||
state.status = format!("Downloading {} installer…", display_name);
|
||||
if let Ok(mut p) = state.download.lock() {
|
||||
*p = DownloadProgress::default();
|
||||
}
|
||||
@@ -256,7 +257,12 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
state.downloaded_temp = Some(path.clone());
|
||||
state.installer = Some(path.clone());
|
||||
state.stage = Stage::Ready;
|
||||
state.status = format!("Downloaded to {}", path.display());
|
||||
let display_name = state
|
||||
.launcher
|
||||
.as_ref()
|
||||
.map(|l| l.display.as_str())
|
||||
.unwrap_or("installer");
|
||||
state.status = format!("Download complete. Press Install → to run the {} installer.", display_name);
|
||||
Task::none()
|
||||
}
|
||||
Message::PrepareDone(Err(e)) => {
|
||||
@@ -309,11 +315,9 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
"✓ Installer finished (exit {code}). {} is ready.",
|
||||
launcher.display,
|
||||
),
|
||||
Ok(code) => format!(
|
||||
"umu-run exited {code} but the expected exe is not at {}.\n\
|
||||
Check the installer's destination path, or edit exe_path in config.",
|
||||
exe.display(),
|
||||
),
|
||||
Ok(_) => "Installation finished but the launcher wasn't found where expected. \
|
||||
Check the log below for details."
|
||||
.to_string(),
|
||||
Err(e) => format!("Install failed: {e}"),
|
||||
};
|
||||
Task::none()
|
||||
@@ -430,7 +434,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
let source_label = if launcher.installer_url.is_some()
|
||||
&& state.source == launcher.installer_url.as_deref().unwrap_or("")
|
||||
{
|
||||
"Official installer URL (auto-filled — or paste your own):"
|
||||
"✓ Official installer detected — or paste a custom URL:"
|
||||
} else {
|
||||
"Installer URL or local .exe path:"
|
||||
};
|
||||
@@ -439,18 +443,37 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
.on_input(Message::SourceChanged)
|
||||
.padding(8);
|
||||
|
||||
let prepare_enabled = matches!(state.stage, Stage::Idle | Stage::Ready | Stage::Finished);
|
||||
let install_enabled = matches!(state.stage, Stage::Ready);
|
||||
let finished = matches!(state.stage, Stage::Finished);
|
||||
let install_success = finished
|
||||
&& launcher
|
||||
.full_exe_path()
|
||||
.exists();
|
||||
let install_success = finished && launcher.full_exe_path().exists();
|
||||
|
||||
let prepare_btn = button(text("Download / Prepare"))
|
||||
.on_press_maybe(prepare_enabled.then_some(Message::PreparePressed));
|
||||
let install_btn = button(text("Run installer"))
|
||||
.on_press_maybe(install_enabled.then_some(Message::InstallPressed));
|
||||
// Single context-aware action button (changes 3 & 4)
|
||||
let action_btn: Option<Element<Message>> = match state.stage {
|
||||
Stage::Idle => {
|
||||
let enabled = !state.source.trim().is_empty();
|
||||
Some(
|
||||
button(text("Download →"))
|
||||
.on_press_maybe(enabled.then_some(Message::PreparePressed))
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
Stage::Busy => Some(
|
||||
button(text("Downloading…"))
|
||||
.on_press_maybe(None::<Message>)
|
||||
.into(),
|
||||
),
|
||||
Stage::Ready => Some(
|
||||
button(text("Install →"))
|
||||
.on_press(Message::InstallPressed)
|
||||
.into(),
|
||||
),
|
||||
Stage::Installing => Some(
|
||||
button(text("Installing…"))
|
||||
.on_press_maybe(None::<Message>)
|
||||
.into(),
|
||||
),
|
||||
Stage::Finished => None,
|
||||
Stage::Picking => None,
|
||||
};
|
||||
|
||||
let status = text(state.status.clone());
|
||||
|
||||
@@ -496,7 +519,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
let close_btn = button(text("Close").size(13))
|
||||
.on_press(Message::Close)
|
||||
.style(button::secondary);
|
||||
let launch_btn = button(text("Launch now").size(13))
|
||||
let launch_btn = button(text("Open launcher").size(13))
|
||||
.on_press_maybe(install_success.then_some(Message::LaunchNow))
|
||||
.style(button::primary);
|
||||
row![close_btn, launch_btn].spacing(10).into()
|
||||
@@ -504,21 +527,37 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
text("").into()
|
||||
};
|
||||
|
||||
let body = column![
|
||||
// Success banner (change 7)
|
||||
let success_banner: Element<Message> = if finished && install_success {
|
||||
text(format!("✓ {} installed successfully!", launcher.display))
|
||||
.size(14)
|
||||
.color(Color::from_rgb(0.4, 0.9, 0.4))
|
||||
.into()
|
||||
} else {
|
||||
text("").into()
|
||||
};
|
||||
|
||||
let mut body = column![
|
||||
header,
|
||||
prefix,
|
||||
expected,
|
||||
text(source_label).size(12),
|
||||
input,
|
||||
row![prepare_btn, install_btn].spacing(12),
|
||||
progress_row,
|
||||
status,
|
||||
finished_row,
|
||||
log_pane,
|
||||
]
|
||||
.spacing(12)
|
||||
.padding(20);
|
||||
|
||||
if let Some(btn) = action_btn {
|
||||
body = body.push(btn);
|
||||
}
|
||||
|
||||
let body = body
|
||||
.push(progress_row)
|
||||
.push(status)
|
||||
.push(success_banner)
|
||||
.push(finished_row)
|
||||
.push(log_pane);
|
||||
|
||||
container(body).into()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user