UI to show modules

This commit is contained in:
2026-02-17 18:19:12 +08:00
parent dcb9012ff6
commit 68c42b34a1
13 changed files with 134 additions and 43 deletions

View File

@@ -1,18 +0,0 @@
use crate::{
lock_r,
models::app_data::AppData,
state::{init_app_data_scoped, AppDataRefreshScope, FDOLL},
};
#[tauri::command]
pub fn get_app_data() -> Result<AppData, String> {
let guard = lock_r!(FDOLL);
Ok(guard.user_data.clone())
}
#[tauri::command]
pub async fn refresh_app_data() -> Result<AppData, String> {
init_app_data_scoped(AppDataRefreshScope::All).await;
let guard = lock_r!(FDOLL);
Ok(guard.user_data.clone())
}

View File

@@ -0,0 +1,25 @@
use crate::{
lock_r,
models::app_data::UserData,
services::presence_modules::models::ModuleMetadata,
state::{init_app_data_scoped, AppDataRefreshScope, FDOLL},
};
#[tauri::command]
pub fn get_app_data() -> Result<UserData, String> {
let guard = lock_r!(FDOLL);
Ok(guard.user_data.clone())
}
#[tauri::command]
pub async fn refresh_app_data() -> Result<UserData, String> {
init_app_data_scoped(AppDataRefreshScope::All).await;
let guard = lock_r!(FDOLL);
Ok(guard.user_data.clone())
}
#[tauri::command]
pub fn get_modules() -> Result<Vec<ModuleMetadata>, String> {
let guard = lock_r!(FDOLL);
Ok(guard.modules.metadatas.clone())
}

View File

@@ -1,5 +1,5 @@
pub mod app; pub mod app;
pub mod app_data; pub mod app_state;
pub mod auth; pub mod auth;
pub mod config; pub mod config;
pub mod dolls; pub mod dolls;

View File

@@ -1,9 +1,12 @@
use crate::services::{ use crate::{
doll_editor::open_doll_editor_window, commands::app_state::get_modules,
scene::{set_pet_menu_state, set_scene_interactive}, services::{
doll_editor::open_doll_editor_window,
scene::{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};
use commands::app_data::{get_app_data, refresh_app_data}; use commands::app_state::{get_app_data, refresh_app_data};
use commands::auth::{change_password, login, logout_and_restart, register, reset_password}; use commands::auth::{change_password, login, logout_and_restart, register, reset_password};
use commands::config::{get_client_config, open_client_config_manager, save_client_config}; use commands::config::{get_client_config, open_client_config_manager, save_client_config};
use commands::dolls::{ use commands::dolls::{
@@ -86,6 +89,7 @@ pub fn run() {
reset_password, reset_password,
logout_and_restart, logout_and_restart,
send_interaction_cmd, send_interaction_cmd,
get_modules
]) ])
.setup(|app| { .setup(|app| {
APP_HANDLE APP_HANDLE

View File

@@ -39,7 +39,7 @@ impl Default for SceneData {
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)] #[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
#[ts(export)] #[ts(export)]
pub struct AppData { pub struct UserData {
pub user: Option<UserProfile>, pub user: Option<UserProfile>,
pub friends: Option<Vec<FriendshipResponseDto>>, pub friends: Option<Vec<FriendshipResponseDto>>,
pub dolls: Option<Vec<DollDto>>, pub dolls: Option<Vec<DollDto>>,

View File

@@ -60,14 +60,7 @@ pub fn init_modules() {
} }
}; };
let state = lock_w!(crate::state::FDOLL); let mut state = lock_w!(crate::state::FDOLL);
let mut state_guard = match state.module_handles.lock() {
Ok(guard) => guard,
Err(e) => {
error!("Failed to lock module handles: {}", e);
return;
}
};
for entry in entries { for entry in entries {
let entry = match entry { let entry = match entry {
@@ -88,7 +81,8 @@ pub fn init_modules() {
if script_path.exists() { if script_path.exists() {
match runtime::spawn_lua_runtime_from_path(&script_path) { match runtime::spawn_lua_runtime_from_path(&script_path) {
Ok(handle) => { Ok(handle) => {
state_guard.push(handle); state.modules.metadatas.push(module_metadata.clone());
state.modules.handles.lock().unwrap().push(handle);
} }
Err(e) => { Err(e) => {
error!( error!(

View File

@@ -10,7 +10,9 @@ pub struct PresenceStatus {
pub graphics_b64: Option<String>, pub graphics_b64: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct ModuleMetadata { pub struct ModuleMetadata {
pub name: String, pub name: String,
pub version: String, pub version: String,

View File

@@ -1,5 +1,7 @@
// in app-core/src/state.rs // in app-core/src/state.rs
use crate::{lock_w, models::app_data::AppData}; use crate::{
lock_w, models::app_data::UserData, services::presence_modules::models::ModuleMetadata,
};
use std::sync::{Arc, LazyLock, RwLock}; use std::sync::{Arc, LazyLock, RwLock};
use tauri::tray::TrayIcon; use tauri::tray::TrayIcon;
use tracing::info; use tracing::info;
@@ -12,14 +14,20 @@ pub use auth::*;
pub use network::*; pub use network::*;
pub use ui::*; pub use ui::*;
#[derive(Default)]
pub struct Modules {
pub handles: std::sync::Mutex<Vec<std::thread::JoinHandle<()>>>,
pub metadatas: Vec<ModuleMetadata>,
}
#[derive(Default)] #[derive(Default)]
pub struct AppState { pub struct AppState {
pub app_config: crate::services::client_config_manager::AppConfig, pub app_config: crate::services::client_config_manager::AppConfig,
pub network: NetworkState, pub network: NetworkState,
pub auth: AuthState, pub auth: AuthState,
pub user_data: AppData, pub user_data: UserData,
pub tray: Option<TrayIcon>, pub tray: Option<TrayIcon>,
pub module_handles: std::sync::Mutex<Vec<std::thread::JoinHandle<()>>>, pub modules: Modules,
} }
// Global application state // Global application state
@@ -36,8 +44,8 @@ pub fn init_app_state() {
guard.app_config = crate::services::client_config_manager::load_app_config(); guard.app_config = crate::services::client_config_manager::load_app_config();
guard.network = init_network_state(); guard.network = init_network_state();
guard.auth = init_auth_state(); guard.auth = init_auth_state();
guard.user_data = AppData::default(); guard.user_data = UserData::default();
guard.module_handles = std::sync::Mutex::new(Vec::new()); guard.modules = Modules::default();
} }
update_display_dimensions_for_scene_state(); update_display_dimensions_for_scene_state();
info!("Initialized FDOLL state (WebSocket client & user data initializing asynchronously)"); info!("Initialized FDOLL state (WebSocket client & user data initializing asynchronously)");

View File

@@ -1,9 +1,9 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { type AppData } from "../types/bindings/AppData"; import { type UserData } from "../types/bindings/UserData";
import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
export let appData = writable<AppData | null>(null); export let appData = writable<UserData | null>(null);
let unlisten: UnlistenFn | null = null; let unlisten: UnlistenFn | null = null;
let isListening = false; let isListening = false;
@@ -12,7 +12,7 @@ export async function initAppDataListener() {
try { try {
if (isListening) return; if (isListening) return;
appData.set(await invoke("get_app_data")); appData.set(await invoke("get_app_data"));
unlisten = await listen<AppData>("app-data-refreshed", (event) => { unlisten = await listen<UserData>("app-data-refreshed", (event) => {
console.log("app-data-refreshed", event.payload); console.log("app-data-refreshed", event.payload);
appData.set(event.payload); appData.set(event.payload);
}); });

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import Friends from "./tabs/friends.svelte"; import Friends from "./tabs/friends.svelte";
import Preferences from "./tabs/preferences.svelte"; import Preferences from "./tabs/preferences.svelte";
import Modules from "./tabs/modules.svelte";
import YourDolls from "./tabs/your-dolls/index.svelte"; import YourDolls from "./tabs/your-dolls/index.svelte";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { onMount } from "svelte"; import { onMount } from "svelte";
@@ -67,6 +68,16 @@
<div class="tab-content bg-base-100 border-base-300 p-4"> <div class="tab-content bg-base-100 border-base-300 p-4">
<Preferences /> <Preferences />
</div> </div>
<input
type="radio"
name="app_menu_tabs"
class="tab"
aria-label="Modules"
/>
<div class="tab-content bg-base-100 border-base-300 p-4">
<Modules />
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,62 @@
<script lang="ts">
import { onMount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import type { ModuleMetadata } from "../../../types/bindings/ModuleMetadata";
let modules: ModuleMetadata[] = [];
let loading = false;
let error: string | null = null;
onMount(async () => {
loading = true;
try {
modules = await invoke("get_modules");
} catch (e) {
error = (e as Error)?.message ?? String(e);
} finally {
loading = false;
}
});
</script>
<div class="modules-page flex flex-col gap-2">
{#if error}
<div class="text-error text-sm">{error}</div>
{/if}
<section class="flex flex-col gap-2">
<div class="collapse bg-base-100 border-base-300 border">
<input type="checkbox" checked />
<div class="collapse-title text-sm opacity-70 py-2">
Loaded Presence Modules
</div>
<div class="collapse-content px-2 -mb-2">
<div class="flex flex-col gap-3">
{#if loading}
<p class="text-sm text-base-content/70">Loading modules...</p>
{:else if modules.length === 0}
<p class="text-sm text-base-content/70">No modules loaded.</p>
{:else}
<div class="flex flex-col gap-2">
{#each modules as module (module.name)}
<div class="card px-3 py-2 bg-base-200/50">
<div class="flex flex-col gap-1">
<div class="font-medium">{module.name}</div>
<div class="text-xs text-base-content/70">
Version: {module.version}
</div>
{#if module.description}
<div class="text-xs text-base-content/60">
{module.description}
</div>
{/if}
</div>
</div>
{/each}
</div>
{/if}
</div>
</div>
</div>
</section>
</div>

View File

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

View File

@@ -4,4 +4,4 @@ import type { FriendshipResponseDto } from "./FriendshipResponseDto";
import type { SceneData } from "./SceneData"; import type { SceneData } from "./SceneData";
import type { UserProfile } from "./UserProfile"; import type { UserProfile } from "./UserProfile";
export type AppData = { user: UserProfile | null, friends: Array<FriendshipResponseDto> | null, dolls: Array<DollDto> | null, scene: SceneData, }; export type UserData = { user: UserProfile | null, friends: Array<FriendshipResponseDto> | null, dolls: Array<DollDto> | null, scene: SceneData, };