v0.1.0-pre.044
This commit is contained in:
7
kb-app-demo-desktop/src/constants.rs
Normal file
7
kb-app-demo-desktop/src/constants.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// file: kb-app-demo-desktop/src/constants.rs
|
||||
// version: 1
|
||||
|
||||
//! Constants for the desktop demo application.
|
||||
|
||||
/// Canonical tracing target for the desktop demo application.
|
||||
pub(crate) const TRACING_TARGET: &str = "kb-app-demo-desktop";
|
||||
93
kb-app-demo-desktop/src/lib.rs
Normal file
93
kb-app-demo-desktop/src/lib.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 1
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod constants;
|
||||
mod splash;
|
||||
|
||||
use tauri::Manager; // rust-rules: trait-import
|
||||
|
||||
/// Runs the desktop demo application.
|
||||
pub async fn run() -> kb_core::Result<()> {
|
||||
let builder = tauri::Builder::default().setup(|app| {
|
||||
let splash_window = match app.get_webview_window("splash") {
|
||||
std::option::Option::Some(window) => window,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"splash window is missing",
|
||||
)));
|
||||
},
|
||||
};
|
||||
let main_window = match app.get_webview_window("main") {
|
||||
std::option::Option::Some(window) => window,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"main window is missing",
|
||||
)));
|
||||
},
|
||||
};
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let started_at = tokio::time::Instant::now();
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"fadein",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
||||
);
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"add_msg",
|
||||
std::option::Option::Some("Initialisation de Khadhroony Bot3..."),
|
||||
std::option::Option::Some("info"),
|
||||
std::option::Option::None,
|
||||
);
|
||||
crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
|
||||
crate::emit_splash_order(
|
||||
&splash_window,
|
||||
"fadeout",
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(crate::SPLASH_CLOSE_WAIT_MS)).await;
|
||||
if let std::result::Result::Err(error) = splash_window.destroy() {
|
||||
tracing::error!(target: crate::TRACING_TARGET, "cannot destroy splash window: {error:?}");
|
||||
}
|
||||
if let std::result::Result::Err(error) = main_window.show() {
|
||||
tracing::error!(target: crate::TRACING_TARGET, "cannot show main window: {error:?}");
|
||||
}
|
||||
if let std::result::Result::Err(error) = main_window.set_focus() {
|
||||
tracing::error!(target: crate::TRACING_TARGET, "cannot focus main window: {error:?}");
|
||||
}
|
||||
});
|
||||
return std::result::Result::Ok(());
|
||||
});
|
||||
let run_result = builder.run(tauri::generate_context!());
|
||||
return match run_result {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::tauri(format!(
|
||||
"cannot run desktop demo application: {error:?}"
|
||||
))),
|
||||
};
|
||||
}
|
||||
|
||||
/// Minimum startup splash duration before fade-out begins.
|
||||
pub(crate) use self::splash::SPLASH_MINIMUM_MS;
|
||||
/// Splash fade duration.
|
||||
pub(crate) use self::splash::SPLASH_FADE_MS;
|
||||
/// Delay after fade-out before destroying the splash window.
|
||||
pub(crate) use self::splash::SPLASH_CLOSE_WAIT_MS;
|
||||
/// Emits one order to the splash frontend.
|
||||
pub(crate) use self::splash::emit_splash_order;
|
||||
/// Waits for the minimum splash duration.
|
||||
pub(crate) use self::splash::wait_until_minimum;
|
||||
/// Canonical tracing target for the desktop demo application.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
42
kb-app-demo-desktop/src/main.rs
Normal file
42
kb-app-demo-desktop/src/main.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
// file: kb-app-demo-desktop/src/main.rs
|
||||
// version: 1
|
||||
|
||||
//! Binary entry point for the desktop demo application.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use fs2::FileExt; // rust-rules: trait-import
|
||||
|
||||
/// Starts the single desktop demo application instance.
|
||||
#[tokio::main]
|
||||
async fn main() -> std::process::ExitCode {
|
||||
let mut lock_path = std::env::temp_dir();
|
||||
lock_path.push("com_sasedev_kb_app_demo_desktop.lock");
|
||||
let lock_file = match std::fs::File::create(&lock_path) {
|
||||
std::result::Result::Ok(file) => file,
|
||||
std::result::Result::Err(error) => {
|
||||
eprintln!("cannot create application lock '{}': {error}", lock_path.display());
|
||||
return std::process::ExitCode::FAILURE;
|
||||
},
|
||||
};
|
||||
if let std::result::Result::Err(error) = lock_file.try_lock_exclusive() {
|
||||
if error.kind() == std::io::ErrorKind::WouldBlock {
|
||||
eprintln!("another kb-app-demo-desktop instance is already running");
|
||||
return std::process::ExitCode::FAILURE;
|
||||
}
|
||||
eprintln!("cannot acquire application lock '{}': {error}", lock_path.display());
|
||||
return std::process::ExitCode::FAILURE;
|
||||
}
|
||||
let _lock_file = lock_file;
|
||||
let run_result = kb_app_demo_desktop::run().await;
|
||||
return match run_result {
|
||||
std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS,
|
||||
std::result::Result::Err(error) => {
|
||||
eprintln!("application error: {error}");
|
||||
std::process::ExitCode::FAILURE
|
||||
},
|
||||
};
|
||||
}
|
||||
84
kb-app-demo-desktop/src/splash.rs
Normal file
84
kb-app-demo-desktop/src/splash.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
// file: kb-app-demo-desktop/src/splash.rs
|
||||
// version: 1
|
||||
|
||||
//! Splash-window payloads and startup sequencing helpers.
|
||||
|
||||
use tauri::Emitter; // rust-rules: trait-import
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Minimum splash duration before the main window is shown.
|
||||
pub(crate) const SPLASH_MINIMUM_MS: u64 = 900;
|
||||
/// Splash fade duration.
|
||||
pub(crate) const SPLASH_FADE_MS: u32 = 500;
|
||||
/// Delay after fade-out before destroying the splash window.
|
||||
pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 550;
|
||||
|
||||
/// Command sent from Rust to the splash frontend.
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/splash/SplashOrder.ts")]
|
||||
pub(crate) struct SplashOrder {
|
||||
/// Command name.
|
||||
pub(crate) order: std::string::String,
|
||||
/// Optional message.
|
||||
pub(crate) msg: std::option::Option<std::string::String>,
|
||||
/// Optional status.
|
||||
pub(crate) status: std::option::Option<std::string::String>,
|
||||
/// Optional animation duration in milliseconds.
|
||||
pub(crate) duration_ms: std::option::Option<u32>,
|
||||
}
|
||||
|
||||
/// Emits one splash command.
|
||||
pub(crate) fn emit_splash_order(
|
||||
splash_window: &tauri::WebviewWindow,
|
||||
order: &str,
|
||||
msg: std::option::Option<&str>,
|
||||
status: std::option::Option<&str>,
|
||||
duration_ms: std::option::Option<u32>,
|
||||
) {
|
||||
let payload = SplashOrder {
|
||||
order: order.to_string(),
|
||||
msg: msg.map(std::string::ToString::to_string),
|
||||
status: status.map(std::string::ToString::to_string),
|
||||
duration_ms,
|
||||
};
|
||||
let emit_result = splash_window.emit("splash", payload);
|
||||
if let std::result::Result::Err(error) = emit_result {
|
||||
tracing::error!(target: crate::TRACING_TARGET, "cannot emit splash order '{order}': {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until the configured minimum splash duration has elapsed.
|
||||
pub(crate) async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) {
|
||||
let minimum = std::time::Duration::from_millis(minimum_ms);
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed >= minimum {
|
||||
return;
|
||||
}
|
||||
tokio::time::sleep(minimum - elapsed).await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn splash_order_serializes_duration_ms() {
|
||||
let order = super::SplashOrder {
|
||||
order: "fadein".to_string(),
|
||||
msg: std::option::Option::None,
|
||||
status: std::option::Option::None,
|
||||
duration_ms: std::option::Option::Some(500),
|
||||
};
|
||||
let value_result = serde_json::to_value(order);
|
||||
let value = match value_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("cannot serialize splash order: {error}"),
|
||||
};
|
||||
assert_eq!(value["duration_ms"], serde_json::json!(500));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splash_timing_is_ordered() {
|
||||
assert!(super::SPLASH_MINIMUM_MS > 0);
|
||||
assert!(super::SPLASH_FADE_MS > 0);
|
||||
assert!(super::SPLASH_CLOSE_WAIT_MS >= u64::from(super::SPLASH_FADE_MS));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user