refactored frontend events for code conciseness

This commit is contained in:
2026-03-08 16:15:32 +08:00
parent 291e0311e8
commit 23c778a0bb
7 changed files with 139 additions and 236 deletions

View File

@@ -1,32 +1,16 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { commands, events, type UserData } from "$lib/bindings"; import { commands, events, type UserData } from "$lib/bindings";
import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; import { createEventSource } from "./listener-utils";
export const appData = writable<UserData | null>(null); export const appData = writable<UserData | null>(null);
const subscription = createListenersSubscription(); export const { start: startAppData, stop: stopAppData } = createEventSource(
async (addEventListener) => {
/**
* Starts listening for app data refresh events.
* Initializes app data from the backend.
*/
export async function startAppData() {
try {
if (subscription.isListening()) return;
appData.set(await commands.getAppData()); appData.set(await commands.getAppData());
const unlisten = await events.appDataRefreshed.listen((event) => { addEventListener(
await events.appDataRefreshed.listen((event) => {
appData.set(event.payload); appData.set(event.payload);
}); }),
subscription.addUnlisten(unlisten); );
subscription.setListening(true); },
} catch (error) { );
console.error(error);
throw error;
}
}
export function stopAppData() {
subscription.stop();
}
setupHmrCleanup(stopAppData);

View File

@@ -1,35 +1,17 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { events, type CursorPositions } from "$lib/bindings"; import { events, type CursorPositions } from "$lib/bindings";
import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; import { createEventSource } 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 },
}); });
const subscription = createListenersSubscription(); export const { start: startCursorTracking, stop: stopCursorTracking } =
createEventSource(async (addEventListener) => {
/** addEventListener(
* Starts tracking the local cursor position. await events.cursorMoved.listen((event) => {
* Initializes cursor position from the backend and listens for updates.
*/
export async function startCursorTracking() {
if (subscription.isListening()) return;
try {
const unlisten = await events.cursorMoved.listen((event) => {
cursorPositionOnScreen.set(event.payload); cursorPositionOnScreen.set(event.payload);
}),
);
}); });
subscription.addUnlisten(unlisten);
subscription.setListening(true);
} catch (err) {
console.error("Failed to initialize cursor tracking:", err);
throw err;
}
}
export function stopCursorTracking() {
subscription.stop();
}
setupHmrCleanup(stopCursorTracking);

View File

@@ -5,11 +5,7 @@ import {
type DollDto, type DollDto,
type OutgoingFriendCursorPayload, type OutgoingFriendCursorPayload,
} from "$lib/bindings"; } from "$lib/bindings";
import { import { createEventSource, removeFromStore } from "./listener-utils";
createListenersSubscription,
removeFromStore,
setupHmrCleanup,
} from "./listener-utils";
type FriendCursorData = { type FriendCursorData = {
position: CursorPositions; position: CursorPositions;
@@ -21,21 +17,12 @@ export const friendsCursorPositions = writable<Record<string, CursorPositions>>(
); );
export const friendsActiveDolls = writable<Record<string, DollDto | null>>({}); export const friendsActiveDolls = writable<Record<string, DollDto | null>>({});
const subscription = createListenersSubscription(); export const {
start: startFriendCursorTracking,
let friendCursorState: Record<string, FriendCursorData> = {}; stop: stopFriendCursorTracking,
} = createEventSource(async (addEventListener) => {
/** let friendCursorState: Record<string, FriendCursorData> = {};
* Starts listening for friend cursor position and active doll changes. addEventListener(
* 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 events.friendCursorPositionUpdated.listen((event) => { await events.friendCursorPositionUpdated.listen((event) => {
const data: OutgoingFriendCursorPayload = event.payload; const data: OutgoingFriendCursorPayload = event.payload;
@@ -50,11 +37,11 @@ export async function startFriendCursorTracking() {
[data.userId]: data.position, [data.userId]: data.position,
}; };
}); });
}); }),
subscription.addUnlisten(unlistenFriendCursor); );
const unlistenFriendDisconnected = await events.friendDisconnected.listen( addEventListener(
(event) => { await events.friendDisconnected.listen((event) => {
const data = event.payload; const data = event.payload;
if (friendCursorState[data.userId]) { if (friendCursorState[data.userId]) {
@@ -64,11 +51,10 @@ export async function startFriendCursorTracking() {
friendsCursorPositions.update((current) => friendsCursorPositions.update((current) =>
removeFromStore(current, data.userId), removeFromStore(current, data.userId),
); );
}, }),
); );
subscription.addUnlisten(unlistenFriendDisconnected);
const unlistenFriendActiveDollChanged = addEventListener(
await events.friendActiveDollChanged.listen((event) => { await events.friendActiveDollChanged.listen((event) => {
const payload = event.payload; const payload = event.payload;
@@ -90,18 +76,6 @@ export async function startFriendCursorTracking() {
}; };
}); });
} }
}); }),
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);

View File

@@ -1,6 +1,6 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { events, type InteractionPayloadDto } from "$lib/bindings"; import { events, type InteractionPayloadDto } from "$lib/bindings";
import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; import { createEventSource } from "./listener-utils";
export const receivedInteractions = writable< export const receivedInteractions = writable<
Map<string, InteractionPayloadDto> Map<string, InteractionPayloadDto>
@@ -22,40 +22,20 @@ export function clearInteraction(userId: string) {
}); });
} }
const subscription = createListenersSubscription(); export const { start: startInteraction, stop: stopInteraction } =
createEventSource(async (addEventListener) => {
/** addEventListener(
* Starts listening for interaction events (received and delivery failed). await events.interactionReceived.listen((event) => {
*/
export async function startInteraction() {
if (subscription.isListening()) return;
try {
const unlistenReceived = await events.interactionReceived.listen(
(event) => {
addInteraction(event.payload); addInteraction(event.payload);
}, }),
); );
subscription.addUnlisten(unlistenReceived);
const unlistenFailed = await events.interactionDeliveryFailed.listen( addEventListener(
(event) => { await events.interactionDeliveryFailed.listen((event) => {
console.error("Interaction delivery failed:", event.payload); console.error("Interaction delivery failed:", event.payload);
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}`,
); );
}, }),
); );
subscription.addUnlisten(unlistenFailed); });
subscription.setListening(true);
} catch (err) {
console.error("Failed to initialize interaction listeners:", err);
throw err;
}
}
export function stopInteraction() {
subscription.stop();
}
setupHmrCleanup(stopInteraction);

View File

@@ -4,7 +4,7 @@ export type ListenerSubscription = {
stop: () => void; stop: () => void;
isListening: () => boolean; isListening: () => boolean;
setListening: (value: boolean) => void; setListening: (value: boolean) => void;
addUnlisten: (unlisten: UnlistenFn | null) => void; addEventListener: (unlisten: UnlistenFn | null) => void;
}; };
export function createListenersSubscription( export function createListenersSubscription(
@@ -26,7 +26,7 @@ export function createListenersSubscription(
setListening: (value) => { setListening: (value) => {
listening = value; listening = value;
}, },
addUnlisten: (unlisten) => { addEventListener: (unlisten) => {
if (unlisten) { if (unlisten) {
unlistens.push(unlisten); unlistens.push(unlisten);
} }
@@ -34,12 +34,39 @@ export function createListenersSubscription(
}; };
} }
export function setupHmrCleanup(cleanup: () => void) { export type EventSource = {
if (import.meta.hot) { start: () => Promise<void>;
import.meta.hot.dispose(() => { stop: () => void;
cleanup(); isListening: () => boolean;
}); };
export function createEventSource(
setup: (addEventListener: (unlisten: UnlistenFn) => void) => Promise<void>,
stopFn: () => void = () => {},
): EventSource {
const subscription = createListenersSubscription(stopFn);
async function start() {
if (subscription.isListening()) return;
try {
await setup((unlisten) => subscription.addEventListener(unlisten));
subscription.setListening(true);
} catch (err) {
subscription.stop();
console.error(`Failed to start:`, err);
throw err;
} }
}
function stop() {
subscription.stop();
}
if (import.meta.hot) {
import.meta.hot.dispose(() => stop());
}
return { start, stop, isListening: () => subscription.isListening() };
} }
export function removeFromStore<T>( export function removeFromStore<T>(

View File

@@ -1,33 +1,15 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { commands, events } from "$lib/bindings"; import { commands, events } from "$lib/bindings";
import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; import { createEventSource } from "./listener-utils";
export const sceneInteractive = writable<boolean>(false); export const sceneInteractive = writable<boolean>(false);
const subscription = createListenersSubscription(); export const { start: startSceneInteractive, stop: stopSceneInteractive } =
createEventSource(async (addEventListener) => {
/**
* Starts listening for scene interactive state changes.
* Initializes the scene interactive state from the backend.
*/
export async function startSceneInteractive() {
if (subscription.isListening()) return;
try {
sceneInteractive.set(await commands.getSceneInteractive()); sceneInteractive.set(await commands.getSceneInteractive());
const unlisten = await events.sceneInteractiveChanged.listen((event) => { addEventListener(
await events.sceneInteractiveChanged.listen((event) => {
sceneInteractive.set(Boolean(event.payload)); sceneInteractive.set(Boolean(event.payload));
}),
);
}); });
subscription.addUnlisten(unlisten);
subscription.setListening(true);
} catch (error) {
console.error("Failed to initialize scene interactive listener:", error);
throw error;
}
}
export function stopSceneInteractive() {
subscription.stop();
}
setupHmrCleanup(stopSceneInteractive);

View File

@@ -1,27 +1,16 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { events, type UserStatusPayload } from "$lib/bindings"; import { events, type UserStatusPayload } from "$lib/bindings";
import { import { createEventSource, removeFromStore } from "./listener-utils";
createListenersSubscription,
removeFromStore,
setupHmrCleanup,
} from "./listener-utils";
export const friendsPresenceStates = writable< export const friendsPresenceStates = writable<
Record<string, UserStatusPayload> Record<string, UserStatusPayload>
>({}); >({});
export const currentPresenceState = writable<UserStatusPayload | null>(null); export const currentPresenceState = writable<UserStatusPayload | null>(null);
const subscription = createListenersSubscription(); export const { start: startUserStatus, stop: stopUserStatus } =
createEventSource(async (addEventListener) => {
/** addEventListener(
* Starts listening for user status changes and friend status updates. await events.friendUserStatusChanged.listen((event) => {
*/
export async function startUserStatus() {
if (subscription.isListening()) return;
try {
const unlistenStatus = await events.friendUserStatusChanged.listen(
(event) => {
const { userId, status } = event.payload; const { userId, status } = event.payload;
const hasValidName = const hasValidName =
@@ -35,36 +24,21 @@ export async function startUserStatus() {
...current, ...current,
[userId]: status, [userId]: status,
})); }));
}, }),
); );
subscription.addUnlisten(unlistenStatus);
const unlistenUserStatusChanged = await events.userStatusChanged.listen( addEventListener(
(event) => { await events.userStatusChanged.listen((event) => {
currentPresenceState.set(event.payload); currentPresenceState.set(event.payload);
}, }),
); );
subscription.addUnlisten(unlistenUserStatusChanged);
const unlistenFriendDisconnected = await events.friendDisconnected.listen( addEventListener(
(event) => { await events.friendDisconnected.listen((event) => {
const { userId } = event.payload; const { userId } = event.payload;
friendsPresenceStates.update((current) => friendsPresenceStates.update((current) =>
removeFromStore(current, userId), removeFromStore(current, userId),
); );
}, }),
); );
subscription.addUnlisten(unlistenFriendDisconnected); });
subscription.setListening(true);
} catch (error) {
console.error("Failed to initialize user status listeners", error);
throw error;
}
}
export function stopUserStatus() {
subscription.stop();
}
setupHmrCleanup(stopUserStatus);