From 59253d286cf535ed738e36319cea90331bce3d2b Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Fri, 6 Mar 2026 16:34:24 +0800 Subject: [PATCH] centralize tauri event names --- src-tauri/Cargo.lock | 23 +++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/services/app_events.rs | 85 ++++++++++++++++++++++++ src-tauri/src/services/cursor.rs | 4 +- src-tauri/src/services/doll_editor.rs | 18 ++--- src-tauri/src/services/mod.rs | 3 +- src-tauri/src/services/scene.rs | 4 +- src-tauri/src/services/ws/user_status.rs | 6 +- src-tauri/src/state/ui.rs | 5 +- src/events/app-data.ts | 3 +- src/events/cursor.ts | 3 +- src/events/scene-interactive.ts | 3 +- src/types/bindings/AppEvents.ts | 3 + src/types/bindings/AppEventsConstants.ts | 14 ++++ 14 files changed, 155 insertions(+), 20 deletions(-) create mode 100644 src-tauri/src/services/app_events.rs create mode 100644 src/types/bindings/AppEvents.ts create mode 100644 src/types/bindings/AppEventsConstants.ts diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3c32f17..9f6e7cb 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1227,6 +1227,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "strum", "tauri", "tauri-build", "tauri-plugin-dialog", @@ -4279,6 +4280,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.110", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c2e5388..c18a4a2 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -25,6 +25,7 @@ tauri-plugin-process = "2" reqwest = { version = "0.12.23", features = ["json", "native-tls", "blocking"] } tokio-util = "0.7" ts-rs = "11.0.1" +strum = { version = "0.26", features = ["derive"] } device_query = "4.0.1" dotenvy = "0.15.7" keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] } diff --git a/src-tauri/src/services/app_events.rs b/src-tauri/src/services/app_events.rs new file mode 100644 index 0000000..9bad269 --- /dev/null +++ b/src-tauri/src/services/app_events.rs @@ -0,0 +1,85 @@ +use serde::Serialize; +#[allow(unused_imports)] +use std::{fs, path::Path}; +use strum::{AsRefStr, EnumIter}; +use ts_rs::TS; + +#[derive(Serialize, TS, EnumIter, AsRefStr)] +#[serde(rename_all = "kebab-case")] +#[ts(export)] +pub enum AppEvents { + CursorPosition, + SceneInteractive, + AppDataRefreshed, + SetInteractionOverlay, + EditDoll, + CreateDoll, + UserStatusChanged, +} + +impl AppEvents { + pub fn as_str(&self) -> &'static str { + match self { + AppEvents::CursorPosition => "cursor-position", + AppEvents::SceneInteractive => "scene-interactive", + AppEvents::AppDataRefreshed => "app-data-refreshed", + AppEvents::SetInteractionOverlay => "set-interaction-overlay", + AppEvents::EditDoll => "edit-doll", + AppEvents::CreateDoll => "create-doll", + AppEvents::UserStatusChanged => "user-status-changed", + } + } +} + +#[test] +fn export_bindings_appeventsconsts() { + use strum::IntoEnumIterator; + + let some_export_dir = std::env::var("TS_RS_EXPORT_DIR") + .ok() + .map(|s| Path::new(&s).to_owned()); + + let Some(export_dir) = some_export_dir else { + eprintln!("TS_RS_EXPORT_DIR not set, skipping constants export"); + return; + }; + + let to_kebab_case = |s: &str| -> String { + let mut result = String::new(); + for (i, c) in s.chars().enumerate() { + if c.is_uppercase() { + if i > 0 { + result.push('-'); + } + result.push(c.to_lowercase().next().unwrap()); + } else { + result.push(c); + } + } + result + }; + + let mut lines = vec![ + r#"// Auto-generated constants - DO NOT EDIT"#.to_string(), + r#"// Generated from Rust AppEvents enum"#.to_string(), + "".to_string(), + "export const AppEvents = {".to_string(), + ]; + + for variant in AppEvents::iter() { + let name = variant.as_ref(); + let kebab = to_kebab_case(name); + lines.push(format!(" {}: \"{}\",", name, kebab)); + } + + lines.push("} as const;".to_string()); + lines.push("".to_string()); + lines.push("export type AppEvents = typeof AppEvents[keyof typeof AppEvents];".to_string()); + + let constants_content = lines.join("\n"); + + let constants_path = export_dir.join("AppEventsConstants.ts"); + if let Err(e) = fs::write(&constants_path, constants_content) { + eprintln!("Failed to write {}: {}", constants_path.display(), e); + } +} diff --git a/src-tauri/src/services/cursor.rs b/src-tauri/src/services/cursor.rs index 33ec00e..27012a1 100644 --- a/src-tauri/src/services/cursor.rs +++ b/src-tauri/src/services/cursor.rs @@ -8,7 +8,7 @@ use tokio::sync::mpsc; use tracing::{debug, error, info, warn}; use ts_rs::TS; -use crate::{get_app_handle, lock_r, state::FDOLL}; +use crate::{get_app_handle, lock_r, services::app_events::AppEvents, state::FDOLL}; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] @@ -103,7 +103,7 @@ async fn init_cursor_tracking_i() -> Result<(), String> { crate::services::ws::report_cursor_data(mapped_for_ws).await; // 2. Broadcast to local windows - if let Err(e) = app_handle.emit("cursor-position", &positions) { + if let Err(e) = app_handle.emit(AppEvents::CursorPosition.as_str(), &positions) { error!("Failed to emit cursor position event: {:?}", e); } } diff --git a/src-tauri/src/services/doll_editor.rs b/src-tauri/src/services/doll_editor.rs index d71162d..5d4afc9 100644 --- a/src-tauri/src/services/doll_editor.rs +++ b/src-tauri/src/services/doll_editor.rs @@ -3,7 +3,7 @@ use tauri::WebviewWindow; use tauri::{Emitter, Listener, Manager}; use tracing::{error, info}; -use crate::get_app_handle; +use crate::{get_app_handle, services::app_events::AppEvents}; static APP_MENU_WINDOW_LABEL: &str = "app_menu"; @@ -76,17 +76,17 @@ pub async fn open_doll_editor_window(doll_id: Option) { // Ensure overlay is active on parent (redundancy for safety) #[cfg(target_os = "macos")] if let Some(parent) = app_handle.get_webview_window(APP_MENU_WINDOW_LABEL) { - if let Err(e) = parent.emit("set-interaction-overlay", true) { + if let Err(e) = parent.emit(AppEvents::SetInteractionOverlay.as_str(), true) { error!("Failed to ensure interaction overlay on parent: {}", e); } } // Emit event to update context if let Some(id) = doll_id { - if let Err(e) = window.emit("edit-doll", id) { + if let Err(e) = window.emit(AppEvents::EditDoll.as_str(), id) { error!("Failed to emit edit-doll event: {}", e); } - } else if let Err(e) = window.emit("create-doll", ()) { + } else if let Err(e) = window.emit(AppEvents::CreateDoll.as_str(), ()) { error!("Failed to emit create-doll event: {}", e); } @@ -136,7 +136,7 @@ pub async fn open_doll_editor_window(doll_id: Option) { let app_handle_clone = get_app_handle().clone(); // Emit event to show overlay - if let Err(e) = parent.emit("set-interaction-overlay", true) { + if let Err(e) = parent.emit(AppEvents::SetInteractionOverlay.as_str(), true) { error!("Failed to emit set-interaction-overlay event: {}", e); } @@ -169,7 +169,7 @@ pub async fn open_doll_editor_window(doll_id: Option) { parent.unlisten(id); } // Remove overlay if we failed - let _ = parent.emit("set-interaction-overlay", false); + let _ = parent.emit(AppEvents::SetInteractionOverlay.as_str(), false); } return; } @@ -204,7 +204,9 @@ pub async fn open_doll_editor_window(doll_id: Option) { parent.unlisten(id); } // Remove overlay - if let Err(e) = parent.emit("set-interaction-overlay", false) { + if let Err(e) = parent + .emit(AppEvents::SetInteractionOverlay.as_str(), false) + { error!("Failed to remove interaction overlay: {}", e); } } @@ -230,7 +232,7 @@ pub async fn open_doll_editor_window(doll_id: Option) { if let Some(id) = parent_focus_listener_id { parent.unlisten(id); } - let _ = parent.emit("set-interaction-overlay", false); + let _ = parent.emit(AppEvents::SetInteractionOverlay.as_str(), false); } } } diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index 600f4e0..c694080 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -2,6 +2,7 @@ use tauri::Manager; use crate::get_app_handle; +pub mod app_events; pub mod app_menu; pub mod auth; pub mod client_config_manager; @@ -10,10 +11,10 @@ pub mod doll_editor; pub mod health_manager; pub mod health_monitor; pub mod interaction; +pub mod petpet; pub mod presence_modules; pub mod scene; pub mod sprite_recolor; -pub mod petpet; pub mod welcome; pub mod ws; diff --git a/src-tauri/src/services/scene.rs b/src-tauri/src/services/scene.rs index 32e4635..3275b61 100644 --- a/src-tauri/src/services/scene.rs +++ b/src-tauri/src/services/scene.rs @@ -8,7 +8,7 @@ use tauri::{Emitter, Manager}; use tauri_plugin_positioner::WindowExt; use tracing::{error, info, warn}; -use crate::get_app_handle; +use crate::{get_app_handle, services::app_events::AppEvents}; pub static SCENE_WINDOW_LABEL: &str = "scene"; pub static SPLASH_WINDOW_LABEL: &str = "splash"; @@ -69,7 +69,7 @@ pub fn update_scene_interactive(interactive: bool, should_click: bool) { } } - if let Err(e) = window.emit("scene-interactive", &interactive) { + if let Err(e) = window.emit(AppEvents::SceneInteractive.as_str(), &interactive) { error!("Failed to emit scene interactive event: {}", e); } } else { diff --git a/src-tauri/src/services/ws/user_status.rs b/src-tauri/src/services/ws/user_status.rs index 4044c81..2968199 100644 --- a/src-tauri/src/services/ws/user_status.rs +++ b/src-tauri/src/services/ws/user_status.rs @@ -7,6 +7,8 @@ use tracing::warn; use crate::services::presence_modules::models::PresenceStatus; +use crate::services::app_events::AppEvents; + use super::{emitter, types::WS_EVENT}; /// User status payload sent to WebSocket server @@ -17,8 +19,6 @@ pub struct UserStatusPayload { pub state: String, } -pub static USER_STATUS_CHANGED: &str = "user-status-changed"; - /// Debouncer for user status reports static USER_STATUS_REPORT_DEBOUNCE: Lazy>>> = Lazy::new(|| Mutex::new(None)); @@ -32,7 +32,7 @@ pub async fn report_user_status(status: UserStatusPayload) { handle.abort(); } - emitter::emit_to_frontend(USER_STATUS_CHANGED, &status); + emitter::emit_to_frontend(AppEvents::UserStatusChanged.as_str(), &status); // Schedule new report after 500ms let handle = async_runtime::spawn(async move { diff --git a/src-tauri/src/state/ui.rs b/src-tauri/src/state/ui.rs index c48ba94..6c240f7 100644 --- a/src-tauri/src/state/ui.rs +++ b/src-tauri/src/state/ui.rs @@ -1,6 +1,7 @@ use crate::{ get_app_handle, lock_r, lock_w, remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote}, + services::app_events::AppEvents, state::FDOLL, }; use std::{collections::HashSet, sync::LazyLock}; @@ -210,7 +211,9 @@ pub async fn init_app_data_scoped(scope: AppDataRefreshScope) { let app_data_clone = guard.user_data.clone(); drop(guard); // Drop lock before emitting to prevent potential deadlocks - if let Err(e) = get_app_handle().emit("app-data-refreshed", &app_data_clone) { + if let Err(e) = + get_app_handle().emit(AppEvents::AppDataRefreshed.as_str(), &app_data_clone) + { warn!("Failed to emit app-data-refreshed event: {}", e); use tauri_plugin_dialog::MessageDialogBuilder; use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; diff --git a/src/events/app-data.ts b/src/events/app-data.ts index fef0875..fb5c0d4 100644 --- a/src/events/app-data.ts +++ b/src/events/app-data.ts @@ -2,6 +2,7 @@ import { writable } from "svelte/store"; import { type UserData } from "../types/bindings/UserData"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { invoke } from "@tauri-apps/api/core"; +import { AppEvents } from "../types/bindings/AppEventsConstants"; export let appData = writable(null); @@ -12,7 +13,7 @@ export async function initAppDataListener() { try { if (isListening) return; appData.set(await invoke("get_app_data")); - unlisten = await listen("app-data-refreshed", (event) => { + unlisten = await listen(AppEvents.AppDataRefreshed, (event) => { console.log("app-data-refreshed", event.payload); appData.set(event.payload); }); diff --git a/src/events/cursor.ts b/src/events/cursor.ts index fc69bc4..937c55b 100644 --- a/src/events/cursor.ts +++ b/src/events/cursor.ts @@ -4,6 +4,7 @@ import { writable } from "svelte/store"; import type { CursorPositions } from "../types/bindings/CursorPositions"; import type { CursorPosition } from "../types/bindings/CursorPosition"; import type { DollDto } from "../types/bindings/DollDto"; +import { AppEvents } from "../types/bindings/AppEventsConstants"; export let cursorPositionOnScreen = writable({ raw: { x: 0, y: 0 }, @@ -52,7 +53,7 @@ export async function initCursorTracking() { try { // Listen to cursor position events (each window subscribes independently) unlistenCursor = await listen( - "cursor-position", + AppEvents.CursorPosition, (event) => { cursorPositionOnScreen.set(event.payload); }, diff --git a/src/events/scene-interactive.ts b/src/events/scene-interactive.ts index 0d75be3..78aebd6 100644 --- a/src/events/scene-interactive.ts +++ b/src/events/scene-interactive.ts @@ -1,5 +1,6 @@ import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { writable } from "svelte/store"; +import { AppEvents } from "../types/bindings/AppEventsConstants"; export const sceneInteractive = writable(false); @@ -12,7 +13,7 @@ export async function initSceneInteractiveListener() { try { // ensure initial default matches backend default sceneInteractive.set(false); - unlisten = await listen("scene-interactive", (event) => { + unlisten = await listen(AppEvents.SceneInteractive, (event) => { sceneInteractive.set(Boolean(event.payload)); }); isListening = true; diff --git a/src/types/bindings/AppEvents.ts b/src/types/bindings/AppEvents.ts new file mode 100644 index 0000000..7a2270b --- /dev/null +++ b/src/types/bindings/AppEvents.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AppEvents = "cursor-position" | "scene-interactive" | "app-data-refreshed" | "set-interaction-overlay" | "edit-doll" | "create-doll" | "user-status-changed"; diff --git a/src/types/bindings/AppEventsConstants.ts b/src/types/bindings/AppEventsConstants.ts new file mode 100644 index 0000000..cbfac87 --- /dev/null +++ b/src/types/bindings/AppEventsConstants.ts @@ -0,0 +1,14 @@ +// Auto-generated constants - DO NOT EDIT +// Generated from Rust AppEvents enum + +export const AppEvents = { + CursorPosition: "cursor-position", + SceneInteractive: "scene-interactive", + AppDataRefreshed: "app-data-refreshed", + SetInteractionOverlay: "set-interaction-overlay", + EditDoll: "edit-doll", + CreateDoll: "create-doll", + UserStatusChanged: "user-status-changed", +} as const; + +export type AppEvents = typeof AppEvents[keyof typeof AppEvents]; \ No newline at end of file