UI to show modules
This commit is contained in:
@@ -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())
|
||||
}
|
||||
25
src-tauri/src/commands/app_state.rs
Normal file
25
src-tauri/src/commands/app_state.rs
Normal 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())
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod app;
|
||||
pub mod app_data;
|
||||
pub mod app_state;
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod dolls;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::services::{
|
||||
doll_editor::open_doll_editor_window,
|
||||
scene::{set_pet_menu_state, set_scene_interactive},
|
||||
use crate::{
|
||||
commands::app_state::get_modules,
|
||||
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_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::config::{get_client_config, open_client_config_manager, save_client_config};
|
||||
use commands::dolls::{
|
||||
@@ -86,6 +89,7 @@ pub fn run() {
|
||||
reset_password,
|
||||
logout_and_restart,
|
||||
send_interaction_cmd,
|
||||
get_modules
|
||||
])
|
||||
.setup(|app| {
|
||||
APP_HANDLE
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Default for SceneData {
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[ts(export)]
|
||||
pub struct AppData {
|
||||
pub struct UserData {
|
||||
pub user: Option<UserProfile>,
|
||||
pub friends: Option<Vec<FriendshipResponseDto>>,
|
||||
pub dolls: Option<Vec<DollDto>>,
|
||||
|
||||
@@ -60,14 +60,7 @@ pub fn init_modules() {
|
||||
}
|
||||
};
|
||||
|
||||
let 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;
|
||||
}
|
||||
};
|
||||
let mut state = lock_w!(crate::state::FDOLL);
|
||||
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
@@ -88,7 +81,8 @@ pub fn init_modules() {
|
||||
if script_path.exists() {
|
||||
match runtime::spawn_lua_runtime_from_path(&script_path) {
|
||||
Ok(handle) => {
|
||||
state_guard.push(handle);
|
||||
state.modules.metadatas.push(module_metadata.clone());
|
||||
state.modules.handles.lock().unwrap().push(handle);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
|
||||
@@ -10,7 +10,9 @@ pub struct PresenceStatus {
|
||||
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 name: String,
|
||||
pub version: String,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// 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 tauri::tray::TrayIcon;
|
||||
use tracing::info;
|
||||
@@ -12,14 +14,20 @@ pub use auth::*;
|
||||
pub use network::*;
|
||||
pub use ui::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Modules {
|
||||
pub handles: std::sync::Mutex<Vec<std::thread::JoinHandle<()>>>,
|
||||
pub metadatas: Vec<ModuleMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AppState {
|
||||
pub app_config: crate::services::client_config_manager::AppConfig,
|
||||
pub network: NetworkState,
|
||||
pub auth: AuthState,
|
||||
pub user_data: AppData,
|
||||
pub user_data: UserData,
|
||||
pub tray: Option<TrayIcon>,
|
||||
pub module_handles: std::sync::Mutex<Vec<std::thread::JoinHandle<()>>>,
|
||||
pub modules: Modules,
|
||||
}
|
||||
|
||||
// Global application state
|
||||
@@ -36,8 +44,8 @@ pub fn init_app_state() {
|
||||
guard.app_config = crate::services::client_config_manager::load_app_config();
|
||||
guard.network = init_network_state();
|
||||
guard.auth = init_auth_state();
|
||||
guard.user_data = AppData::default();
|
||||
guard.module_handles = std::sync::Mutex::new(Vec::new());
|
||||
guard.user_data = UserData::default();
|
||||
guard.modules = Modules::default();
|
||||
}
|
||||
update_display_dimensions_for_scene_state();
|
||||
info!("Initialized FDOLL state (WebSocket client & user data initializing asynchronously)");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 { 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 isListening = false;
|
||||
@@ -12,7 +12,7 @@ export async function initAppDataListener() {
|
||||
try {
|
||||
if (isListening) return;
|
||||
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);
|
||||
appData.set(event.payload);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Friends from "./tabs/friends.svelte";
|
||||
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 { onMount } from "svelte";
|
||||
@@ -67,6 +68,16 @@
|
||||
<div class="tab-content bg-base-100 border-base-300 p-4">
|
||||
<Preferences />
|
||||
</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>
|
||||
|
||||
62
src/routes/app-menu/tabs/modules.svelte
Normal file
62
src/routes/app-menu/tabs/modules.svelte
Normal 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>
|
||||
3
src/types/bindings/ModuleMetadata.ts
Normal file
3
src/types/bindings/ModuleMetadata.ts
Normal 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, };
|
||||
@@ -4,4 +4,4 @@ import type { FriendshipResponseDto } from "./FriendshipResponseDto";
|
||||
import type { SceneData } from "./SceneData";
|
||||
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, };
|
||||
Reference in New Issue
Block a user