diff --git a/Cargo.toml b/Cargo.toml index 5fb36d1..dee766b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 4 +# version: 5 [workspace] resolver = "3" @@ -14,7 +14,7 @@ members = [ "kb-onchain-transport", "kb-store", "kb-wallet", - "kb-app-demo", + "kb-app-demo-desktop", ] [workspace.package] diff --git a/config/example.config.json b/config/example.config.json index 4a87581..f0f0239 100644 --- a/config/example.config.json +++ b/config/example.config.json @@ -47,7 +47,8 @@ "kb-onchain-transport", "kb-wallet", "kb-pipeline", - "kb_app_demo", + "kb-pipeline-demo-scenarios", + "kb-app-demo-desktop", "kb_executor_metadata_metaplex_token_metadata", "kb_executor_metadata_spl_name_service", "kb_executor_spl_account_compression", @@ -104,7 +105,7 @@ "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { @@ -147,42 +148,42 @@ ] }, { - "name": "file_kb_app_demo_debug", + "name": "file_kb-app-demo-desktop_debug", "enabled": true, "sink": "file", "level": "debug", - "path": "logs/devnet/kb_app_demo/debug.log", + "path": "logs/devnet/kb-app-demo-desktop/debug.log", "rotation": "daily", "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { - "name": "file_kb_app_demo_info", + "name": "file_kb-app-demo-desktop_info", "enabled": true, "sink": "file", "level": "info", - "path": "logs/devnet/kb_app_demo/info.log", + "path": "logs/devnet/kb-app-demo-desktop/info.log", "rotation": "daily", "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { - "name": "file_kb_app_demo_error", + "name": "file_kb-app-demo-desktop_error", "enabled": true, "sink": "file", "level": "error", - "path": "logs/devnet/kb_app_demo/error.jsonl", + "path": "logs/devnet/kb-app-demo-desktop/error.jsonl", "rotation": "daily", "format": "json", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { @@ -936,7 +937,8 @@ "format": "human", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -949,7 +951,8 @@ "format": "human", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -962,7 +965,8 @@ "format": "json", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -1662,7 +1666,7 @@ "format": "compact", "ansi": true, "targets": [ - "kb_app_demo", + "kb-app-demo-desktop", "kb-lib.decoder.metadata.metaplex_token_metadata", "kb-lib.decoder.solana.core", "kb-lib.decoder.spl.associated_token_account", @@ -1691,6 +1695,7 @@ "kb-lib.materializer.token", "kb-lib.materializer.transaction", "kb-pipeline", + "kb-pipeline-demo-scenarios", "kb-onchain-transport", "kb-store", "kb-wallet" @@ -1745,7 +1750,7 @@ "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { @@ -1788,42 +1793,42 @@ ] }, { - "name": "file_kb_app_demo_debug", + "name": "file_kb-app-demo-desktop_debug", "enabled": true, "sink": "file", "level": "debug", - "path": "logs/mainnet_research/kb_app_demo/debug.log", + "path": "logs/mainnet_research/kb-app-demo-desktop/debug.log", "rotation": "daily", "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { - "name": "file_kb_app_demo_info", + "name": "file_kb-app-demo-desktop_info", "enabled": true, "sink": "file", "level": "info", - "path": "logs/mainnet_research/kb_app_demo/info.log", + "path": "logs/mainnet_research/kb-app-demo-desktop/info.log", "rotation": "daily", "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { - "name": "file_kb_app_demo_error", + "name": "file_kb-app-demo-desktop_error", "enabled": true, "sink": "file", "level": "error", - "path": "logs/mainnet_research/kb_app_demo/error.jsonl", + "path": "logs/mainnet_research/kb-app-demo-desktop/error.jsonl", "rotation": "daily", "format": "json", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { @@ -2577,7 +2582,8 @@ "format": "human", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -2590,7 +2596,8 @@ "format": "human", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -2603,7 +2610,8 @@ "format": "json", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -3503,7 +3511,7 @@ "format": "compact", "ansi": true, "targets": [ - "kb_app_demo", + "kb-app-demo-desktop", "kb-lib.decoder.metadata.metaplex_token_metadata", "kb-lib.decoder.solana.core", "kb-lib.decoder.spl.associated_token_account", @@ -3532,6 +3540,7 @@ "kb-lib.materializer.token", "kb-lib.materializer.transaction", "kb-pipeline", + "kb-pipeline-demo-scenarios", "kb-onchain-transport", "kb-store", "kb-wallet" @@ -3586,7 +3595,7 @@ "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { @@ -3629,42 +3638,42 @@ ] }, { - "name": "file_kb_app_demo_debug", + "name": "file_kb-app-demo-desktop_debug", "enabled": true, "sink": "file", "level": "debug", - "path": "logs/mainnet/kb_app_demo/debug.log", + "path": "logs/mainnet/kb-app-demo-desktop/debug.log", "rotation": "daily", "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { - "name": "file_kb_app_demo_info", + "name": "file_kb-app-demo-desktop_info", "enabled": true, "sink": "file", "level": "info", - "path": "logs/mainnet/kb_app_demo/info.log", + "path": "logs/mainnet/kb-app-demo-desktop/info.log", "rotation": "daily", "format": "human", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { - "name": "file_kb_app_demo_error", + "name": "file_kb-app-demo-desktop_error", "enabled": true, "sink": "file", "level": "error", - "path": "logs/mainnet/kb_app_demo/error.jsonl", + "path": "logs/mainnet/kb-app-demo-desktop/error.jsonl", "rotation": "daily", "format": "json", "ansi": false, "targets": [ - "kb_app_demo" + "kb-app-demo-desktop" ] }, { @@ -4418,7 +4427,8 @@ "format": "human", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -4431,7 +4441,8 @@ "format": "human", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { @@ -4444,7 +4455,8 @@ "format": "json", "ansi": false, "targets": [ - "kb-pipeline" + "kb-pipeline", + "kb-pipeline-demo-scenarios" ] }, { diff --git a/kb-app-demo-desktop/Cargo.toml b/kb-app-demo-desktop/Cargo.toml new file mode 100644 index 0000000..564488c --- /dev/null +++ b/kb-app-demo-desktop/Cargo.toml @@ -0,0 +1,33 @@ +# file: kb-app-demo-desktop/Cargo.toml +# version: 1 + +[package] +name = "kb-app-demo-desktop" +version.workspace = true +edition.workspace = true +license.workspace = true +publish.workspace = true + +[lib] +name = "kb_app_demo_desktop" +path = "src/lib.rs" + +[[bin]] +name = "kb-app-demo-desktop" +path = "src/main.rs" + +[build-dependencies] +tauri-build.workspace = true + +[dependencies] +fs2.workspace = true +kb-core = { path = "../kb-core" } +serde.workspace = true +serde_json.workspace = true +tauri.workspace = true +tokio.workspace = true +tracing.workspace = true +ts-rs.workspace = true + +[lints] +workspace = true diff --git a/kb-app-demo-desktop/build.rs b/kb-app-demo-desktop/build.rs new file mode 100644 index 0000000..b44d4c8 --- /dev/null +++ b/kb-app-demo-desktop/build.rs @@ -0,0 +1,12 @@ +// file: kb-app-demo-desktop/build.rs +// version: 1 + +//! Build script for the desktop demo application. + +#![forbid(unsafe_code)] +#![deny(unreachable_pub)] +#![warn(missing_docs)] + +fn main() { + tauri_build::build() +} diff --git a/kb-app-demo-desktop/capabilities/default.json b/kb-app-demo-desktop/capabilities/default.json new file mode 100644 index 0000000..b9e4a9f --- /dev/null +++ b/kb-app-demo-desktop/capabilities/default.json @@ -0,0 +1,12 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Default desktop demo capability", + "windows": [ + "splash", + "main" + ], + "permissions": [ + "core:default" + ] +} \ No newline at end of file diff --git a/kb-app-demo-desktop/frontend/fonts/DOS_Amazigh.ttf b/kb-app-demo-desktop/frontend/fonts/DOS_Amazigh.ttf new file mode 100644 index 0000000..e558a29 Binary files /dev/null and b/kb-app-demo-desktop/frontend/fonts/DOS_Amazigh.ttf differ diff --git a/kb-app-demo-desktop/frontend/imgs/logo.png b/kb-app-demo-desktop/frontend/imgs/logo.png new file mode 100644 index 0000000..94e89ca Binary files /dev/null and b/kb-app-demo-desktop/frontend/imgs/logo.png differ diff --git a/kb-app-demo-desktop/frontend/imgs/splash.png b/kb-app-demo-desktop/frontend/imgs/splash.png new file mode 100644 index 0000000..a27d1eb Binary files /dev/null and b/kb-app-demo-desktop/frontend/imgs/splash.png differ diff --git a/kb-app-demo-desktop/frontend/main.html b/kb-app-demo-desktop/frontend/main.html new file mode 100644 index 0000000..fd5d845 --- /dev/null +++ b/kb-app-demo-desktop/frontend/main.html @@ -0,0 +1,18 @@ + + + + + + + + Khadhroony Bot3 + + + +
+

Khadhroony Bot3

+

La fenêtre splash de kb-app-demo-desktop est migrée.

+

La fenêtre principale sera complétée dans la prochaine tranche.

+
+ + diff --git a/kb-app-demo-desktop/frontend/sass/splash.scss b/kb-app-demo-desktop/frontend/sass/splash.scss new file mode 100644 index 0000000..a593bb0 --- /dev/null +++ b/kb-app-demo-desktop/frontend/sass/splash.scss @@ -0,0 +1,8 @@ +/* file: kb-app-demo-desktop/frontend/sass/splash.scss */ +/* version: 1 */ +html, body { width: 100%; height: 100%; margin: 0; background: transparent; overflow: hidden; } +body { display: grid; place-items: center; font-family: sans-serif; } +#splash-container { width: 100%; height: 100%; display: grid; place-content: center; justify-items: center; gap: 1rem; opacity: 0; background: radial-gradient(circle, #1f2937, #030712); color: #f9fafb; } +#splash-container img { width: min(55vw, 420px); max-height: 55vh; object-fit: contain; } +#splash-container h1 { margin: 0; letter-spacing: .08em; } +#messages-container { min-height: 1.5rem; color: #d1d5db; } diff --git a/kb-app-demo-desktop/frontend/splash.html b/kb-app-demo-desktop/frontend/splash.html new file mode 100644 index 0000000..70c476a --- /dev/null +++ b/kb-app-demo-desktop/frontend/splash.html @@ -0,0 +1,19 @@ + + + + + + + + Chargement — Khadhroony Bot3 + + + +
+ Khadhroony Bot3 +

Khadhroony Bot3

+
+
+ + + diff --git a/kb-app-demo-desktop/frontend/ts/splash.ts b/kb-app-demo-desktop/frontend/ts/splash.ts new file mode 100644 index 0000000..40089d9 --- /dev/null +++ b/kb-app-demo-desktop/frontend/ts/splash.ts @@ -0,0 +1,40 @@ +// file: kb-app-demo-desktop/frontend/ts/splash.ts +// version: 1 + +import { listen } from "@tauri-apps/api/event"; + +type SplashOrder = { + order: string; + msg: string | null; + status: string | null; + duration_ms: number | null; +}; + +const container = document.querySelector("#splash-container"); +const messages = document.querySelector("#messages-container"); + +function duration(order: SplashOrder): number { + if (typeof order.duration_ms !== "number" || !Number.isFinite(order.duration_ms) || order.duration_ms <= 0) { + return 500; + } + return order.duration_ms; +} + +async function applyOrder(order: SplashOrder): Promise { + if (!container) { + return; + } + if (order.order === "add_msg" && messages && order.msg) { + messages.textContent = order.msg; + messages.dataset.status = order.status ?? "info"; + return; + } + if (order.order === "fadein" || order.order === "fadeout") { + container.style.transition = `opacity ${duration(order)}ms ease-in-out`; + container.style.opacity = order.order === "fadein" ? "1" : "0"; + } +} + +void listen("splash", event => { + void applyOrder(event.payload); +}); diff --git a/kb-app-demo-desktop/icons/favicon.ico b/kb-app-demo-desktop/icons/favicon.ico new file mode 100644 index 0000000..9dfa172 Binary files /dev/null and b/kb-app-demo-desktop/icons/favicon.ico differ diff --git a/kb-app-demo-desktop/icons/favicon.png b/kb-app-demo-desktop/icons/favicon.png new file mode 100644 index 0000000..94e89ca Binary files /dev/null and b/kb-app-demo-desktop/icons/favicon.png differ diff --git a/kb-app-demo-desktop/package.json b/kb-app-demo-desktop/package.json new file mode 100644 index 0000000..cd722b4 --- /dev/null +++ b/kb-app-demo-desktop/package.json @@ -0,0 +1,34 @@ +{ + "name": "kb-app-demo-desktop", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@andypf/json-viewer": "^2.8", + "@fltsci/tauri-plugin-tracing": "^0.3", + "@fortawesome/fontawesome-free": "^7.2", + "@tauri-apps/api": "^2.11", + "bootstrap": "^5.3", + "datatables.net-bs5": "^2.3", + "datatables.net-select-bs5": "^3.1", + "echarts": "^6.1", + "markdown-it": "^14.2", + "resize-observer-polyfill": "^1.5", + "simplebar": "^6.3" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.11", + "@types/bootstrap": "^5.2", + "@types/markdown-it": "^14.1", + "@types/node": "^25.9", + "sass-embedded": "^1.100", + "typescript": "^5.9", + "vite": "^8.1" + } +} \ No newline at end of file diff --git a/kb-app-demo-desktop/src/constants.rs b/kb-app-demo-desktop/src/constants.rs new file mode 100644 index 0000000..0f4e5f3 --- /dev/null +++ b/kb-app-demo-desktop/src/constants.rs @@ -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"; diff --git a/kb-app-demo-desktop/src/lib.rs b/kb-app-demo-desktop/src/lib.rs new file mode 100644 index 0000000..9cdafa8 --- /dev/null +++ b/kb-app-demo-desktop/src/lib.rs @@ -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; diff --git a/kb-app-demo-desktop/src/main.rs b/kb-app-demo-desktop/src/main.rs new file mode 100644 index 0000000..49be447 --- /dev/null +++ b/kb-app-demo-desktop/src/main.rs @@ -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 + }, + }; +} diff --git a/kb-app-demo-desktop/src/splash.rs b/kb-app-demo-desktop/src/splash.rs new file mode 100644 index 0000000..1ae2209 --- /dev/null +++ b/kb-app-demo-desktop/src/splash.rs @@ -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, + /// Optional status. + pub(crate) status: std::option::Option, + /// Optional animation duration in milliseconds. + pub(crate) duration_ms: std::option::Option, +} + +/// 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, +) { + 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)); + } +} diff --git a/kb-app-demo-desktop/tauri.conf.json b/kb-app-demo-desktop/tauri.conf.json new file mode 100644 index 0000000..625c955 --- /dev/null +++ b/kb-app-demo-desktop/tauri.conf.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Khadhroony Bot3 Demo Desktop", + "version": "0.1.0", + "identifier": "com.sasedev.kb-app-demo-desktop", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "./dist" + }, + "app": { + "windows": [ + { + "label": "splash", + "url": "splash.html", + "title": "Chargement — Khadhroony Bot3", + "width": 960, + "height": 637, + "resizable": false, + "decorations": false, + "transparent": true, + "center": true, + "alwaysOnTop": true + }, + { + "label": "main", + "url": "main.html", + "title": "Khadhroony Bot3", + "width": 1200, + "height": 768, + "minWidth": 800, + "minHeight": 600, + "center": true, + "visible": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/favicon.png", + "icons/favicon.ico" + ] + } +} \ No newline at end of file diff --git a/kb-app-demo-desktop/tsconfig.json b/kb-app-demo-desktop/tsconfig.json new file mode 100644 index 0000000..016ec89 --- /dev/null +++ b/kb-app-demo-desktop/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "types": [ + "vite/client", + "node" + ] + }, + "include": [ + "frontend", + "vite.config.ts" + ] +} \ No newline at end of file diff --git a/kb-app-demo-desktop/vite.config.ts b/kb-app-demo-desktop/vite.config.ts new file mode 100644 index 0000000..cbb4a50 --- /dev/null +++ b/kb-app-demo-desktop/vite.config.ts @@ -0,0 +1,84 @@ +// file: kb-app-demo-desktop/vite.config.ts +// version: 2 + +import { defineConfig, normalizePath } from "vite"; +import { NodePackageImporter } from "sass-embedded"; +import { resolve } from "path"; + +const host = process.env.TAURI_DEV_HOST; + +export default defineConfig(() => ({ + + envPrefix: ['VITE_', 'TAURI_ENV_*'], + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + root: 'frontend', // Set this to your frontend directory + publicDir: 'public', + build: { + outDir: './dist', // Output directory for the build + emptyOutDir: true, + rollupOptions: { + input: { + "splash": normalizePath(resolve(__dirname, 'frontend/splash.html')), + "main": normalizePath(resolve(__dirname, 'frontend/main.html')) + }, + output: { + entryFileNames: 'js/[name]-[hash].js', + chunkFileNames: 'js/chunks/[name]-[hash].js', + assetFileNames: (assetInfo) => { + const originalName = assetInfo.name ?? ''; + const ext = originalName.substring(originalName.lastIndexOf('.') + 1).toLowerCase(); + if (ext === 'js') { + return 'js/[name]-[hash][extname]'; + } + // CSS + if (ext === 'css') { + return 'css/[name]-[hash][extname]'; + } + if (['eot', 'otf', 'ttf', 'woff', 'woff2'].includes(ext)) { + return 'fonts/[name]-[hash][extname]'; + } + if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico'].includes(ext)) { + return 'imgs/[name][extname]'; + } + if (['mp4', 'webm'].includes(ext)) { + return 'videos/[name][extname]'; + } + return 'otherassets/[name][extname]'; + }, + }, + }, + minify: true, + sourcemap: false, + cssCodeSplit: true + }, + css: { + preprocessorOptions: { + scss: { + quietDeps: true, + silenceDeprecations: ["import", "color-functions", "global-builtin",], + verbose: false, + api: 'modern', + importers: [new NodePackageImporter()], + } + } + }, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: false, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + ignored: ["**/src/**"], + }, + }, +})); diff --git a/kb-app-demo/Cargo.toml b/kb-app-demo/Cargo.toml deleted file mode 100644 index 8c0e2c4..0000000 --- a/kb-app-demo/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -# file: kb-app-demo/Cargo.toml -# version: 2 - -[package] -name = "kb-app-demo" -version.workspace = true -edition.workspace = true -license.workspace = true -publish.workspace = true - -[dependencies] -kb-config = { path = "../kb-config" } -kb-core = { path = "../kb-core" } -kb-lib = { path = "../kb-lib" } -kb-logging = { path = "../kb-logging" } -kb-pipeline = { path = "../kb-pipeline" } -kb-program-ids = { path = "../kb-program-ids" } -kb-onchain-transport = { path = "../kb-onchain-transport" } -kb-store = { path = "../kb-store" } -kb-wallet = { path = "../kb-wallet" } - -[lints] -workspace = true diff --git a/kb-app-demo/src/main.rs b/kb-app-demo/src/main.rs deleted file mode 100644 index e16b3e8..0000000 --- a/kb-app-demo/src/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -// file: kb-app-demo/src/main.rs -// version: 2 - -#![forbid(unsafe_code)] -#![deny(unreachable_pub)] -#![warn(missing_docs)] - -//! Minimal migration smoke-test application. - -fn main() { - println!("khadhroony-bot3 workspace scaffold"); - println!("logging target: {}", kb_logging::tracing_target()); - println!( - "pipeline: {} v{}", - kb_pipeline::CORE_EXTRACTION_PROCESSOR_NAME, - kb_pipeline::CORE_EXTRACTION_PROCESSOR_VERSION - ); -} diff --git a/kb-config/src/settings.rs b/kb-config/src/settings.rs index 5152523..3f69533 100644 --- a/kb-config/src/settings.rs +++ b/kb-config/src/settings.rs @@ -1319,7 +1319,7 @@ mod tests { && target.path == format!("logs/{directory}/app.log") && target.level == "debug" && target.format == "human" - && target.targets == std::vec!["kb_app_demo".to_string()]; + && target.targets == std::vec!["kb-app-demo-desktop".to_string()]; })); for crate_name in &operational_crates { for (suffix, level, format) in [