v0.1.0-pre.045
This commit is contained in:
@@ -9,8 +9,9 @@ license.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "kb_app_demo_desktop"
|
name = "kb_app_demo_desktop_lib"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kb-app-demo-desktop"
|
name = "kb-app-demo-desktop"
|
||||||
|
|||||||
@@ -1,8 +1,117 @@
|
|||||||
/* file: kb-app-demo-desktop/frontend/sass/splash.scss */
|
// file: kb-app-demo-desktop/frontend/sass/splash.scss
|
||||||
/* version: 1 */
|
// version: 2
|
||||||
html, body { width: 100%; height: 100%; margin: 0; background: transparent; overflow: hidden; }
|
|
||||||
body { display: grid; place-items: center; font-family: sans-serif; }
|
@font-face {
|
||||||
#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; }
|
font-family: 'Dos Amazigh';
|
||||||
#splash-container img { width: min(55vw, 420px); max-height: 55vh; object-fit: contain; }
|
src: url('../fonts/DOS_Amazigh.ttf') format('truetype');
|
||||||
#splash-container h1 { margin: 0; letter-spacing: .08em; }
|
font-weight: normal;
|
||||||
#messages-container { min-height: 1.5rem; color: #d1d5db; }
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-container {
|
||||||
|
position: relative;
|
||||||
|
width: 960px;
|
||||||
|
height: 637px;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-image {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app-name {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-family: 'Dos Amazigh', sans-serif;
|
||||||
|
font-size: 96px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 2;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-info,
|
||||||
|
#messages-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-info::-webkit-scrollbar,
|
||||||
|
#messages-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-info {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 50%;
|
||||||
|
max-height: 30%;
|
||||||
|
color: white;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 8px;
|
||||||
|
z-index: 3;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 30%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 10px 10px 40px;
|
||||||
|
z-index: 2;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-message {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
animation: fadeIn 0.3s ease-in-out;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-message.info { background-color: rgba(33, 150, 243, 0.2); }
|
||||||
|
.splash-message.warning { background-color: rgba(255, 152, 0, 0.2); }
|
||||||
|
.splash-message.error { background-color: rgba(244, 67, 54, 0.2); }
|
||||||
|
.splash-message.success { background-color: rgba(76, 175, 80, 0.2); }
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
<!-- file: kb-app-demo-desktop/frontend/splash.html -->
|
<!-- file: kb-app-demo-desktop/frontend/splash.html -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta charset="UTF-8" />
|
||||||
<title>Chargement — Khadhroony Bot3</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="stylesheet" href="sass/splash.scss" />
|
<title>Chargement — Khadhroony Bot3</title>
|
||||||
</head>
|
<link rel="stylesheet" href="sass/splash.scss" />
|
||||||
<body>
|
</head>
|
||||||
<main id="splash-container">
|
|
||||||
<img src="imgs/splash.png" alt="Khadhroony Bot3" />
|
<body>
|
||||||
<h1>Khadhroony Bot3</h1>
|
<div id="splash-container" style="opacity: 0;">
|
||||||
<div id="messages-container" aria-live="polite"></div>
|
<img id="splash-image" src="imgs/splash.png" alt="Chargement de Khadhroony Bot3" />
|
||||||
</main>
|
<div id="app-name">Khadhroony</div>
|
||||||
<script type="module" src="ts/splash.ts"></script>
|
<div id="debug-info" aria-live="polite"></div>
|
||||||
</body>
|
<div id="messages-container" aria-live="polite"></div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="ts/splash.ts" defer></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,40 +1,112 @@
|
|||||||
// file: kb-app-demo-desktop/frontend/ts/splash.ts
|
// file: kb-app-demo-desktop/frontend/ts/splash.ts
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import type { SplashOrder } from "./bindings/kb_app_demo_desktop/splash/SplashOrder.ts";
|
||||||
|
|
||||||
type SplashOrder = {
|
const defaultFadeDurationMs = 500;
|
||||||
order: string;
|
let activeOpacityFrame: number | null = null;
|
||||||
msg: string | null;
|
|
||||||
status: string | null;
|
|
||||||
duration_ms: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const container = document.querySelector<HTMLElement>("#splash-container");
|
function normalizeDurationMs(value: number | null | undefined): number {
|
||||||
const messages = document.querySelector<HTMLElement>("#messages-container");
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
||||||
|
return defaultFadeDurationMs;
|
||||||
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;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyOrder(order: SplashOrder): Promise<void> {
|
function easeInOut(progress: number): number {
|
||||||
if (!container) {
|
if (progress < 0.5) {
|
||||||
|
return 2 * progress * progress;
|
||||||
|
}
|
||||||
|
return 1 - Math.pow(-2 * progress + 2, 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
|
||||||
|
if (activeOpacityFrame !== null) {
|
||||||
|
cancelAnimationFrame(activeOpacityFrame);
|
||||||
|
activeOpacityFrame = null;
|
||||||
|
}
|
||||||
|
element.style.opacity = fromOpacity.toString();
|
||||||
|
element.style.willChange = "opacity";
|
||||||
|
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
const startedAt = performance.now();
|
||||||
|
const opacityDelta = toOpacity - fromOpacity;
|
||||||
|
const updateOpacity = (currentTime: number): void => {
|
||||||
|
const elapsedMs = currentTime - startedAt;
|
||||||
|
const rawProgress = Math.min(elapsedMs / durationMs, 1);
|
||||||
|
const nextOpacity = fromOpacity + opacityDelta * easeInOut(rawProgress);
|
||||||
|
element.style.opacity = nextOpacity.toString();
|
||||||
|
if (rawProgress >= 1) {
|
||||||
|
element.style.opacity = toOpacity.toString();
|
||||||
|
element.style.willChange = "auto";
|
||||||
|
activeOpacityFrame = null;
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activeOpacityFrame = requestAnimationFrame(updateOpacity);
|
||||||
|
};
|
||||||
|
activeOpacityFrame = requestAnimationFrame(updateOpacity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToLatest(element: HTMLElement): void {
|
||||||
|
element.scrollTop = element.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLogMessage(message: string): void {
|
||||||
|
const debugInfo = document.getElementById("debug-info");
|
||||||
|
if (!debugInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (order.order === "add_msg" && messages && order.msg) {
|
const line = document.createElement("div");
|
||||||
messages.textContent = order.msg;
|
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
||||||
messages.dataset.status = order.status ?? "info";
|
debugInfo.appendChild(line);
|
||||||
|
scrollToLatest(debugInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMessage(message: string, status: string): void {
|
||||||
|
const messagesContainer = document.getElementById("messages-container");
|
||||||
|
if (!messagesContainer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (order.order === "fadein" || order.order === "fadeout") {
|
const messageElement = document.createElement("div");
|
||||||
container.style.transition = `opacity ${duration(order)}ms ease-in-out`;
|
messageElement.className = `splash-message ${status}`;
|
||||||
container.style.opacity = order.order === "fadein" ? "1" : "0";
|
messageElement.textContent = message;
|
||||||
|
messagesContainer.appendChild(messageElement);
|
||||||
|
scrollToLatest(messagesContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSplashOrder(order: SplashOrder): Promise<void> {
|
||||||
|
const container = document.getElementById("splash-container");
|
||||||
|
if (order.order === "fadein" && container) {
|
||||||
|
await animateOpacity(container, 0, 1, normalizeDurationMs(order.duration_ms));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.order === "fadeout" && container) {
|
||||||
|
await animateOpacity(container, 1, 0, normalizeDurationMs(order.duration_ms));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.order === "add_msg" && order.msg && order.status) {
|
||||||
|
addMessage(order.msg, order.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.order === "add_log" && order.msg) {
|
||||||
|
addLogMessage(order.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void listen<SplashOrder>("splash", event => {
|
async function initializeSplash(): Promise<void> {
|
||||||
void applyOrder(event.payload);
|
const container = document.getElementById("splash-container");
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = "0";
|
||||||
|
container.style.willChange = "opacity";
|
||||||
|
}
|
||||||
|
await listen<SplashOrder>("splash", event => {
|
||||||
|
void handleSplashOrder(event.payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
void initializeSplash();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-app-demo-desktop/src/lib.rs
|
// file: kb-app-demo-desktop/src/lib.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||||
|
|
||||||
@@ -9,85 +9,23 @@
|
|||||||
|
|
||||||
mod constants;
|
mod constants;
|
||||||
mod splash;
|
mod splash;
|
||||||
|
mod tauri;
|
||||||
use tauri::Manager; // rust-rules: trait-import
|
|
||||||
|
|
||||||
/// Runs the desktop demo application.
|
/// Runs the desktop demo application.
|
||||||
pub async fn run() -> kb_core::Result<()> {
|
pub use self::tauri::run;
|
||||||
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.
|
/// Delay after fade-out before destroying the splash window.
|
||||||
pub(crate) use self::splash::SPLASH_CLOSE_WAIT_MS;
|
pub(crate) use self::splash::SPLASH_CLOSE_WAIT_MS;
|
||||||
|
/// Splash fade duration.
|
||||||
|
pub(crate) use self::splash::SPLASH_FADE_MS;
|
||||||
|
/// Minimum startup splash duration before fade-out begins.
|
||||||
|
pub(crate) use self::splash::SPLASH_MINIMUM_MS;
|
||||||
|
/// Command sent from Rust to the splash frontend.
|
||||||
|
pub(crate) use self::splash::SplashOrder;
|
||||||
/// Emits one order to the splash frontend.
|
/// Emits one order to the splash frontend.
|
||||||
pub(crate) use self::splash::emit_splash_order;
|
pub(crate) use self::splash::emit_splash_order;
|
||||||
/// Waits for the minimum splash duration.
|
/// Waits for the minimum splash duration.
|
||||||
pub(crate) use self::splash::wait_until_minimum;
|
pub(crate) use self::splash::wait_until_minimum;
|
||||||
|
// Keep the canonical tracing target as the final facade export.
|
||||||
/// Canonical tracing target for the desktop demo application.
|
/// Canonical tracing target for the desktop demo application.
|
||||||
pub(crate) use self::constants::TRACING_TARGET;
|
pub(crate) use self::constants::TRACING_TARGET;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async fn main() -> std::process::ExitCode {
|
|||||||
return std::process::ExitCode::FAILURE;
|
return std::process::ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
let _lock_file = lock_file;
|
let _lock_file = lock_file;
|
||||||
let run_result = kb_app_demo_desktop::run().await;
|
let run_result = kb_app_demo_desktop_lib::run();
|
||||||
return match run_result {
|
return match run_result {
|
||||||
std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS,
|
std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS,
|
||||||
std::result::Result::Err(error) => {
|
std::result::Result::Err(error) => {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ use tauri::Emitter; // rust-rules: trait-import
|
|||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: derive-import
|
||||||
|
|
||||||
/// Minimum splash duration before the main window is shown.
|
/// Minimum splash duration before the main window is shown.
|
||||||
pub(crate) const SPLASH_MINIMUM_MS: u64 = 900;
|
pub(crate) const SPLASH_MINIMUM_MS: u64 = 3100;
|
||||||
/// Splash fade duration.
|
/// Splash fade duration.
|
||||||
pub(crate) const SPLASH_FADE_MS: u32 = 500;
|
pub(crate) const SPLASH_FADE_MS: u32 = 3000;
|
||||||
/// Delay after fade-out before destroying the splash window.
|
/// Delay after fade-out before destroying the splash window.
|
||||||
pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 550;
|
pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 3100;
|
||||||
|
|
||||||
/// Command sent from Rust to the splash frontend.
|
/// Command sent from Rust to the splash frontend.
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
|
||||||
@@ -35,7 +35,7 @@ pub(crate) fn emit_splash_order(
|
|||||||
status: std::option::Option<&str>,
|
status: std::option::Option<&str>,
|
||||||
duration_ms: std::option::Option<u32>,
|
duration_ms: std::option::Option<u32>,
|
||||||
) {
|
) {
|
||||||
let payload = SplashOrder {
|
let payload = crate::SplashOrder {
|
||||||
order: order.to_string(),
|
order: order.to_string(),
|
||||||
msg: msg.map(std::string::ToString::to_string),
|
msg: msg.map(std::string::ToString::to_string),
|
||||||
status: status.map(std::string::ToString::to_string),
|
status: status.map(std::string::ToString::to_string),
|
||||||
@@ -61,7 +61,7 @@ pub(crate) async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms:
|
|||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn splash_order_serializes_duration_ms() {
|
fn splash_order_serializes_duration_ms() {
|
||||||
let order = super::SplashOrder {
|
let order = crate::SplashOrder {
|
||||||
order: "fadein".to_string(),
|
order: "fadein".to_string(),
|
||||||
msg: std::option::Option::None,
|
msg: std::option::Option::None,
|
||||||
status: std::option::Option::None,
|
status: std::option::Option::None,
|
||||||
|
|||||||
73
kb-app-demo-desktop/src/tauri.rs
Normal file
73
kb-app-demo-desktop/src/tauri.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// file: kb-app-demo-desktop/src/tauri.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Tauri runtime assembly and private command wrappers.
|
||||||
|
|
||||||
|
use tauri::Manager; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Runs the desktop demo application.
|
||||||
|
pub 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:?}"
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use ts_rs::TS; // rust-rules: derive-import
|
|||||||
|
|
||||||
/// Solana transaction signature.
|
/// Solana transaction signature.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
||||||
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/Signature.ts")]
|
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/MdSignature.ts")]
|
||||||
pub struct MdSignature(pub std::string::String);
|
pub struct MdSignature(pub std::string::String);
|
||||||
|
|
||||||
/// Solana slot.
|
/// Solana slot.
|
||||||
@@ -24,23 +24,23 @@ pub struct MdSignature(pub std::string::String);
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
TS,
|
TS,
|
||||||
)]
|
)]
|
||||||
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/Slot.ts")]
|
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/MdSlot.ts")]
|
||||||
pub struct MdSlot(pub u64);
|
pub struct MdSlot(pub u64);
|
||||||
|
|
||||||
/// Solana program id.
|
/// Solana program id.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
||||||
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/ProgramId.ts")]
|
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/MdProgramId.ts")]
|
||||||
pub struct MdProgramId(pub std::string::String);
|
pub struct MdProgramId(pub std::string::String);
|
||||||
|
|
||||||
/// Solana public key.
|
/// Solana public key.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
||||||
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/Pubkey.ts")]
|
#[ts(export, export_to = "../frontend/ts/bindings/kb_lib/model/solana/MdPubkey.ts")]
|
||||||
pub struct MdPubkey(pub std::string::String);
|
pub struct MdPubkey(pub std::string::String);
|
||||||
|
|
||||||
/// Stable top-level or inner instruction path.
|
/// Stable top-level or inner instruction path.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, TS)]
|
||||||
#[ts(
|
#[ts(
|
||||||
export,
|
export,
|
||||||
export_to = "../frontend/ts/bindings/kb_lib/model/solana/InstructionPath.ts"
|
export_to = "../frontend/ts/bindings/kb_lib/model/solana/MdInstructionPath.ts"
|
||||||
)]
|
)]
|
||||||
pub struct MdInstructionPath(pub std::string::String);
|
pub struct MdInstructionPath(pub std::string::String);
|
||||||
|
|||||||
Reference in New Issue
Block a user