migrate from ts-rs to tauri-specta
This commit is contained in:
41
AGENTS.md
41
AGENTS.md
@@ -6,56 +6,35 @@ Passive social app connecting peers through mouse cursor interactions in the for
|
|||||||
|
|
||||||
Desktop client app for Friendolls.
|
Desktop client app for Friendolls.
|
||||||
|
|
||||||
## Build/Lint/Test Commands
|
## Commands
|
||||||
|
|
||||||
### Full App (Standard)
|
Check code integrity after every significant change:
|
||||||
|
|
||||||
- **Dev**: `pnpm dev` (runs Tauri dev mode)
|
- `cd src-tauri && cargo check` for Rust local backend
|
||||||
|
- `pnpm check` for Svelte frontend
|
||||||
|
|
||||||
### Frontend (SvelteKit + TypeScript)
|
Generate TypeScript bindings with `tauri-specta` when new tauri commands, events or Rust models is added / modified:
|
||||||
|
|
||||||
- **Build**: `pnpm build`
|
- `timeout 30 pnpm tauri dev`
|
||||||
- **Dev server**: `pnpm dev`
|
|
||||||
- **Type check**: `svelte-kit sync && svelte-check --tsconfig ./tsconfig.json`
|
|
||||||
- **Watch type check**: `svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch`
|
|
||||||
|
|
||||||
### Backend (Rust + Tauri)
|
|
||||||
|
|
||||||
- **Build**: `cargo build`
|
|
||||||
- **Check**: `cargo check`
|
|
||||||
- **Lint**: `cargo clippy`
|
|
||||||
- **Test**: `cargo test`
|
|
||||||
- **Run single test**: `cargo test <test_name>`
|
|
||||||
- **Generate TypeScript bindings (from project root)**: `TS_RS_EXPORT_DIR="../src/types/bindings" cargo test export_bindings --manifest-path="./src-tauri/Cargo.toml"`
|
|
||||||
|
|
||||||
## Code Style Guidelines
|
|
||||||
|
|
||||||
### TypeScript/Svelte
|
### TypeScript/Svelte
|
||||||
|
|
||||||
- **Strict TypeScript**: `"strict": true` enabled
|
|
||||||
- **Imports**: At top of file, before exports
|
|
||||||
- **Naming**: camelCase for variables/functions, PascalCase for types/interfaces
|
|
||||||
- **Modules**: ES modules only
|
|
||||||
- **Styling**: TailwindCSS + DaisyUI
|
|
||||||
- **Framework**: SvelteKit in SPA mode (SSR disabled for Tauri)
|
- **Framework**: SvelteKit in SPA mode (SSR disabled for Tauri)
|
||||||
- **Error handling**: Standard try/catch with console.error logging
|
- **Styling**: TailwindCSS + DaisyUI
|
||||||
- **Responsibility**: Minimal logic & data handling, should play as stateless dumb client
|
- **Responsibility**: Minimal logic & data handling, should play as stateless dumb client, communicate with Rust local backend via Tauri events
|
||||||
|
|
||||||
### Rust
|
### Rust
|
||||||
|
|
||||||
- **Error handling**: `thiserror::Error` derive with descriptive error messages
|
- **Error handling**: `thiserror::Error` derive with descriptive error messages
|
||||||
- **Logging**: `tracing` crate for structured logging (info/warn/error)
|
- **Logging**: `tracing` crate for structured logging (info/warn/error)
|
||||||
- **Async**: `tokio` runtime with `async`/`await`
|
- **Async**: `tokio` runtime with `async`/`await`
|
||||||
- **Serialization**: `serde` with `Serialize`/`Deserialize`
|
|
||||||
- **Naming**: snake_case for functions/variables, PascalCase for types/structs
|
- **Naming**: snake_case for functions/variables, PascalCase for types/structs
|
||||||
- **Documentation**: Comprehensive doc comments with examples for public APIs
|
|
||||||
- **State management**: Custom macros (`lock_r!`/`lock_w!`) for thread-safe access
|
- **State management**: Custom macros (`lock_r!`/`lock_w!`) for thread-safe access
|
||||||
- **Security**: Use secure storage (keyring) for sensitive data, proper PKCE flow for OAuth
|
- **Security**: Use secure storage (keyring) for sensitive data
|
||||||
- **Imports**: Group by standard library, then external crates, then local modules
|
|
||||||
- **Responsibility**: Handles app state & data, business logic, controls UI via events.
|
- **Responsibility**: Handles app state & data, business logic, controls UI via events.
|
||||||
|
|
||||||
## Note
|
## Note
|
||||||
|
|
||||||
Be sure to gather sufficient context from codebase before proceeding with changes. Observe patterns and follow trends.
|
Be sure to gather sufficient context from codebase before proceeding with changes. Observe patterns and follow trends.
|
||||||
|
|
||||||
Do not run the app yourself. `cd src-tauri && cargo check` & `pnpm check` to confirm your changes are error-free. Don't perform git actions yourself.
|
Do not run the app without timeout. `cd src-tauri && cargo check` & `pnpm check` to confirm your changes are error-free. Don't perform git actions yourself.
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -2,16 +2,4 @@
|
|||||||
|
|
||||||
This repository contins source for Friendolls desktop app. Will add more info when the app scales.
|
This repository contins source for Friendolls desktop app. Will add more info when the app scales.
|
||||||
|
|
||||||
Run the following command in project root after changes to models on Rust side to generate TypeScript type bindings from Rust models
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# average unix shells
|
|
||||||
TS_RS_EXPORT_DIR="../src/types/bindings" cargo test export_bindings --manifest-path="./src-tauri/Cargo.toml"
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# powershell
|
|
||||||
$Env:TS_RS_EXPORT_DIR = "../src/types/bindings"; cargo test export_bindings --manifest-path="./src-tauri/Cargo.toml"
|
|
||||||
```
|
|
||||||
|
|
||||||
> _To the gods of programming, please grant me the perseverance to push through and get this app into production_ 🙏
|
> _To the gods of programming, please grant me the perseverance to push through and get this app into production_ 🙏
|
||||||
|
|||||||
120
src-tauri/Cargo.lock
generated
120
src-tauri/Cargo.lock
generated
@@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "Inflector"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -1227,6 +1233,8 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"specta",
|
||||||
|
"specta-typescript",
|
||||||
"strum",
|
"strum",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
@@ -1235,13 +1243,13 @@ dependencies = [
|
|||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-positioner",
|
"tauri-plugin-positioner",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
|
"tauri-specta",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"ts-rs",
|
|
||||||
"url",
|
"url",
|
||||||
"windows 0.58.0",
|
"windows 0.58.0",
|
||||||
]
|
]
|
||||||
@@ -3035,6 +3043,12 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -4237,6 +4251,50 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta"
|
||||||
|
version = "2.0.0-rc.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971"
|
||||||
|
dependencies = [
|
||||||
|
"paste",
|
||||||
|
"specta-macros",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta-macros"
|
||||||
|
version = "2.0.0-rc.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517"
|
||||||
|
dependencies = [
|
||||||
|
"Inflector",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.110",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta-serde"
|
||||||
|
version = "0.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77216504061374659e7245eac53d30c7b3e5fe64b88da97c753e7184b0781e63"
|
||||||
|
dependencies = [
|
||||||
|
"specta",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "specta-typescript"
|
||||||
|
version = "0.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3220a0c365e51e248ac98eab5a6a32f544ff6f961906f09d3ee10903a4f52b2d"
|
||||||
|
dependencies = [
|
||||||
|
"specta",
|
||||||
|
"specta-serde",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -4487,6 +4545,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serialize-to-javascript",
|
"serialize-to-javascript",
|
||||||
|
"specta",
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-macros",
|
"tauri-macros",
|
||||||
@@ -4737,6 +4796,34 @@ dependencies = [
|
|||||||
"wry",
|
"wry",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-specta"
|
||||||
|
version = "2.0.0-rc.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b23c0132dd3cf6064e5cd919b82b3f47780e9280e7b5910babfe139829b76655"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"specta",
|
||||||
|
"specta-typescript",
|
||||||
|
"tauri",
|
||||||
|
"tauri-specta-macros",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-specta-macros"
|
||||||
|
version = "2.0.0-rc.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a4aa93823e07859546aa796b8a5d608190cd8037a3a5dce3eb63d491c34bda8"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.110",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -4810,15 +4897,6 @@ dependencies = [
|
|||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "termcolor"
|
|
||||||
version = "1.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@@ -5225,28 +5303,6 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ts-rs"
|
|
||||||
version = "11.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror 2.0.17",
|
|
||||||
"ts-rs-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ts-rs-macros"
|
|
||||||
version = "11.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.110",
|
|
||||||
"termcolor",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ tauri-plugin-positioner = "2"
|
|||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
reqwest = { version = "0.12.23", features = ["json", "native-tls", "blocking"] }
|
reqwest = { version = "0.12.23", features = ["json", "native-tls", "blocking"] }
|
||||||
tokio-util = "0.7"
|
tokio-util = "0.7"
|
||||||
ts-rs = "11.0.1"
|
specta = "=2.0.0-rc.22"
|
||||||
|
specta-typescript = "0.0.9"
|
||||||
|
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
device_query = "4.0.1"
|
device_query = "4.0.1"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::services::auth::get_session_token;
|
|||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn quit_app() -> Result<(), String> {
|
pub fn quit_app() -> Result<(), String> {
|
||||||
let app_handle = get_app_handle();
|
let app_handle = get_app_handle();
|
||||||
app_handle.exit(0);
|
app_handle.exit(0);
|
||||||
@@ -11,6 +12,7 @@ pub fn quit_app() -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn restart_app() {
|
pub fn restart_app() {
|
||||||
let app_handle = get_app_handle();
|
let app_handle = get_app_handle();
|
||||||
app_handle.restart();
|
app_handle.restart();
|
||||||
@@ -21,6 +23,7 @@ pub fn restart_app() {
|
|||||||
/// Validates server health, checks for a valid session token,
|
/// Validates server health, checks for a valid session token,
|
||||||
/// then reconstructs the user session (re-fetches app data + WebSocket).
|
/// then reconstructs the user session (re-fetches app data + WebSocket).
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn retry_connection() -> Result<(), String> {
|
pub async fn retry_connection() -> Result<(), String> {
|
||||||
info!("Retrying connection...");
|
info!("Retrying connection...");
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_app_data() -> Result<UserData, String> {
|
pub fn get_app_data() -> Result<UserData, String> {
|
||||||
let guard = lock_r!(FDOLL);
|
let guard = lock_r!(FDOLL);
|
||||||
Ok(guard.user_data.clone())
|
Ok(guard.user_data.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn refresh_app_data() -> Result<UserData, String> {
|
pub async fn refresh_app_data() -> Result<UserData, String> {
|
||||||
init_app_data_scoped(AppDataRefreshScope::All).await;
|
init_app_data_scoped(AppDataRefreshScope::All).await;
|
||||||
let guard = lock_r!(FDOLL);
|
let guard = lock_r!(FDOLL);
|
||||||
@@ -19,6 +21,7 @@ pub async fn refresh_app_data() -> Result<UserData, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_modules() -> Result<Vec<ModuleMetadata>, String> {
|
pub fn get_modules() -> Result<Vec<ModuleMetadata>, String> {
|
||||||
let guard = lock_r!(FDOLL);
|
let guard = lock_r!(FDOLL);
|
||||||
Ok(guard.modules.metadatas.clone())
|
Ok(guard.modules.metadatas.clone())
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use crate::services::auth;
|
use crate::services::auth;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn logout_and_restart() -> Result<(), String> {
|
pub async fn logout_and_restart() -> Result<(), String> {
|
||||||
auth::logout_and_restart().await.map_err(|e| e.to_string())
|
auth::logout_and_restart().await.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn login(email: String, password: String) -> Result<(), String> {
|
pub async fn login(email: String, password: String) -> Result<(), String> {
|
||||||
auth::login_and_init_session(&email, &password)
|
auth::login_and_init_session(&email, &password)
|
||||||
.await
|
.await
|
||||||
@@ -13,6 +15,7 @@ pub async fn login(email: String, password: String) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: String,
|
||||||
@@ -30,6 +33,7 @@ pub async fn register(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn change_password(
|
pub async fn change_password(
|
||||||
current_password: String,
|
current_password: String,
|
||||||
new_password: String,
|
new_password: String,
|
||||||
@@ -40,6 +44,7 @@ pub async fn change_password(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn reset_password(old_password: String, new_password: String) -> Result<(), String> {
|
pub async fn reset_password(old_password: String, new_password: String) -> Result<(), String> {
|
||||||
auth::reset_password(&old_password, &new_password)
|
auth::reset_password(&old_password, &new_password)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_client_config() -> AppConfig {
|
pub fn get_client_config() -> AppConfig {
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
guard.app_config = load_app_config();
|
guard.app_config = load_app_config();
|
||||||
@@ -14,6 +15,7 @@ pub fn get_client_config() -> AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn save_client_config(config: AppConfig) -> Result<(), String> {
|
pub fn save_client_config(config: AppConfig) -> Result<(), String> {
|
||||||
match save_app_config(config) {
|
match save_app_config(config) {
|
||||||
Ok(saved) => {
|
Ok(saved) => {
|
||||||
@@ -26,6 +28,7 @@ pub fn save_client_config(config: AppConfig) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn open_client_config_manager() -> Result<(), String> {
|
pub async fn open_client_config_manager() -> Result<(), String> {
|
||||||
open_config_manager_window().map_err(|e| e.to_string())
|
open_config_manager_window().map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_dolls() -> Result<Vec<DollDto>, String> {
|
pub async fn get_dolls() -> Result<Vec<DollDto>, String> {
|
||||||
DollsRemote::new()
|
DollsRemote::new()
|
||||||
.get_dolls()
|
.get_dolls()
|
||||||
@@ -17,6 +18,7 @@ pub async fn get_dolls() -> Result<Vec<DollDto>, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn get_doll(id: String) -> Result<DollDto, String> {
|
pub async fn get_doll(id: String) -> Result<DollDto, String> {
|
||||||
DollsRemote::new()
|
DollsRemote::new()
|
||||||
.get_doll(&id)
|
.get_doll(&id)
|
||||||
@@ -25,6 +27,7 @@ pub async fn get_doll(id: String) -> Result<DollDto, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn create_doll(dto: CreateDollDto) -> Result<DollDto, String> {
|
pub async fn create_doll(dto: CreateDollDto) -> Result<DollDto, String> {
|
||||||
let result = DollsRemote::new()
|
let result = DollsRemote::new()
|
||||||
.create_doll(dto)
|
.create_doll(dto)
|
||||||
@@ -37,6 +40,7 @@ pub async fn create_doll(dto: CreateDollDto) -> Result<DollDto, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn update_doll(id: String, dto: UpdateDollDto) -> Result<DollDto, String> {
|
pub async fn update_doll(id: String, dto: UpdateDollDto) -> Result<DollDto, String> {
|
||||||
let result = DollsRemote::new()
|
let result = DollsRemote::new()
|
||||||
.update_doll(&id, dto)
|
.update_doll(&id, dto)
|
||||||
@@ -55,6 +59,7 @@ pub async fn update_doll(id: String, dto: UpdateDollDto) -> Result<DollDto, Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn delete_doll(id: String) -> Result<(), String> {
|
pub async fn delete_doll(id: String) -> Result<(), String> {
|
||||||
DollsRemote::new()
|
DollsRemote::new()
|
||||||
.delete_doll(&id)
|
.delete_doll(&id)
|
||||||
@@ -73,6 +78,7 @@ pub async fn delete_doll(id: String) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn set_active_doll(doll_id: String) -> Result<(), String> {
|
pub async fn set_active_doll(doll_id: String) -> Result<(), String> {
|
||||||
UserRemote::new()
|
UserRemote::new()
|
||||||
.set_active_doll(&doll_id)
|
.set_active_doll(&doll_id)
|
||||||
@@ -85,6 +91,7 @@ pub async fn set_active_doll(doll_id: String) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn remove_active_doll() -> Result<(), String> {
|
pub async fn remove_active_doll() -> Result<(), String> {
|
||||||
UserRemote::new()
|
UserRemote::new()
|
||||||
.remove_active_doll()
|
.remove_active_doll()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::state::AppDataRefreshScope;
|
|||||||
use crate::commands::refresh_app_data;
|
use crate::commands::refresh_app_data;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn list_friends() -> Result<Vec<FriendshipResponseDto>, String> {
|
pub async fn list_friends() -> Result<Vec<FriendshipResponseDto>, String> {
|
||||||
FriendRemote::new()
|
FriendRemote::new()
|
||||||
.get_friends()
|
.get_friends()
|
||||||
@@ -14,6 +15,7 @@ pub async fn list_friends() -> Result<Vec<FriendshipResponseDto>, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn search_users(username: Option<String>) -> Result<Vec<UserBasicDto>, String> {
|
pub async fn search_users(username: Option<String>) -> Result<Vec<UserBasicDto>, String> {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Tauri command search_users called with username: {:?}",
|
"Tauri command search_users called with username: {:?}",
|
||||||
@@ -30,6 +32,7 @@ pub async fn search_users(username: Option<String>) -> Result<Vec<UserBasicDto>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn send_friend_request(
|
pub async fn send_friend_request(
|
||||||
request: SendFriendRequestDto,
|
request: SendFriendRequestDto,
|
||||||
) -> Result<FriendRequestResponseDto, String> {
|
) -> Result<FriendRequestResponseDto, String> {
|
||||||
@@ -44,6 +47,7 @@ pub async fn send_friend_request(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn received_friend_requests() -> Result<Vec<FriendRequestResponseDto>, String> {
|
pub async fn received_friend_requests() -> Result<Vec<FriendRequestResponseDto>, String> {
|
||||||
FriendRemote::new()
|
FriendRemote::new()
|
||||||
.get_received_requests()
|
.get_received_requests()
|
||||||
@@ -52,6 +56,7 @@ pub async fn received_friend_requests() -> Result<Vec<FriendRequestResponseDto>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn sent_friend_requests() -> Result<Vec<FriendRequestResponseDto>, String> {
|
pub async fn sent_friend_requests() -> Result<Vec<FriendRequestResponseDto>, String> {
|
||||||
FriendRemote::new()
|
FriendRemote::new()
|
||||||
.get_sent_requests()
|
.get_sent_requests()
|
||||||
@@ -60,6 +65,7 @@ pub async fn sent_friend_requests() -> Result<Vec<FriendRequestResponseDto>, Str
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn accept_friend_request(request_id: String) -> Result<FriendRequestResponseDto, String> {
|
pub async fn accept_friend_request(request_id: String) -> Result<FriendRequestResponseDto, String> {
|
||||||
let result = FriendRemote::new()
|
let result = FriendRemote::new()
|
||||||
.accept_friend_request(&request_id)
|
.accept_friend_request(&request_id)
|
||||||
@@ -72,6 +78,7 @@ pub async fn accept_friend_request(request_id: String) -> Result<FriendRequestRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn deny_friend_request(request_id: String) -> Result<FriendRequestResponseDto, String> {
|
pub async fn deny_friend_request(request_id: String) -> Result<FriendRequestResponseDto, String> {
|
||||||
let result = FriendRemote::new()
|
let result = FriendRemote::new()
|
||||||
.deny_friend_request(&request_id)
|
.deny_friend_request(&request_id)
|
||||||
@@ -84,6 +91,7 @@ pub async fn deny_friend_request(request_id: String) -> Result<FriendRequestResp
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn unfriend(friend_id: String) -> Result<(), String> {
|
pub async fn unfriend(friend_id: String) -> Result<(), String> {
|
||||||
FriendRemote::new()
|
FriendRemote::new()
|
||||||
.unfriend(&friend_id)
|
.unfriend(&friend_id)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{models::interaction::SendInteractionDto, services::interaction::send_interaction};
|
use crate::{models::interaction::SendInteractionDto, services::interaction::send_interaction};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn send_interaction_cmd(dto: SendInteractionDto) -> Result<(), String> {
|
pub async fn send_interaction_cmd(dto: SendInteractionDto) -> Result<(), String> {
|
||||||
send_interaction(dto).await
|
send_interaction(dto).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::models::dolls::DollDto;
|
|||||||
use crate::services::petpet;
|
use crate::services::petpet;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn encode_pet_doll_gif_base64(doll: DollDto) -> Result<String, String> {
|
pub fn encode_pet_doll_gif_base64(doll: DollDto) -> Result<String, String> {
|
||||||
petpet::encode_pet_doll_gif_base64(doll)
|
petpet::encode_pet_doll_gif_base64(doll)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::services::sprite_recolor;
|
use crate::services::sprite_recolor;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn recolor_gif_base64(
|
pub fn recolor_gif_base64(
|
||||||
white_color_hex: String,
|
white_color_hex: String,
|
||||||
black_color_hex: String,
|
black_color_hex: String,
|
||||||
|
|||||||
@@ -19,7 +19,17 @@ use commands::friends::{
|
|||||||
use commands::interaction::send_interaction_cmd;
|
use commands::interaction::send_interaction_cmd;
|
||||||
use commands::sprite::recolor_gif_base64;
|
use commands::sprite::recolor_gif_base64;
|
||||||
use commands::petpet::encode_pet_doll_gif_base64;
|
use commands::petpet::encode_pet_doll_gif_base64;
|
||||||
|
use specta_typescript::Typescript;
|
||||||
use tauri::async_runtime;
|
use tauri::async_runtime;
|
||||||
|
use tauri_specta::{Builder as SpectaBuilder, ErrorHandlingMode, collect_commands, collect_events};
|
||||||
|
|
||||||
|
use crate::services::app_events::{
|
||||||
|
AppDataRefreshed, CreateDoll, CursorMoved, EditDoll, FriendActiveDollChanged,
|
||||||
|
FriendCursorPositionUpdated, FriendDisconnected, FriendRequestAccepted,
|
||||||
|
FriendRequestDenied, FriendRequestReceived, FriendUserStatusChanged,
|
||||||
|
InteractionDeliveryFailed, InteractionReceived, SceneInteractiveChanged,
|
||||||
|
SetInteractionOverlay, Unfriended, UserStatusChanged,
|
||||||
|
};
|
||||||
|
|
||||||
static APP_HANDLE: std::sync::OnceLock<tauri::AppHandle<tauri::Wry>> = std::sync::OnceLock::new();
|
static APP_HANDLE: std::sync::OnceLock<tauri::AppHandle<tauri::Wry>> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
@@ -51,12 +61,9 @@ fn register_app_events(event: tauri::RunEvent) {
|
|||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
let specta_builder = SpectaBuilder::<tauri::Wry>::new()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.error_handling(ErrorHandlingMode::Throw)
|
||||||
.plugin(tauri_plugin_positioner::init())
|
.commands(collect_commands![
|
||||||
.plugin(tauri_plugin_dialog::init())
|
|
||||||
.plugin(tauri_plugin_process::init())
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
|
||||||
get_app_data,
|
get_app_data,
|
||||||
refresh_app_data,
|
refresh_app_data,
|
||||||
list_friends,
|
list_friends,
|
||||||
@@ -94,7 +101,39 @@ pub fn run() {
|
|||||||
send_interaction_cmd,
|
send_interaction_cmd,
|
||||||
get_modules
|
get_modules
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.events(collect_events![
|
||||||
|
CursorMoved,
|
||||||
|
SceneInteractiveChanged,
|
||||||
|
AppDataRefreshed,
|
||||||
|
SetInteractionOverlay,
|
||||||
|
EditDoll,
|
||||||
|
CreateDoll,
|
||||||
|
UserStatusChanged,
|
||||||
|
FriendCursorPositionUpdated,
|
||||||
|
FriendDisconnected,
|
||||||
|
FriendActiveDollChanged,
|
||||||
|
FriendUserStatusChanged,
|
||||||
|
InteractionReceived,
|
||||||
|
InteractionDeliveryFailed,
|
||||||
|
FriendRequestReceived,
|
||||||
|
FriendRequestAccepted,
|
||||||
|
FriendRequestDenied,
|
||||||
|
Unfriended
|
||||||
|
]);
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
specta_builder
|
||||||
|
.export(Typescript::default(), "../src/lib/bindings.ts")
|
||||||
|
.expect("Failed to export TypeScript bindings");
|
||||||
|
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.plugin(tauri_plugin_positioner::init())
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.plugin(tauri_plugin_process::init())
|
||||||
|
.invoke_handler(specta_builder.invoke_handler())
|
||||||
|
.setup(move |app| {
|
||||||
|
specta_builder.mount_events(app);
|
||||||
APP_HANDLE
|
APP_HANDLE
|
||||||
.set(app.handle().to_owned())
|
.set(app.handle().to_owned())
|
||||||
.expect("Failed to init app handle.");
|
.expect("Failed to init app handle.");
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use specta::Type;
|
||||||
|
|
||||||
use crate::models::{dolls::DollDto, friends::FriendshipResponseDto, user::UserProfile};
|
use crate::models::{dolls::DollDto, friends::FriendshipResponseDto, user::UserProfile};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[ts(export)]
|
|
||||||
pub struct DisplayData {
|
pub struct DisplayData {
|
||||||
pub screen_width: i32,
|
pub screen_width: i32,
|
||||||
pub screen_height: i32,
|
pub screen_height: i32,
|
||||||
@@ -21,8 +20,7 @@ impl Default for DisplayData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[ts(export)]
|
|
||||||
pub struct SceneData {
|
pub struct SceneData {
|
||||||
pub display: DisplayData,
|
pub display: DisplayData,
|
||||||
pub grid_size: i32,
|
pub grid_size: i32,
|
||||||
@@ -37,8 +35,7 @@ impl Default for SceneData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[ts(export)]
|
|
||||||
pub struct UserData {
|
pub struct UserData {
|
||||||
pub user: Option<UserProfile>,
|
pub user: Option<UserProfile>,
|
||||||
pub friends: Option<Vec<FriendshipResponseDto>>,
|
pub friends: Option<Vec<FriendshipResponseDto>>,
|
||||||
|
|||||||
@@ -1,40 +1,35 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use specta::Type;
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct DollColorSchemeDto {
|
pub struct DollColorSchemeDto {
|
||||||
pub outline: String,
|
pub outline: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct DollConfigurationDto {
|
pub struct DollConfigurationDto {
|
||||||
pub color_scheme: DollColorSchemeDto,
|
pub color_scheme: DollColorSchemeDto,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct CreateDollDto {
|
pub struct CreateDollDto {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub configuration: Option<DollConfigurationDto>,
|
pub configuration: Option<DollConfigurationDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct UpdateDollDto {
|
pub struct UpdateDollDto {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub configuration: Option<DollConfigurationDto>,
|
pub configuration: Option<DollConfigurationDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct DollDto {
|
pub struct DollDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -1,79 +1,70 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use specta::Type;
|
||||||
|
|
||||||
use super::dolls::DollDto;
|
use super::dolls::DollDto;
|
||||||
use super::friends::UserBasicDto;
|
use super::friends::UserBasicDto;
|
||||||
use crate::services::presence_modules::models::PresenceStatus;
|
use crate::services::presence_modules::models::PresenceStatus;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[ts(export)]
|
|
||||||
pub enum UserStatusState {
|
pub enum UserStatusState {
|
||||||
Idle,
|
Idle,
|
||||||
Resting,
|
Resting,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct UserStatusPayload {
|
pub struct UserStatusPayload {
|
||||||
pub presence_status: PresenceStatus,
|
pub presence_status: PresenceStatus,
|
||||||
pub state: UserStatusState,
|
pub state: UserStatusState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendUserStatusPayload {
|
pub struct FriendUserStatusPayload {
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
pub status: UserStatusPayload,
|
pub status: UserStatusPayload,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendDisconnectedPayload {
|
pub struct FriendDisconnectedPayload {
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendActiveDollChangedPayload {
|
pub struct FriendActiveDollChangedPayload {
|
||||||
pub friend_id: String,
|
pub friend_id: String,
|
||||||
pub doll: Option<DollDto>,
|
pub doll: Option<DollDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendRequestReceivedPayload {
|
pub struct FriendRequestReceivedPayload {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub sender: UserBasicDto,
|
pub sender: UserBasicDto,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendRequestAcceptedPayload {
|
pub struct FriendRequestAcceptedPayload {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub friend: UserBasicDto,
|
pub friend: UserBasicDto,
|
||||||
pub accepted_at: String,
|
pub accepted_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendRequestDeniedPayload {
|
pub struct FriendRequestDeniedPayload {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub denier: UserBasicDto,
|
pub denier: UserBasicDto,
|
||||||
pub denied_at: String,
|
pub denied_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct UnfriendedPayload {
|
pub struct UnfriendedPayload {
|
||||||
pub friend_id: String,
|
pub friend_id: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use specta::Type;
|
||||||
|
|
||||||
use super::dolls::DollDto;
|
use super::dolls::DollDto;
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct UserBasicDto {
|
pub struct UserBasicDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -13,25 +12,22 @@ pub struct UserBasicDto {
|
|||||||
pub active_doll: Option<DollDto>,
|
pub active_doll: Option<DollDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendshipResponseDto {
|
pub struct FriendshipResponseDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub friend: Option<UserBasicDto>,
|
pub friend: Option<UserBasicDto>,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct SendFriendRequestDto {
|
pub struct SendFriendRequestDto {
|
||||||
pub receiver_id: String,
|
pub receiver_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct FriendRequestResponseDto {
|
pub struct FriendRequestResponseDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub sender: UserBasicDto,
|
pub sender: UserBasicDto,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use ts_rs::TS;
|
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct HealthResponseDto {
|
pub struct HealthResponseDto {
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use specta::Type;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[ts(export)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SendInteractionDto {
|
pub struct SendInteractionDto {
|
||||||
pub recipient_user_id: String,
|
pub recipient_user_id: String,
|
||||||
@@ -11,8 +10,7 @@ pub struct SendInteractionDto {
|
|||||||
pub type_: String,
|
pub type_: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[ts(export)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct InteractionPayloadDto {
|
pub struct InteractionPayloadDto {
|
||||||
pub sender_user_id: String,
|
pub sender_user_id: String,
|
||||||
@@ -23,8 +21,7 @@ pub struct InteractionPayloadDto {
|
|||||||
pub timestamp: String,
|
pub timestamp: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, TS)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
||||||
#[ts(export)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct InteractionDeliveryFailedDto {
|
pub struct InteractionDeliveryFailedDto {
|
||||||
pub recipient_user_id: String,
|
pub recipient_user_id: String,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use specta::Type;
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct UserProfile {
|
pub struct UserProfile {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -1,105 +1,84 @@
|
|||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
#[allow(unused_imports)]
|
use specta::Type;
|
||||||
use std::{fs, path::Path};
|
use tauri_specta::Event;
|
||||||
use strum::{AsRefStr, EnumIter};
|
|
||||||
use ts_rs::TS;
|
|
||||||
|
|
||||||
#[derive(Serialize, TS, EnumIter, AsRefStr)]
|
use crate::{
|
||||||
#[serde(rename_all = "kebab-case")]
|
models::{
|
||||||
#[ts(export)]
|
app_data::UserData,
|
||||||
pub enum AppEvents {
|
event_payloads::{
|
||||||
CursorPosition,
|
FriendActiveDollChangedPayload, FriendDisconnectedPayload,
|
||||||
SceneInteractive,
|
FriendRequestAcceptedPayload, FriendRequestDeniedPayload, FriendRequestReceivedPayload,
|
||||||
AppDataRefreshed,
|
FriendUserStatusPayload, UnfriendedPayload, UserStatusPayload,
|
||||||
SetInteractionOverlay,
|
},
|
||||||
EditDoll,
|
interaction::{InteractionDeliveryFailedDto, InteractionPayloadDto},
|
||||||
CreateDoll,
|
},
|
||||||
UserStatusChanged,
|
services::{cursor::CursorPositions, ws::OutgoingFriendCursorPayload},
|
||||||
FriendCursorPosition,
|
|
||||||
FriendDisconnected,
|
|
||||||
FriendActiveDollChanged,
|
|
||||||
FriendUserStatus,
|
|
||||||
InteractionReceived,
|
|
||||||
InteractionDeliveryFailed,
|
|
||||||
FriendRequestReceived,
|
|
||||||
FriendRequestAccepted,
|
|
||||||
FriendRequestDenied,
|
|
||||||
Unfriended,
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
AppEvents::FriendCursorPosition => "friend-cursor-position",
|
|
||||||
AppEvents::FriendDisconnected => "friend-disconnected",
|
|
||||||
AppEvents::FriendActiveDollChanged => "friend-active-doll-changed",
|
|
||||||
AppEvents::FriendUserStatus => "friend-user-status",
|
|
||||||
AppEvents::InteractionReceived => "interaction-received",
|
|
||||||
AppEvents::InteractionDeliveryFailed => "interaction-delivery-failed",
|
|
||||||
AppEvents::FriendRequestReceived => "friend-request-received",
|
|
||||||
AppEvents::FriendRequestAccepted => "friend-request-accepted",
|
|
||||||
AppEvents::FriendRequestDenied => "friend-request-denied",
|
|
||||||
AppEvents::Unfriended => "unfriended",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
let mut result = String::new();
|
#[tauri_specta(event_name = "cursor-position")]
|
||||||
for (i, c) in s.chars().enumerate() {
|
pub struct CursorMoved(pub CursorPositions);
|
||||||
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![
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
r#"// Auto-generated constants - DO NOT EDIT"#.to_string(),
|
#[tauri_specta(event_name = "scene-interactive")]
|
||||||
r#"// Generated from Rust AppEvents enum"#.to_string(),
|
pub struct SceneInteractiveChanged(pub bool);
|
||||||
"".to_string(),
|
|
||||||
"export const AppEvents = {".to_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for variant in AppEvents::iter() {
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
let name = variant.as_ref();
|
#[tauri_specta(event_name = "app-data-refreshed")]
|
||||||
let kebab = to_kebab_case(name);
|
pub struct AppDataRefreshed(pub UserData);
|
||||||
lines.push(format!(" {}: \"{}\",", name, kebab));
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.push("} as const;".to_string());
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
lines.push("".to_string());
|
#[tauri_specta(event_name = "set-interaction-overlay")]
|
||||||
lines.push("export type AppEvents = typeof AppEvents[keyof typeof AppEvents];".to_string());
|
pub struct SetInteractionOverlay(pub bool);
|
||||||
|
|
||||||
let constants_content = lines.join("\n");
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "edit-doll")]
|
||||||
|
pub struct EditDoll(pub String);
|
||||||
|
|
||||||
let constants_path = export_dir.join("AppEventsConstants.ts");
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
if let Err(e) = fs::write(&constants_path, constants_content) {
|
#[tauri_specta(event_name = "create-doll")]
|
||||||
eprintln!("Failed to write {}: {}", constants_path.display(), e);
|
pub struct CreateDoll;
|
||||||
}
|
|
||||||
}
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "user-status-changed")]
|
||||||
|
pub struct UserStatusChanged(pub UserStatusPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "friend-cursor-position")]
|
||||||
|
pub struct FriendCursorPositionUpdated(pub OutgoingFriendCursorPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "friend-disconnected")]
|
||||||
|
pub struct FriendDisconnected(pub FriendDisconnectedPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "friend-active-doll-changed")]
|
||||||
|
pub struct FriendActiveDollChanged(pub FriendActiveDollChangedPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "friend-user-status")]
|
||||||
|
pub struct FriendUserStatusChanged(pub FriendUserStatusPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "interaction-received")]
|
||||||
|
pub struct InteractionReceived(pub InteractionPayloadDto);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "interaction-delivery-failed")]
|
||||||
|
pub struct InteractionDeliveryFailed(pub InteractionDeliveryFailedDto);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "friend-request-received")]
|
||||||
|
pub struct FriendRequestReceived(pub FriendRequestReceivedPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "friend-request-accepted")]
|
||||||
|
pub struct FriendRequestAccepted(pub FriendRequestAcceptedPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "friend-request-denied")]
|
||||||
|
pub struct FriendRequestDenied(pub FriendRequestDeniedPayload);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[tauri_specta(event_name = "unfriended")]
|
||||||
|
pub struct Unfriended(pub UnfriendedPayload);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
@@ -8,7 +9,7 @@ use url::Url;
|
|||||||
|
|
||||||
use crate::get_app_handle;
|
use crate::get_app_handle;
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub api_base_url: Option<String>,
|
pub api_base_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
use device_query::{DeviceEvents, DeviceEventsHandler};
|
use device_query::{DeviceEvents, DeviceEventsHandler};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tauri::Emitter;
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
use ts_rs::TS;
|
|
||||||
|
|
||||||
use crate::{get_app_handle, lock_r, services::app_events::AppEvents, state::FDOLL};
|
use crate::{
|
||||||
|
get_app_handle,
|
||||||
|
lock_r,
|
||||||
|
services::app_events::CursorMoved,
|
||||||
|
state::FDOLL,
|
||||||
|
};
|
||||||
|
use tauri_specta::Event as _;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct CursorPosition {
|
pub struct CursorPosition {
|
||||||
pub x: f64,
|
pub x: f64,
|
||||||
pub y: f64,
|
pub y: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct CursorPositions {
|
pub struct CursorPositions {
|
||||||
pub raw: CursorPosition,
|
pub raw: CursorPosition,
|
||||||
pub mapped: CursorPosition,
|
pub mapped: CursorPosition,
|
||||||
@@ -103,7 +106,7 @@ async fn init_cursor_tracking_i() -> Result<(), String> {
|
|||||||
crate::services::ws::report_cursor_data(mapped_for_ws).await;
|
crate::services::ws::report_cursor_data(mapped_for_ws).await;
|
||||||
|
|
||||||
// 2. Broadcast to local windows
|
// 2. Broadcast to local windows
|
||||||
if let Err(e) = app_handle.emit(AppEvents::CursorPosition.as_str(), &positions) {
|
if let Err(e) = CursorMoved(positions).emit(app_handle) {
|
||||||
error!("Failed to emit cursor position event: {:?}", e);
|
error!("Failed to emit cursor position event: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use tauri::WebviewWindow;
|
use tauri::WebviewWindow;
|
||||||
use tauri::{Emitter, Listener, Manager};
|
use tauri::{Listener, Manager};
|
||||||
|
use tauri_specta::Event as _;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{get_app_handle, services::app_events::AppEvents};
|
use crate::{
|
||||||
|
get_app_handle,
|
||||||
|
services::app_events::{CreateDoll, EditDoll, SetInteractionOverlay},
|
||||||
|
};
|
||||||
|
|
||||||
static APP_MENU_WINDOW_LABEL: &str = "app_menu";
|
static APP_MENU_WINDOW_LABEL: &str = "app_menu";
|
||||||
|
|
||||||
@@ -51,6 +55,7 @@ fn set_window_interaction(window: &WebviewWindow, enable: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub async fn open_doll_editor_window(doll_id: Option<String>) {
|
pub async fn open_doll_editor_window(doll_id: Option<String>) {
|
||||||
let app_handle = get_app_handle().clone();
|
let app_handle = get_app_handle().clone();
|
||||||
|
|
||||||
@@ -76,17 +81,17 @@ pub async fn open_doll_editor_window(doll_id: Option<String>) {
|
|||||||
// Ensure overlay is active on parent (redundancy for safety)
|
// Ensure overlay is active on parent (redundancy for safety)
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
if let Some(parent) = app_handle.get_webview_window(APP_MENU_WINDOW_LABEL) {
|
if let Some(parent) = app_handle.get_webview_window(APP_MENU_WINDOW_LABEL) {
|
||||||
if let Err(e) = parent.emit(AppEvents::SetInteractionOverlay.as_str(), true) {
|
if let Err(e) = SetInteractionOverlay(true).emit(&parent) {
|
||||||
error!("Failed to ensure interaction overlay on parent: {}", e);
|
error!("Failed to ensure interaction overlay on parent: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit event to update context
|
// Emit event to update context
|
||||||
if let Some(id) = doll_id {
|
if let Some(id) = doll_id {
|
||||||
if let Err(e) = window.emit(AppEvents::EditDoll.as_str(), id) {
|
if let Err(e) = EditDoll(id).emit(&window) {
|
||||||
error!("Failed to emit edit-doll event: {}", e);
|
error!("Failed to emit edit-doll event: {}", e);
|
||||||
}
|
}
|
||||||
} else if let Err(e) = window.emit(AppEvents::CreateDoll.as_str(), ()) {
|
} else if let Err(e) = CreateDoll.emit(&window) {
|
||||||
error!("Failed to emit create-doll event: {}", e);
|
error!("Failed to emit create-doll event: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +141,7 @@ pub async fn open_doll_editor_window(doll_id: Option<String>) {
|
|||||||
let app_handle_clone = get_app_handle().clone();
|
let app_handle_clone = get_app_handle().clone();
|
||||||
|
|
||||||
// Emit event to show overlay
|
// Emit event to show overlay
|
||||||
if let Err(e) = parent.emit(AppEvents::SetInteractionOverlay.as_str(), true) {
|
if let Err(e) = SetInteractionOverlay(true).emit(&parent) {
|
||||||
error!("Failed to emit set-interaction-overlay event: {}", e);
|
error!("Failed to emit set-interaction-overlay event: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +174,7 @@ pub async fn open_doll_editor_window(doll_id: Option<String>) {
|
|||||||
parent.unlisten(id);
|
parent.unlisten(id);
|
||||||
}
|
}
|
||||||
// Remove overlay if we failed
|
// Remove overlay if we failed
|
||||||
let _ = parent.emit(AppEvents::SetInteractionOverlay.as_str(), false);
|
let _ = SetInteractionOverlay(false).emit(&parent);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -204,9 +209,7 @@ pub async fn open_doll_editor_window(doll_id: Option<String>) {
|
|||||||
parent.unlisten(id);
|
parent.unlisten(id);
|
||||||
}
|
}
|
||||||
// Remove overlay
|
// Remove overlay
|
||||||
if let Err(e) = parent
|
if let Err(e) = SetInteractionOverlay(false).emit(&parent) {
|
||||||
.emit(AppEvents::SetInteractionOverlay.as_str(), false)
|
|
||||||
{
|
|
||||||
error!("Failed to remove interaction overlay: {}", e);
|
error!("Failed to remove interaction overlay: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,7 +235,7 @@ pub async fn open_doll_editor_window(doll_id: Option<String>) {
|
|||||||
if let Some(id) = parent_focus_listener_id {
|
if let Some(id) = parent_focus_listener_id {
|
||||||
parent.unlisten(id);
|
parent.unlisten(id);
|
||||||
}
|
}
|
||||||
let _ = parent.emit(AppEvents::SetInteractionOverlay.as_str(), false);
|
let _ = SetInteractionOverlay(false).emit(&parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use specta::Type;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct PresenceStatus {
|
pub struct PresenceStatus {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub subtitle: Option<String>,
|
pub subtitle: Option<String>,
|
||||||
pub graphics_b64: Option<String>,
|
pub graphics_b64: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
|
||||||
pub struct ModuleMetadata {
|
pub struct ModuleMetadata {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ use std::thread;
|
|||||||
|
|
||||||
use device_query::{DeviceQuery, DeviceState, Keycode};
|
use device_query::{DeviceQuery, DeviceState, Keycode};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use tauri::{Emitter, Manager};
|
use tauri::Manager;
|
||||||
use tauri_plugin_positioner::WindowExt;
|
use tauri_plugin_positioner::WindowExt;
|
||||||
|
use tauri_specta::Event as _;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::{get_app_handle, services::app_events::AppEvents};
|
use crate::{get_app_handle, services::app_events::SceneInteractiveChanged};
|
||||||
|
|
||||||
pub static SCENE_WINDOW_LABEL: &str = "scene";
|
pub static SCENE_WINDOW_LABEL: &str = "scene";
|
||||||
pub static SPLASH_WINDOW_LABEL: &str = "splash";
|
pub static SPLASH_WINDOW_LABEL: &str = "splash";
|
||||||
@@ -69,7 +70,7 @@ pub fn update_scene_interactive(interactive: bool, should_click: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = window.emit(AppEvents::SceneInteractive.as_str(), &interactive) {
|
if let Err(e) = SceneInteractiveChanged(interactive).emit(&window) {
|
||||||
error!("Failed to emit scene interactive event: {}", e);
|
error!("Failed to emit scene interactive event: {}", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -78,16 +79,19 @@ pub fn update_scene_interactive(interactive: bool, should_click: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_scene_interactive(interactive: bool, should_click: bool) {
|
pub fn set_scene_interactive(interactive: bool, should_click: bool) {
|
||||||
update_scene_interactive(interactive, should_click);
|
update_scene_interactive(interactive, should_click);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn get_scene_interactive() -> Result<bool, String> {
|
pub fn get_scene_interactive() -> Result<bool, String> {
|
||||||
Ok(scene_interactive_state().load(Ordering::SeqCst))
|
Ok(scene_interactive_state().load(Ordering::SeqCst))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
pub fn set_pet_menu_state(id: String, open: bool) {
|
pub fn set_pet_menu_state(id: String, open: bool) {
|
||||||
let menus_arc = get_open_pet_menus();
|
let menus_arc = get_open_pet_menus();
|
||||||
let should_update = {
|
let should_update = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use rust_socketio::Payload;
|
use rust_socketio::Payload;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::{async_runtime, Emitter};
|
use tauri::async_runtime;
|
||||||
|
use tauri_specta::Event;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -110,11 +111,8 @@ pub async fn ws_emit_soft<T: Serialize + Send + 'static>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit event to frontend (Tauri window)
|
pub fn emit_to_frontend_typed<E: Event + Serialize + Clone>(event: &E) {
|
||||||
///
|
if let Err(e) = event.emit(get_app_handle()) {
|
||||||
/// Handles error logging consistently.
|
error!("Failed to emit {} event to frontend: {:?}", E::NAME, e);
|
||||||
pub fn emit_to_frontend<T: Serialize + Clone>(event: &str, payload: T) {
|
|
||||||
if let Err(e) = get_app_handle().emit(event, payload) {
|
|
||||||
error!("Failed to emit {} event to frontend: {:?}", event, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ use crate::models::event_payloads::{
|
|||||||
FriendRequestDeniedPayload, FriendRequestReceivedPayload, FriendUserStatusPayload,
|
FriendRequestDeniedPayload, FriendRequestReceivedPayload, FriendUserStatusPayload,
|
||||||
UnfriendedPayload,
|
UnfriendedPayload,
|
||||||
};
|
};
|
||||||
use crate::services::app_events::AppEvents;
|
use crate::services::app_events::{
|
||||||
|
FriendActiveDollChanged, FriendCursorPositionUpdated, FriendDisconnected,
|
||||||
|
FriendRequestAccepted, FriendRequestDenied, FriendRequestReceived, FriendUserStatusChanged,
|
||||||
|
Unfriended,
|
||||||
|
};
|
||||||
use crate::services::cursor::{normalized_to_absolute, CursorPositions};
|
use crate::services::cursor::{normalized_to_absolute, CursorPositions};
|
||||||
use crate::state::AppDataRefreshScope;
|
use crate::state::AppDataRefreshScope;
|
||||||
|
|
||||||
@@ -21,7 +25,7 @@ pub fn on_friend_request_received(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendRequestReceivedPayload>(payload, "friend-request-received")
|
utils::extract_and_parse::<FriendRequestReceivedPayload>(payload, "friend-request-received")
|
||||||
{
|
{
|
||||||
emitter::emit_to_frontend(AppEvents::FriendRequestReceived.as_str(), data);
|
emitter::emit_to_frontend_typed(&FriendRequestReceived(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +34,7 @@ pub fn on_friend_request_accepted(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendRequestAcceptedPayload>(payload, "friend-request-accepted")
|
utils::extract_and_parse::<FriendRequestAcceptedPayload>(payload, "friend-request-accepted")
|
||||||
{
|
{
|
||||||
emitter::emit_to_frontend(AppEvents::FriendRequestAccepted.as_str(), data);
|
emitter::emit_to_frontend_typed(&FriendRequestAccepted(data));
|
||||||
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,14 +44,14 @@ pub fn on_friend_request_denied(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendRequestDeniedPayload>(payload, "friend-request-denied")
|
utils::extract_and_parse::<FriendRequestDeniedPayload>(payload, "friend-request-denied")
|
||||||
{
|
{
|
||||||
emitter::emit_to_frontend(AppEvents::FriendRequestDenied.as_str(), data);
|
emitter::emit_to_frontend_typed(&FriendRequestDenied(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for unfriended event
|
/// Handler for unfriended event
|
||||||
pub fn on_unfriended(payload: Payload, _socket: RawClient) {
|
pub fn on_unfriended(payload: Payload, _socket: RawClient) {
|
||||||
if let Ok(data) = utils::extract_and_parse::<UnfriendedPayload>(payload, "unfriended") {
|
if let Ok(data) = utils::extract_and_parse::<UnfriendedPayload>(payload, "unfriended") {
|
||||||
emitter::emit_to_frontend(AppEvents::Unfriended.as_str(), data);
|
emitter::emit_to_frontend_typed(&Unfriended(data));
|
||||||
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +72,7 @@ pub fn on_friend_cursor_position(payload: Payload, _socket: RawClient) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
emitter::emit_to_frontend(AppEvents::FriendCursorPosition.as_str(), outgoing_payload);
|
emitter::emit_to_frontend_typed(&FriendCursorPositionUpdated(outgoing_payload));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +81,7 @@ pub fn on_friend_disconnected(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendDisconnectedPayload>(payload, "friend-disconnected")
|
utils::extract_and_parse::<FriendDisconnectedPayload>(payload, "friend-disconnected")
|
||||||
{
|
{
|
||||||
emitter::emit_to_frontend(AppEvents::FriendDisconnected.as_str(), data);
|
emitter::emit_to_frontend_typed(&FriendDisconnected(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +114,7 @@ pub fn on_friend_active_doll_changed(payload: Payload, _socket: RawClient) {
|
|||||||
payload,
|
payload,
|
||||||
"friend-active-doll-changed",
|
"friend-active-doll-changed",
|
||||||
) {
|
) {
|
||||||
emitter::emit_to_frontend(AppEvents::FriendActiveDollChanged.as_str(), data);
|
emitter::emit_to_frontend_typed(&FriendActiveDollChanged(data));
|
||||||
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,6 +124,6 @@ pub fn on_friend_user_status(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendUserStatusPayload>(payload, "friend-user-status")
|
utils::extract_and_parse::<FriendUserStatusPayload>(payload, "friend-user-status")
|
||||||
{
|
{
|
||||||
emitter::emit_to_frontend(AppEvents::FriendUserStatus.as_str(), data);
|
emitter::emit_to_frontend_typed(&FriendUserStatusChanged(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use rust_socketio::{Payload, RawClient};
|
use rust_socketio::{Payload, RawClient};
|
||||||
|
|
||||||
use crate::models::interaction::{InteractionDeliveryFailedDto, InteractionPayloadDto};
|
use crate::models::interaction::{InteractionDeliveryFailedDto, InteractionPayloadDto};
|
||||||
use crate::services::app_events::AppEvents;
|
use crate::services::app_events::{InteractionDeliveryFailed, InteractionReceived};
|
||||||
|
|
||||||
use super::{emitter, utils};
|
use super::{emitter, utils};
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ pub fn on_interaction_received(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<InteractionPayloadDto>(payload, "interaction-received")
|
utils::extract_and_parse::<InteractionPayloadDto>(payload, "interaction-received")
|
||||||
{
|
{
|
||||||
emitter::emit_to_frontend(AppEvents::InteractionReceived.as_str(), data);
|
emitter::emit_to_frontend_typed(&InteractionReceived(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +20,6 @@ pub fn on_interaction_delivery_failed(payload: Payload, _socket: RawClient) {
|
|||||||
payload,
|
payload,
|
||||||
"interaction-delivery-failed",
|
"interaction-delivery-failed",
|
||||||
) {
|
) {
|
||||||
emitter::emit_to_frontend(AppEvents::InteractionDeliveryFailed.as_str(), data);
|
emitter::emit_to_frontend_typed(&InteractionDeliveryFailed(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ pub mod client;
|
|||||||
// Re-export public API
|
// Re-export public API
|
||||||
pub use cursor::report_cursor_data;
|
pub use cursor::report_cursor_data;
|
||||||
pub use emitter::ws_emit_soft;
|
pub use emitter::ws_emit_soft;
|
||||||
|
pub use types::OutgoingFriendCursorPayload;
|
||||||
pub use types::WS_EVENT;
|
pub use types::WS_EVENT;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
|
|
||||||
/// WebSocket event constants
|
/// WebSocket event constants
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
@@ -37,7 +38,7 @@ pub struct IncomingFriendCursorPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Outgoing friend cursor position to frontend
|
/// Outgoing friend cursor position to frontend
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OutgoingFriendCursorPayload {
|
pub struct OutgoingFriendCursorPayload {
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use tauri_specta::Event as _;
|
||||||
use tauri::async_runtime::{self, JoinHandle};
|
use tauri::async_runtime::{self, JoinHandle};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
@@ -6,7 +7,7 @@ use tracing::warn;
|
|||||||
|
|
||||||
use crate::models::event_payloads::UserStatusPayload;
|
use crate::models::event_payloads::UserStatusPayload;
|
||||||
|
|
||||||
use crate::services::app_events::AppEvents;
|
use crate::services::app_events::UserStatusChanged;
|
||||||
|
|
||||||
use super::{emitter, types::WS_EVENT};
|
use super::{emitter, types::WS_EVENT};
|
||||||
|
|
||||||
@@ -23,7 +24,9 @@ pub async fn report_user_status(status: UserStatusPayload) {
|
|||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter::emit_to_frontend(AppEvents::UserStatusChanged.as_str(), &status);
|
if let Err(e) = UserStatusChanged(status.clone()).emit(crate::get_app_handle()) {
|
||||||
|
warn!("Failed to emit user-status-changed event: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule new report after 500ms
|
// Schedule new report after 500ms
|
||||||
let handle = async_runtime::spawn(async move {
|
let handle = async_runtime::spawn(async move {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
get_app_handle, lock_r, lock_w,
|
get_app_handle, lock_r, lock_w,
|
||||||
remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote},
|
remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote},
|
||||||
services::app_events::AppEvents,
|
services::app_events::AppDataRefreshed,
|
||||||
state::FDOLL,
|
state::FDOLL,
|
||||||
};
|
};
|
||||||
use std::{collections::HashSet, sync::LazyLock};
|
use std::{collections::HashSet, sync::LazyLock};
|
||||||
use tauri::Emitter;
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
use tauri_specta::Event as _;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
pub fn update_display_dimensions_for_scene_state() {
|
pub fn update_display_dimensions_for_scene_state() {
|
||||||
@@ -211,9 +211,7 @@ pub async fn init_app_data_scoped(scope: AppDataRefreshScope) {
|
|||||||
let app_data_clone = guard.user_data.clone();
|
let app_data_clone = guard.user_data.clone();
|
||||||
drop(guard); // Drop lock before emitting to prevent potential deadlocks
|
drop(guard); // Drop lock before emitting to prevent potential deadlocks
|
||||||
|
|
||||||
if let Err(e) =
|
if let Err(e) = AppDataRefreshed(app_data_clone).emit(get_app_handle()) {
|
||||||
get_app_handle().emit(AppEvents::AppDataRefreshed.as_str(), &app_data_clone)
|
|
||||||
{
|
|
||||||
warn!("Failed to emit app-data-refreshed event: {}", e);
|
warn!("Failed to emit app-data-refreshed event: {}", e);
|
||||||
use tauri_plugin_dialog::MessageDialogBuilder;
|
use tauri_plugin_dialog::MessageDialogBuilder;
|
||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { type UserData } from "../types/bindings/UserData";
|
import { commands, events, type UserData } from "$lib/bindings";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
|
||||||
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
||||||
|
|
||||||
export const appData = writable<UserData | null>(null);
|
export const appData = writable<UserData | null>(null);
|
||||||
@@ -16,13 +13,10 @@ const subscription = createListenerSubscription();
|
|||||||
export async function startAppData() {
|
export async function startAppData() {
|
||||||
try {
|
try {
|
||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
appData.set(await invoke("get_app_data"));
|
appData.set(await commands.getAppData());
|
||||||
const unlisten = await listen<UserData>(
|
const unlisten = await events.appDataRefreshed.listen((event) => {
|
||||||
AppEvents.AppDataRefreshed,
|
|
||||||
(event) => {
|
|
||||||
appData.set(event.payload);
|
appData.set(event.payload);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
subscription.setUnlisten(unlisten);
|
subscription.setUnlisten(unlisten);
|
||||||
subscription.setListening(true);
|
subscription.setListening(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { CursorPositions } from "../types/bindings/CursorPositions";
|
import { events, type CursorPositions } from "$lib/bindings";
|
||||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
|
||||||
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
||||||
|
|
||||||
export const cursorPositionOnScreen = writable<CursorPositions>({
|
export const cursorPositionOnScreen = writable<CursorPositions>({
|
||||||
@@ -20,12 +17,9 @@ export async function startCursorTracking() {
|
|||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const unlisten = await listen<CursorPositions>(
|
const unlisten = await events.cursorMoved.listen((event) => {
|
||||||
AppEvents.CursorPosition,
|
|
||||||
(event) => {
|
|
||||||
cursorPositionOnScreen.set(event.payload);
|
cursorPositionOnScreen.set(event.payload);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
subscription.setUnlisten(unlisten);
|
subscription.setUnlisten(unlisten);
|
||||||
subscription.setListening(true);
|
subscription.setListening(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { CursorPositions } from "../types/bindings/CursorPositions";
|
import {
|
||||||
import type { DollDto } from "../types/bindings/DollDto";
|
events,
|
||||||
import type { FriendDisconnectedPayload } from "../types/bindings/FriendDisconnectedPayload";
|
type CursorPositions,
|
||||||
import type { FriendActiveDollChangedPayload } from "../types/bindings/FriendActiveDollChangedPayload";
|
type DollDto,
|
||||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
type FriendActiveDollChangedPayload,
|
||||||
|
type FriendDisconnectedPayload,
|
||||||
|
type OutgoingFriendCursorPayload,
|
||||||
|
} from "$lib/bindings";
|
||||||
import {
|
import {
|
||||||
createMultiListenerSubscription,
|
createMultiListenerSubscription,
|
||||||
removeFromStore,
|
removeFromStore,
|
||||||
setupHmrCleanup,
|
setupHmrCleanup,
|
||||||
} from "./listener-utils";
|
} from "./listener-utils";
|
||||||
|
|
||||||
export type FriendCursorPosition = {
|
|
||||||
userId: string;
|
|
||||||
position: CursorPositions;
|
|
||||||
};
|
|
||||||
|
|
||||||
type FriendCursorData = {
|
type FriendCursorData = {
|
||||||
position: CursorPositions;
|
position: CursorPositions;
|
||||||
lastUpdated: number;
|
lastUpdated: number;
|
||||||
@@ -40,10 +37,9 @@ export async function startFriendCursorTracking() {
|
|||||||
try {
|
try {
|
||||||
// TODO: Add initial sync for existing friends' cursors and dolls if needed
|
// TODO: Add initial sync for existing friends' cursors and dolls if needed
|
||||||
|
|
||||||
const unlistenFriendCursor = await listen<FriendCursorPosition>(
|
const unlistenFriendCursor = await events.friendCursorPositionUpdated.listen(
|
||||||
AppEvents.FriendCursorPosition,
|
|
||||||
(event) => {
|
(event) => {
|
||||||
const data = event.payload;
|
const data: OutgoingFriendCursorPayload = event.payload;
|
||||||
|
|
||||||
friendCursorState[data.userId] = {
|
friendCursorState[data.userId] = {
|
||||||
position: data.position,
|
position: data.position,
|
||||||
@@ -60,8 +56,7 @@ export async function startFriendCursorTracking() {
|
|||||||
);
|
);
|
||||||
subscription.addUnlisten(unlistenFriendCursor);
|
subscription.addUnlisten(unlistenFriendCursor);
|
||||||
|
|
||||||
const unlistenFriendDisconnected = await listen<FriendDisconnectedPayload>(
|
const unlistenFriendDisconnected = await events.friendDisconnected.listen(
|
||||||
AppEvents.FriendDisconnected,
|
|
||||||
(event) => {
|
(event) => {
|
||||||
const data = event.payload;
|
const data = event.payload;
|
||||||
|
|
||||||
@@ -77,8 +72,7 @@ export async function startFriendCursorTracking() {
|
|||||||
subscription.addUnlisten(unlistenFriendDisconnected);
|
subscription.addUnlisten(unlistenFriendDisconnected);
|
||||||
|
|
||||||
const unlistenFriendActiveDollChanged =
|
const unlistenFriendActiveDollChanged =
|
||||||
await listen<FriendActiveDollChangedPayload>(
|
await events.friendActiveDollChanged.listen(
|
||||||
AppEvents.FriendActiveDollChanged,
|
|
||||||
(event) => {
|
(event) => {
|
||||||
const payload = event.payload;
|
const payload = event.payload;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { InteractionPayloadDto } from "../types/bindings/InteractionPayloadDto";
|
import {
|
||||||
import type { InteractionDeliveryFailedDto } from "../types/bindings/InteractionDeliveryFailedDto";
|
events,
|
||||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
type InteractionDeliveryFailedDto,
|
||||||
|
type InteractionPayloadDto,
|
||||||
|
} from "$lib/bindings";
|
||||||
import {
|
import {
|
||||||
createMultiListenerSubscription,
|
createMultiListenerSubscription,
|
||||||
setupHmrCleanup,
|
setupHmrCleanup,
|
||||||
@@ -37,16 +38,12 @@ export async function startInteraction() {
|
|||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const unlistenReceived = await listen<InteractionPayloadDto>(
|
const unlistenReceived = await events.interactionReceived.listen((event) => {
|
||||||
AppEvents.InteractionReceived,
|
|
||||||
(event) => {
|
|
||||||
addInteraction(event.payload);
|
addInteraction(event.payload);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
subscription.addUnlisten(unlistenReceived);
|
subscription.addUnlisten(unlistenReceived);
|
||||||
|
|
||||||
const unlistenFailed = await listen<InteractionDeliveryFailedDto>(
|
const unlistenFailed = await events.interactionDeliveryFailed.listen(
|
||||||
AppEvents.InteractionDeliveryFailed,
|
|
||||||
(event) => {
|
(event) => {
|
||||||
console.error("Interaction delivery failed:", event.payload);
|
console.error("Interaction delivery failed:", event.payload);
|
||||||
alert(
|
alert(
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
import { commands, events } from "$lib/bindings";
|
||||||
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
||||||
|
|
||||||
export const sceneInteractive = writable<boolean>(false);
|
export const sceneInteractive = writable<boolean>(false);
|
||||||
@@ -16,13 +14,10 @@ export async function startSceneInteractive() {
|
|||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sceneInteractive.set(await invoke("get_scene_interactive"));
|
sceneInteractive.set(await commands.getSceneInteractive());
|
||||||
const unlisten = await listen<boolean>(
|
const unlisten = await events.sceneInteractiveChanged.listen((event) => {
|
||||||
AppEvents.SceneInteractive,
|
|
||||||
(event) => {
|
|
||||||
sceneInteractive.set(Boolean(event.payload));
|
sceneInteractive.set(Boolean(event.payload));
|
||||||
},
|
});
|
||||||
);
|
|
||||||
subscription.setUnlisten(unlisten);
|
subscription.setUnlisten(unlisten);
|
||||||
subscription.setListening(true);
|
subscription.setListening(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { FriendDisconnectedPayload } from "../types/bindings/FriendDisconnectedPayload";
|
import {
|
||||||
import type { FriendUserStatusPayload } from "../types/bindings/FriendUserStatusPayload";
|
events,
|
||||||
import type { UserStatusPayload } from "../types/bindings/UserStatusPayload";
|
type FriendDisconnectedPayload,
|
||||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
type UserStatusPayload,
|
||||||
|
} from "$lib/bindings";
|
||||||
import {
|
import {
|
||||||
createMultiListenerSubscription,
|
createMultiListenerSubscription,
|
||||||
removeFromStore,
|
removeFromStore,
|
||||||
@@ -24,9 +24,7 @@ export async function startUserStatus() {
|
|||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const unlistenStatus = await listen<FriendUserStatusPayload>(
|
const unlistenStatus = await events.friendUserStatusChanged.listen((event) => {
|
||||||
AppEvents.FriendUserStatus,
|
|
||||||
(event) => {
|
|
||||||
const { userId, status } = event.payload;
|
const { userId, status } = event.payload;
|
||||||
|
|
||||||
const hasValidName =
|
const hasValidName =
|
||||||
@@ -40,20 +38,17 @@ export async function startUserStatus() {
|
|||||||
...current,
|
...current,
|
||||||
[userId]: status,
|
[userId]: status,
|
||||||
}));
|
}));
|
||||||
},
|
});
|
||||||
);
|
|
||||||
subscription.addUnlisten(unlistenStatus);
|
subscription.addUnlisten(unlistenStatus);
|
||||||
|
|
||||||
const unlistenUserStatusChanged = await listen<UserStatusPayload>(
|
const unlistenUserStatusChanged = await events.userStatusChanged.listen(
|
||||||
AppEvents.UserStatusChanged,
|
|
||||||
(event) => {
|
(event) => {
|
||||||
currentPresenceState.set(event.payload);
|
currentPresenceState.set(event.payload);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
subscription.addUnlisten(unlistenUserStatusChanged);
|
subscription.addUnlisten(unlistenUserStatusChanged);
|
||||||
|
|
||||||
const unlistenFriendDisconnected = await listen<FriendDisconnectedPayload>(
|
const unlistenFriendDisconnected = await events.friendDisconnected.listen(
|
||||||
AppEvents.FriendDisconnected,
|
|
||||||
(event) => {
|
(event) => {
|
||||||
const { userId } = event.payload;
|
const { userId } = event.payload;
|
||||||
friendsPresenceStates.update((current) =>
|
friendsPresenceStates.update((current) =>
|
||||||
|
|||||||
281
src/lib/bindings.ts
Normal file
281
src/lib/bindings.ts
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
|
||||||
|
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||||
|
|
||||||
|
/** user-defined commands **/
|
||||||
|
|
||||||
|
|
||||||
|
export const commands = {
|
||||||
|
async getAppData() : Promise<UserData> {
|
||||||
|
return await TAURI_INVOKE("get_app_data");
|
||||||
|
},
|
||||||
|
async refreshAppData() : Promise<UserData> {
|
||||||
|
return await TAURI_INVOKE("refresh_app_data");
|
||||||
|
},
|
||||||
|
async listFriends() : Promise<FriendshipResponseDto[]> {
|
||||||
|
return await TAURI_INVOKE("list_friends");
|
||||||
|
},
|
||||||
|
async searchUsers(username: string | null) : Promise<UserBasicDto[]> {
|
||||||
|
return await TAURI_INVOKE("search_users", { username });
|
||||||
|
},
|
||||||
|
async sendFriendRequest(request: SendFriendRequestDto) : Promise<FriendRequestResponseDto> {
|
||||||
|
return await TAURI_INVOKE("send_friend_request", { request });
|
||||||
|
},
|
||||||
|
async receivedFriendRequests() : Promise<FriendRequestResponseDto[]> {
|
||||||
|
return await TAURI_INVOKE("received_friend_requests");
|
||||||
|
},
|
||||||
|
async sentFriendRequests() : Promise<FriendRequestResponseDto[]> {
|
||||||
|
return await TAURI_INVOKE("sent_friend_requests");
|
||||||
|
},
|
||||||
|
async acceptFriendRequest(requestId: string) : Promise<FriendRequestResponseDto> {
|
||||||
|
return await TAURI_INVOKE("accept_friend_request", { requestId });
|
||||||
|
},
|
||||||
|
async denyFriendRequest(requestId: string) : Promise<FriendRequestResponseDto> {
|
||||||
|
return await TAURI_INVOKE("deny_friend_request", { requestId });
|
||||||
|
},
|
||||||
|
async unfriend(friendId: string) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("unfriend", { friendId });
|
||||||
|
},
|
||||||
|
async getDolls() : Promise<DollDto[]> {
|
||||||
|
return await TAURI_INVOKE("get_dolls");
|
||||||
|
},
|
||||||
|
async getDoll(id: string) : Promise<DollDto> {
|
||||||
|
return await TAURI_INVOKE("get_doll", { id });
|
||||||
|
},
|
||||||
|
async createDoll(dto: CreateDollDto) : Promise<DollDto> {
|
||||||
|
return await TAURI_INVOKE("create_doll", { dto });
|
||||||
|
},
|
||||||
|
async updateDoll(id: string, dto: UpdateDollDto) : Promise<DollDto> {
|
||||||
|
return await TAURI_INVOKE("update_doll", { id, dto });
|
||||||
|
},
|
||||||
|
async deleteDoll(id: string) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("delete_doll", { id });
|
||||||
|
},
|
||||||
|
async setActiveDoll(dollId: string) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("set_active_doll", { dollId });
|
||||||
|
},
|
||||||
|
async removeActiveDoll() : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("remove_active_doll");
|
||||||
|
},
|
||||||
|
async recolorGifBase64(whiteColorHex: string, blackColorHex: string, applyTexture: boolean) : Promise<string> {
|
||||||
|
return await TAURI_INVOKE("recolor_gif_base64", { whiteColorHex, blackColorHex, applyTexture });
|
||||||
|
},
|
||||||
|
async encodePetDollGifBase64(doll: DollDto) : Promise<string> {
|
||||||
|
return await TAURI_INVOKE("encode_pet_doll_gif_base64", { doll });
|
||||||
|
},
|
||||||
|
async quitApp() : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("quit_app");
|
||||||
|
},
|
||||||
|
async restartApp() : Promise<void> {
|
||||||
|
await TAURI_INVOKE("restart_app");
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Attempt to re-establish the user session without restarting the app.
|
||||||
|
*
|
||||||
|
* Validates server health, checks for a valid session token,
|
||||||
|
* then reconstructs the user session (re-fetches app data + WebSocket).
|
||||||
|
*/
|
||||||
|
async retryConnection() : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("retry_connection");
|
||||||
|
},
|
||||||
|
async getClientConfig() : Promise<AppConfig> {
|
||||||
|
return await TAURI_INVOKE("get_client_config");
|
||||||
|
},
|
||||||
|
async saveClientConfig(config: AppConfig) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("save_client_config", { config });
|
||||||
|
},
|
||||||
|
async openClientConfigManager() : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("open_client_config_manager");
|
||||||
|
},
|
||||||
|
async openDollEditorWindow(dollId: string | null) : Promise<void> {
|
||||||
|
await TAURI_INVOKE("open_doll_editor_window", { dollId });
|
||||||
|
},
|
||||||
|
async getSceneInteractive() : Promise<boolean> {
|
||||||
|
return await TAURI_INVOKE("get_scene_interactive");
|
||||||
|
},
|
||||||
|
async setSceneInteractive(interactive: boolean, shouldClick: boolean) : Promise<void> {
|
||||||
|
await TAURI_INVOKE("set_scene_interactive", { interactive, shouldClick });
|
||||||
|
},
|
||||||
|
async setPetMenuState(id: string, open: boolean) : Promise<void> {
|
||||||
|
await TAURI_INVOKE("set_pet_menu_state", { id, open });
|
||||||
|
},
|
||||||
|
async login(email: string, password: string) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("login", { email, password });
|
||||||
|
},
|
||||||
|
async register(email: string, password: string, name: string | null, username: string | null) : Promise<string> {
|
||||||
|
return await TAURI_INVOKE("register", { email, password, name, username });
|
||||||
|
},
|
||||||
|
async changePassword(currentPassword: string, newPassword: string) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("change_password", { currentPassword, newPassword });
|
||||||
|
},
|
||||||
|
async resetPassword(oldPassword: string, newPassword: string) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("reset_password", { oldPassword, newPassword });
|
||||||
|
},
|
||||||
|
async logoutAndRestart() : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("logout_and_restart");
|
||||||
|
},
|
||||||
|
async sendInteractionCmd(dto: SendInteractionDto) : Promise<null> {
|
||||||
|
return await TAURI_INVOKE("send_interaction_cmd", { dto });
|
||||||
|
},
|
||||||
|
async getModules() : Promise<ModuleMetadata[]> {
|
||||||
|
return await TAURI_INVOKE("get_modules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** user-defined events **/
|
||||||
|
|
||||||
|
|
||||||
|
export const events = __makeEvents__<{
|
||||||
|
appDataRefreshed: AppDataRefreshed,
|
||||||
|
createDoll: CreateDoll,
|
||||||
|
cursorMoved: CursorMoved,
|
||||||
|
editDoll: EditDoll,
|
||||||
|
friendActiveDollChanged: FriendActiveDollChanged,
|
||||||
|
friendCursorPositionUpdated: FriendCursorPositionUpdated,
|
||||||
|
friendDisconnected: FriendDisconnected,
|
||||||
|
friendRequestAccepted: FriendRequestAccepted,
|
||||||
|
friendRequestDenied: FriendRequestDenied,
|
||||||
|
friendRequestReceived: FriendRequestReceived,
|
||||||
|
friendUserStatusChanged: FriendUserStatusChanged,
|
||||||
|
interactionDeliveryFailed: InteractionDeliveryFailed,
|
||||||
|
interactionReceived: InteractionReceived,
|
||||||
|
sceneInteractiveChanged: SceneInteractiveChanged,
|
||||||
|
setInteractionOverlay: SetInteractionOverlay,
|
||||||
|
unfriended: Unfriended,
|
||||||
|
userStatusChanged: UserStatusChanged
|
||||||
|
}>({
|
||||||
|
appDataRefreshed: "app-data-refreshed",
|
||||||
|
createDoll: "create-doll",
|
||||||
|
cursorMoved: "cursor-moved",
|
||||||
|
editDoll: "edit-doll",
|
||||||
|
friendActiveDollChanged: "friend-active-doll-changed",
|
||||||
|
friendCursorPositionUpdated: "friend-cursor-position-updated",
|
||||||
|
friendDisconnected: "friend-disconnected",
|
||||||
|
friendRequestAccepted: "friend-request-accepted",
|
||||||
|
friendRequestDenied: "friend-request-denied",
|
||||||
|
friendRequestReceived: "friend-request-received",
|
||||||
|
friendUserStatusChanged: "friend-user-status-changed",
|
||||||
|
interactionDeliveryFailed: "interaction-delivery-failed",
|
||||||
|
interactionReceived: "interaction-received",
|
||||||
|
sceneInteractiveChanged: "scene-interactive-changed",
|
||||||
|
setInteractionOverlay: "set-interaction-overlay",
|
||||||
|
unfriended: "unfriended",
|
||||||
|
userStatusChanged: "user-status-changed"
|
||||||
|
})
|
||||||
|
|
||||||
|
/** user-defined constants **/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** user-defined types **/
|
||||||
|
|
||||||
|
export type AppConfig = { api_base_url: string | null }
|
||||||
|
export type AppDataRefreshed = UserData
|
||||||
|
export type CreateDoll = null
|
||||||
|
export type CreateDollDto = { name: string; configuration: DollConfigurationDto | null }
|
||||||
|
export type CursorMoved = CursorPositions
|
||||||
|
export type CursorPosition = { x: number; y: number }
|
||||||
|
export type CursorPositions = { raw: CursorPosition; mapped: CursorPosition }
|
||||||
|
export type DisplayData = { screen_width: number; screen_height: number; monitor_scale_factor: number }
|
||||||
|
export type DollColorSchemeDto = { outline: string; body: string }
|
||||||
|
export type DollConfigurationDto = { colorScheme: DollColorSchemeDto }
|
||||||
|
export type DollDto = { id: string; name: string; configuration: DollConfigurationDto; createdAt: string; updatedAt: string }
|
||||||
|
export type EditDoll = string
|
||||||
|
export type FriendActiveDollChanged = FriendActiveDollChangedPayload
|
||||||
|
export type FriendActiveDollChangedPayload = { friendId: string; doll: DollDto | null }
|
||||||
|
export type FriendCursorPositionUpdated = OutgoingFriendCursorPayload
|
||||||
|
export type FriendDisconnected = FriendDisconnectedPayload
|
||||||
|
export type FriendDisconnectedPayload = { userId: string }
|
||||||
|
export type FriendRequestAccepted = FriendRequestAcceptedPayload
|
||||||
|
export type FriendRequestAcceptedPayload = { id: string; friend: UserBasicDto; acceptedAt: string }
|
||||||
|
export type FriendRequestDenied = FriendRequestDeniedPayload
|
||||||
|
export type FriendRequestDeniedPayload = { id: string; denier: UserBasicDto; deniedAt: string }
|
||||||
|
export type FriendRequestReceived = FriendRequestReceivedPayload
|
||||||
|
export type FriendRequestReceivedPayload = { id: string; sender: UserBasicDto; createdAt: string }
|
||||||
|
export type FriendRequestResponseDto = { id: string; sender: UserBasicDto; receiver: UserBasicDto; status: string; createdAt: string; updatedAt: string }
|
||||||
|
export type FriendUserStatusChanged = FriendUserStatusPayload
|
||||||
|
export type FriendUserStatusPayload = { userId: string; status: UserStatusPayload }
|
||||||
|
export type FriendshipResponseDto = { id: string; friend: UserBasicDto | null; createdAt: string }
|
||||||
|
export type InteractionDeliveryFailed = InteractionDeliveryFailedDto
|
||||||
|
export type InteractionDeliveryFailedDto = { recipientUserId: string; reason: string }
|
||||||
|
export type InteractionPayloadDto = { senderUserId: string; senderName: string; content: string; type: string; timestamp: string }
|
||||||
|
export type InteractionReceived = InteractionPayloadDto
|
||||||
|
export type ModuleMetadata = { id: string; name: string; version: string; description: string | null }
|
||||||
|
/**
|
||||||
|
* Outgoing friend cursor position to frontend
|
||||||
|
*/
|
||||||
|
export type OutgoingFriendCursorPayload = { userId: string; position: CursorPositions }
|
||||||
|
export type PresenceStatus = { title: string | null; subtitle: string | null; graphicsB64: string | null }
|
||||||
|
export type SceneData = { display: DisplayData; grid_size: number }
|
||||||
|
export type SceneInteractiveChanged = boolean
|
||||||
|
export type SendFriendRequestDto = { receiverId: string }
|
||||||
|
export type SendInteractionDto = { recipientUserId: string; content: string; type: string }
|
||||||
|
export type SetInteractionOverlay = boolean
|
||||||
|
export type Unfriended = UnfriendedPayload
|
||||||
|
export type UnfriendedPayload = { friendId: string }
|
||||||
|
export type UpdateDollDto = { name: string | null; configuration: DollConfigurationDto | null }
|
||||||
|
export type UserBasicDto = { id: string; name: string; username: string | null; activeDoll: DollDto | null }
|
||||||
|
export type UserData = { user: UserProfile | null; friends: FriendshipResponseDto[] | null; dolls: DollDto[] | null; scene: SceneData }
|
||||||
|
export type UserProfile = { id: string; name: string; email: string; username: string | null; roles: string[]; createdAt: string; updatedAt: string; lastLoginAt: string | null; activeDollId: string | null }
|
||||||
|
export type UserStatusChanged = UserStatusPayload
|
||||||
|
export type UserStatusPayload = { presenceStatus: PresenceStatus; state: UserStatusState }
|
||||||
|
export type UserStatusState = "idle" | "resting"
|
||||||
|
|
||||||
|
/** tauri-specta globals **/
|
||||||
|
|
||||||
|
import {
|
||||||
|
invoke as TAURI_INVOKE,
|
||||||
|
Channel as TAURI_CHANNEL,
|
||||||
|
} from "@tauri-apps/api/core";
|
||||||
|
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
|
||||||
|
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
|
||||||
|
|
||||||
|
type __EventObj__<T> = {
|
||||||
|
listen: (
|
||||||
|
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||||
|
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
||||||
|
once: (
|
||||||
|
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||||
|
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
||||||
|
emit: null extends T
|
||||||
|
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||||
|
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Result<T, E> =
|
||||||
|
| { status: "ok"; data: T }
|
||||||
|
| { status: "error"; error: E };
|
||||||
|
|
||||||
|
function __makeEvents__<T extends Record<string, any>>(
|
||||||
|
mappings: Record<keyof T, string>,
|
||||||
|
) {
|
||||||
|
return new Proxy(
|
||||||
|
{} as unknown as {
|
||||||
|
[K in keyof T]: __EventObj__<T[K]> & {
|
||||||
|
(handle: __WebviewWindow__): __EventObj__<T[K]>;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get: (_, event) => {
|
||||||
|
const name = mappings[event as keyof T];
|
||||||
|
|
||||||
|
return new Proxy((() => {}) as any, {
|
||||||
|
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
||||||
|
listen: (arg: any) => window.listen(name, arg),
|
||||||
|
once: (arg: any) => window.once(name, arg),
|
||||||
|
emit: (arg: any) => window.emit(name, arg),
|
||||||
|
}),
|
||||||
|
get: (_, command: keyof __EventObj__<any>) => {
|
||||||
|
switch (command) {
|
||||||
|
case "listen":
|
||||||
|
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
|
||||||
|
case "once":
|
||||||
|
return (arg: any) => TAURI_API_EVENT.once(name, arg);
|
||||||
|
case "emit":
|
||||||
|
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "$lib/bindings";
|
||||||
import onekoGif from "../../assets/oneko/oneko.gif";
|
import onekoGif from "../../assets/oneko/oneko.gif";
|
||||||
|
|
||||||
export interface RecolorOptions {
|
export interface RecolorOptions {
|
||||||
@@ -15,11 +15,11 @@ export async function getSpriteSheetUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await invoke<string>("recolor_gif_base64", {
|
const result = await commands.recolorGifBase64(
|
||||||
whiteColorHex: options.bodyColor,
|
options.bodyColor,
|
||||||
blackColorHex: options.outlineColor,
|
options.outlineColor,
|
||||||
applyTexture: options.applyTexture ?? true,
|
options.applyTexture ?? true,
|
||||||
});
|
);
|
||||||
return `data:image/gif;base64,${result}`;
|
return `data:image/gif;base64,${result}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to recolor sprite:", e);
|
console.error("Failed to recolor sprite:", e);
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
import Preferences from "./tabs/preferences.svelte";
|
import Preferences from "./tabs/preferences.svelte";
|
||||||
import Modules from "./tabs/modules.svelte";
|
import Modules from "./tabs/modules.svelte";
|
||||||
import YourDolls from "./tabs/your-dolls/index.svelte";
|
import YourDolls from "./tabs/your-dolls/index.svelte";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { events } from "$lib/bindings";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { AppEvents } from "../../types/bindings/AppEventsConstants";
|
|
||||||
|
|
||||||
let showInteractionOverlay = false;
|
let showInteractionOverlay = false;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const unlisten = listen(AppEvents.SetInteractionOverlay, (event) => {
|
const unlisten = events.setInteractionOverlay.listen((event) => {
|
||||||
showInteractionOverlay = event.payload as boolean;
|
showInteractionOverlay = event.payload as boolean;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import {
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
commands,
|
||||||
|
events,
|
||||||
|
type FriendRequestResponseDto,
|
||||||
|
type FriendshipResponseDto,
|
||||||
|
type UserBasicDto,
|
||||||
|
} from "$lib/bindings";
|
||||||
import { appData } from "../../../events/app-data";
|
import { appData } from "../../../events/app-data";
|
||||||
import { AppEvents } from "../../../types/bindings/AppEventsConstants";
|
|
||||||
import type { FriendRequestResponseDto } from "../../../types/bindings/FriendRequestResponseDto.js";
|
|
||||||
import type { FriendshipResponseDto } from "../../../types/bindings/FriendshipResponseDto.js";
|
|
||||||
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto.js";
|
|
||||||
|
|
||||||
let received: FriendRequestResponseDto[] = [];
|
let received: FriendRequestResponseDto[] = [];
|
||||||
let sent: FriendRequestResponseDto[] = [];
|
let sent: FriendRequestResponseDto[] = [];
|
||||||
@@ -51,27 +52,27 @@
|
|||||||
refreshSent();
|
refreshSent();
|
||||||
|
|
||||||
unlisteners.push(
|
unlisteners.push(
|
||||||
await listen(AppEvents.FriendRequestReceived, () => {
|
await events.friendRequestReceived.listen(() => {
|
||||||
refreshReceived();
|
refreshReceived();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
unlisteners.push(
|
unlisteners.push(
|
||||||
await listen(AppEvents.FriendRequestAccepted, () => {
|
await events.friendRequestAccepted.listen(() => {
|
||||||
refreshSent();
|
refreshSent();
|
||||||
invoke("refresh_app_data");
|
commands.refreshAppData();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
unlisteners.push(
|
unlisteners.push(
|
||||||
await listen(AppEvents.FriendRequestDenied, () => {
|
await events.friendRequestDenied.listen(() => {
|
||||||
refreshSent();
|
refreshSent();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
unlisteners.push(
|
unlisteners.push(
|
||||||
await listen(AppEvents.Unfriended, () => {
|
await events.unfriended.listen(() => {
|
||||||
invoke("refresh_app_data");
|
commands.refreshAppData();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
async function refreshReceived() {
|
async function refreshReceived() {
|
||||||
loading.received = true;
|
loading.received = true;
|
||||||
try {
|
try {
|
||||||
received = await invoke("received_friend_requests");
|
received = await commands.receivedFriendRequests();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
async function refreshSent() {
|
async function refreshSent() {
|
||||||
loading.sent = true;
|
loading.sent = true;
|
||||||
try {
|
try {
|
||||||
sent = await invoke("sent_friend_requests");
|
sent = await commands.sentFriendRequests();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -105,8 +106,8 @@
|
|||||||
async function handleAccept(id: string) {
|
async function handleAccept(id: string) {
|
||||||
loading.action = true;
|
loading.action = true;
|
||||||
try {
|
try {
|
||||||
await invoke("accept_friend_request", { requestId: id });
|
await commands.acceptFriendRequest(id);
|
||||||
await Promise.all([refreshReceived(), invoke("refresh_app_data")]);
|
await Promise.all([refreshReceived(), commands.refreshAppData()]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -117,7 +118,7 @@
|
|||||||
async function handleDeny(id: string) {
|
async function handleDeny(id: string) {
|
||||||
loading.action = true;
|
loading.action = true;
|
||||||
try {
|
try {
|
||||||
await invoke("deny_friend_request", { requestId: id });
|
await commands.denyFriendRequest(id);
|
||||||
await refreshReceived();
|
await refreshReceived();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
@@ -129,8 +130,8 @@
|
|||||||
async function handleUnfriend(friendId: string) {
|
async function handleUnfriend(friendId: string) {
|
||||||
loading.action = true;
|
loading.action = true;
|
||||||
try {
|
try {
|
||||||
await invoke("unfriend", { friendId });
|
await commands.unfriend(friendId);
|
||||||
await invoke("refresh_app_data");
|
await commands.refreshAppData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -157,9 +158,7 @@
|
|||||||
error = null;
|
error = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await invoke<UserBasicDto[]>("search_users", {
|
const results = await commands.searchUsers(sanitizedTerm);
|
||||||
username: sanitizedTerm,
|
|
||||||
});
|
|
||||||
const match = results.find(
|
const match = results.find(
|
||||||
(user) => user.username?.toLowerCase() === normalizedTerm,
|
(user) => user.username?.toLowerCase() === normalizedTerm,
|
||||||
);
|
);
|
||||||
@@ -181,9 +180,7 @@
|
|||||||
async function handleSendRequest(receiverId: string) {
|
async function handleSendRequest(receiverId: string) {
|
||||||
loading.action = true;
|
loading.action = true;
|
||||||
try {
|
try {
|
||||||
await invoke("send_friend_request", {
|
await commands.sendFriendRequest({ receiverId });
|
||||||
request: { receiverId },
|
|
||||||
});
|
|
||||||
await refreshSent();
|
await refreshSent();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = (e as Error)?.message ?? String(e);
|
const msg = (e as Error)?.message ?? String(e);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands, type ModuleMetadata } from "$lib/bindings";
|
||||||
import type { ModuleMetadata } from "../../../types/bindings/ModuleMetadata";
|
|
||||||
|
|
||||||
let modules: ModuleMetadata[] = [];
|
let modules: ModuleMetadata[] = [];
|
||||||
let loading = false;
|
let loading = false;
|
||||||
@@ -10,7 +9,7 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
modules = await invoke("get_modules");
|
modules = await commands.getModules();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "$lib/bindings";
|
||||||
import { appData } from "../../../events/app-data";
|
import { appData } from "../../../events/app-data";
|
||||||
import Power from "../../../assets/icons/power.svelte";
|
import Power from "../../../assets/icons/power.svelte";
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
if (signingOut) return;
|
if (signingOut) return;
|
||||||
signingOut = true;
|
signingOut = true;
|
||||||
try {
|
try {
|
||||||
await invoke("logout_and_restart");
|
await commands.logoutAndRestart();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to sign out", error);
|
console.error("Failed to sign out", error);
|
||||||
signingOut = false;
|
signingOut = false;
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
const openClientConfigManager = async () => {
|
const openClientConfigManager = async () => {
|
||||||
try {
|
try {
|
||||||
await invoke("open_client_config_manager");
|
await commands.openClientConfigManager();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to open client config manager", error);
|
console.error("Failed to open client config manager", error);
|
||||||
}
|
}
|
||||||
@@ -49,10 +49,10 @@
|
|||||||
|
|
||||||
isChangingPassword = true;
|
isChangingPassword = true;
|
||||||
try {
|
try {
|
||||||
await invoke("change_password", {
|
await commands.changePassword(
|
||||||
currentPassword: passwordForm.currentPassword,
|
passwordForm.currentPassword,
|
||||||
newPassword: passwordForm.newPassword,
|
passwordForm.newPassword,
|
||||||
});
|
);
|
||||||
passwordSuccess = "Password updated";
|
passwordSuccess = "Password updated";
|
||||||
passwordForm.currentPassword = "";
|
passwordForm.currentPassword = "";
|
||||||
passwordForm.newPassword = "";
|
passwordForm.newPassword = "";
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
<button
|
<button
|
||||||
class="btn btn-error btn-square btn-soft"
|
class="btn btn-error btn-square btn-soft"
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
await invoke("quit_app");
|
await commands.quitApp();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="scale-50">
|
<div class="scale-50">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { DollDto } from "../../../../types/bindings/DollDto";
|
import type { DollDto, UserProfile } from "$lib/bindings";
|
||||||
import type { UserProfile } from "../../../../types/bindings/UserProfile";
|
|
||||||
import DollPreview from "../../components/doll-preview.svelte";
|
import DollPreview from "../../components/doll-preview.svelte";
|
||||||
import PawPrint from "../../../../assets/icons/paw-print.svelte";
|
import PawPrint from "../../../../assets/icons/paw-print.svelte";
|
||||||
import Backpack from "../../../../assets/icons/backpack.svelte";
|
import Backpack from "../../../../assets/icons/backpack.svelte";
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands, type DollDto, type UserProfile } from "$lib/bindings";
|
||||||
import { appData } from "../../../../events/app-data";
|
import { appData } from "../../../../events/app-data";
|
||||||
import type { DollDto } from "../../../../types/bindings/DollDto";
|
|
||||||
import type { UserProfile } from "../../../../types/bindings/UserProfile";
|
|
||||||
import DollsList from "./dolls-list.svelte";
|
import DollsList from "./dolls-list.svelte";
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
@@ -16,17 +14,17 @@
|
|||||||
$: initialLoading = $appData === null;
|
$: initialLoading = $appData === null;
|
||||||
|
|
||||||
async function openCreateModal() {
|
async function openCreateModal() {
|
||||||
await invoke("open_doll_editor_window", { dollId: null });
|
await commands.openDollEditorWindow(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openEditModal(doll: DollDto) {
|
async function openEditModal(doll: DollDto) {
|
||||||
await invoke("open_doll_editor_window", { dollId: doll.id });
|
await commands.openDollEditorWindow(doll.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSetActiveDoll(dollId: string) {
|
async function handleSetActiveDoll(dollId: string) {
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
await invoke("set_active_doll", { dollId });
|
await commands.setActiveDoll(dollId);
|
||||||
// No manual refresh needed - backend will refresh and emit app-data-refreshed
|
// No manual refresh needed - backend will refresh and emit app-data-refreshed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
@@ -38,7 +36,7 @@
|
|||||||
async function handleRemoveActiveDoll() {
|
async function handleRemoveActiveDoll() {
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
await invoke("remove_active_doll");
|
await commands.removeActiveDoll();
|
||||||
// No manual refresh needed - backend will refresh and emit app-data-refreshed
|
// No manual refresh needed - backend will refresh and emit app-data-refreshed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = (e as Error)?.message ?? String(e);
|
error = (e as Error)?.message ?? String(e);
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands, type AppConfig } from "$lib/bindings";
|
||||||
|
|
||||||
type AppConfig = {
|
|
||||||
api_base_url?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
let form: AppConfig = {
|
let form: AppConfig = {
|
||||||
api_base_url: "",
|
api_base_url: "",
|
||||||
@@ -17,7 +13,7 @@
|
|||||||
|
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const config = (await invoke("get_client_config")) as AppConfig;
|
const config = await commands.getClientConfig();
|
||||||
form = {
|
form = {
|
||||||
api_base_url: config.api_base_url ?? "",
|
api_base_url: config.api_base_url ?? "",
|
||||||
};
|
};
|
||||||
@@ -55,10 +51,8 @@
|
|||||||
successMessage = "";
|
successMessage = "";
|
||||||
restartError = "";
|
restartError = "";
|
||||||
try {
|
try {
|
||||||
await invoke("save_client_config", {
|
await commands.saveClientConfig({
|
||||||
config: {
|
|
||||||
api_base_url: form.api_base_url?.trim() || null,
|
api_base_url: form.api_base_url?.trim() || null,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
successMessage = "Success. Restart to apply changes.";
|
successMessage = "Success. Restart to apply changes.";
|
||||||
@@ -72,7 +66,7 @@
|
|||||||
const restart = async () => {
|
const restart = async () => {
|
||||||
restartError = "";
|
restartError = "";
|
||||||
try {
|
try {
|
||||||
await invoke("restart_app");
|
await commands.restartApp();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
restartError = `Restart failed: ${err}`;
|
restartError = `Restart failed: ${err}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import {
|
||||||
|
commands,
|
||||||
|
type CreateDollDto,
|
||||||
|
type DollDto,
|
||||||
|
type UpdateDollDto,
|
||||||
|
} from "$lib/bindings";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
import type { DollDto } from "../../types/bindings/DollDto";
|
|
||||||
import type { CreateDollDto } from "../../types/bindings/CreateDollDto";
|
|
||||||
import type { UpdateDollDto } from "../../types/bindings/UpdateDollDto";
|
|
||||||
import DollPreview from "../app-menu/components/doll-preview.svelte";
|
import DollPreview from "../app-menu/components/doll-preview.svelte";
|
||||||
|
|
||||||
let mode: "create" | "edit" = "create";
|
let mode: "create" | "edit" = "create";
|
||||||
@@ -35,7 +37,7 @@
|
|||||||
async function fetchDoll(id: string) {
|
async function fetchDoll(id: string) {
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
const doll: DollDto = await invoke("get_doll", { id });
|
const doll: DollDto = await commands.getDoll(id);
|
||||||
name = doll.name;
|
name = doll.name;
|
||||||
bodyColor = doll.configuration.colorScheme.body;
|
bodyColor = doll.configuration.colorScheme.body;
|
||||||
outlineColor = doll.configuration.colorScheme.outline;
|
outlineColor = doll.configuration.colorScheme.outline;
|
||||||
@@ -62,7 +64,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await invoke("create_doll", { dto });
|
await commands.createDoll(dto);
|
||||||
} else if (dollId) {
|
} else if (dollId) {
|
||||||
const dto: UpdateDollDto = {
|
const dto: UpdateDollDto = {
|
||||||
name,
|
name,
|
||||||
@@ -73,7 +75,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await invoke("update_doll", { id: dollId, dto });
|
await commands.updateDoll(dollId, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close window on success
|
// Close window on success
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "$lib/bindings";
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
|
|
||||||
let errorMessage = "";
|
let errorMessage = "";
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
isRetrying = true;
|
isRetrying = true;
|
||||||
errorMessage = "";
|
errorMessage = "";
|
||||||
try {
|
try {
|
||||||
await invoke("retry_connection");
|
await commands.retryConnection();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMessage = `${err}`;
|
errorMessage = `${err}`;
|
||||||
isRetrying = false;
|
isRetrying = false;
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
class="btn btn-outline"
|
class="btn btn-outline"
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
try {
|
try {
|
||||||
await invoke("open_client_config_manager");
|
await commands.openClientConfigManager();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMessage = `Failed to open config manager: ${err}`;
|
errorMessage = `Failed to open config manager: ${err}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
friendsPresenceStates,
|
friendsPresenceStates,
|
||||||
currentPresenceState,
|
currentPresenceState,
|
||||||
} from "../../events/user-status";
|
} from "../../events/user-status";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "$lib/bindings";
|
||||||
import DebugBar from "./components/debug-bar.svelte";
|
import DebugBar from "./components/debug-bar.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -16,10 +16,7 @@
|
|||||||
class="absolute inset-0 z-10 size-full"
|
class="absolute inset-0 z-10 size-full"
|
||||||
aria-label="Deactive scene interactive"
|
aria-label="Deactive scene interactive"
|
||||||
onmousedown={async () => {
|
onmousedown={async () => {
|
||||||
await invoke("set_scene_interactive", {
|
await commands.setSceneInteractive(false, true);
|
||||||
interactive: false,
|
|
||||||
shouldClick: true,
|
|
||||||
});
|
|
||||||
}}> </button
|
}}> </button
|
||||||
>
|
>
|
||||||
<div id="debug-bar">
|
<div id="debug-bar">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PresenceStatus } from "../../../types/bindings/PresenceStatus";
|
import type { PresenceStatus, UserStatusPayload } from "$lib/bindings";
|
||||||
import type { UserStatusPayload } from "../../../types/bindings/UserStatusPayload";
|
|
||||||
|
|
||||||
interface Friend {
|
interface Friend {
|
||||||
friend?: {
|
friend?: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { commands } from "$lib/bindings";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
import DollPreview from "../app-menu/components/doll-preview.svelte";
|
import DollPreview from "../app-menu/components/doll-preview.svelte";
|
||||||
import ExternalLink from "../../assets/icons/external-link.svelte";
|
import ExternalLink from "../../assets/icons/external-link.svelte";
|
||||||
@@ -31,22 +31,19 @@
|
|||||||
errorMessage = "";
|
errorMessage = "";
|
||||||
try {
|
try {
|
||||||
if (useRegister) {
|
if (useRegister) {
|
||||||
await invoke("register", {
|
await commands.register(
|
||||||
email: form.email.trim(),
|
form.email.trim(),
|
||||||
password: form.password,
|
form.password,
|
||||||
name: form.name.trim() || null,
|
form.name.trim() || null,
|
||||||
username: form.username.trim() || null,
|
form.username.trim() || null,
|
||||||
});
|
);
|
||||||
useRegister = false;
|
useRegister = false;
|
||||||
resetRegisterFields();
|
resetRegisterFields();
|
||||||
form.password = "";
|
form.password = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await invoke("login", {
|
await commands.login(form.email.trim(), form.password);
|
||||||
email: form.email.trim(),
|
|
||||||
password: form.password,
|
|
||||||
});
|
|
||||||
await getCurrentWebviewWindow().close();
|
await getCurrentWebviewWindow().close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to authenticate", error);
|
console.error("Failed to authenticate", error);
|
||||||
@@ -62,7 +59,7 @@
|
|||||||
|
|
||||||
const openClientConfigManager = async () => {
|
const openClientConfigManager = async () => {
|
||||||
try {
|
try {
|
||||||
await invoke("open_client_config_manager");
|
await commands.openClientConfigManager();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to open client config manager", error);
|
console.error("Failed to open client config manager", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
// 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" | "friend-cursor-position" | "friend-disconnected" | "friend-active-doll-changed" | "friend-user-status" | "interaction-received" | "interaction-delivery-failed" | "friend-request-received" | "friend-request-accepted" | "friend-request-denied" | "unfriended";
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// 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",
|
|
||||||
FriendCursorPosition: "friend-cursor-position",
|
|
||||||
FriendDisconnected: "friend-disconnected",
|
|
||||||
FriendActiveDollChanged: "friend-active-doll-changed",
|
|
||||||
FriendUserStatus: "friend-user-status",
|
|
||||||
InteractionReceived: "interaction-received",
|
|
||||||
InteractionDeliveryFailed: "interaction-delivery-failed",
|
|
||||||
FriendRequestReceived: "friend-request-received",
|
|
||||||
FriendRequestAccepted: "friend-request-accepted",
|
|
||||||
FriendRequestDenied: "friend-request-denied",
|
|
||||||
Unfriended: "unfriended",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type AppEvents = typeof AppEvents[keyof typeof AppEvents];
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DollConfigurationDto } from "./DollConfigurationDto";
|
|
||||||
|
|
||||||
export type CreateDollDto = { name: string, configuration: DollConfigurationDto | null, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type CursorPosition = { x: number, y: number, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { CursorPosition } from "./CursorPosition";
|
|
||||||
|
|
||||||
export type CursorPositions = { raw: CursorPosition, mapped: CursorPosition, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type DisplayData = { screen_width: number, screen_height: number, monitor_scale_factor: number, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type DollColorSchemeDto = { outline: string, body: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DollColorSchemeDto } from "./DollColorSchemeDto";
|
|
||||||
|
|
||||||
export type DollConfigurationDto = { colorScheme: DollColorSchemeDto, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DollConfigurationDto } from "./DollConfigurationDto";
|
|
||||||
|
|
||||||
export type DollDto = { id: string, name: string, configuration: DollConfigurationDto, createdAt: string, updatedAt: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DollDto } from "./DollDto";
|
|
||||||
|
|
||||||
export type FriendActiveDollChangedPayload = { friendId: string, doll: DollDto | null, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type FriendDisconnectedPayload = { userId: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { UserBasicDto } from "./UserBasicDto";
|
|
||||||
|
|
||||||
export type FriendRequestAcceptedPayload = { id: string, friend: UserBasicDto, acceptedAt: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { UserBasicDto } from "./UserBasicDto";
|
|
||||||
|
|
||||||
export type FriendRequestDeniedPayload = { id: string, denier: UserBasicDto, deniedAt: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { UserBasicDto } from "./UserBasicDto";
|
|
||||||
|
|
||||||
export type FriendRequestReceivedPayload = { id: string, sender: UserBasicDto, createdAt: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { UserBasicDto } from "./UserBasicDto";
|
|
||||||
|
|
||||||
export type FriendRequestResponseDto = { id: string, sender: UserBasicDto, receiver: UserBasicDto, status: string, createdAt: string, updatedAt: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { UserStatusPayload } from "./UserStatusPayload";
|
|
||||||
|
|
||||||
export type FriendUserStatusPayload = { userId: string, status: UserStatusPayload, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { UserBasicDto } from "./UserBasicDto";
|
|
||||||
|
|
||||||
export type FriendshipResponseDto = { id: string, friend: UserBasicDto | null, createdAt: string, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type HealthResponseDto = { status: string, version: string, uptimeSecs: bigint, db: string, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type InteractionDeliveryFailedDto = { recipientUserId: string, reason: string, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type InteractionPayloadDto = { senderUserId: string, senderName: string, content: string, type: string, timestamp: string, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type ModuleMetadata = { id: string, name: string, version: string, description: string | null, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type PresenceStatus = { title: string | null, subtitle: string | null, graphicsB64: string | null, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DisplayData } from "./DisplayData";
|
|
||||||
|
|
||||||
export type SceneData = { display: DisplayData, grid_size: number, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type SendFriendRequestDto = { receiverId: string, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type SendInteractionDto = { recipientUserId: string, content: string, type: string, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type UnfriendedPayload = { friendId: string, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DollConfigurationDto } from "./DollConfigurationDto";
|
|
||||||
|
|
||||||
export type UpdateDollDto = { name: string | null, configuration: DollConfigurationDto | null, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DollDto } from "./DollDto";
|
|
||||||
|
|
||||||
export type UserBasicDto = { id: string, name: string, username: string | null, activeDoll: DollDto | null, };
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DollDto } from "./DollDto";
|
|
||||||
import type { FriendshipResponseDto } from "./FriendshipResponseDto";
|
|
||||||
import type { SceneData } from "./SceneData";
|
|
||||||
import type { UserProfile } from "./UserProfile";
|
|
||||||
|
|
||||||
export type UserData = { user: UserProfile | null, friends: Array<FriendshipResponseDto> | null, dolls: Array<DollDto> | null, scene: SceneData, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type UserProfile = { id: string, name: string, email: string, username: string | null, roles: Array<string>, createdAt: string, updatedAt: string, lastLoginAt: string | null, activeDollId: string | null, };
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { PresenceStatus } from "./PresenceStatus";
|
|
||||||
import type { UserStatusState } from "./UserStatusState";
|
|
||||||
|
|
||||||
export type UserStatusPayload = { presenceStatus: PresenceStatus, state: UserStatusState, };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
|
|
||||||
export type UserStatusState = "idle" | "resting";
|
|
||||||
Reference in New Issue
Block a user