frontend events system refactor
This commit is contained in:
@@ -15,6 +15,16 @@ pub enum AppEvents {
|
||||
EditDoll,
|
||||
CreateDoll,
|
||||
UserStatusChanged,
|
||||
FriendCursorPosition,
|
||||
FriendDisconnected,
|
||||
FriendActiveDollChanged,
|
||||
FriendUserStatus,
|
||||
InteractionReceived,
|
||||
InteractionDeliveryFailed,
|
||||
FriendRequestReceived,
|
||||
FriendRequestAccepted,
|
||||
FriendRequestDenied,
|
||||
Unfriended,
|
||||
}
|
||||
|
||||
impl AppEvents {
|
||||
@@ -27,6 +37,16 @@ impl AppEvents {
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
use rust_socketio::{Payload, RawClient};
|
||||
use tracing::info;
|
||||
|
||||
use crate::services::app_events::AppEvents;
|
||||
use crate::services::cursor::{normalized_to_absolute, CursorPositions};
|
||||
use crate::state::AppDataRefreshScope;
|
||||
|
||||
use super::{
|
||||
emitter, refresh,
|
||||
types::{IncomingFriendCursorPayload, OutgoingFriendCursorPayload, WS_EVENT},
|
||||
types::{IncomingFriendCursorPayload, OutgoingFriendCursorPayload},
|
||||
utils,
|
||||
};
|
||||
|
||||
/// Handler for friend-request-received event
|
||||
pub fn on_friend_request_received(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(value) = utils::extract_text_value(payload, "friend-request-received") {
|
||||
emitter::emit_to_frontend(WS_EVENT::FRIEND_REQUEST_RECEIVED, value);
|
||||
emitter::emit_to_frontend(AppEvents::FriendRequestReceived.as_str(), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for friend-request-accepted event
|
||||
pub fn on_friend_request_accepted(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(value) = utils::extract_text_value(payload, "friend-request-accepted") {
|
||||
emitter::emit_to_frontend(WS_EVENT::FRIEND_REQUEST_ACCEPTED, value);
|
||||
emitter::emit_to_frontend(AppEvents::FriendRequestAccepted.as_str(), value);
|
||||
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
||||
}
|
||||
}
|
||||
@@ -28,14 +29,14 @@ pub fn on_friend_request_accepted(payload: Payload, _socket: RawClient) {
|
||||
/// Handler for friend-request-denied event
|
||||
pub fn on_friend_request_denied(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(value) = utils::extract_text_value(payload, "friend-request-denied") {
|
||||
emitter::emit_to_frontend(WS_EVENT::FRIEND_REQUEST_DENIED, value);
|
||||
emitter::emit_to_frontend(AppEvents::FriendRequestDenied.as_str(), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for unfriended event
|
||||
pub fn on_unfriended(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(value) = utils::extract_text_value(payload, "unfriended") {
|
||||
emitter::emit_to_frontend(WS_EVENT::UNFRIENDED, value);
|
||||
emitter::emit_to_frontend(AppEvents::Unfriended.as_str(), value);
|
||||
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
||||
}
|
||||
}
|
||||
@@ -56,14 +57,14 @@ pub fn on_friend_cursor_position(payload: Payload, _socket: RawClient) {
|
||||
},
|
||||
};
|
||||
|
||||
emitter::emit_to_frontend(WS_EVENT::FRIEND_CURSOR_POSITION, outgoing_payload);
|
||||
emitter::emit_to_frontend(AppEvents::FriendCursorPosition.as_str(), outgoing_payload);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for friend-disconnected event
|
||||
pub fn on_friend_disconnected(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(value) = utils::extract_text_value(payload, "friend-disconnected") {
|
||||
emitter::emit_to_frontend(WS_EVENT::FRIEND_DISCONNECTED, value);
|
||||
emitter::emit_to_frontend(AppEvents::FriendDisconnected.as_str(), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +94,7 @@ fn handle_friend_doll_change(event_name: &str, payload: Payload) {
|
||||
/// Handler for friend-active-doll-changed event
|
||||
pub fn on_friend_active_doll_changed(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(value) = utils::extract_text_value(payload, "friend-active-doll-changed") {
|
||||
emitter::emit_to_frontend(WS_EVENT::FRIEND_ACTIVE_DOLL_CHANGED, value);
|
||||
emitter::emit_to_frontend(AppEvents::FriendActiveDollChanged.as_str(), value);
|
||||
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
||||
}
|
||||
}
|
||||
@@ -101,6 +102,6 @@ pub fn on_friend_active_doll_changed(payload: Payload, _socket: RawClient) {
|
||||
/// Handler for friend-user-status event
|
||||
pub fn on_friend_user_status(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(value) = utils::extract_text_value(payload, "friend-user-status") {
|
||||
emitter::emit_to_frontend(WS_EVENT::FRIEND_USER_STATUS, value);
|
||||
emitter::emit_to_frontend(AppEvents::FriendUserStatus.as_str(), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use rust_socketio::{Payload, RawClient};
|
||||
|
||||
use crate::models::interaction::{InteractionDeliveryFailedDto, InteractionPayloadDto};
|
||||
use crate::services::app_events::AppEvents;
|
||||
|
||||
use super::{emitter, types::WS_EVENT, utils};
|
||||
use super::{emitter, utils};
|
||||
|
||||
/// Handler for interaction-received event
|
||||
pub fn on_interaction_received(payload: Payload, _socket: RawClient) {
|
||||
if let Ok(data) =
|
||||
utils::extract_and_parse::<InteractionPayloadDto>(payload, "interaction-received")
|
||||
{
|
||||
emitter::emit_to_frontend(WS_EVENT::INTERACTION_RECEIVED, data);
|
||||
emitter::emit_to_frontend(AppEvents::InteractionReceived.as_str(), data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +20,6 @@ pub fn on_interaction_delivery_failed(payload: Payload, _socket: RawClient) {
|
||||
payload,
|
||||
"interaction-delivery-failed",
|
||||
) {
|
||||
emitter::emit_to_frontend(WS_EVENT::INTERACTION_DELIVERY_FAILED, data);
|
||||
emitter::emit_to_frontend(AppEvents::InteractionDeliveryFailed.as_str(), data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import { writable } from "svelte/store";
|
||||
import { type UserData } from "../types/bindings/UserData";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
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";
|
||||
|
||||
export let appData = writable<UserData | null>(null);
|
||||
export const appData = writable<UserData | null>(null);
|
||||
|
||||
let unlisten: UnlistenFn | null = null;
|
||||
let isListening = false;
|
||||
const subscription = createListenerSubscription();
|
||||
|
||||
export async function initAppDataListener() {
|
||||
try {
|
||||
if (isListening) return;
|
||||
if (subscription.isListening()) return;
|
||||
appData.set(await invoke("get_app_data"));
|
||||
unlisten = await listen<UserData>(AppEvents.AppDataRefreshed, (event) => {
|
||||
const unlisten = await listen<UserData>(
|
||||
AppEvents.AppDataRefreshed,
|
||||
(event) => {
|
||||
console.log("app-data-refreshed", event.payload);
|
||||
appData.set(event.payload);
|
||||
});
|
||||
|
||||
isListening = true;
|
||||
},
|
||||
);
|
||||
subscription.setUnlisten(unlisten);
|
||||
subscription.setListening(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
@@ -26,16 +29,7 @@ export async function initAppDataListener() {
|
||||
}
|
||||
|
||||
export function stopAppDataListener() {
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
unlisten = null;
|
||||
isListening = false;
|
||||
}
|
||||
subscription.stop();
|
||||
}
|
||||
|
||||
// Handle HMR (Hot Module Replacement) cleanup
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.dispose(() => {
|
||||
stopAppDataListener();
|
||||
});
|
||||
}
|
||||
setupHmrCleanup(stopAppDataListener);
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { writable } from "svelte/store";
|
||||
import type { CursorPositions } from "../types/bindings/CursorPositions";
|
||||
import type { CursorPosition } from "../types/bindings/CursorPosition";
|
||||
import type { DollDto } from "../types/bindings/DollDto";
|
||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||
import {
|
||||
createMultiListenerSubscription,
|
||||
parseEventPayload,
|
||||
removeFromStore,
|
||||
setupHmrCleanup,
|
||||
} from "./listener-utils";
|
||||
|
||||
export let cursorPositionOnScreen = writable<CursorPositions>({
|
||||
export const cursorPositionOnScreen = writable<CursorPositions>({
|
||||
raw: { x: 0, y: 0 },
|
||||
mapped: { x: 0, y: 0 },
|
||||
});
|
||||
@@ -26,16 +32,12 @@ type FriendCursorData = {
|
||||
// The exported store will only expose the position part to consumers,
|
||||
// but internally we manage the full data.
|
||||
// Actually, it's easier if we just export the positions and manage state internally.
|
||||
export let friendsCursorPositions = writable<Record<string, CursorPositions>>(
|
||||
export const friendsCursorPositions = writable<Record<string, CursorPositions>>(
|
||||
{},
|
||||
);
|
||||
export let friendsActiveDolls = writable<Record<string, DollDto | null>>({});
|
||||
export const friendsActiveDolls = writable<Record<string, DollDto | null>>({});
|
||||
|
||||
let unlistenCursor: UnlistenFn | null = null;
|
||||
let unlistenFriendCursor: UnlistenFn | null = null;
|
||||
let unlistenFriendDisconnected: UnlistenFn | null = null;
|
||||
let unlistenFriendActiveDollChanged: UnlistenFn | null = null;
|
||||
let isListening = false;
|
||||
const subscription = createMultiListenerSubscription();
|
||||
|
||||
// Internal state to track timestamps
|
||||
let friendCursorState: Record<string, FriendCursorData> = {};
|
||||
@@ -46,22 +48,21 @@ let friendCursorState: Record<string, FriendCursorData> = {};
|
||||
* but all windows can independently listen to the broadcast events.
|
||||
*/
|
||||
export async function initCursorTracking() {
|
||||
if (isListening) {
|
||||
return;
|
||||
}
|
||||
if (subscription.isListening()) return;
|
||||
|
||||
try {
|
||||
// Listen to cursor position events (each window subscribes independently)
|
||||
unlistenCursor = await listen<CursorPositions>(
|
||||
const unlistenCursor = await listen<CursorPositions>(
|
||||
AppEvents.CursorPosition,
|
||||
(event) => {
|
||||
cursorPositionOnScreen.set(event.payload);
|
||||
},
|
||||
);
|
||||
subscription.addUnlisten(unlistenCursor);
|
||||
|
||||
// Listen to friend cursor position events
|
||||
unlistenFriendCursor = await listen<FriendCursorPosition>(
|
||||
"friend-cursor-position",
|
||||
const unlistenFriendCursor = await listen<FriendCursorPosition>(
|
||||
AppEvents.FriendCursorPosition,
|
||||
(event) => {
|
||||
// We now receive a clean object from Rust
|
||||
const data = event.payload;
|
||||
@@ -80,20 +81,16 @@ export async function initCursorTracking() {
|
||||
});
|
||||
},
|
||||
);
|
||||
subscription.addUnlisten(unlistenFriendCursor);
|
||||
|
||||
// Listen to friend disconnected events
|
||||
unlistenFriendDisconnected = await listen<[{ userId: string }]>(
|
||||
"friend-disconnected",
|
||||
(event) => {
|
||||
let payload = event.payload;
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse friend disconnected payload:", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const unlistenFriendDisconnected = await listen<
|
||||
[{ userId: string }] | { userId: string } | string
|
||||
>(AppEvents.FriendDisconnected, (event) => {
|
||||
const payload = parseEventPayload<
|
||||
[{ userId: string }] | { userId: string }
|
||||
>(event.payload, "friend-disconnected");
|
||||
if (!payload) return;
|
||||
|
||||
const data = Array.isArray(payload) ? payload[0] : payload;
|
||||
|
||||
@@ -103,35 +100,25 @@ export async function initCursorTracking() {
|
||||
}
|
||||
|
||||
// Update svelte store
|
||||
friendsCursorPositions.update((current) => {
|
||||
const next = { ...current };
|
||||
delete next[data.userId];
|
||||
return next;
|
||||
});
|
||||
},
|
||||
friendsCursorPositions.update((current) =>
|
||||
removeFromStore(current, data.userId),
|
||||
);
|
||||
});
|
||||
subscription.addUnlisten(unlistenFriendDisconnected);
|
||||
|
||||
// Listen to friend active doll changed events
|
||||
unlistenFriendActiveDollChanged = await listen<
|
||||
const unlistenFriendActiveDollChanged = await listen<
|
||||
| string
|
||||
| {
|
||||
friendId: string;
|
||||
doll: DollDto | null;
|
||||
}
|
||||
>("friend-active-doll-changed", (event) => {
|
||||
let data = event.payload;
|
||||
|
||||
if (typeof data === "string") {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Failed to parse friend-active-doll-changed payload:",
|
||||
e,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
>(AppEvents.FriendActiveDollChanged, (event) => {
|
||||
const data = parseEventPayload<{
|
||||
friendId: string;
|
||||
doll: DollDto | null;
|
||||
}>(event.payload, "friend-active-doll-changed");
|
||||
if (!data) return;
|
||||
|
||||
// Cast to expected type after parsing
|
||||
const payload = data as { friendId: string; doll: DollDto | null };
|
||||
@@ -149,11 +136,9 @@ export async function initCursorTracking() {
|
||||
});
|
||||
|
||||
// Also remove from cursor positions so the sprite disappears
|
||||
friendsCursorPositions.update((current) => {
|
||||
const next = { ...current };
|
||||
delete next[payload.friendId];
|
||||
return next;
|
||||
});
|
||||
friendsCursorPositions.update((current) =>
|
||||
removeFromStore(current, payload.friendId),
|
||||
);
|
||||
} else {
|
||||
// Update or add the new doll configuration
|
||||
friendsActiveDolls.update((current) => {
|
||||
@@ -164,8 +149,9 @@ export async function initCursorTracking() {
|
||||
});
|
||||
}
|
||||
});
|
||||
subscription.addUnlisten(unlistenFriendActiveDollChanged);
|
||||
|
||||
isListening = true;
|
||||
subscription.setListening(true);
|
||||
} catch (err) {
|
||||
console.error("Failed to initialize cursor tracking:", err);
|
||||
throw err;
|
||||
@@ -177,28 +163,7 @@ export async function initCursorTracking() {
|
||||
* Note: This doesn't stop the Rust-side tracking, just stops this window from receiving events.
|
||||
*/
|
||||
export function stopCursorTracking() {
|
||||
if (unlistenCursor) {
|
||||
unlistenCursor();
|
||||
unlistenCursor = null;
|
||||
}
|
||||
if (unlistenFriendCursor) {
|
||||
unlistenFriendCursor();
|
||||
unlistenFriendCursor = null;
|
||||
}
|
||||
if (unlistenFriendDisconnected) {
|
||||
unlistenFriendDisconnected();
|
||||
unlistenFriendDisconnected = null;
|
||||
}
|
||||
if (unlistenFriendActiveDollChanged) {
|
||||
unlistenFriendActiveDollChanged();
|
||||
unlistenFriendActiveDollChanged = null;
|
||||
}
|
||||
isListening = false;
|
||||
subscription.stop();
|
||||
}
|
||||
|
||||
// Handle HMR (Hot Module Replacement) cleanup
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.dispose(() => {
|
||||
stopCursorTracking();
|
||||
});
|
||||
}
|
||||
setupHmrCleanup(stopCursorTracking);
|
||||
|
||||
@@ -2,20 +2,27 @@ import { listen } from "@tauri-apps/api/event";
|
||||
import { addInteraction } from "$lib/stores/interaction-store";
|
||||
import type { InteractionPayloadDto } from "../types/bindings/InteractionPayloadDto";
|
||||
import type { InteractionDeliveryFailedDto } from "../types/bindings/InteractionDeliveryFailedDto";
|
||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||
import {
|
||||
createMultiListenerSubscription,
|
||||
setupHmrCleanup,
|
||||
} from "./listener-utils";
|
||||
|
||||
let unlistenReceived: (() => void) | undefined;
|
||||
let unlistenFailed: (() => void) | undefined;
|
||||
const subscription = createMultiListenerSubscription();
|
||||
|
||||
export async function initInteractionListeners() {
|
||||
unlistenReceived = await listen<InteractionPayloadDto>(
|
||||
"interaction-received",
|
||||
if (subscription.isListening()) return;
|
||||
|
||||
const unlistenReceived = await listen<InteractionPayloadDto>(
|
||||
AppEvents.InteractionReceived,
|
||||
(event) => {
|
||||
addInteraction(event.payload);
|
||||
},
|
||||
);
|
||||
subscription.addUnlisten(unlistenReceived);
|
||||
|
||||
unlistenFailed = await listen<InteractionDeliveryFailedDto>(
|
||||
"interaction-delivery-failed",
|
||||
const unlistenFailed = await listen<InteractionDeliveryFailedDto>(
|
||||
AppEvents.InteractionDeliveryFailed,
|
||||
(event) => {
|
||||
console.error("Interaction delivery failed:", event.payload);
|
||||
// You might want to show a toast or alert here
|
||||
@@ -24,9 +31,12 @@ export async function initInteractionListeners() {
|
||||
);
|
||||
},
|
||||
);
|
||||
subscription.addUnlisten(unlistenFailed);
|
||||
subscription.setListening(true);
|
||||
}
|
||||
|
||||
export function stopInteractionListeners() {
|
||||
if (unlistenReceived) unlistenReceived();
|
||||
if (unlistenFailed) unlistenFailed();
|
||||
subscription.stop();
|
||||
}
|
||||
|
||||
setupHmrCleanup(stopInteractionListeners);
|
||||
|
||||
101
src/events/listener-utils.ts
Normal file
101
src/events/listener-utils.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
export type ListenerSubscription = {
|
||||
stop: () => void;
|
||||
isListening: () => boolean;
|
||||
setListening: (value: boolean) => void;
|
||||
setUnlisten: (unlisten: UnlistenFn | null) => void;
|
||||
};
|
||||
|
||||
export type MultiListenerSubscription = {
|
||||
stop: () => void;
|
||||
isListening: () => boolean;
|
||||
setListening: (value: boolean) => void;
|
||||
addUnlisten: (unlisten: UnlistenFn | null) => void;
|
||||
};
|
||||
|
||||
export function createListenerSubscription(
|
||||
stopFn: () => void = () => {},
|
||||
): ListenerSubscription {
|
||||
let unlisten: UnlistenFn | null = null;
|
||||
let listening = false;
|
||||
|
||||
return {
|
||||
stop: () => {
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
unlisten = null;
|
||||
}
|
||||
listening = false;
|
||||
stopFn();
|
||||
},
|
||||
isListening: () => listening,
|
||||
setListening: (value) => {
|
||||
listening = value;
|
||||
},
|
||||
setUnlisten: (next) => {
|
||||
unlisten = next;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createMultiListenerSubscription(
|
||||
stopFn: () => void = () => {},
|
||||
): MultiListenerSubscription {
|
||||
let unlistens: UnlistenFn[] = [];
|
||||
let listening = false;
|
||||
|
||||
return {
|
||||
stop: () => {
|
||||
for (const unlisten of unlistens) {
|
||||
unlisten();
|
||||
}
|
||||
unlistens = [];
|
||||
listening = false;
|
||||
stopFn();
|
||||
},
|
||||
isListening: () => listening,
|
||||
setListening: (value) => {
|
||||
listening = value;
|
||||
},
|
||||
addUnlisten: (unlisten) => {
|
||||
if (unlisten) {
|
||||
unlistens.push(unlisten);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function setupHmrCleanup(cleanup: () => void) {
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.dispose(() => {
|
||||
cleanup();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function parseEventPayload<T>(
|
||||
payload: unknown,
|
||||
errorLabel: string,
|
||||
): T | null {
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
return JSON.parse(payload) as T;
|
||||
} catch (error) {
|
||||
console.error(`Failed to parse ${errorLabel} payload`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
export function removeFromStore<T>(
|
||||
current: Record<string, T>,
|
||||
key: string,
|
||||
): Record<string, T> {
|
||||
if (!(key in current)) return current;
|
||||
const next = { ...current };
|
||||
delete next[key];
|
||||
return next;
|
||||
}
|
||||
@@ -1,22 +1,26 @@
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { writable } from "svelte/store";
|
||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
||||
|
||||
export const sceneInteractive = writable<boolean>(false);
|
||||
|
||||
let unlisten: UnlistenFn | null = null;
|
||||
let isListening = false;
|
||||
const subscription = createListenerSubscription();
|
||||
|
||||
export async function initSceneInteractiveListener() {
|
||||
if (isListening) return;
|
||||
if (subscription.isListening()) return;
|
||||
|
||||
try {
|
||||
// ensure initial default matches backend default
|
||||
sceneInteractive.set(false);
|
||||
unlisten = await listen<boolean>(AppEvents.SceneInteractive, (event) => {
|
||||
const unlisten = await listen<boolean>(
|
||||
AppEvents.SceneInteractive,
|
||||
(event) => {
|
||||
sceneInteractive.set(Boolean(event.payload));
|
||||
});
|
||||
isListening = true;
|
||||
},
|
||||
);
|
||||
subscription.setUnlisten(unlisten);
|
||||
subscription.setListening(true);
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize scene interactive listener:", error);
|
||||
throw error;
|
||||
@@ -24,15 +28,7 @@ export async function initSceneInteractiveListener() {
|
||||
}
|
||||
|
||||
export function stopSceneInteractiveListener() {
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
unlisten = null;
|
||||
isListening = false;
|
||||
}
|
||||
subscription.stop();
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.dispose(() => {
|
||||
stopSceneInteractiveListener();
|
||||
});
|
||||
}
|
||||
setupHmrCleanup(stopSceneInteractiveListener);
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { writable } from "svelte/store";
|
||||
import type { PresenceStatus } from "../types/bindings/PresenceStatus";
|
||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||
import {
|
||||
createMultiListenerSubscription,
|
||||
parseEventPayload,
|
||||
removeFromStore,
|
||||
setupHmrCleanup,
|
||||
} from "./listener-utils";
|
||||
|
||||
export type UserStatus = {
|
||||
presenceStatus: PresenceStatus;
|
||||
@@ -9,27 +16,23 @@ export type UserStatus = {
|
||||
|
||||
export const friendsUserStatuses = writable<Record<string, UserStatus>>({});
|
||||
|
||||
let unlistenStatus: UnlistenFn | null = null;
|
||||
let unlistenFriendDisconnected: UnlistenFn | null = null;
|
||||
let isListening = false;
|
||||
const subscription = createMultiListenerSubscription();
|
||||
|
||||
export async function initUserStatusListeners() {
|
||||
if (isListening) return;
|
||||
if (subscription.isListening()) return;
|
||||
|
||||
try {
|
||||
unlistenStatus = await listen<unknown>("friend-user-status", (event) => {
|
||||
let payload = event.payload as any;
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse friend-user-status payload", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const unlistenStatus = await listen<unknown>(
|
||||
AppEvents.FriendUserStatus,
|
||||
(event) => {
|
||||
const payload = parseEventPayload<{
|
||||
userId?: string;
|
||||
status?: UserStatus;
|
||||
}>(event.payload, "friend-user-status");
|
||||
if (!payload) return;
|
||||
|
||||
const userId = payload?.userId as string | undefined;
|
||||
const status = payload?.status as UserStatus | undefined;
|
||||
const userId = payload.userId;
|
||||
const status = payload.status;
|
||||
|
||||
if (!userId || !status) return;
|
||||
if (!status.presenceStatus) return;
|
||||
@@ -51,33 +54,27 @@ export async function initUserStatusListeners() {
|
||||
state: status.state,
|
||||
},
|
||||
}));
|
||||
});
|
||||
},
|
||||
);
|
||||
subscription.addUnlisten(unlistenStatus);
|
||||
|
||||
unlistenFriendDisconnected = await listen<
|
||||
const unlistenFriendDisconnected = await listen<
|
||||
[{ userId: string }] | { userId: string } | string
|
||||
>("friend-disconnected", (event) => {
|
||||
let payload = event.payload as any;
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse friend-disconnected payload", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
>(AppEvents.FriendDisconnected, (event) => {
|
||||
const payload = parseEventPayload<
|
||||
[{ userId: string }] | { userId: string }
|
||||
>(event.payload, "friend-disconnected");
|
||||
if (!payload) return;
|
||||
|
||||
const data = Array.isArray(payload) ? payload[0] : payload;
|
||||
const userId = data?.userId as string | undefined;
|
||||
if (!userId) return;
|
||||
|
||||
friendsUserStatuses.update((current) => {
|
||||
const next = { ...current };
|
||||
delete next[userId];
|
||||
return next;
|
||||
});
|
||||
friendsUserStatuses.update((current) => removeFromStore(current, userId));
|
||||
});
|
||||
subscription.addUnlisten(unlistenFriendDisconnected);
|
||||
|
||||
isListening = true;
|
||||
subscription.setListening(true);
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize user status listeners", error);
|
||||
throw error;
|
||||
@@ -85,19 +82,7 @@ export async function initUserStatusListeners() {
|
||||
}
|
||||
|
||||
export function stopUserStatusListeners() {
|
||||
if (unlistenStatus) {
|
||||
unlistenStatus();
|
||||
unlistenStatus = null;
|
||||
}
|
||||
if (unlistenFriendDisconnected) {
|
||||
unlistenFriendDisconnected();
|
||||
unlistenFriendDisconnected = null;
|
||||
}
|
||||
isListening = false;
|
||||
subscription.stop();
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.dispose(() => {
|
||||
stopUserStatusListeners();
|
||||
});
|
||||
}
|
||||
setupHmrCleanup(stopUserStatusListeners);
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
import YourDolls from "./tabs/your-dolls/index.svelte";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { onMount } from "svelte";
|
||||
import { AppEvents } from "../../types/bindings/AppEventsConstants";
|
||||
|
||||
let showInteractionOverlay = false;
|
||||
|
||||
onMount(() => {
|
||||
const unlisten = listen("set-interaction-overlay", (event) => {
|
||||
const unlisten = listen(AppEvents.SetInteractionOverlay, (event) => {
|
||||
showInteractionOverlay = event.payload as boolean;
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
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";
|
||||
@@ -50,26 +51,26 @@
|
||||
refreshSent();
|
||||
|
||||
unlisteners.push(
|
||||
await listen("friend-request-received", () => {
|
||||
await listen(AppEvents.FriendRequestReceived, () => {
|
||||
refreshReceived();
|
||||
}),
|
||||
);
|
||||
|
||||
unlisteners.push(
|
||||
await listen("friend-request-accepted", () => {
|
||||
await listen(AppEvents.FriendRequestAccepted, () => {
|
||||
refreshSent();
|
||||
invoke("refresh_app_data");
|
||||
}),
|
||||
);
|
||||
|
||||
unlisteners.push(
|
||||
await listen("friend-request-denied", () => {
|
||||
await listen(AppEvents.FriendRequestDenied, () => {
|
||||
refreshSent();
|
||||
}),
|
||||
);
|
||||
|
||||
unlisteners.push(
|
||||
await listen("unfriended", () => {
|
||||
await listen(AppEvents.Unfriended, () => {
|
||||
invoke("refresh_app_data");
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -13,9 +13,13 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import DesktopPet from "./components/DesktopPet.svelte";
|
||||
import FullscreenModal from "./components/FullscreenModal.svelte";
|
||||
import { receivedInteractions, clearInteraction } from "$lib/stores/interaction-store";
|
||||
import {
|
||||
receivedInteractions,
|
||||
clearInteraction,
|
||||
} from "$lib/stores/interaction-store";
|
||||
import { INTERACTION_TYPE_HEADPAT } from "$lib/constants/interaction";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { AppEvents } from "../../types/bindings/AppEventsConstants";
|
||||
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
|
||||
import { onMount } from "svelte";
|
||||
import type { PresenceStatus } from "../../types/bindings/PresenceStatus";
|
||||
@@ -61,7 +65,9 @@
|
||||
let userPetpetGif = "";
|
||||
if (userDoll) {
|
||||
try {
|
||||
const gifBase64 = await invoke<string>("encode_pet_doll_gif_base64", { doll: userDoll });
|
||||
const gifBase64 = await invoke<string>("encode_pet_doll_gif_base64", {
|
||||
doll: userDoll,
|
||||
});
|
||||
userPetpetGif = `data:image/gif;base64,${gifBase64}`;
|
||||
} catch (e) {
|
||||
console.error("Failed to generate user petpet:", e);
|
||||
@@ -102,9 +108,14 @@
|
||||
if (interaction.type === INTERACTION_TYPE_HEADPAT) {
|
||||
if (showFullscreenModal) {
|
||||
// Queue the headpat for later (deduplicate by replacing existing from same user)
|
||||
const existingIndex = headpatQueue.findIndex((h) => h.userId === userId);
|
||||
const existingIndex = headpatQueue.findIndex(
|
||||
(h) => h.userId === userId,
|
||||
);
|
||||
if (existingIndex >= 0) {
|
||||
headpatQueue[existingIndex] = { userId, content: interaction.content };
|
||||
headpatQueue[existingIndex] = {
|
||||
userId,
|
||||
content: interaction.content,
|
||||
};
|
||||
} else {
|
||||
headpatQueue.push({ userId, content: interaction.content });
|
||||
}
|
||||
@@ -165,10 +176,12 @@
|
||||
let presenceStatus: PresenceStatus | null = $state(null);
|
||||
|
||||
onMount(() => {
|
||||
const unlisten = listen<UserStatus>("user-status-changed", (event) => {
|
||||
console.log("event received");
|
||||
const unlisten = listen<UserStatus>(
|
||||
AppEvents.UserStatusChanged,
|
||||
(event) => {
|
||||
presenceStatus = event.payload.presenceStatus;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unlisten.then((u) => u());
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AppEvents = "cursor-position" | "scene-interactive" | "app-data-refreshed" | "set-interaction-overlay" | "edit-doll" | "create-doll" | "user-status-changed";
|
||||
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";
|
||||
|
||||
@@ -9,6 +9,16 @@ export const AppEvents = {
|
||||
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];
|
||||
Reference in New Issue
Block a user