mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 02:37:01 -06:00
feat: startup update prompt modal and direct self-updating (#4138)
* feat: startup update prompt modal and direct self-updating The update flow (check at start or on request, the startup prompt, the self-update worker) is craft-update's Updater; the app only supplies the suite, its close path and its status window. The check at start is opt-in for the desktop app, and the Windows update helper runs before argument parsing. Ref: storytold/craft-libs#319 * refactor(updates): hold the install back while documents are unsaved * fix(updates): drop the manual window's custom id; pin craft-update to craft-libs main (db19c37) * fix(updates): address review feedback Wake the UI when a manual check is queued from a menu callback; the update prompt owns the keyboard while it is open (LightCraft); Download opens through the desktop URL hook (FilmCraft); the Help menu dispatches through menus::invoke (FigCraft); print jobs and comment drafts hold an install back (WordCraft); Check for Updates is a registered command (GridCraft); an install holds back while documents are unsaved. * fix(updates): localize the update status messages * build: resolve the craft-update pin after rebasing
This commit is contained in:
Generated
+206
-1
@@ -999,6 +999,31 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-update"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=db19c373c8d59a545972ca340987aecfb3611a4b#db19c373c8d59a545972ca340987aecfb3611a4b"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dunce",
|
||||
"egui",
|
||||
"flate2",
|
||||
"getrandom 0.3.4",
|
||||
"log",
|
||||
"plist",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"ureq",
|
||||
"verifysign",
|
||||
"winreg",
|
||||
"winsafe",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-window"
|
||||
version = "0.1.0"
|
||||
@@ -1125,6 +1150,12 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
|
||||
|
||||
[[package]]
|
||||
name = "dify"
|
||||
version = "0.8.0"
|
||||
@@ -3004,6 +3035,18 @@ dependencies = [
|
||||
"jni-sys 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.31.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
@@ -3019,6 +3062,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -3671,6 +3720,19 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "plist"
|
||||
version = "1.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327a4cb2ad49dc48adfdc9da89e22c64d488b508c67a044d71ba729e4521a285"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"indexmap",
|
||||
"quick-xml 0.42.0",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
@@ -3758,6 +3820,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958"
|
||||
|
||||
[[package]]
|
||||
name = "presser"
|
||||
version = "0.3.1"
|
||||
@@ -3868,6 +3936,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.12"
|
||||
@@ -4289,7 +4366,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
@@ -4489,6 +4568,10 @@ name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "send_wrapper"
|
||||
@@ -5257,6 +5340,36 @@ dependencies = [
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
@@ -5539,6 +5652,12 @@ dependencies = [
|
||||
"rustc-hash 2.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
@@ -5643,6 +5762,35 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cde93ff05758a36a851a398a772b0b79a82fbe3c186a46f2696baaeaf84c966b"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"flate2",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"ureq-proto",
|
||||
"utf8-zero",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841583fc36089d7d59419568ae157f43a5a03a2e5a81ba87c77d1596ef46e922"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -5705,6 +5853,12 @@ dependencies = [
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-zero"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
@@ -5731,6 +5885,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clipboard-win",
|
||||
"craft-print",
|
||||
"craft-update",
|
||||
"craft-window",
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -6191,6 +6346,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"craft-keymap",
|
||||
"craft-ui",
|
||||
"craft-update",
|
||||
"egui",
|
||||
"egui_extras",
|
||||
"egui_kittest",
|
||||
@@ -6273,6 +6429,16 @@ dependencies = [
|
||||
"vello_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "verifysign"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6acfcc42a8d44f340d001193d97ef56bf33210b51ad760ae8308c97d5241ca51"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -6590,7 +6756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quick-xml 0.41.0",
|
||||
"quote",
|
||||
]
|
||||
|
||||
@@ -7142,6 +7308,16 @@ dependencies = [
|
||||
"xml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winresource"
|
||||
version = "0.1.34"
|
||||
@@ -7549,6 +7725,23 @@ dependencies = [
|
||||
"syn 3.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "9.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2886b00003a72c4ccee3f42cfc34908d33b2508a74d827dc2a4244706475c704"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"getrandom 0.4.3",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"nix",
|
||||
"typed-path",
|
||||
"windows-sys 0.61.2",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
@@ -7561,6 +7754,18 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.3"
|
||||
|
||||
@@ -19,6 +19,7 @@ craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "a091680
|
||||
# Decides who draws the window frame (desktop vs. app) so the suite shares one native titlebar per
|
||||
# desktop. The `egui` feature adds `craft_window::decorate`. (craft-libs#217.)
|
||||
craft-window = { git = "https://github.com/storytold/craft-libs", rev = "a0916808f7d78bed8c7b90cdf937176015454a7f", features = ["egui"] }
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "db19c373c8d59a545972ca340987aecfb3611a4b", features = ["egui"] }
|
||||
# internal
|
||||
vectorcraft-geom = { path = "crates/geom" }
|
||||
vectorcraft-color = { path = "crates/color" }
|
||||
|
||||
@@ -43,6 +43,7 @@ winit = { version = "0.30", default-features = false }
|
||||
craft-print = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
craft-update = { workspace = true }
|
||||
clipboard-win = { workspace = true }
|
||||
# Which GPU drives which display (EnumDisplayDevices, src/gpu.rs), so the window is drawn on the
|
||||
# GPU that shows it: drawing on a GPU without a monitor reset the display driver. Safe wrappers over
|
||||
|
||||
@@ -813,6 +813,11 @@ Without a window (converting, scripting, the MCP server), use vectorcraft-cli:
|
||||
";
|
||||
|
||||
fn main() -> std::process::ExitCode {
|
||||
// Windows: the update helper is a copy of this program started with `--finish-update`.
|
||||
#[cfg(windows)]
|
||||
if let Some(result) = craft_update::self_update::finish(&craft_update::suite::VECTORCRAFT, &std::env::args_os().skip(1).collect::<Vec<_>>()) {
|
||||
std::process::exit(i32::from(result.is_err()));
|
||||
}
|
||||
// Before any thread starts: the pointer must find a cursor theme on Wayland (#2450).
|
||||
#[cfg(target_os = "linux")]
|
||||
cursor_theme::apply();
|
||||
@@ -1045,6 +1050,7 @@ fn main() -> std::process::ExitCode {
|
||||
prefs_dir::attach_profile_store(&mut session);
|
||||
prefs_dir::attach_plugin_store(&mut session);
|
||||
let mut app = VectorcraftApp::new(session, services(cc.winit_window().cloned(), cc.egui_ctx.clone()));
|
||||
app.set_startup_update_check(true);
|
||||
load_prefs(&mut app, saved);
|
||||
// The saved PDF presets live in their own folder (also read by vectorcraft-cli),
|
||||
// which takes over from the copy the preferences file used to hold.
|
||||
|
||||
@@ -53,6 +53,7 @@ log = { workspace = true }
|
||||
# Floating panels open in OS windows of their own on the desktop; on the web they stay in the page.
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
craft-ui = { workspace = true, features = ["native-windows"] }
|
||||
craft-update = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# `now_ms` (src/lib.rs) reads the page's `performance.now()` with this crate in the web build. It is
|
||||
|
||||
@@ -67,6 +67,7 @@ mod tools_float;
|
||||
pub mod touch;
|
||||
mod ui_fonts;
|
||||
pub mod unsaved;
|
||||
pub mod updates_ui;
|
||||
pub mod widgets;
|
||||
pub mod workspaces;
|
||||
|
||||
@@ -496,6 +497,7 @@ pub struct VectorcraftApp {
|
||||
ui_ctx: Option<egui::Context>,
|
||||
/// Installed fonts added to the UI's for characters its own fonts lack (CJK names…).
|
||||
ui_fonts: ui_fonts::UiFonts,
|
||||
pub(crate) updates_rt: updates_ui::Runtime,
|
||||
/// The status bar's frame rate ([`frame_rate`]).
|
||||
frame_rate: frame_rate::FrameRate,
|
||||
/// Canvas rect of the last frame (screen points), for control-channel coordinate mapping.
|
||||
@@ -802,6 +804,7 @@ impl VectorcraftApp {
|
||||
context: 0,
|
||||
ui_ctx: None,
|
||||
ui_fonts: Default::default(),
|
||||
updates_rt: Default::default(),
|
||||
frame_rate: Default::default(),
|
||||
canvas_rect: None,
|
||||
hover_doc: None,
|
||||
@@ -1748,6 +1751,7 @@ impl VectorcraftApp {
|
||||
let now = now_secs();
|
||||
self.perf.fps = self.frame_rate.frame(now, self.perf.frame_ms / 1000.0);
|
||||
self.sync_views();
|
||||
updates_ui::tick(self, ctx);
|
||||
// Read the system clipboard only when that alone decides whether Paste is enabled. A
|
||||
// background thread reads it where the host installs one (an unresponsive owner must never
|
||||
// stall the frame loop); otherwise, or once that thread is gone, it is read here, at most a
|
||||
@@ -2306,6 +2310,7 @@ impl VectorcraftApp {
|
||||
// The canvas and the panels: one docking layout. Tab hides the panels but not the canvas.
|
||||
egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.pasteboard)).show(ui, |ui| panel_docking::show(self, ui));
|
||||
dialogs::show(self, &ctx);
|
||||
updates_ui::show(self, &ctx);
|
||||
export_progress::show(self, &ctx);
|
||||
plugin_runs::show(self, &ctx);
|
||||
palette::show(self, &ctx);
|
||||
|
||||
@@ -554,6 +554,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"{} opens the Save PDF dialog; with params = document.exportPdf options written to path (asked when missing; viewAfterSaving opens the file) → {path, bytes, warnings}",
|
||||
),
|
||||
("help.about", "About VectorCraft", "", "{}"),
|
||||
("help.checkForUpdates", "Check for Updates…", "", "{}"),
|
||||
("help.commandPalette", "Search Commands…", "Cmd+Shift+/", "{}"),
|
||||
("app.quit", "Quit VectorCraft", "Cmd+Q", "{}"),
|
||||
(
|
||||
@@ -1679,6 +1680,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
|
||||
app.ui.about = true;
|
||||
Ok(Value::Null)
|
||||
}
|
||||
"help.checkForUpdates" => crate::updates_ui::check(app),
|
||||
"help.commandPalette" => {
|
||||
app.ui.palette_open = !app.ui.palette_open;
|
||||
app.ui.palette_query.clear();
|
||||
@@ -3223,6 +3225,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
|
||||
c("Search Commands…", "help.commandPalette"),
|
||||
todos("VectorCraft Help…", "F1"),
|
||||
Sep,
|
||||
c("Check for Updates…", "help.checkForUpdates"),
|
||||
c("About VectorCraft", "help.about"),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
//! Software updates, background check at startup, and startup prompt modal.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use craft_update::updater::{Action, Status, Updater};
|
||||
|
||||
pub(crate) struct Runtime {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
updater: Updater,
|
||||
/// The desktop app turns the check at start on; tests and the web build leave it off.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
startup_check: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
check_requested: bool,
|
||||
/// The context of the last frame, to wake the UI when a check is queued from a menu callback.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
repaint: Option<egui::Context>,
|
||||
pub(crate) manual_window_open: bool,
|
||||
pub(crate) status_message: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Runtime {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
updater: Updater::new(&craft_update::suite::VECTORCRAFT, env!("CARGO_PKG_VERSION")),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
startup_check: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
check_requested: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
repaint: None,
|
||||
manual_window_open: false,
|
||||
status_message: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VectorcraftApp {
|
||||
/// Whether the app asks for a newer release when it starts (off until the desktop app turns it on).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_startup_update_check(&mut self, on: bool) {
|
||||
self.updates_rt.startup_check = on;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
app.updates_rt.check_requested = true;
|
||||
if let Some(ctx) = &app.updates_rt.repaint {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
Ok(Value::Null)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
app.updates_rt.manual_window_open = true;
|
||||
app.updates_rt.status_message = Some("Update check is not supported in the web browser.".into());
|
||||
Ok(Value::Null)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tick(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let rt = &mut app.updates_rt;
|
||||
rt.repaint = Some(ctx.clone());
|
||||
if std::mem::take(&mut rt.check_requested) {
|
||||
rt.updater.check_now(ctx);
|
||||
} else if rt.startup_check {
|
||||
rt.updater.check_at_start(ctx);
|
||||
}
|
||||
rt.updater.tick(ctx);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let _ = (app, ctx);
|
||||
}
|
||||
|
||||
pub(crate) fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
// The restart helper is scheduled before the app closes, so no update starts over unsaved work.
|
||||
let clean = !crate::unsaved::any_dirty(app);
|
||||
match app.updates_rt.updater.show_if(ctx, clean) {
|
||||
Action::OpenUrl(url) => app.open_url(&url),
|
||||
Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
|
||||
Action::None => {}
|
||||
}
|
||||
// The window answers Help ▸ Check for Updates, so it follows the updater's status.
|
||||
let rt = &mut app.updates_rt;
|
||||
(rt.manual_window_open, rt.status_message) = match rt.updater.status() {
|
||||
Status::Idle => (false, None),
|
||||
Status::Checking => (true, Some(tl!("Checking for updates…").to_string())),
|
||||
Status::UpToDate => {
|
||||
(true, Some(tl!("VectorCraft {version} is currently the newest version available.").replace("{version}", env!("CARGO_PKG_VERSION"))))
|
||||
}
|
||||
Status::Failed(e) => (true, Some(tl!("Could not check for updates:\n{error}").replace("{error}", e))),
|
||||
};
|
||||
}
|
||||
|
||||
if app.updates_rt.manual_window_open {
|
||||
let mut open = true;
|
||||
egui::Window::new(tl!("VectorCraft Updates"))
|
||||
.open(&mut open)
|
||||
.collapsible(false)
|
||||
.resizable(false)
|
||||
.pivot(egui::Align2::CENTER_CENTER)
|
||||
.default_pos(ctx.content_rect().center())
|
||||
.show(ctx, |ui| {
|
||||
if let Some(msg) = &app.updates_rt.status_message {
|
||||
ui.label(msg);
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
if ui.button(tl!("OK")).clicked() {
|
||||
app.updates_rt.manual_window_open = false;
|
||||
}
|
||||
});
|
||||
if !open {
|
||||
app.updates_rt.manual_window_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Closing the window dismisses the outcome it showed.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if !app.updates_rt.manual_window_open {
|
||||
app.updates_rt.updater.dismiss_status();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
#[test]
|
||||
fn default_runtime_is_idle() {
|
||||
let rt = Runtime::default();
|
||||
assert!(!rt.manual_window_open);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
assert!(!rt.startup_check);
|
||||
assert!(!rt.updater.prompt_open());
|
||||
assert_eq!(rt.updater.status(), &Status::Idle);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn startup_prompt_shows_and_closes() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
let ctx = egui::Context::default();
|
||||
let release = craft_update::Release {
|
||||
tag: "v99.0.0".into(),
|
||||
version: craft_update::parse_version("99.0.0"),
|
||||
name: "VectorCraft 99.0.0".into(),
|
||||
html_url: "https://github.com/storytold/vectorcraft/releases/tag/v99.0.0".into(),
|
||||
..Default::default()
|
||||
};
|
||||
app.updates_rt.updater.offer(release);
|
||||
|
||||
let mut output = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
show(&mut app, ui.ctx());
|
||||
});
|
||||
output.textures_delta.clear();
|
||||
assert!(app.updates_rt.updater.prompt_open());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user