From 5066e453e275b000dad9f72e2a8a42e384db92c8 Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Sat, 1 Aug 2026 02:43:14 +0800 Subject: [PATCH] friends list UI --- src-tauri/src/friends/mod.rs | 62 +++++++++++++++--- src-tauri/src/lib.rs | 7 ++- src-tauri/src/windowing/mod.rs | 30 +++++++++ src/lib/bindings.ts | 6 ++ src/lib/components/friends-list.svelte | 87 ++++++++++++++++++++++++++ src/routes/+page.svelte | 8 ++- 6 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 src-tauri/src/windowing/mod.rs create mode 100644 src/lib/components/friends-list.svelte diff --git a/src-tauri/src/friends/mod.rs b/src-tauri/src/friends/mod.rs index 569a397..cde521c 100644 --- a/src-tauri/src/friends/mod.rs +++ b/src-tauri/src/friends/mod.rs @@ -1,7 +1,8 @@ use crate::db::{self, AppDatabase}; use serde::{Deserialize, Serialize}; use specta::Type; -use tauri::State; +use tauri::{AppHandle, State}; +use tauri_specta::Event; #[derive(Debug, Clone, Serialize, Deserialize, Type, sqlx::FromRow)] #[serde(rename_all = "camelCase")] @@ -10,9 +11,32 @@ pub struct Friend { pub display_name: String, } +#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)] +#[serde(rename_all = "camelCase")] +pub struct FriendsChanged { + pub friends: Vec, +} + +async fn all(database: &AppDatabase) -> Result, sqlx::Error> { + sqlx::query_as::<_, Friend>( + "SELECT id, display_name FROM friends ORDER BY display_name COLLATE NOCASE, id", + ) + .fetch_all(database.pool()) + .await +} + +async fn emit_changed(handle: &AppHandle, database: &AppDatabase) -> Result<(), String> { + FriendsChanged { + friends: all(database).await.map_err(db::command_error)?, + } + .emit(handle) + .map_err(db::command_error) +} + #[tauri::command] #[specta::specta] pub async fn create_friend( + handle: AppHandle, database: State<'_, AppDatabase>, friend: Friend, ) -> Result { @@ -23,18 +47,26 @@ pub async fn create_friend( .await .map_err(db::command_error)?; + emit_changed(&handle, &database).await?; + Ok(friend) } #[tauri::command] #[specta::specta] -pub async fn list_friends(database: State<'_, AppDatabase>) -> Result, String> { - sqlx::query_as::<_, Friend>( - "SELECT id, display_name FROM friends ORDER BY display_name COLLATE NOCASE, id", - ) - .fetch_all(database.pool()) - .await - .map_err(db::command_error) +pub async fn list_friends( + handle: AppHandle, + database: State<'_, AppDatabase>, +) -> Result, String> { + let friends = all(&database).await.map_err(db::command_error)?; + + FriendsChanged { + friends: friends.clone(), + } + .emit(&handle) + .map_err(db::command_error)?; + + Ok(friends) } #[tauri::command] @@ -52,12 +84,22 @@ pub async fn get_friend( #[tauri::command] #[specta::specta] -pub async fn delete_friend(database: State<'_, AppDatabase>, id: String) -> Result { +pub async fn delete_friend( + handle: AppHandle, + database: State<'_, AppDatabase>, + id: String, +) -> Result { let result = sqlx::query("DELETE FROM friends WHERE id = ?1") .bind(id) .execute(database.pool()) .await .map_err(db::command_error)?; - Ok(result.rows_affected() > 0) + let changed = result.rows_affected() > 0; + + if changed { + emit_changed(&handle, &database).await?; + } + + Ok(changed) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f1fa0f0..939bfbd 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -23,7 +23,9 @@ pub fn run() { crate::friends::get_friend, crate::friends::delete_friend ]) - .events(tauri_specta::collect_events![]); + .events(tauri_specta::collect_events![ + crate::friends::FriendsChanged + ]); #[cfg(debug_assertions)] specta_builder @@ -36,7 +38,8 @@ pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .invoke_handler(specta_builder.invoke_handler()) - .setup(|app| { + .setup(move |app| { + specta_builder.mount_events(app); tauri::async_runtime::block_on(launch_app(app))?; Ok(()) }) diff --git a/src-tauri/src/windowing/mod.rs b/src-tauri/src/windowing/mod.rs new file mode 100644 index 0000000..54e270e --- /dev/null +++ b/src-tauri/src/windowing/mod.rs @@ -0,0 +1,30 @@ +use tauri::{AppHandle, Manager, WebviewUrl}; + +const WINDOW_LABEL: &str = "main"; + +pub fn init(handle: &AppHandle) { + if let Some(window) = handle.get_webview_window(WINDOW_LABEL) { + if !window.is_visible().unwrap() { + window.center().unwrap(); + window.show().unwrap(); + }; + window.set_focus().unwrap(); + return; + }; + + let builder = + tauri::WebviewWindowBuilder::new(handle, WINDOW_LABEL, WebviewUrl::App("/".into())) + .title("Wyd") + .inner_size(800.0, 600.0) + .min_inner_size(500.0, 400.0) + .visible(true); + + let window = builder.build().unwrap(); + let window_c = window.clone(); + window.on_window_event(move |event: &tauri::WindowEvent| { + if let tauri::WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + window_c.hide().unwrap(); + } + }); +} diff --git a/src/lib/bindings.ts b/src/lib/bindings.ts index 8f472ae..8fc788a 100644 --- a/src/lib/bindings.ts +++ b/src/lib/bindings.ts @@ -22,6 +22,11 @@ async deleteFriend(id: string) : Promise { /** user-defined events **/ +export const events = __makeEvents__<{ +friendsChanged: FriendsChanged +}>({ +friendsChanged: "friends-changed" +}) /** user-defined constants **/ @@ -30,6 +35,7 @@ async deleteFriend(id: string) : Promise { /** user-defined types **/ export type Friend = { id: string; displayName: string } +export type FriendsChanged = { friends: Friend[] } /** tauri-specta globals **/ diff --git a/src/lib/components/friends-list.svelte b/src/lib/components/friends-list.svelte new file mode 100644 index 0000000..062a15d --- /dev/null +++ b/src/lib/components/friends-list.svelte @@ -0,0 +1,87 @@ + + +
+

Friends

+ +
+

+ +

+ +

+ +

+ + +
+ + {#if error} +

{error}

+ {/if} + + {#if friends.length === 0} +

No friends saved.

+ {:else} +
    + {#each friends as friend (friend.id)} +
  • + {friend.displayName} ({friend.id}) + +
  • + {/each} +
+ {/if} +
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 9a4e1cf..74a87de 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,3 +1,9 @@ + +
-
Hello, wyd?
+
+ +