refactored svelte tauri events
This commit is contained in:
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
commands::app_state::get_modules,
|
commands::app_state::get_modules,
|
||||||
services::{
|
services::{
|
||||||
doll_editor::open_doll_editor_window,
|
doll_editor::open_doll_editor_window,
|
||||||
scene::{set_pet_menu_state, set_scene_interactive},
|
scene::{get_scene_interactive, set_pet_menu_state, set_scene_interactive},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use commands::app::{quit_app, restart_app, retry_connection};
|
use commands::app::{quit_app, restart_app, retry_connection};
|
||||||
@@ -83,6 +83,7 @@ pub fn run() {
|
|||||||
save_client_config,
|
save_client_config,
|
||||||
open_client_config_manager,
|
open_client_config_manager,
|
||||||
open_doll_editor_window,
|
open_doll_editor_window,
|
||||||
|
get_scene_interactive,
|
||||||
set_scene_interactive,
|
set_scene_interactive,
|
||||||
set_pet_menu_state,
|
set_pet_menu_state,
|
||||||
login,
|
login,
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ pub fn set_scene_interactive(interactive: bool, should_click: bool) {
|
|||||||
update_scene_interactive(interactive, should_click);
|
update_scene_interactive(interactive, should_click);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_scene_interactive() -> Result<bool, String> {
|
||||||
|
Ok(scene_interactive_state().load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
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();
|
||||||
|
|||||||
@@ -9,14 +9,17 @@ export const appData = writable<UserData | null>(null);
|
|||||||
|
|
||||||
const subscription = createListenerSubscription();
|
const subscription = createListenerSubscription();
|
||||||
|
|
||||||
export async function initAppDataListener() {
|
/**
|
||||||
|
* Starts listening for app data refresh events.
|
||||||
|
* Initializes app data from the backend.
|
||||||
|
*/
|
||||||
|
export async function startAppData() {
|
||||||
try {
|
try {
|
||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
appData.set(await invoke("get_app_data"));
|
appData.set(await invoke("get_app_data"));
|
||||||
const unlisten = await listen<UserData>(
|
const unlisten = await listen<UserData>(
|
||||||
AppEvents.AppDataRefreshed,
|
AppEvents.AppDataRefreshed,
|
||||||
(event) => {
|
(event) => {
|
||||||
console.log("app-data-refreshed", event.payload);
|
|
||||||
appData.set(event.payload);
|
appData.set(event.payload);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -28,8 +31,8 @@ export async function initAppDataListener() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopAppDataListener() {
|
export function stopAppData() {
|
||||||
subscription.stop();
|
subscription.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHmrCleanup(stopAppDataListener);
|
setupHmrCleanup(stopAppData);
|
||||||
|
|||||||
@@ -2,155 +2,31 @@ import { invoke } from "@tauri-apps/api/core";
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
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 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 { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||||
import {
|
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
||||||
createMultiListenerSubscription,
|
|
||||||
parseEventPayload,
|
|
||||||
removeFromStore,
|
|
||||||
setupHmrCleanup,
|
|
||||||
} from "./listener-utils";
|
|
||||||
|
|
||||||
export const cursorPositionOnScreen = writable<CursorPositions>({
|
export const cursorPositionOnScreen = writable<CursorPositions>({
|
||||||
raw: { x: 0, y: 0 },
|
raw: { x: 0, y: 0 },
|
||||||
mapped: { x: 0, y: 0 },
|
mapped: { x: 0, y: 0 },
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FriendCursorPosition = {
|
const subscription = createListenerSubscription();
|
||||||
userId: string;
|
|
||||||
position: CursorPositions;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map of userId -> { position: CursorPositions, lastUpdated: number }
|
|
||||||
// We store the timestamp to detect stale cursors
|
|
||||||
type FriendCursorData = {
|
|
||||||
position: CursorPositions;
|
|
||||||
lastUpdated: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 const friendsCursorPositions = writable<Record<string, CursorPositions>>(
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
export const friendsActiveDolls = writable<Record<string, DollDto | null>>({});
|
|
||||||
|
|
||||||
const subscription = createMultiListenerSubscription();
|
|
||||||
|
|
||||||
// Internal state to track timestamps
|
|
||||||
let friendCursorState: Record<string, FriendCursorData> = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize cursor tracking for this window.
|
* Starts tracking the local cursor position.
|
||||||
* Can be called from multiple windows - only the first call starts tracking on the Rust side,
|
* Initializes cursor position from the backend and listens for updates.
|
||||||
* but all windows can independently listen to the broadcast events.
|
|
||||||
*/
|
*/
|
||||||
export async function initCursorTracking() {
|
export async function startCursorTracking() {
|
||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Listen to cursor position events (each window subscribes independently)
|
const unlisten = await listen<CursorPositions>(
|
||||||
const unlistenCursor = await listen<CursorPositions>(
|
|
||||||
AppEvents.CursorPosition,
|
AppEvents.CursorPosition,
|
||||||
(event) => {
|
(event) => {
|
||||||
cursorPositionOnScreen.set(event.payload);
|
cursorPositionOnScreen.set(event.payload);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
subscription.addUnlisten(unlistenCursor);
|
subscription.setUnlisten(unlisten);
|
||||||
|
|
||||||
// Listen to friend cursor position events
|
|
||||||
const unlistenFriendCursor = await listen<FriendCursorPosition>(
|
|
||||||
AppEvents.FriendCursorPosition,
|
|
||||||
(event) => {
|
|
||||||
// We now receive a clean object from Rust
|
|
||||||
const data = event.payload;
|
|
||||||
|
|
||||||
// Update internal state with timestamp
|
|
||||||
friendCursorState[data.userId] = {
|
|
||||||
position: data.position,
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
friendsCursorPositions.update((current) => {
|
|
||||||
return {
|
|
||||||
...current,
|
|
||||||
[data.userId]: data.position,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
subscription.addUnlisten(unlistenFriendCursor);
|
|
||||||
|
|
||||||
// Listen to friend disconnected events
|
|
||||||
const unlistenFriendDisconnected = await listen<
|
|
||||||
[{ userId: string }] | { userId: string } | string
|
|
||||||
>(AppEvents.FriendDisconnected, (event) => {
|
|
||||||
const payload = parseEventPayload<
|
|
||||||
[{ userId: string }] | { userId: string }
|
|
||||||
>(event.payload, AppEvents.FriendDisconnected);
|
|
||||||
if (!payload) return;
|
|
||||||
|
|
||||||
const data = Array.isArray(payload) ? payload[0] : payload;
|
|
||||||
|
|
||||||
// Remove from internal state
|
|
||||||
if (friendCursorState[data.userId]) {
|
|
||||||
delete friendCursorState[data.userId];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update svelte store
|
|
||||||
friendsCursorPositions.update((current) =>
|
|
||||||
removeFromStore(current, data.userId),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
subscription.addUnlisten(unlistenFriendDisconnected);
|
|
||||||
|
|
||||||
// Listen to friend active doll changed events
|
|
||||||
const unlistenFriendActiveDollChanged = await listen<
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
friendId: string;
|
|
||||||
doll: DollDto | null;
|
|
||||||
}
|
|
||||||
>(AppEvents.FriendActiveDollChanged, (event) => {
|
|
||||||
const data = parseEventPayload<{
|
|
||||||
friendId: string;
|
|
||||||
doll: DollDto | null;
|
|
||||||
}>(event.payload, AppEvents.FriendActiveDollChanged);
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
// Cast to expected type after parsing
|
|
||||||
const payload = data as { friendId: string; doll: DollDto | null };
|
|
||||||
|
|
||||||
if (!payload.doll) {
|
|
||||||
// If doll is null, it means the friend deactivated their doll.
|
|
||||||
|
|
||||||
// Update the active dolls store to explicitly set this friend's doll to null
|
|
||||||
// We MUST set it to null instead of deleting it, otherwise the UI might
|
|
||||||
// fall back to the initial appData snapshot which might still have the old doll.
|
|
||||||
friendsActiveDolls.update((current) => {
|
|
||||||
const next = { ...current };
|
|
||||||
next[payload.friendId] = null;
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also remove from cursor positions so the sprite disappears
|
|
||||||
friendsCursorPositions.update((current) =>
|
|
||||||
removeFromStore(current, payload.friendId),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Update or add the new doll configuration
|
|
||||||
friendsActiveDolls.update((current) => {
|
|
||||||
return {
|
|
||||||
...current,
|
|
||||||
[payload.friendId]: payload.doll!,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
subscription.addUnlisten(unlistenFriendActiveDollChanged);
|
|
||||||
|
|
||||||
subscription.setListening(true);
|
subscription.setListening(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to initialize cursor tracking:", err);
|
console.error("Failed to initialize cursor tracking:", err);
|
||||||
@@ -158,10 +34,6 @@ export async function initCursorTracking() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop listening to cursor events in this window.
|
|
||||||
* Note: This doesn't stop the Rust-side tracking, just stops this window from receiving events.
|
|
||||||
*/
|
|
||||||
export function stopCursorTracking() {
|
export function stopCursorTracking() {
|
||||||
subscription.stop();
|
subscription.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
129
src/events/friend-cursor.ts
Normal file
129
src/events/friend-cursor.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
import type { CursorPositions } from "../types/bindings/CursorPositions";
|
||||||
|
import type { DollDto } from "../types/bindings/DollDto";
|
||||||
|
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||||
|
import {
|
||||||
|
createMultiListenerSubscription,
|
||||||
|
parseEventPayload,
|
||||||
|
removeFromStore,
|
||||||
|
setupHmrCleanup,
|
||||||
|
} from "./listener-utils";
|
||||||
|
|
||||||
|
export type FriendCursorPosition = {
|
||||||
|
userId: string;
|
||||||
|
position: CursorPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FriendCursorData = {
|
||||||
|
position: CursorPositions;
|
||||||
|
lastUpdated: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const friendsCursorPositions = writable<Record<string, CursorPositions>>(
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
export const friendsActiveDolls = writable<Record<string, DollDto | null>>({});
|
||||||
|
|
||||||
|
const subscription = createMultiListenerSubscription();
|
||||||
|
|
||||||
|
let friendCursorState: Record<string, FriendCursorData> = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts listening for friend cursor position and active doll changes.
|
||||||
|
* Also handles friend disconnection events.
|
||||||
|
*/
|
||||||
|
export async function startFriendCursorTracking() {
|
||||||
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: Add initial sync for existing friends' cursors and dolls if needed
|
||||||
|
|
||||||
|
const unlistenFriendCursor = await listen<FriendCursorPosition>(
|
||||||
|
AppEvents.FriendCursorPosition,
|
||||||
|
(event) => {
|
||||||
|
const data = event.payload;
|
||||||
|
|
||||||
|
friendCursorState[data.userId] = {
|
||||||
|
position: data.position,
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
friendsCursorPositions.update((current) => {
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
[data.userId]: data.position,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
subscription.addUnlisten(unlistenFriendCursor);
|
||||||
|
|
||||||
|
const unlistenFriendDisconnected = await listen<
|
||||||
|
[{ userId: string }] | { userId: string } | string
|
||||||
|
>(AppEvents.FriendDisconnected, (event) => {
|
||||||
|
const payload = parseEventPayload<
|
||||||
|
[{ userId: string }] | { userId: string }
|
||||||
|
>(event.payload, AppEvents.FriendDisconnected);
|
||||||
|
if (!payload) return;
|
||||||
|
|
||||||
|
const data = Array.isArray(payload) ? payload[0] : payload;
|
||||||
|
|
||||||
|
if (friendCursorState[data.userId]) {
|
||||||
|
delete friendCursorState[data.userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
friendsCursorPositions.update((current) =>
|
||||||
|
removeFromStore(current, data.userId),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
subscription.addUnlisten(unlistenFriendDisconnected);
|
||||||
|
|
||||||
|
const unlistenFriendActiveDollChanged = await listen<
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
friendId: string;
|
||||||
|
doll: DollDto | null;
|
||||||
|
}
|
||||||
|
>(AppEvents.FriendActiveDollChanged, (event) => {
|
||||||
|
const data = parseEventPayload<{
|
||||||
|
friendId: string;
|
||||||
|
doll: DollDto | null;
|
||||||
|
}>(event.payload, AppEvents.FriendActiveDollChanged);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const payload = data as { friendId: string; doll: DollDto | null };
|
||||||
|
|
||||||
|
if (!payload.doll) {
|
||||||
|
friendsActiveDolls.update((current) => {
|
||||||
|
const next = { ...current };
|
||||||
|
next[payload.friendId] = null;
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
friendsCursorPositions.update((current) =>
|
||||||
|
removeFromStore(current, payload.friendId),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
friendsActiveDolls.update((current) => {
|
||||||
|
return {
|
||||||
|
...current,
|
||||||
|
[payload.friendId]: payload.doll!,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
subscription.addUnlisten(unlistenFriendActiveDollChanged);
|
||||||
|
|
||||||
|
subscription.setListening(true);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to initialize friend cursor tracking:", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopFriendCursorTracking() {
|
||||||
|
subscription.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupHmrCleanup(stopFriendCursorTracking);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { addInteraction } from "$lib/stores/interaction-store";
|
import { writable } from "svelte/store";
|
||||||
import type { InteractionPayloadDto } from "../types/bindings/InteractionPayloadDto";
|
import type { InteractionPayloadDto } from "../types/bindings/InteractionPayloadDto";
|
||||||
import type { InteractionDeliveryFailedDto } from "../types/bindings/InteractionDeliveryFailedDto";
|
import type { InteractionDeliveryFailedDto } from "../types/bindings/InteractionDeliveryFailedDto";
|
||||||
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
import { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||||
@@ -8,11 +8,35 @@ import {
|
|||||||
setupHmrCleanup,
|
setupHmrCleanup,
|
||||||
} from "./listener-utils";
|
} from "./listener-utils";
|
||||||
|
|
||||||
|
export const receivedInteractions = writable<Map<string, InteractionPayloadDto>>(
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
|
||||||
|
export function addInteraction(interaction: InteractionPayloadDto) {
|
||||||
|
receivedInteractions.update((map) => {
|
||||||
|
const newMap = new Map(map);
|
||||||
|
newMap.set(interaction.senderUserId, interaction);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearInteraction(userId: string) {
|
||||||
|
receivedInteractions.update((map) => {
|
||||||
|
const newMap = new Map(map);
|
||||||
|
newMap.delete(userId);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const subscription = createMultiListenerSubscription();
|
const subscription = createMultiListenerSubscription();
|
||||||
|
|
||||||
export async function initInteractionListeners() {
|
/**
|
||||||
|
* Starts listening for interaction events (received and delivery failed).
|
||||||
|
*/
|
||||||
|
export async function startInteraction() {
|
||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
const unlistenReceived = await listen<InteractionPayloadDto>(
|
const unlistenReceived = await listen<InteractionPayloadDto>(
|
||||||
AppEvents.InteractionReceived,
|
AppEvents.InteractionReceived,
|
||||||
(event) => {
|
(event) => {
|
||||||
@@ -25,7 +49,6 @@ export async function initInteractionListeners() {
|
|||||||
AppEvents.InteractionDeliveryFailed,
|
AppEvents.InteractionDeliveryFailed,
|
||||||
(event) => {
|
(event) => {
|
||||||
console.error("Interaction delivery failed:", event.payload);
|
console.error("Interaction delivery failed:", event.payload);
|
||||||
// You might want to show a toast or alert here
|
|
||||||
alert(
|
alert(
|
||||||
`Failed to send message to user ${event.payload.recipientUserId}: ${event.payload.reason}`,
|
`Failed to send message to user ${event.payload.recipientUserId}: ${event.payload.reason}`,
|
||||||
);
|
);
|
||||||
@@ -33,10 +56,14 @@ export async function initInteractionListeners() {
|
|||||||
);
|
);
|
||||||
subscription.addUnlisten(unlistenFailed);
|
subscription.addUnlisten(unlistenFailed);
|
||||||
subscription.setListening(true);
|
subscription.setListening(true);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to initialize interaction listeners:", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopInteractionListeners() {
|
export function stopInteraction() {
|
||||||
subscription.stop();
|
subscription.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHmrCleanup(stopInteractionListeners);
|
setupHmrCleanup(stopInteraction);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
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 { AppEvents } from "../types/bindings/AppEventsConstants";
|
||||||
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
|
||||||
@@ -7,12 +8,15 @@ export const sceneInteractive = writable<boolean>(false);
|
|||||||
|
|
||||||
const subscription = createListenerSubscription();
|
const subscription = createListenerSubscription();
|
||||||
|
|
||||||
export async function initSceneInteractiveListener() {
|
/**
|
||||||
|
* Starts listening for scene interactive state changes.
|
||||||
|
* Initializes the scene interactive state from the backend.
|
||||||
|
*/
|
||||||
|
export async function startSceneInteractive() {
|
||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ensure initial default matches backend default
|
sceneInteractive.set(await invoke("get_scene_interactive"));
|
||||||
sceneInteractive.set(false);
|
|
||||||
const unlisten = await listen<boolean>(
|
const unlisten = await listen<boolean>(
|
||||||
AppEvents.SceneInteractive,
|
AppEvents.SceneInteractive,
|
||||||
(event) => {
|
(event) => {
|
||||||
@@ -27,8 +31,8 @@ export async function initSceneInteractiveListener() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopSceneInteractiveListener() {
|
export function stopSceneInteractive() {
|
||||||
subscription.stop();
|
subscription.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHmrCleanup(stopSceneInteractiveListener);
|
setupHmrCleanup(stopSceneInteractive);
|
||||||
|
|||||||
@@ -9,17 +9,20 @@ import {
|
|||||||
setupHmrCleanup,
|
setupHmrCleanup,
|
||||||
} from "./listener-utils";
|
} from "./listener-utils";
|
||||||
|
|
||||||
export type UserStatus = {
|
export type PresenceState = {
|
||||||
presenceStatus: PresenceStatus;
|
presenceStatus: PresenceStatus;
|
||||||
state: "idle" | "resting";
|
state: "idle" | "resting";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const friendsUserStatuses = writable<Record<string, UserStatus>>({});
|
export const friendsPresenceStates = writable<Record<string, PresenceState>>({});
|
||||||
export const currentUserStatus = writable<UserStatus | null>(null);
|
export const currentPresenceState = writable<PresenceState | null>(null);
|
||||||
|
|
||||||
const subscription = createMultiListenerSubscription();
|
const subscription = createMultiListenerSubscription();
|
||||||
|
|
||||||
export async function initUserStatusListeners() {
|
/**
|
||||||
|
* Starts listening for user status changes and friend status updates.
|
||||||
|
*/
|
||||||
|
export async function startUserStatus() {
|
||||||
if (subscription.isListening()) return;
|
if (subscription.isListening()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -28,7 +31,7 @@ export async function initUserStatusListeners() {
|
|||||||
(event) => {
|
(event) => {
|
||||||
const payload = parseEventPayload<{
|
const payload = parseEventPayload<{
|
||||||
userId?: string;
|
userId?: string;
|
||||||
status?: UserStatus;
|
status?: PresenceState;
|
||||||
}>(event.payload, AppEvents.FriendUserStatus);
|
}>(event.payload, AppEvents.FriendUserStatus);
|
||||||
if (!payload) return;
|
if (!payload) return;
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ export async function initUserStatusListeners() {
|
|||||||
|
|
||||||
if (status.state !== "idle" && status.state !== "resting") return;
|
if (status.state !== "idle" && status.state !== "resting") return;
|
||||||
|
|
||||||
friendsUserStatuses.update((current) => ({
|
friendsPresenceStates.update((current) => ({
|
||||||
...current,
|
...current,
|
||||||
[userId]: {
|
[userId]: {
|
||||||
presenceStatus: status.presenceStatus,
|
presenceStatus: status.presenceStatus,
|
||||||
@@ -59,10 +62,10 @@ export async function initUserStatusListeners() {
|
|||||||
);
|
);
|
||||||
subscription.addUnlisten(unlistenStatus);
|
subscription.addUnlisten(unlistenStatus);
|
||||||
|
|
||||||
const unlistenUserStatusChanged = await listen<UserStatus>(
|
const unlistenUserStatusChanged = await listen<PresenceState>(
|
||||||
AppEvents.UserStatusChanged,
|
AppEvents.UserStatusChanged,
|
||||||
(event) => {
|
(event) => {
|
||||||
currentUserStatus.set(event.payload);
|
currentPresenceState.set(event.payload);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
subscription.addUnlisten(unlistenUserStatusChanged);
|
subscription.addUnlisten(unlistenUserStatusChanged);
|
||||||
@@ -79,7 +82,7 @@ export async function initUserStatusListeners() {
|
|||||||
const userId = data?.userId as string | undefined;
|
const userId = data?.userId as string | undefined;
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
friendsUserStatuses.update((current) => removeFromStore(current, userId));
|
friendsPresenceStates.update((current) => removeFromStore(current, userId));
|
||||||
});
|
});
|
||||||
subscription.addUnlisten(unlistenFriendDisconnected);
|
subscription.addUnlisten(unlistenFriendDisconnected);
|
||||||
|
|
||||||
@@ -90,8 +93,8 @@ export async function initUserStatusListeners() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopUserStatusListeners() {
|
export function stopUserStatus() {
|
||||||
subscription.stop();
|
subscription.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHmrCleanup(stopUserStatusListeners);
|
setupHmrCleanup(stopUserStatus);
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { writable } from "svelte/store";
|
|
||||||
import type { InteractionPayloadDto } from "../../types/bindings/InteractionPayloadDto";
|
|
||||||
|
|
||||||
// Map senderUserId -> InteractionPayloadDto
|
|
||||||
export const receivedInteractions = writable<Map<string, InteractionPayloadDto>>(new Map());
|
|
||||||
|
|
||||||
export function addInteraction(interaction: InteractionPayloadDto) {
|
|
||||||
receivedInteractions.update((map) => {
|
|
||||||
// For now, we only store the latest message per user.
|
|
||||||
// In the future, we could store an array if we want a history.
|
|
||||||
const newMap = new Map(map);
|
|
||||||
newMap.set(interaction.senderUserId, interaction);
|
|
||||||
return newMap;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearInteraction(userId: string) {
|
|
||||||
receivedInteractions.update((map) => {
|
|
||||||
const newMap = new Map(map);
|
|
||||||
newMap.delete(userId);
|
|
||||||
return newMap;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,29 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
import { initCursorTracking, stopCursorTracking } from "../events/cursor";
|
import { startCursorTracking, stopCursorTracking } from "../events/cursor";
|
||||||
import { initAppDataListener } from "../events/app-data";
|
|
||||||
import { initInteractionListeners, stopInteractionListeners } from "../events/interaction";
|
|
||||||
import {
|
import {
|
||||||
initSceneInteractiveListener,
|
startFriendCursorTracking,
|
||||||
stopSceneInteractiveListener,
|
stopFriendCursorTracking,
|
||||||
|
} from "../events/friend-cursor";
|
||||||
|
import { startAppData } from "../events/app-data";
|
||||||
|
import { startInteraction, stopInteraction } from "../events/interaction";
|
||||||
|
import {
|
||||||
|
startSceneInteractive,
|
||||||
|
stopSceneInteractive,
|
||||||
} from "../events/scene-interactive";
|
} from "../events/scene-interactive";
|
||||||
import { initUserStatusListeners, stopUserStatusListeners } from "../events/user-status";
|
import { startUserStatus, stopUserStatus } from "../events/user-status";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
if (browser) {
|
if (browser) {
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await initCursorTracking();
|
await startAppData();
|
||||||
await initAppDataListener();
|
await startCursorTracking();
|
||||||
await initSceneInteractiveListener();
|
await startFriendCursorTracking();
|
||||||
await initInteractionListeners();
|
await startSceneInteractive();
|
||||||
await initUserStatusListeners();
|
await startInteraction();
|
||||||
|
await startUserStatus();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to initialize event listeners:", err);
|
console.error("Failed to initialize event listeners:", err);
|
||||||
}
|
}
|
||||||
@@ -26,9 +31,10 @@
|
|||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
stopCursorTracking();
|
stopCursorTracking();
|
||||||
stopSceneInteractiveListener();
|
stopFriendCursorTracking();
|
||||||
stopInteractionListeners();
|
stopSceneInteractive();
|
||||||
stopUserStatusListeners();
|
stopInteraction();
|
||||||
|
stopUserStatus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { cursorPositionOnScreen } from "../../events/cursor";
|
||||||
cursorPositionOnScreen,
|
import { friendsCursorPositions } from "../../events/friend-cursor";
|
||||||
friendsCursorPositions,
|
|
||||||
} from "../../events/cursor";
|
|
||||||
import { appData } from "../../events/app-data";
|
import { appData } from "../../events/app-data";
|
||||||
import { sceneInteractive } from "../../events/scene-interactive";
|
import { sceneInteractive } from "../../events/scene-interactive";
|
||||||
import {
|
import {
|
||||||
friendsUserStatuses,
|
friendsPresenceStates,
|
||||||
currentUserStatus,
|
currentPresenceState,
|
||||||
} from "../../events/user-status";
|
} from "../../events/user-status";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import DebugBar from "./components/debug-bar.svelte";
|
import DebugBar from "./components/debug-bar.svelte";
|
||||||
@@ -28,10 +26,10 @@
|
|||||||
<DebugBar
|
<DebugBar
|
||||||
isInteractive={$sceneInteractive}
|
isInteractive={$sceneInteractive}
|
||||||
cursorPosition={$cursorPositionOnScreen}
|
cursorPosition={$cursorPositionOnScreen}
|
||||||
presenceStatus={$currentUserStatus?.presenceStatus ?? null}
|
presenceStatus={$currentPresenceState?.presenceStatus ?? null}
|
||||||
friendsCursorPositions={$friendsCursorPositions}
|
friendsCursorPositions={$friendsCursorPositions}
|
||||||
friends={$appData?.friends ?? []}
|
friends={$appData?.friends ?? []}
|
||||||
friendsUserStatuses={$friendsUserStatuses}
|
friendsPresenceStates={$friendsPresenceStates}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PresenceStatus } from "../../../types/bindings/PresenceStatus";
|
import type { PresenceStatus } from "../../../types/bindings/PresenceStatus";
|
||||||
import type { UserStatus } from "../../../events/user-status";
|
import type { PresenceState } from "../../../events/user-status";
|
||||||
|
|
||||||
interface Friend {
|
interface Friend {
|
||||||
friend?: {
|
friend?: {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
presenceStatus: PresenceStatus | null;
|
presenceStatus: PresenceStatus | null;
|
||||||
friendsCursorPositions: Record<string, { mapped: { x: number; y: number } }>;
|
friendsCursorPositions: Record<string, { mapped: { x: number; y: number } }>;
|
||||||
friends: Friend[];
|
friends: Friend[];
|
||||||
friendsUserStatuses: Record<string, UserStatus>;
|
friendsPresenceStates: Record<string, PresenceState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
presenceStatus,
|
presenceStatus,
|
||||||
friendsCursorPositions,
|
friendsCursorPositions,
|
||||||
friends,
|
friends,
|
||||||
friendsUserStatuses,
|
friendsPresenceStates,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
function getFriendById(userId: string) {
|
function getFriendById(userId: string) {
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFriendStatus(userId: string) {
|
function getFriendStatus(userId: string) {
|
||||||
return friendsUserStatuses[userId];
|
return friendsPresenceStates[userId];
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user