diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 67e9b29..dd3e158 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,7 +2,7 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main", "scene", "app_menu"], + "windows": ["main", "scene", "app_menu", "doll_editor"], "permissions": [ "core:default", "opener:default", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 430b02e..1aafc05 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,6 +7,7 @@ use crate::{ }, remotes::user::UserRemote, services::cursor::start_cursor_tracking, + services::doll_editor::open_doll_editor_window, state::{init_app_data, FDOLL}, }; use tauri::async_runtime; @@ -188,6 +189,14 @@ async fn get_dolls() -> Result, String> { .map_err(|e| e.to_string()) } +#[tauri::command] +async fn get_doll(id: String) -> Result { + DollsRemote::new() + .get_doll(&id) + .await + .map_err(|e| e.to_string()) +} + #[tauri::command] async fn create_doll(dto: CreateDollDto) -> Result { DollsRemote::new() @@ -268,13 +277,15 @@ pub fn run() { deny_friend_request, unfriend, get_dolls, + get_doll, create_doll, update_doll, delete_doll, set_active_doll, remove_active_doll, recolor_gif_base64, - quit_app + quit_app, + open_doll_editor_window ]) .setup(|app| { APP_HANDLE diff --git a/src-tauri/src/remotes/dolls.rs b/src-tauri/src/remotes/dolls.rs index 79c4fb1..23bf501 100644 --- a/src-tauri/src/remotes/dolls.rs +++ b/src-tauri/src/remotes/dolls.rs @@ -105,6 +105,30 @@ impl DollsRemote { Ok(dolls) } + pub async fn get_doll(&self, id: &str) -> Result { + let url = format!("{}/dolls/{}", self.base_url, id); + tracing::info!("DollsRemote::get_doll - Sending GET request to URL: {}", url); + + let resp = with_auth(self.client.get(url)).await.send().await?; + + let resp = resp.error_for_status().map_err(|e| { + tracing::error!("DollsRemote::get_doll - HTTP error: {}", e); + e + })?; + + let text = resp.text().await.map_err(|e| { + tracing::error!("DollsRemote::get_doll - Failed to read response text: {}", e); + e + })?; + + let doll: DollDto = serde_json::from_str(&text).map_err(|e| { + tracing::error!("DollsRemote::get_doll - Failed to parse JSON: {}", e); + e + })?; + + Ok(doll) + } + pub async fn create_doll(&self, dto: CreateDollDto) -> Result { let url = format!("{}/dolls", self.base_url); tracing::info!("DollsRemote::create_doll - Sending POST request to URL: {}", url); diff --git a/src-tauri/src/services/doll_editor.rs b/src-tauri/src/services/doll_editor.rs new file mode 100644 index 0000000..da78d69 --- /dev/null +++ b/src-tauri/src/services/doll_editor.rs @@ -0,0 +1,85 @@ +use tauri::{Emitter, Manager}; +use tracing::{error, info}; + +use crate::get_app_handle; + +static DOLL_EDITOR_WINDOW_LABEL: &str = "doll_editor"; +static APP_MENU_WINDOW_LABEL: &str = "app_menu"; + +#[tauri::command] +pub async fn open_doll_editor_window(doll_id: Option) { + let app_handle = get_app_handle().clone(); + + // Dispatch to main thread to avoid potential deadlocks on Windows when setting parent window + let _ = app_handle.run_on_main_thread(move || { + let app_handle = get_app_handle(); + + // Check if the window already exists + let existing_window = app_handle.get_webview_window(DOLL_EDITOR_WINDOW_LABEL); + if let Some(window) = existing_window { + // If it exists, we might want to reload it with new params or just focus it + if let Err(e) = window.set_focus() { + error!("Failed to focus existing doll editor window: {}", e); + } + + // Emit event to update context + if let Some(id) = doll_id { + if let Err(e) = window.emit("edit-doll", id) { + error!("Failed to emit edit-doll event: {}", e); + } + } else { + if let Err(e) = window.emit("create-doll", ()) { + error!("Failed to emit create-doll event: {}", e); + } + } + + return; + } + + let url_path = if let Some(id) = doll_id { + format!("/app-menu/tabs/your-dolls/doll-editor?id={}", id) + } else { + "/app-menu/tabs/your-dolls/doll-editor".to_string() + }; + + let mut builder = tauri::WebviewWindowBuilder::new( + app_handle, + DOLL_EDITOR_WINDOW_LABEL, + tauri::WebviewUrl::App(url_path.into()), + ) + .title("Doll Editor") + .inner_size(300.0, 400.0) + .resizable(false) + .decorations(true) + .transparent(true) + .shadow(true) + .visible(true) + .skip_taskbar(false) + .always_on_top(true) // Helper window, nice to stay on top + .visible_on_all_workspaces(false); + + // Set parent if app menu exists + if let Some(parent) = app_handle.get_webview_window(APP_MENU_WINDOW_LABEL) { + match builder.parent(&parent) { + Ok(b) => builder = b, + Err(e) => { + error!("Failed to set parent for doll editor window: {}", e); + // If we fail to set parent, we effectively lost the builder because .parent() consumes it. + // We must return here to avoid using moved value. + return; + } + }; + } + + match builder.build() { + Ok(window) => { + info!("{} window builder succeeded", DOLL_EDITOR_WINDOW_LABEL); + #[cfg(debug_assertions)] + window.open_devtools(); + } + Err(e) => { + error!("Failed to build {} window: {}", DOLL_EDITOR_WINDOW_LABEL, e); + } + }; + }); +} diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index 5d5d7d7..2910782 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -1,6 +1,7 @@ pub mod app_menu; pub mod auth; pub mod cursor; +pub mod doll_editor; pub mod scene; pub mod sprite_recolor; pub mod ws; diff --git a/src-tauri/src/services/ws.rs b/src-tauri/src/services/ws.rs index 49c543a..74f8576 100644 --- a/src-tauri/src/services/ws.rs +++ b/src-tauri/src/services/ws.rs @@ -39,6 +39,9 @@ impl WS_EVENT { pub const FRIEND_DOLL_CREATED: &str = "friend-doll-created"; pub const FRIEND_DOLL_UPDATED: &str = "friend-doll-updated"; pub const FRIEND_DOLL_DELETED: &str = "friend-doll-deleted"; + pub const DOLL_CREATED: &str = "doll.created"; + pub const DOLL_UPDATED: &str = "doll.updated"; + pub const DOLL_DELETED: &str = "doll.deleted"; } fn on_friend_request_received(payload: Payload, _socket: RawClient) { @@ -184,6 +187,54 @@ fn on_friend_doll_deleted(payload: Payload, _socket: RawClient) { } } +fn on_doll_created(payload: Payload, _socket: RawClient) { + match payload { + Payload::Text(values) => { + if let Some(first_value) = values.first() { + info!("Received doll.created event: {:?}", first_value); + if let Err(e) = get_app_handle().emit(WS_EVENT::DOLL_CREATED, first_value) { + error!("Failed to emit doll.created event: {:?}", e); + } + } else { + info!("Received doll.created event with empty payload"); + } + } + _ => error!("Received unexpected payload format for doll.created"), + } +} + +fn on_doll_updated(payload: Payload, _socket: RawClient) { + match payload { + Payload::Text(values) => { + if let Some(first_value) = values.first() { + info!("Received doll.updated event: {:?}", first_value); + if let Err(e) = get_app_handle().emit(WS_EVENT::DOLL_UPDATED, first_value) { + error!("Failed to emit doll.updated event: {:?}", e); + } + } else { + info!("Received doll.updated event with empty payload"); + } + } + _ => error!("Received unexpected payload format for doll.updated"), + } +} + +fn on_doll_deleted(payload: Payload, _socket: RawClient) { + match payload { + Payload::Text(values) => { + if let Some(first_value) = values.first() { + info!("Received doll.deleted event: {:?}", first_value); + if let Err(e) = get_app_handle().emit(WS_EVENT::DOLL_DELETED, first_value) { + error!("Failed to emit doll.deleted event: {:?}", e); + } + } else { + info!("Received doll.deleted event with empty payload"); + } + } + _ => error!("Received unexpected payload format for doll.deleted"), + } +} + pub async fn report_cursor_data(cursor_position: CursorPosition) { // Only attempt to get clients if lock_r succeeds (it should, but safety first) // and if clients are actually initialized. @@ -272,6 +323,9 @@ pub async fn build_ws_client( .on(WS_EVENT::FRIEND_DOLL_CREATED, on_friend_doll_created) .on(WS_EVENT::FRIEND_DOLL_UPDATED, on_friend_doll_updated) .on(WS_EVENT::FRIEND_DOLL_DELETED, on_friend_doll_deleted) + .on(WS_EVENT::DOLL_CREATED, on_doll_created) + .on(WS_EVENT::DOLL_UPDATED, on_doll_updated) + .on(WS_EVENT::DOLL_DELETED, on_doll_deleted) .auth(json!({ "token": token })) .connect() }) diff --git a/src/routes/app-menu/tabs/your-dolls/doll-editor-window.svelte b/src/routes/app-menu/tabs/your-dolls/doll-editor-window.svelte new file mode 100644 index 0000000..6a55a92 --- /dev/null +++ b/src/routes/app-menu/tabs/your-dolls/doll-editor-window.svelte @@ -0,0 +1,117 @@ + + +
+ {#if loading} +
+ +
+ {:else if error} +
+
+ {error} +
+ +
+ {:else} + + {/if} +
diff --git a/src/routes/app-menu/tabs/your-dolls/doll-editor.svelte b/src/routes/app-menu/tabs/your-dolls/doll-editor.svelte index 6f75ac8..78f78a6 100644 --- a/src/routes/app-menu/tabs/your-dolls/doll-editor.svelte +++ b/src/routes/app-menu/tabs/your-dolls/doll-editor.svelte @@ -12,6 +12,7 @@ outlineColor: string, ) => void; export let onCancel: () => void; + export let standalone = false; let name = initialName; let bodyColor = initialBodyColor; @@ -29,9 +30,84 @@ } -{#if isOpen} - -{/if} + {/if} diff --git a/src/routes/app-menu/tabs/your-dolls/doll-editor/+page.svelte b/src/routes/app-menu/tabs/your-dolls/doll-editor/+page.svelte new file mode 100644 index 0000000..18204f3 --- /dev/null +++ b/src/routes/app-menu/tabs/your-dolls/doll-editor/+page.svelte @@ -0,0 +1,5 @@ + + + diff --git a/src/routes/app-menu/tabs/your-dolls/index.svelte b/src/routes/app-menu/tabs/your-dolls/index.svelte index cae5f1d..d7f8357 100644 --- a/src/routes/app-menu/tabs/your-dolls/index.svelte +++ b/src/routes/app-menu/tabs/your-dolls/index.svelte @@ -1,27 +1,46 @@