petpet headpat init
This commit is contained in:
93
src-tauri/Cargo.lock
generated
93
src-tauri/Cargo.lock
generated
@@ -53,6 +53,19 @@ version = "1.0.100"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "apng"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a79142fc4e25db11e703bf09431aad1488d65d2e1bb4d0f664a8f8af000054c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"flate2",
|
||||||
|
"image",
|
||||||
|
"png",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ashpd"
|
name = "ashpd"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -389,12 +402,6 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder-lite"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -1203,7 +1210,7 @@ dependencies = [
|
|||||||
"dotenvy",
|
"dotenvy",
|
||||||
"enigo",
|
"enigo",
|
||||||
"flate2",
|
"flate2",
|
||||||
"gif",
|
"gif 0.14.1",
|
||||||
"image",
|
"image",
|
||||||
"keyring",
|
"keyring",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -1212,6 +1219,7 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"petpet",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -1495,6 +1503,16 @@ dependencies = [
|
|||||||
"wasip2",
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gif"
|
||||||
|
version = "0.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
|
||||||
|
dependencies = [
|
||||||
|
"color_quant",
|
||||||
|
"weezl",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gif"
|
name = "gif"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@@ -1889,7 +1907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
|
checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"png 0.17.16",
|
"png",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2002,17 +2020,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.9"
|
version = "0.24.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder",
|
||||||
"color_quant",
|
"color_quant",
|
||||||
"gif",
|
"gif 0.13.3",
|
||||||
"moxcms",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png 0.18.0",
|
"png",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2464,16 +2481,6 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "moxcms"
|
|
||||||
version = "0.7.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
"pxfm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "muda"
|
name = "muda"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
@@ -2489,7 +2496,7 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.17.16",
|
"png",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
@@ -3039,6 +3046,16 @@ version = "2.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petpet"
|
||||||
|
version = "2.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9723170e1a50808a2a790e96a36e23762b30c318861c0acf4b4ab51d8944f924"
|
||||||
|
dependencies = [
|
||||||
|
"apng",
|
||||||
|
"image",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -3228,19 +3245,6 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "png"
|
|
||||||
version = "0.18.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"crc32fast",
|
|
||||||
"fdeflate",
|
|
||||||
"flate2",
|
|
||||||
"miniz_oxide",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polling"
|
name = "polling"
|
||||||
version = "3.11.0"
|
version = "3.11.0"
|
||||||
@@ -3352,15 +3356,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pxfm"
|
|
||||||
version = "0.1.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.38.4"
|
version = "0.38.4"
|
||||||
@@ -4518,7 +4513,7 @@ dependencies = [
|
|||||||
"ico",
|
"ico",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"plist",
|
"plist",
|
||||||
"png 0.17.16",
|
"png",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"semver",
|
"semver",
|
||||||
@@ -5195,7 +5190,7 @@ dependencies = [
|
|||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.17.16",
|
"png",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
|
|||||||
@@ -40,12 +40,13 @@ flate2 = "1.0.28"
|
|||||||
rust_socketio = "0.6.0"
|
rust_socketio = "0.6.0"
|
||||||
tracing-appender = "0.2.4"
|
tracing-appender = "0.2.4"
|
||||||
tauri-plugin-dialog = "2.4.2"
|
tauri-plugin-dialog = "2.4.2"
|
||||||
image = {version = "0.25.9", default-features = false, features = ["gif", "png"] }
|
image = {version = "0.24.9", default-features = false, features = ["gif", "png"] }
|
||||||
gif = "0.14.1"
|
gif = "0.14.1"
|
||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
enigo = { version = "0.6.1", features = ["wayland"] }
|
enigo = { version = "0.6.1", features = ["wayland"] }
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
mlua = { version = "0.11", default-features = false, features = ["lua54", "vendored", "serde", "async"] }
|
mlua = { version = "0.11", default-features = false, features = ["lua54", "vendored", "serde", "async"] }
|
||||||
|
petpet = "2.4.3"
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-global-shortcut = "2"
|
tauri-plugin-global-shortcut = "2"
|
||||||
tauri-plugin-positioner = "2"
|
tauri-plugin-positioner = "2"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub mod dolls;
|
|||||||
pub mod friends;
|
pub mod friends;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
pub mod sprite;
|
pub mod sprite;
|
||||||
|
pub mod petpet;
|
||||||
|
|
||||||
use crate::lock_r;
|
use crate::lock_r;
|
||||||
use crate::state::{init_app_data_scoped, AppDataRefreshScope, FDOLL};
|
use crate::state::{init_app_data_scoped, AppDataRefreshScope, FDOLL};
|
||||||
|
|||||||
7
src-tauri/src/commands/petpet.rs
Normal file
7
src-tauri/src/commands/petpet.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use crate::models::dolls::DollDto;
|
||||||
|
use crate::services::petpet;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn encode_pet_doll_gif_base64(doll: DollDto) -> Result<String, String> {
|
||||||
|
petpet::encode_pet_doll_gif_base64(doll)
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ 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 tauri::async_runtime;
|
use tauri::async_runtime;
|
||||||
|
|
||||||
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();
|
||||||
@@ -74,6 +75,7 @@ pub fn run() {
|
|||||||
set_active_doll,
|
set_active_doll,
|
||||||
remove_active_doll,
|
remove_active_doll,
|
||||||
recolor_gif_base64,
|
recolor_gif_base64,
|
||||||
|
encode_pet_doll_gif_base64,
|
||||||
quit_app,
|
quit_app,
|
||||||
restart_app,
|
restart_app,
|
||||||
retry_connection,
|
retry_connection,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tracing::{error, info};
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
lock_r, models::interaction::SendInteractionDto, services::ws::WS_EVENT, state::FDOLL,
|
lock_r, models::interaction::SendInteractionDto, services::ws::WS_EVENT, state::FDOLL,
|
||||||
@@ -25,23 +25,12 @@ pub async fn send_interaction(dto: SendInteractionDto) -> Result<(), String> {
|
|||||||
// The DTO structure matches what the server expects:
|
// The DTO structure matches what the server expects:
|
||||||
// { recipientUserId, content, type } (handled by serde rename_all="camelCase")
|
// { recipientUserId, content, type } (handled by serde rename_all="camelCase")
|
||||||
|
|
||||||
// Note: The `type` field in DTO is mapped to `type_` in Rust struct but serialized as `type`
|
|
||||||
// due to camelCase renaming (if we rely on TS-RS output) or manual renaming.
|
|
||||||
// Wait, `type` is a reserved keyword in Rust so we used `type_`.
|
|
||||||
// We need to ensure serialization maps `type_` to `type`.
|
|
||||||
// However, serde's `rename_all = "camelCase"` handles `recipient_user_id` -> `recipientUserId`.
|
|
||||||
// It does NOT automatically handle `type_` -> `type`.
|
|
||||||
// We should add `#[serde(rename = "type")]` to the `type_` field in the model.
|
|
||||||
// I will fix the model first to ensure correct serialization.
|
|
||||||
|
|
||||||
let payload = json!({
|
let payload = json!({
|
||||||
"recipientUserId": dto.recipient_user_id,
|
"recipientUserId": dto.recipient_user_id,
|
||||||
"content": dto.content,
|
"content": dto.content,
|
||||||
"type": dto.type_
|
"type": dto.type_
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("Sending interaction via WS: {:?}", payload);
|
|
||||||
|
|
||||||
// Blocking emission because rust_socketio::Client::emit is synchronous/blocking
|
// Blocking emission because rust_socketio::Client::emit is synchronous/blocking
|
||||||
// but we are in an async context. Ideally we spawn_blocking.
|
// but we are in an async context. Ideally we spawn_blocking.
|
||||||
let spawn_result = tauri::async_runtime::spawn_blocking(move || {
|
let spawn_result = tauri::async_runtime::spawn_blocking(move || {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod interaction;
|
|||||||
pub mod presence_modules;
|
pub mod presence_modules;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod sprite_recolor;
|
pub mod sprite_recolor;
|
||||||
|
pub mod petpet;
|
||||||
pub mod welcome;
|
pub mod welcome;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|
||||||
|
|||||||
25
src-tauri/src/services/petpet/mod.rs
Normal file
25
src-tauri/src/services/petpet/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::models::dolls::DollDto;
|
||||||
|
use crate::services::sprite_recolor;
|
||||||
|
use image::{imageops::FilterType, RgbaImage};
|
||||||
|
use petpet::{encode_gif, generate};
|
||||||
|
|
||||||
|
pub fn encode_pet_doll_gif_base64(doll: DollDto) -> Result<String, String> {
|
||||||
|
let body_color = &doll.configuration.color_scheme.body;
|
||||||
|
let outline_color = &doll.configuration.color_scheme.outline;
|
||||||
|
|
||||||
|
// Get recolored image
|
||||||
|
let img: RgbaImage = sprite_recolor::get_recolored_image(body_color, outline_color)
|
||||||
|
.map_err(|e| format!("Failed to recolor image: {}", e))?;
|
||||||
|
|
||||||
|
// Generate petpet frames
|
||||||
|
let frames = generate(img, FilterType::Lanczos3, None)
|
||||||
|
.map_err(|e| format!("Failed to generate petpet frames: {}", e))?;
|
||||||
|
|
||||||
|
// Encode to GIF
|
||||||
|
let mut output = Vec::new();
|
||||||
|
encode_gif(frames, &mut output, 10).map_err(|e| format!("Failed to encode GIF: {}", e))?;
|
||||||
|
|
||||||
|
// Base64
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
Ok(general_purpose::STANDARD.encode(&output))
|
||||||
|
}
|
||||||
@@ -226,3 +226,30 @@ fn pick_color_from_palette(palette: &[Rgba<u8>], x: u32, y: u32) -> Rgba<u8> {
|
|||||||
// SAFETY: index is guaranteed to be < palette.len() due to modulo operation
|
// SAFETY: index is guaranteed to be < palette.len() due to modulo operation
|
||||||
unsafe { *palette.get_unchecked(index) }
|
unsafe { *palette.get_unchecked(index) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_recolored_image(
|
||||||
|
white_color_hex: &str,
|
||||||
|
black_color_hex: &str,
|
||||||
|
) -> Result<RgbaImage, Box<dyn std::error::Error>> {
|
||||||
|
let white_color = parse_hex_color(white_color_hex)?;
|
||||||
|
let black_color = parse_hex_color(black_color_hex)?;
|
||||||
|
|
||||||
|
// Get pre-decoded GIF data
|
||||||
|
let decoded_gif = get_decoded_gif();
|
||||||
|
|
||||||
|
// Take first frame
|
||||||
|
let frame = &decoded_gif.frames[0];
|
||||||
|
let mut img = RgbaImage::from_raw(
|
||||||
|
decoded_gif.width as u32,
|
||||||
|
decoded_gif.height as u32,
|
||||||
|
frame.pixels.clone(),
|
||||||
|
)
|
||||||
|
.ok_or("Failed to create image from frame")?;
|
||||||
|
|
||||||
|
// Recolor the image
|
||||||
|
let white_palette = generate_color_palette(white_color, 7);
|
||||||
|
let black_palette = generate_color_palette(black_color, 7);
|
||||||
|
recolor_image(&mut img, &white_palette, &black_palette, true);
|
||||||
|
|
||||||
|
Ok(img)
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@
|
|||||||
userStatus={getFriendStatus(userId)}
|
userStatus={getFriendStatus(userId)}
|
||||||
{doll}
|
{doll}
|
||||||
{isInteractive}
|
{isInteractive}
|
||||||
|
senderDoll={getUserDoll()}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto";
|
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto";
|
||||||
import type { PresenceStatus } from "../../../types/bindings/PresenceStatus";
|
import type { PresenceStatus } from "../../../types/bindings/PresenceStatus";
|
||||||
import type { UserStatus } from "../../../events/user-status";
|
import type { UserStatus } from "../../../events/user-status";
|
||||||
|
import type { InteractionPayloadDto } from "../../../types/bindings/InteractionPayloadDto";
|
||||||
|
|
||||||
export let id = "";
|
export let id = "";
|
||||||
export let targetX = 0;
|
export let targetX = 0;
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
export let userStatus: UserStatus | undefined = undefined;
|
export let userStatus: UserStatus | undefined = undefined;
|
||||||
export let doll: DollDto | undefined = undefined;
|
export let doll: DollDto | undefined = undefined;
|
||||||
export let isInteractive = false;
|
export let isInteractive = false;
|
||||||
|
export let senderDoll: DollDto | undefined = undefined;
|
||||||
|
|
||||||
const { position, currentSprite, updatePosition, setPosition } = usePetState(
|
const { position, currentSprite, updatePosition, setPosition } = usePetState(
|
||||||
32,
|
32,
|
||||||
@@ -33,17 +35,14 @@
|
|||||||
let spriteSheetUrl = onekoGif;
|
let spriteSheetUrl = onekoGif;
|
||||||
|
|
||||||
let isPetMenuOpen = false;
|
let isPetMenuOpen = false;
|
||||||
let receivedMessage: string | undefined = undefined;
|
let receivedInteraction: InteractionPayloadDto | undefined = undefined;
|
||||||
let messageTimer: number | undefined = undefined;
|
let messageTimer: number | undefined = undefined;
|
||||||
|
|
||||||
// Watch for received interactions for this user
|
// Watch for received interactions for this user
|
||||||
$: {
|
$: {
|
||||||
const interaction = $receivedInteractions.get(user.id);
|
const interaction = $receivedInteractions.get(user.id);
|
||||||
if (interaction && interaction.content !== receivedMessage) {
|
if (interaction && interaction !== receivedInteraction) {
|
||||||
console.log(
|
receivedInteraction = interaction;
|
||||||
`Received interaction for ${user.id}: ${interaction.content}`,
|
|
||||||
);
|
|
||||||
receivedMessage = interaction.content;
|
|
||||||
isPetMenuOpen = true;
|
isPetMenuOpen = true;
|
||||||
|
|
||||||
// Make scene interactive so user can see it
|
// Make scene interactive so user can see it
|
||||||
@@ -58,7 +57,7 @@
|
|||||||
// Auto-close and clear after 8 seconds
|
// Auto-close and clear after 8 seconds
|
||||||
messageTimer = setTimeout(() => {
|
messageTimer = setTimeout(() => {
|
||||||
isPetMenuOpen = false;
|
isPetMenuOpen = false;
|
||||||
receivedMessage = undefined;
|
receivedInteraction = undefined;
|
||||||
clearInteraction(user.id);
|
clearInteraction(user.id);
|
||||||
// We probably shouldn't disable interactivity globally here as other pets might be active,
|
// We probably shouldn't disable interactivity globally here as other pets might be active,
|
||||||
// but 'set_pet_menu_state' in backend handles the window transparency logic per pet/menu.
|
// but 'set_pet_menu_state' in backend handles the window transparency logic per pet/menu.
|
||||||
@@ -85,7 +84,7 @@
|
|||||||
// Actually, `isInteractive` is a prop passed from +page.svelte probably based on hover state.
|
// Actually, `isInteractive` is a prop passed from +page.svelte probably based on hover state.
|
||||||
// If we want the menu to stay open during the message, we should probably ignore this auto-close behavior if a message is present.
|
// If we want the menu to stay open during the message, we should probably ignore this auto-close behavior if a message is present.
|
||||||
|
|
||||||
$: if (!receivedMessage && !isInteractive) {
|
$: if (!receivedInteraction && !isInteractive) {
|
||||||
isPetMenuOpen = false;
|
isPetMenuOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +153,13 @@
|
|||||||
aria-label="Pet Menu"
|
aria-label="Pet Menu"
|
||||||
>
|
>
|
||||||
{#if doll}
|
{#if doll}
|
||||||
<PetMenu {doll} {user} {userStatus} {receivedMessage} />
|
<PetMenu
|
||||||
|
{doll}
|
||||||
|
{user}
|
||||||
|
{userStatus}
|
||||||
|
{receivedInteraction}
|
||||||
|
{senderDoll}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -176,7 +181,7 @@
|
|||||||
isPetMenuOpen = !isPetMenuOpen;
|
isPetMenuOpen = !isPetMenuOpen;
|
||||||
if (!isPetMenuOpen) {
|
if (!isPetMenuOpen) {
|
||||||
// Clear message when closing menu manually
|
// Clear message when closing menu manually
|
||||||
receivedMessage = undefined;
|
receivedInteraction = undefined;
|
||||||
clearInteraction(user.id);
|
clearInteraction(user.id);
|
||||||
if (messageTimer) clearTimeout(messageTimer);
|
if (messageTimer) clearTimeout(messageTimer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto";
|
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto";
|
||||||
import type { SendInteractionDto } from "../../../types/bindings/SendInteractionDto";
|
import type { SendInteractionDto } from "../../../types/bindings/SendInteractionDto";
|
||||||
import type { UserStatus } from "../../../events/user-status";
|
import type { UserStatus } from "../../../events/user-status";
|
||||||
|
import type { InteractionPayloadDto } from "../../../types/bindings/InteractionPayloadDto";
|
||||||
|
|
||||||
export let doll: DollDto;
|
export let doll: DollDto;
|
||||||
export let user: UserBasicDto;
|
export let user: UserBasicDto;
|
||||||
export let userStatus: UserStatus | undefined = undefined;
|
export let userStatus: UserStatus | undefined = undefined;
|
||||||
export let receivedMessage: string | undefined = undefined;
|
export let receivedInteraction: InteractionPayloadDto | undefined = undefined;
|
||||||
|
export let senderDoll: DollDto | undefined = undefined;
|
||||||
|
|
||||||
let showMessageInput = false;
|
let showMessageInput = false;
|
||||||
let messageContent = "";
|
let messageContent = "";
|
||||||
@@ -32,6 +34,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendHeadpat() {
|
||||||
|
if (!senderDoll) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gifBase64 = await invoke("encode_pet_doll_gif_base64", { doll: senderDoll }) as string;
|
||||||
|
const dto: SendInteractionDto = {
|
||||||
|
recipientUserId: user.id,
|
||||||
|
content: gifBase64,
|
||||||
|
type: "headpat",
|
||||||
|
};
|
||||||
|
|
||||||
|
await invoke("send_interaction_cmd", { dto });
|
||||||
|
messageContent = "";
|
||||||
|
showMessageInput = false;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to send interaction:", e);
|
||||||
|
alert("Failed to send headpat: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeydown(event: KeyboardEvent) {
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
sendMessage();
|
sendMessage();
|
||||||
@@ -62,11 +84,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if receivedMessage}
|
{#if receivedInteraction}
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="text-sm max-w-[140px]">
|
{#if receivedInteraction.type === "headpat"}
|
||||||
{receivedMessage}
|
<img
|
||||||
</div>
|
src={`data:image/gif;base64,${receivedInteraction.content}`}
|
||||||
|
alt="Headpat GIF"
|
||||||
|
class="max-w-[140px] h-auto"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<div class="text-sm max-w-[140px]">
|
||||||
|
{receivedInteraction.content}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if showMessageInput}
|
{:else if showMessageInput}
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
@@ -89,7 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-row gap-1 w-full *:flex-1 *:btn *:btn-sm">
|
<div class="flex flex-row gap-1 w-full *:flex-1 *:btn *:btn-sm">
|
||||||
<button disabled>Headpat</button>
|
<button onclick={sendHeadpat}>Headpat</button>
|
||||||
<button onclick={() => (showMessageInput = true)}>Message</button>
|
<button onclick={() => (showMessageInput = true)}>Message</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-1 w-full *:flex-1 *:btn *:btn-sm">
|
<div class="flex flex-row gap-1 w-full *:flex-1 *:btn *:btn-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user