migrate from ts-rs to tauri-specta

This commit is contained in:
2026-03-07 18:36:51 +08:00
parent f65d837841
commit 4d7e97771a
86 changed files with 766 additions and 609 deletions

View File

@@ -1,8 +1,5 @@
import { writable } from "svelte/store";
import { type UserData } from "../types/bindings/UserData";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import { AppEvents } from "../types/bindings/AppEventsConstants";
import { commands, events, type UserData } from "$lib/bindings";
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
export const appData = writable<UserData | null>(null);
@@ -16,13 +13,10 @@ const subscription = createListenerSubscription();
export async function startAppData() {
try {
if (subscription.isListening()) return;
appData.set(await invoke("get_app_data"));
const unlisten = await listen<UserData>(
AppEvents.AppDataRefreshed,
(event) => {
appData.set(event.payload);
},
);
appData.set(await commands.getAppData());
const unlisten = await events.appDataRefreshed.listen((event) => {
appData.set(event.payload);
});
subscription.setUnlisten(unlisten);
subscription.setListening(true);
} catch (error) {

View File

@@ -1,8 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { writable } from "svelte/store";
import type { CursorPositions } from "../types/bindings/CursorPositions";
import { AppEvents } from "../types/bindings/AppEventsConstants";
import { events, type CursorPositions } from "$lib/bindings";
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
export const cursorPositionOnScreen = writable<CursorPositions>({
@@ -20,12 +17,9 @@ export async function startCursorTracking() {
if (subscription.isListening()) return;
try {
const unlisten = await listen<CursorPositions>(
AppEvents.CursorPosition,
(event) => {
cursorPositionOnScreen.set(event.payload);
},
);
const unlisten = await events.cursorMoved.listen((event) => {
cursorPositionOnScreen.set(event.payload);
});
subscription.setUnlisten(unlisten);
subscription.setListening(true);
} catch (err) {

View File

@@ -1,21 +1,18 @@
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 type { FriendDisconnectedPayload } from "../types/bindings/FriendDisconnectedPayload";
import type { FriendActiveDollChangedPayload } from "../types/bindings/FriendActiveDollChangedPayload";
import { AppEvents } from "../types/bindings/AppEventsConstants";
import {
events,
type CursorPositions,
type DollDto,
type FriendActiveDollChangedPayload,
type FriendDisconnectedPayload,
type OutgoingFriendCursorPayload,
} from "$lib/bindings";
import {
createMultiListenerSubscription,
removeFromStore,
setupHmrCleanup,
} from "./listener-utils";
export type FriendCursorPosition = {
userId: string;
position: CursorPositions;
};
type FriendCursorData = {
position: CursorPositions;
lastUpdated: number;
@@ -40,10 +37,9 @@ export async function startFriendCursorTracking() {
try {
// TODO: Add initial sync for existing friends' cursors and dolls if needed
const unlistenFriendCursor = await listen<FriendCursorPosition>(
AppEvents.FriendCursorPosition,
const unlistenFriendCursor = await events.friendCursorPositionUpdated.listen(
(event) => {
const data = event.payload;
const data: OutgoingFriendCursorPayload = event.payload;
friendCursorState[data.userId] = {
position: data.position,
@@ -60,8 +56,7 @@ export async function startFriendCursorTracking() {
);
subscription.addUnlisten(unlistenFriendCursor);
const unlistenFriendDisconnected = await listen<FriendDisconnectedPayload>(
AppEvents.FriendDisconnected,
const unlistenFriendDisconnected = await events.friendDisconnected.listen(
(event) => {
const data = event.payload;
@@ -77,8 +72,7 @@ export async function startFriendCursorTracking() {
subscription.addUnlisten(unlistenFriendDisconnected);
const unlistenFriendActiveDollChanged =
await listen<FriendActiveDollChangedPayload>(
AppEvents.FriendActiveDollChanged,
await events.friendActiveDollChanged.listen(
(event) => {
const payload = event.payload;

View File

@@ -1,8 +1,9 @@
import { listen } from "@tauri-apps/api/event";
import { writable } from "svelte/store";
import type { InteractionPayloadDto } from "../types/bindings/InteractionPayloadDto";
import type { InteractionDeliveryFailedDto } from "../types/bindings/InteractionDeliveryFailedDto";
import { AppEvents } from "../types/bindings/AppEventsConstants";
import {
events,
type InteractionDeliveryFailedDto,
type InteractionPayloadDto,
} from "$lib/bindings";
import {
createMultiListenerSubscription,
setupHmrCleanup,
@@ -37,16 +38,12 @@ export async function startInteraction() {
if (subscription.isListening()) return;
try {
const unlistenReceived = await listen<InteractionPayloadDto>(
AppEvents.InteractionReceived,
(event) => {
addInteraction(event.payload);
},
);
const unlistenReceived = await events.interactionReceived.listen((event) => {
addInteraction(event.payload);
});
subscription.addUnlisten(unlistenReceived);
const unlistenFailed = await listen<InteractionDeliveryFailedDto>(
AppEvents.InteractionDeliveryFailed,
const unlistenFailed = await events.interactionDeliveryFailed.listen(
(event) => {
console.error("Interaction delivery failed:", event.payload);
alert(

View File

@@ -1,7 +1,5 @@
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import { writable } from "svelte/store";
import { AppEvents } from "../types/bindings/AppEventsConstants";
import { commands, events } from "$lib/bindings";
import { createListenerSubscription, setupHmrCleanup } from "./listener-utils";
export const sceneInteractive = writable<boolean>(false);
@@ -16,13 +14,10 @@ export async function startSceneInteractive() {
if (subscription.isListening()) return;
try {
sceneInteractive.set(await invoke("get_scene_interactive"));
const unlisten = await listen<boolean>(
AppEvents.SceneInteractive,
(event) => {
sceneInteractive.set(Boolean(event.payload));
},
);
sceneInteractive.set(await commands.getSceneInteractive());
const unlisten = await events.sceneInteractiveChanged.listen((event) => {
sceneInteractive.set(Boolean(event.payload));
});
subscription.setUnlisten(unlisten);
subscription.setListening(true);
} catch (error) {

View File

@@ -1,9 +1,9 @@
import { listen } from "@tauri-apps/api/event";
import { writable } from "svelte/store";
import type { FriendDisconnectedPayload } from "../types/bindings/FriendDisconnectedPayload";
import type { FriendUserStatusPayload } from "../types/bindings/FriendUserStatusPayload";
import type { UserStatusPayload } from "../types/bindings/UserStatusPayload";
import { AppEvents } from "../types/bindings/AppEventsConstants";
import {
events,
type FriendDisconnectedPayload,
type UserStatusPayload,
} from "$lib/bindings";
import {
createMultiListenerSubscription,
removeFromStore,
@@ -24,36 +24,31 @@ export async function startUserStatus() {
if (subscription.isListening()) return;
try {
const unlistenStatus = await listen<FriendUserStatusPayload>(
AppEvents.FriendUserStatus,
(event) => {
const { userId, status } = event.payload;
const unlistenStatus = await events.friendUserStatusChanged.listen((event) => {
const { userId, status } = event.payload;
const hasValidName =
(typeof status.presenceStatus.title === "string" &&
status.presenceStatus.title.trim() !== "") ||
(typeof status.presenceStatus.subtitle === "string" &&
status.presenceStatus.subtitle.trim() !== "");
if (!hasValidName) return;
const hasValidName =
(typeof status.presenceStatus.title === "string" &&
status.presenceStatus.title.trim() !== "") ||
(typeof status.presenceStatus.subtitle === "string" &&
status.presenceStatus.subtitle.trim() !== "");
if (!hasValidName) return;
friendsPresenceStates.update((current) => ({
...current,
[userId]: status,
}));
},
);
friendsPresenceStates.update((current) => ({
...current,
[userId]: status,
}));
});
subscription.addUnlisten(unlistenStatus);
const unlistenUserStatusChanged = await listen<UserStatusPayload>(
AppEvents.UserStatusChanged,
const unlistenUserStatusChanged = await events.userStatusChanged.listen(
(event) => {
currentPresenceState.set(event.payload);
},
);
subscription.addUnlisten(unlistenUserStatusChanged);
const unlistenFriendDisconnected = await listen<FriendDisconnectedPayload>(
AppEvents.FriendDisconnected,
const unlistenFriendDisconnected = await events.friendDisconnected.listen(
(event) => {
const { userId } = event.payload;
friendsPresenceStates.update((current) =>

281
src/lib/bindings.ts Normal file
View File

@@ -0,0 +1,281 @@
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
/** user-defined commands **/
export const commands = {
async getAppData() : Promise<UserData> {
return await TAURI_INVOKE("get_app_data");
},
async refreshAppData() : Promise<UserData> {
return await TAURI_INVOKE("refresh_app_data");
},
async listFriends() : Promise<FriendshipResponseDto[]> {
return await TAURI_INVOKE("list_friends");
},
async searchUsers(username: string | null) : Promise<UserBasicDto[]> {
return await TAURI_INVOKE("search_users", { username });
},
async sendFriendRequest(request: SendFriendRequestDto) : Promise<FriendRequestResponseDto> {
return await TAURI_INVOKE("send_friend_request", { request });
},
async receivedFriendRequests() : Promise<FriendRequestResponseDto[]> {
return await TAURI_INVOKE("received_friend_requests");
},
async sentFriendRequests() : Promise<FriendRequestResponseDto[]> {
return await TAURI_INVOKE("sent_friend_requests");
},
async acceptFriendRequest(requestId: string) : Promise<FriendRequestResponseDto> {
return await TAURI_INVOKE("accept_friend_request", { requestId });
},
async denyFriendRequest(requestId: string) : Promise<FriendRequestResponseDto> {
return await TAURI_INVOKE("deny_friend_request", { requestId });
},
async unfriend(friendId: string) : Promise<null> {
return await TAURI_INVOKE("unfriend", { friendId });
},
async getDolls() : Promise<DollDto[]> {
return await TAURI_INVOKE("get_dolls");
},
async getDoll(id: string) : Promise<DollDto> {
return await TAURI_INVOKE("get_doll", { id });
},
async createDoll(dto: CreateDollDto) : Promise<DollDto> {
return await TAURI_INVOKE("create_doll", { dto });
},
async updateDoll(id: string, dto: UpdateDollDto) : Promise<DollDto> {
return await TAURI_INVOKE("update_doll", { id, dto });
},
async deleteDoll(id: string) : Promise<null> {
return await TAURI_INVOKE("delete_doll", { id });
},
async setActiveDoll(dollId: string) : Promise<null> {
return await TAURI_INVOKE("set_active_doll", { dollId });
},
async removeActiveDoll() : Promise<null> {
return await TAURI_INVOKE("remove_active_doll");
},
async recolorGifBase64(whiteColorHex: string, blackColorHex: string, applyTexture: boolean) : Promise<string> {
return await TAURI_INVOKE("recolor_gif_base64", { whiteColorHex, blackColorHex, applyTexture });
},
async encodePetDollGifBase64(doll: DollDto) : Promise<string> {
return await TAURI_INVOKE("encode_pet_doll_gif_base64", { doll });
},
async quitApp() : Promise<null> {
return await TAURI_INVOKE("quit_app");
},
async restartApp() : Promise<void> {
await TAURI_INVOKE("restart_app");
},
/**
* Attempt to re-establish the user session without restarting the app.
*
* Validates server health, checks for a valid session token,
* then reconstructs the user session (re-fetches app data + WebSocket).
*/
async retryConnection() : Promise<null> {
return await TAURI_INVOKE("retry_connection");
},
async getClientConfig() : Promise<AppConfig> {
return await TAURI_INVOKE("get_client_config");
},
async saveClientConfig(config: AppConfig) : Promise<null> {
return await TAURI_INVOKE("save_client_config", { config });
},
async openClientConfigManager() : Promise<null> {
return await TAURI_INVOKE("open_client_config_manager");
},
async openDollEditorWindow(dollId: string | null) : Promise<void> {
await TAURI_INVOKE("open_doll_editor_window", { dollId });
},
async getSceneInteractive() : Promise<boolean> {
return await TAURI_INVOKE("get_scene_interactive");
},
async setSceneInteractive(interactive: boolean, shouldClick: boolean) : Promise<void> {
await TAURI_INVOKE("set_scene_interactive", { interactive, shouldClick });
},
async setPetMenuState(id: string, open: boolean) : Promise<void> {
await TAURI_INVOKE("set_pet_menu_state", { id, open });
},
async login(email: string, password: string) : Promise<null> {
return await TAURI_INVOKE("login", { email, password });
},
async register(email: string, password: string, name: string | null, username: string | null) : Promise<string> {
return await TAURI_INVOKE("register", { email, password, name, username });
},
async changePassword(currentPassword: string, newPassword: string) : Promise<null> {
return await TAURI_INVOKE("change_password", { currentPassword, newPassword });
},
async resetPassword(oldPassword: string, newPassword: string) : Promise<null> {
return await TAURI_INVOKE("reset_password", { oldPassword, newPassword });
},
async logoutAndRestart() : Promise<null> {
return await TAURI_INVOKE("logout_and_restart");
},
async sendInteractionCmd(dto: SendInteractionDto) : Promise<null> {
return await TAURI_INVOKE("send_interaction_cmd", { dto });
},
async getModules() : Promise<ModuleMetadata[]> {
return await TAURI_INVOKE("get_modules");
}
}
/** user-defined events **/
export const events = __makeEvents__<{
appDataRefreshed: AppDataRefreshed,
createDoll: CreateDoll,
cursorMoved: CursorMoved,
editDoll: EditDoll,
friendActiveDollChanged: FriendActiveDollChanged,
friendCursorPositionUpdated: FriendCursorPositionUpdated,
friendDisconnected: FriendDisconnected,
friendRequestAccepted: FriendRequestAccepted,
friendRequestDenied: FriendRequestDenied,
friendRequestReceived: FriendRequestReceived,
friendUserStatusChanged: FriendUserStatusChanged,
interactionDeliveryFailed: InteractionDeliveryFailed,
interactionReceived: InteractionReceived,
sceneInteractiveChanged: SceneInteractiveChanged,
setInteractionOverlay: SetInteractionOverlay,
unfriended: Unfriended,
userStatusChanged: UserStatusChanged
}>({
appDataRefreshed: "app-data-refreshed",
createDoll: "create-doll",
cursorMoved: "cursor-moved",
editDoll: "edit-doll",
friendActiveDollChanged: "friend-active-doll-changed",
friendCursorPositionUpdated: "friend-cursor-position-updated",
friendDisconnected: "friend-disconnected",
friendRequestAccepted: "friend-request-accepted",
friendRequestDenied: "friend-request-denied",
friendRequestReceived: "friend-request-received",
friendUserStatusChanged: "friend-user-status-changed",
interactionDeliveryFailed: "interaction-delivery-failed",
interactionReceived: "interaction-received",
sceneInteractiveChanged: "scene-interactive-changed",
setInteractionOverlay: "set-interaction-overlay",
unfriended: "unfriended",
userStatusChanged: "user-status-changed"
})
/** user-defined constants **/
/** user-defined types **/
export type AppConfig = { api_base_url: string | null }
export type AppDataRefreshed = UserData
export type CreateDoll = null
export type CreateDollDto = { name: string; configuration: DollConfigurationDto | null }
export type CursorMoved = CursorPositions
export type CursorPosition = { x: number; y: number }
export type CursorPositions = { raw: CursorPosition; mapped: CursorPosition }
export type DisplayData = { screen_width: number; screen_height: number; monitor_scale_factor: number }
export type DollColorSchemeDto = { outline: string; body: string }
export type DollConfigurationDto = { colorScheme: DollColorSchemeDto }
export type DollDto = { id: string; name: string; configuration: DollConfigurationDto; createdAt: string; updatedAt: string }
export type EditDoll = string
export type FriendActiveDollChanged = FriendActiveDollChangedPayload
export type FriendActiveDollChangedPayload = { friendId: string; doll: DollDto | null }
export type FriendCursorPositionUpdated = OutgoingFriendCursorPayload
export type FriendDisconnected = FriendDisconnectedPayload
export type FriendDisconnectedPayload = { userId: string }
export type FriendRequestAccepted = FriendRequestAcceptedPayload
export type FriendRequestAcceptedPayload = { id: string; friend: UserBasicDto; acceptedAt: string }
export type FriendRequestDenied = FriendRequestDeniedPayload
export type FriendRequestDeniedPayload = { id: string; denier: UserBasicDto; deniedAt: string }
export type FriendRequestReceived = FriendRequestReceivedPayload
export type FriendRequestReceivedPayload = { id: string; sender: UserBasicDto; createdAt: string }
export type FriendRequestResponseDto = { id: string; sender: UserBasicDto; receiver: UserBasicDto; status: string; createdAt: string; updatedAt: string }
export type FriendUserStatusChanged = FriendUserStatusPayload
export type FriendUserStatusPayload = { userId: string; status: UserStatusPayload }
export type FriendshipResponseDto = { id: string; friend: UserBasicDto | null; createdAt: string }
export type InteractionDeliveryFailed = InteractionDeliveryFailedDto
export type InteractionDeliveryFailedDto = { recipientUserId: string; reason: string }
export type InteractionPayloadDto = { senderUserId: string; senderName: string; content: string; type: string; timestamp: string }
export type InteractionReceived = InteractionPayloadDto
export type ModuleMetadata = { id: string; name: string; version: string; description: string | null }
/**
* Outgoing friend cursor position to frontend
*/
export type OutgoingFriendCursorPayload = { userId: string; position: CursorPositions }
export type PresenceStatus = { title: string | null; subtitle: string | null; graphicsB64: string | null }
export type SceneData = { display: DisplayData; grid_size: number }
export type SceneInteractiveChanged = boolean
export type SendFriendRequestDto = { receiverId: string }
export type SendInteractionDto = { recipientUserId: string; content: string; type: string }
export type SetInteractionOverlay = boolean
export type Unfriended = UnfriendedPayload
export type UnfriendedPayload = { friendId: string }
export type UpdateDollDto = { name: string | null; configuration: DollConfigurationDto | null }
export type UserBasicDto = { id: string; name: string; username: string | null; activeDoll: DollDto | null }
export type UserData = { user: UserProfile | null; friends: FriendshipResponseDto[] | null; dolls: DollDto[] | null; scene: SceneData }
export type UserProfile = { id: string; name: string; email: string; username: string | null; roles: string[]; createdAt: string; updatedAt: string; lastLoginAt: string | null; activeDollId: string | null }
export type UserStatusChanged = UserStatusPayload
export type UserStatusPayload = { presenceStatus: PresenceStatus; state: UserStatusState }
export type UserStatusState = "idle" | "resting"
/** tauri-specta globals **/
import {
invoke as TAURI_INVOKE,
Channel as TAURI_CHANNEL,
} from "@tauri-apps/api/core";
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
type __EventObj__<T> = {
listen: (
cb: TAURI_API_EVENT.EventCallback<T>,
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
once: (
cb: TAURI_API_EVENT.EventCallback<T>,
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
emit: null extends T
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
};
export type Result<T, E> =
| { status: "ok"; data: T }
| { status: "error"; error: E };
function __makeEvents__<T extends Record<string, any>>(
mappings: Record<keyof T, string>,
) {
return new Proxy(
{} as unknown as {
[K in keyof T]: __EventObj__<T[K]> & {
(handle: __WebviewWindow__): __EventObj__<T[K]>;
};
},
{
get: (_, event) => {
const name = mappings[event as keyof T];
return new Proxy((() => {}) as any, {
apply: (_, __, [window]: [__WebviewWindow__]) => ({
listen: (arg: any) => window.listen(name, arg),
once: (arg: any) => window.once(name, arg),
emit: (arg: any) => window.emit(name, arg),
}),
get: (_, command: keyof __EventObj__<any>) => {
switch (command) {
case "listen":
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
case "once":
return (arg: any) => TAURI_API_EVENT.once(name, arg);
case "emit":
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
}
},
});
},
},
);
}

View File

@@ -1,4 +1,4 @@
import { invoke } from "@tauri-apps/api/core";
import { commands } from "$lib/bindings";
import onekoGif from "../../assets/oneko/oneko.gif";
export interface RecolorOptions {
@@ -15,11 +15,11 @@ export async function getSpriteSheetUrl(
}
try {
const result = await invoke<string>("recolor_gif_base64", {
whiteColorHex: options.bodyColor,
blackColorHex: options.outlineColor,
applyTexture: options.applyTexture ?? true,
});
const result = await commands.recolorGifBase64(
options.bodyColor,
options.outlineColor,
options.applyTexture ?? true,
);
return `data:image/gif;base64,${result}`;
} catch (e) {
console.error("Failed to recolor sprite:", e);

View File

@@ -3,14 +3,13 @@
import Preferences from "./tabs/preferences.svelte";
import Modules from "./tabs/modules.svelte";
import YourDolls from "./tabs/your-dolls/index.svelte";
import { listen } from "@tauri-apps/api/event";
import { events } from "$lib/bindings";
import { onMount } from "svelte";
import { AppEvents } from "../../types/bindings/AppEventsConstants";
let showInteractionOverlay = false;
onMount(() => {
const unlisten = listen(AppEvents.SetInteractionOverlay, (event) => {
const unlisten = events.setInteractionOverlay.listen((event) => {
showInteractionOverlay = event.payload as boolean;
});

View File

@@ -1,12 +1,13 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import {
commands,
events,
type FriendRequestResponseDto,
type FriendshipResponseDto,
type UserBasicDto,
} from "$lib/bindings";
import { appData } from "../../../events/app-data";
import { AppEvents } from "../../../types/bindings/AppEventsConstants";
import type { FriendRequestResponseDto } from "../../../types/bindings/FriendRequestResponseDto.js";
import type { FriendshipResponseDto } from "../../../types/bindings/FriendshipResponseDto.js";
import type { UserBasicDto } from "../../../types/bindings/UserBasicDto.js";
let received: FriendRequestResponseDto[] = [];
let sent: FriendRequestResponseDto[] = [];
@@ -51,27 +52,27 @@
refreshSent();
unlisteners.push(
await listen(AppEvents.FriendRequestReceived, () => {
await events.friendRequestReceived.listen(() => {
refreshReceived();
}),
);
unlisteners.push(
await listen(AppEvents.FriendRequestAccepted, () => {
await events.friendRequestAccepted.listen(() => {
refreshSent();
invoke("refresh_app_data");
commands.refreshAppData();
}),
);
unlisteners.push(
await listen(AppEvents.FriendRequestDenied, () => {
await events.friendRequestDenied.listen(() => {
refreshSent();
}),
);
unlisteners.push(
await listen(AppEvents.Unfriended, () => {
invoke("refresh_app_data");
await events.unfriended.listen(() => {
commands.refreshAppData();
}),
);
});
@@ -83,7 +84,7 @@
async function refreshReceived() {
loading.received = true;
try {
received = await invoke("received_friend_requests");
received = await commands.receivedFriendRequests();
} catch (e) {
error = (e as Error)?.message ?? String(e);
} finally {
@@ -94,7 +95,7 @@
async function refreshSent() {
loading.sent = true;
try {
sent = await invoke("sent_friend_requests");
sent = await commands.sentFriendRequests();
} catch (e) {
error = (e as Error)?.message ?? String(e);
} finally {
@@ -105,8 +106,8 @@
async function handleAccept(id: string) {
loading.action = true;
try {
await invoke("accept_friend_request", { requestId: id });
await Promise.all([refreshReceived(), invoke("refresh_app_data")]);
await commands.acceptFriendRequest(id);
await Promise.all([refreshReceived(), commands.refreshAppData()]);
} catch (e) {
error = (e as Error)?.message ?? String(e);
} finally {
@@ -117,7 +118,7 @@
async function handleDeny(id: string) {
loading.action = true;
try {
await invoke("deny_friend_request", { requestId: id });
await commands.denyFriendRequest(id);
await refreshReceived();
} catch (e) {
error = (e as Error)?.message ?? String(e);
@@ -129,8 +130,8 @@
async function handleUnfriend(friendId: string) {
loading.action = true;
try {
await invoke("unfriend", { friendId });
await invoke("refresh_app_data");
await commands.unfriend(friendId);
await commands.refreshAppData();
} catch (e) {
error = (e as Error)?.message ?? String(e);
} finally {
@@ -157,9 +158,7 @@
error = null;
try {
const results = await invoke<UserBasicDto[]>("search_users", {
username: sanitizedTerm,
});
const results = await commands.searchUsers(sanitizedTerm);
const match = results.find(
(user) => user.username?.toLowerCase() === normalizedTerm,
);
@@ -181,9 +180,7 @@
async function handleSendRequest(receiverId: string) {
loading.action = true;
try {
await invoke("send_friend_request", {
request: { receiverId },
});
await commands.sendFriendRequest({ receiverId });
await refreshSent();
} catch (e) {
const msg = (e as Error)?.message ?? String(e);

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import type { ModuleMetadata } from "../../../types/bindings/ModuleMetadata";
import { commands, type ModuleMetadata } from "$lib/bindings";
let modules: ModuleMetadata[] = [];
let loading = false;
@@ -10,7 +9,7 @@
onMount(async () => {
loading = true;
try {
modules = await invoke("get_modules");
modules = await commands.getModules();
} catch (e) {
error = (e as Error)?.message ?? String(e);
} finally {

View File

@@ -1,5 +1,5 @@
<script>
import { invoke } from "@tauri-apps/api/core";
<script lang="ts">
import { commands } from "$lib/bindings";
import { appData } from "../../../events/app-data";
import Power from "../../../assets/icons/power.svelte";
@@ -17,7 +17,7 @@
if (signingOut) return;
signingOut = true;
try {
await invoke("logout_and_restart");
await commands.logoutAndRestart();
} catch (error) {
console.error("Failed to sign out", error);
signingOut = false;
@@ -26,7 +26,7 @@
const openClientConfigManager = async () => {
try {
await invoke("open_client_config_manager");
await commands.openClientConfigManager();
} catch (error) {
console.error("Failed to open client config manager", error);
}
@@ -49,10 +49,10 @@
isChangingPassword = true;
try {
await invoke("change_password", {
currentPassword: passwordForm.currentPassword,
newPassword: passwordForm.newPassword,
});
await commands.changePassword(
passwordForm.currentPassword,
passwordForm.newPassword,
);
passwordSuccess = "Password updated";
passwordForm.currentPassword = "";
passwordForm.newPassword = "";
@@ -131,7 +131,7 @@
<button
class="btn btn-error btn-square btn-soft"
onclick={async () => {
await invoke("quit_app");
await commands.quitApp();
}}
>
<div class="scale-50">

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import type { DollDto } from "../../../../types/bindings/DollDto";
import type { UserProfile } from "../../../../types/bindings/UserProfile";
import type { DollDto, UserProfile } from "$lib/bindings";
import DollPreview from "../../components/doll-preview.svelte";
import PawPrint from "../../../../assets/icons/paw-print.svelte";
import Backpack from "../../../../assets/icons/backpack.svelte";

View File

@@ -1,8 +1,6 @@
<script lang="ts">
import { invoke } from "@tauri-apps/api/core";
import { commands, type DollDto, type UserProfile } from "$lib/bindings";
import { appData } from "../../../../events/app-data";
import type { DollDto } from "../../../../types/bindings/DollDto";
import type { UserProfile } from "../../../../types/bindings/UserProfile";
import DollsList from "./dolls-list.svelte";
let loading = false;
@@ -16,17 +14,17 @@
$: initialLoading = $appData === null;
async function openCreateModal() {
await invoke("open_doll_editor_window", { dollId: null });
await commands.openDollEditorWindow(null);
}
async function openEditModal(doll: DollDto) {
await invoke("open_doll_editor_window", { dollId: doll.id });
await commands.openDollEditorWindow(doll.id);
}
async function handleSetActiveDoll(dollId: string) {
try {
loading = true;
await invoke("set_active_doll", { dollId });
await commands.setActiveDoll(dollId);
// No manual refresh needed - backend will refresh and emit app-data-refreshed
} catch (e) {
error = (e as Error)?.message ?? String(e);
@@ -38,7 +36,7 @@
async function handleRemoveActiveDoll() {
try {
loading = true;
await invoke("remove_active_doll");
await commands.removeActiveDoll();
// No manual refresh needed - backend will refresh and emit app-data-refreshed
} catch (e) {
error = (e as Error)?.message ?? String(e);

View File

@@ -1,10 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
type AppConfig = {
api_base_url?: string | null;
};
import { commands, type AppConfig } from "$lib/bindings";
let form: AppConfig = {
api_base_url: "",
@@ -17,7 +13,7 @@
const loadConfig = async () => {
try {
const config = (await invoke("get_client_config")) as AppConfig;
const config = await commands.getClientConfig();
form = {
api_base_url: config.api_base_url ?? "",
};
@@ -55,10 +51,8 @@
successMessage = "";
restartError = "";
try {
await invoke("save_client_config", {
config: {
api_base_url: form.api_base_url?.trim() || null,
},
await commands.saveClientConfig({
api_base_url: form.api_base_url?.trim() || null,
});
successMessage = "Success. Restart to apply changes.";
@@ -72,7 +66,7 @@
const restart = async () => {
restartError = "";
try {
await invoke("restart_app");
await commands.restartApp();
} catch (err) {
restartError = `Restart failed: ${err}`;
}

View File

@@ -1,10 +1,12 @@
<script lang="ts">
import { onMount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import {
commands,
type CreateDollDto,
type DollDto,
type UpdateDollDto,
} from "$lib/bindings";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import type { DollDto } from "../../types/bindings/DollDto";
import type { CreateDollDto } from "../../types/bindings/CreateDollDto";
import type { UpdateDollDto } from "../../types/bindings/UpdateDollDto";
import DollPreview from "../app-menu/components/doll-preview.svelte";
let mode: "create" | "edit" = "create";
@@ -35,7 +37,7 @@
async function fetchDoll(id: string) {
loading = true;
try {
const doll: DollDto = await invoke("get_doll", { id });
const doll: DollDto = await commands.getDoll(id);
name = doll.name;
bodyColor = doll.configuration.colorScheme.body;
outlineColor = doll.configuration.colorScheme.outline;
@@ -62,7 +64,7 @@
},
},
};
await invoke("create_doll", { dto });
await commands.createDoll(dto);
} else if (dollId) {
const dto: UpdateDollDto = {
name,
@@ -73,7 +75,7 @@
},
},
};
await invoke("update_doll", { id: dollId, dto });
await commands.updateDoll(dollId, dto);
}
// Close window on success

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import { commands } from "$lib/bindings";
import { page } from "$app/stores";
let errorMessage = "";
@@ -15,7 +15,7 @@
isRetrying = true;
errorMessage = "";
try {
await invoke("retry_connection");
await commands.retryConnection();
} catch (err) {
errorMessage = `${err}`;
isRetrying = false;
@@ -55,7 +55,7 @@
class="btn btn-outline"
onclick={async () => {
try {
await invoke("open_client_config_manager");
await commands.openClientConfigManager();
} catch (err) {
errorMessage = `Failed to open config manager: ${err}`;
}

View File

@@ -7,7 +7,7 @@
friendsPresenceStates,
currentPresenceState,
} from "../../events/user-status";
import { invoke } from "@tauri-apps/api/core";
import { commands } from "$lib/bindings";
import DebugBar from "./components/debug-bar.svelte";
</script>
@@ -16,10 +16,7 @@
class="absolute inset-0 z-10 size-full"
aria-label="Deactive scene interactive"
onmousedown={async () => {
await invoke("set_scene_interactive", {
interactive: false,
shouldClick: true,
});
await commands.setSceneInteractive(false, true);
}}>&nbsp;</button
>
<div id="debug-bar">

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import type { PresenceStatus } from "../../../types/bindings/PresenceStatus";
import type { UserStatusPayload } from "../../../types/bindings/UserStatusPayload";
import type { PresenceStatus, UserStatusPayload } from "$lib/bindings";
interface Friend {
friend?: {

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { invoke } from "@tauri-apps/api/core";
import { commands } from "$lib/bindings";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import DollPreview from "../app-menu/components/doll-preview.svelte";
import ExternalLink from "../../assets/icons/external-link.svelte";
@@ -31,22 +31,19 @@
errorMessage = "";
try {
if (useRegister) {
await invoke("register", {
email: form.email.trim(),
password: form.password,
name: form.name.trim() || null,
username: form.username.trim() || null,
});
await commands.register(
form.email.trim(),
form.password,
form.name.trim() || null,
form.username.trim() || null,
);
useRegister = false;
resetRegisterFields();
form.password = "";
return;
}
await invoke("login", {
email: form.email.trim(),
password: form.password,
});
await commands.login(form.email.trim(), form.password);
await getCurrentWebviewWindow().close();
} catch (error) {
console.error("Failed to authenticate", error);
@@ -62,7 +59,7 @@
const openClientConfigManager = async () => {
try {
await invoke("open_client_config_manager");
await commands.openClientConfigManager();
} catch (error) {
console.error("Failed to open client config manager", error);
}

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type AppEvents = "cursor-position" | "scene-interactive" | "app-data-refreshed" | "set-interaction-overlay" | "edit-doll" | "create-doll" | "user-status-changed" | "friend-cursor-position" | "friend-disconnected" | "friend-active-doll-changed" | "friend-user-status" | "interaction-received" | "interaction-delivery-failed" | "friend-request-received" | "friend-request-accepted" | "friend-request-denied" | "unfriended";

View File

@@ -1,24 +0,0 @@
// Auto-generated constants - DO NOT EDIT
// Generated from Rust AppEvents enum
export const AppEvents = {
CursorPosition: "cursor-position",
SceneInteractive: "scene-interactive",
AppDataRefreshed: "app-data-refreshed",
SetInteractionOverlay: "set-interaction-overlay",
EditDoll: "edit-doll",
CreateDoll: "create-doll",
UserStatusChanged: "user-status-changed",
FriendCursorPosition: "friend-cursor-position",
FriendDisconnected: "friend-disconnected",
FriendActiveDollChanged: "friend-active-doll-changed",
FriendUserStatus: "friend-user-status",
InteractionReceived: "interaction-received",
InteractionDeliveryFailed: "interaction-delivery-failed",
FriendRequestReceived: "friend-request-received",
FriendRequestAccepted: "friend-request-accepted",
FriendRequestDenied: "friend-request-denied",
Unfriended: "unfriended",
} as const;
export type AppEvents = typeof AppEvents[keyof typeof AppEvents];

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DollConfigurationDto } from "./DollConfigurationDto";
export type CreateDollDto = { name: string, configuration: DollConfigurationDto | null, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CursorPosition = { x: number, y: number, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CursorPosition } from "./CursorPosition";
export type CursorPositions = { raw: CursorPosition, mapped: CursorPosition, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type DisplayData = { screen_width: number, screen_height: number, monitor_scale_factor: number, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type DollColorSchemeDto = { outline: string, body: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DollColorSchemeDto } from "./DollColorSchemeDto";
export type DollConfigurationDto = { colorScheme: DollColorSchemeDto, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DollConfigurationDto } from "./DollConfigurationDto";
export type DollDto = { id: string, name: string, configuration: DollConfigurationDto, createdAt: string, updatedAt: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DollDto } from "./DollDto";
export type FriendActiveDollChangedPayload = { friendId: string, doll: DollDto | null, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FriendDisconnectedPayload = { userId: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UserBasicDto } from "./UserBasicDto";
export type FriendRequestAcceptedPayload = { id: string, friend: UserBasicDto, acceptedAt: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UserBasicDto } from "./UserBasicDto";
export type FriendRequestDeniedPayload = { id: string, denier: UserBasicDto, deniedAt: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UserBasicDto } from "./UserBasicDto";
export type FriendRequestReceivedPayload = { id: string, sender: UserBasicDto, createdAt: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UserBasicDto } from "./UserBasicDto";
export type FriendRequestResponseDto = { id: string, sender: UserBasicDto, receiver: UserBasicDto, status: string, createdAt: string, updatedAt: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UserStatusPayload } from "./UserStatusPayload";
export type FriendUserStatusPayload = { userId: string, status: UserStatusPayload, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UserBasicDto } from "./UserBasicDto";
export type FriendshipResponseDto = { id: string, friend: UserBasicDto | null, createdAt: string, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HealthResponseDto = { status: string, version: string, uptimeSecs: bigint, db: string, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InteractionDeliveryFailedDto = { recipientUserId: string, reason: string, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InteractionPayloadDto = { senderUserId: string, senderName: string, content: string, type: string, timestamp: string, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ModuleMetadata = { id: string, name: string, version: string, description: string | null, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PresenceStatus = { title: string | null, subtitle: string | null, graphicsB64: string | null, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DisplayData } from "./DisplayData";
export type SceneData = { display: DisplayData, grid_size: number, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SendFriendRequestDto = { receiverId: string, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SendInteractionDto = { recipientUserId: string, content: string, type: string, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type UnfriendedPayload = { friendId: string, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DollConfigurationDto } from "./DollConfigurationDto";
export type UpdateDollDto = { name: string | null, configuration: DollConfigurationDto | null, };

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DollDto } from "./DollDto";
export type UserBasicDto = { id: string, name: string, username: string | null, activeDoll: DollDto | null, };

View File

@@ -1,7 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DollDto } from "./DollDto";
import type { FriendshipResponseDto } from "./FriendshipResponseDto";
import type { SceneData } from "./SceneData";
import type { UserProfile } from "./UserProfile";
export type UserData = { user: UserProfile | null, friends: Array<FriendshipResponseDto> | null, dolls: Array<DollDto> | null, scene: SceneData, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type UserProfile = { id: string, name: string, email: string, username: string | null, roles: Array<string>, createdAt: string, updatedAt: string, lastLoginAt: string | null, activeDollId: string | null, };

View File

@@ -1,5 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PresenceStatus } from "./PresenceStatus";
import type { UserStatusState } from "./UserStatusState";
export type UserStatusPayload = { presenceStatus: PresenceStatus, state: UserStatusState, };

View File

@@ -1,3 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type UserStatusState = "idle" | "resting";