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;
|
||||||
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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>>,
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)");
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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 { 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, };
|
||||||
Reference in New Issue
Block a user