diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index d9c742d8..6c83ffbd 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -10,7 +10,7 @@ export type ClusterModel = { id: number; folder_name: string; stage: ClusterStag export type ClusterStage = "notready" | "downloading" | "repairing" | "ready" -export type ClusterUpdate = { name: string | null; icon_url: Icon | null } +export type ClusterUpdate = { name: string | null; icon_url: Icon | null; setting_profile_name: string | null } export type CreateCluster = { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null } @@ -181,7 +181,7 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean } -export type SettingsOsExtra = { enable_gamemode: boolean | null } +export type SettingsOsExtra = Record export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated" @@ -244,7 +244,7 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'core':'{"open":["input"],"setDefaultUser":["uuid"],"getScreenshots":["id"],"getClusters":[],"getClusterById":["id"],"getDefaultUser":["fallback"],"removeCluster":["id"],"getProfileOrDefault":["name"],"getRunningProcessesByClusterId":["cluster_id"],"getWorlds":["id"],"fetchMinecraftProfile":["uuid"],"getUser":["uuid"],"getLogByName":["id","name"],"writeSettings":["setting"],"installModpack":["modpack","cluster_id"],"killProcess":["pid"],"updateClusterProfile":["name","profile"],"getUsersFromAuthor":["provider","author"],"getUsers":[],"readSettings":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getPackage":["provider","slug"],"updateClusterById":["id","request"],"getLogs":["id"],"getGameVersions":[],"getGlobalProfile":[],"searchPackages":["provider","query"],"getRunningProcesses":[],"isClusterRunning":["cluster_id"],"getMultiplePackages":["provider","slugs"],"getLoadersForVersion":["mc_version"],"openMsaLogin":[],"removeUser":["uuid"],"createCluster":["options"],"getPackageBody":["provider","body"],"launchCluster":["id","uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"]}', 'oneclient':'{"openDevTools":[],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}' } +const ARGS_MAP = { 'oneclient':'{"getBundlesFor":["cluster_id"],"getClustersGroupedByMajor":[],"openDevTools":[]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'core':'{"updateClusterProfile":["name","profile"],"getPackageBody":["provider","body"],"killProcess":["pid"],"getWorlds":["id"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getLoadersForVersion":["mc_version"],"createSettingsProfile":["name"],"getProfileOrDefault":["name"],"getGlobalProfile":[],"updateClusterById":["id","request"],"getUsers":[],"getRunningProcesses":[],"fetchMinecraftProfile":["uuid"],"getScreenshots":["id"],"open":["input"],"isClusterRunning":["cluster_id"],"installModpack":["modpack","cluster_id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"searchPackages":["provider","query"],"getUser":["uuid"],"getLogByName":["id","name"],"removeCluster":["id"],"setDefaultUser":["uuid"],"launchCluster":["id","uuid"],"getClusters":[],"getDefaultUser":["fallback"],"getRunningProcessesByClusterId":["cluster_id"],"getLogs":["id"],"getGameVersions":[],"openMsaLogin":[],"getPackage":["provider","slug"],"createCluster":["options"],"removeUser":["uuid"],"readSettings":[],"getUsersFromAuthor":["provider","author"],"writeSettings":["setting"],"getMultiplePackages":["provider","slugs"],"getClusterById":["id"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' } export type Router = { 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, @@ -262,6 +262,7 @@ killProcess: (pid: number) => Promise, getProfileOrDefault: (name: string | null) => Promise, getGlobalProfile: () => Promise, updateClusterProfile: (name: string, profile: ProfileUpdate) => Promise, +createSettingsProfile: (name: string) => Promise, getGameVersions: () => Promise, getLoadersForVersion: (mcVersion: string) => Promise, getUsers: () => Promise, @@ -282,11 +283,11 @@ getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise Promise, fetchMinecraftProfile: (uuid: string) => Promise, open: (input: string) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, 'oneclient': { openDevTools: () => Promise, getClustersGroupedByMajor: () => Promise>, getBundlesFor: (clusterId: number) => Promise }, -'folders': { fromCluster: (folderName: string) => Promise, -openCluster: (folderName: string) => Promise }, 'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, process: (event: ProcessPayload) => Promise } }; diff --git a/apps/oneclient/frontend/src/hooks/useSettings.ts b/apps/oneclient/frontend/src/hooks/useSettings.ts index a8a2d315..15f0ced8 100644 --- a/apps/oneclient/frontend/src/hooks/useSettings.ts +++ b/apps/oneclient/frontend/src/hooks/useSettings.ts @@ -1,7 +1,7 @@ -import type { Settings } from '@/bindings.gen'; +import type { ClusterModel, ProfileUpdate, SettingProfileModel, Settings } from '@/bindings.gen'; import { bindings } from '@/main'; import { useCommandSuspense } from '@onelauncher/common'; -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; export function useSettings() { const settings = useCommandSuspense(['readSettings'], bindings.core.readSettings); @@ -23,3 +23,46 @@ export function useSettings() { return { setting, setSetting, createSetting }; } + +const emptyUpdate: ProfileUpdate = { + res: null, + force_fullscreen: null, + mem_max: null, + launch_args: null, + launch_env: null, + hook_pre: null, + hook_wrapper: null, + hook_post: null, +}; + +export function useClusterProfile(cluster: ClusterModel) { + const [profileName, setProfileName] = useState(cluster.setting_profile_name); + const profileSrc = useCommandSuspense(['getProfileOrDefault', profileName], () => bindings.core.getProfileOrDefault(profileName)); + const [profile, setProfile] = useState(profileSrc.data); + + useEffect(() => { + setProfile(profileSrc.data); + }, [profileSrc.data]); + + const updateProfile = useMemo(() => + async (update: Partial) => { + setProfile(profile => ({ ...profile, ...update })); + let name = profileName; + if (!profileName) { + name = globalThis.crypto.randomUUID(); + await bindings.core.createSettingsProfile(name); + await bindings.core.updateClusterById(cluster.id, { + setting_profile_name: cluster.name, + name: null, + icon_url: null, + }); + setProfileName(name); + } + if (!name) + throw new Error('No settings profile name'); + await bindings.core.updateClusterProfile(name, { ...emptyUpdate, ...update }); + profileSrc.refetch(); + }, [profileName, cluster.name, cluster.id]); + // const updateProfile = useMemo(() => (update: Partial) => setProfile({ ...profile.data, ...update }), [setProfile, profile.data]); + return { profile, setProfile, updateProfile }; +} diff --git a/apps/oneclient/frontend/src/routes/app/cluster/route.tsx b/apps/oneclient/frontend/src/routes/app/cluster/route.tsx index a890bd11..2a4a37a6 100644 --- a/apps/oneclient/frontend/src/routes/app/cluster/route.tsx +++ b/apps/oneclient/frontend/src/routes/app/cluster/route.tsx @@ -73,6 +73,7 @@ function RouteComponent() { Overview Logs Browser + Settings {isRunning && } {isRunning && Process} diff --git a/apps/oneclient/frontend/src/routes/app/cluster/settings.tsx b/apps/oneclient/frontend/src/routes/app/cluster/settings.tsx new file mode 100644 index 00000000..64a1ff66 --- /dev/null +++ b/apps/oneclient/frontend/src/routes/app/cluster/settings.tsx @@ -0,0 +1,116 @@ +import { SheetPage } from '@/components'; +import SettingsRow from '@/components/SettingsRow'; +import { useClusterProfile } from '@/hooks/useSettings'; +import { bindings } from '@/main'; +import { Switch, TextField } from '@onelauncher/common/components'; +import { createFileRoute } from '@tanstack/react-router'; +import { Database01Icon, FilePlus02Icon, FileX02Icon, LayoutTopIcon, Maximize01Icon, ParagraphWrapIcon, XIcon } from '@untitled-theme/icons-react'; + +export const Route = createFileRoute('/app/cluster/settings')({ + component: RouteComponent, +}); + +function RouteComponent() { + const { cluster } = Route.useRouteContext(); + const { profile, updateProfile } = useClusterProfile(cluster); + return ( + +

Settings

+ Process + + } + title="Pre-Launch Command" + > + updateProfile({ hook_pre: e.currentTarget.value })} + placeholder="echo 'Game started'" + value={profile.hook_pre ?? undefined} + /> + + + } + title="Wrapper Command" + > + updateProfile({ hook_wrapper: e.currentTarget.value })} + placeholder="gamescope" + value={profile.hook_wrapper ?? undefined} + /> + + + } + title="Post-Exit Command" + > + updateProfile({ hook_post: e.currentTarget.value })} + placeholder="echo 'Game exited'" + value={profile.hook_post ?? undefined} + /> + + + Game + + } + title="Force Fullscreen" + > + updateProfile({ force_fullscreen: val })} + /> + + + } + title="Resolution" + > +
+ { + updateProfile({ res: { height: profile.res?.height ?? 720, width: Number(e.currentTarget.value) } }); + }} + type="number" + value={profile.res?.width} + /> + + { + updateProfile({ res: { width: profile.res?.height ?? 1280, height: Number(e.currentTarget.value) } }); + }} + type="number" + value={profile.res?.height} + /> +
+
+ + } + title="Memory" + > +
+
+ { + updateProfile({ mem_max: Number(e.currentTarget.value) }); + }} + type="number" + value={profile.mem_max ?? undefined} + /> +
+
+
+
+ ); +} diff --git a/packages/core/src/api/tauri/commands.rs b/packages/core/src/api/tauri/commands.rs index 560057f5..84cb5dd1 100644 --- a/packages/core/src/api/tauri/commands.rs +++ b/packages/core/src/api/tauri/commands.rs @@ -4,6 +4,7 @@ use crate::api::packages::data::{ }; use crate::api::packages::modpack::data::ModpackArchive; use crate::api::packages::provider::ProviderExt; +use crate::api::setting_profiles::get_global_profile; use crate::store::processes::Process; use crate::store::{Settings, State}; use crate::utils::pagination::Paginated; @@ -89,6 +90,9 @@ pub trait TauriLauncherApi { name: String, profile: ProfileUpdate, ) -> LauncherResult; + + #[taurpc(alias = "createSettingsProfile")] + async fn create_settings_profile(name: String) -> LauncherResult; // endregion: profiles // MARK: API: metadata @@ -215,6 +219,7 @@ pub struct CreateCluster { pub struct ClusterUpdate { name: Option, icon_url: Option, + setting_profile_name: Option, } #[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)] @@ -323,6 +328,10 @@ impl TauriLauncherApi for TauriLauncherApiImpl { api::cluster::dao::set_icon_by_id(id, &icon_url).await?; } + if let Some(setting_profile_name) = request.setting_profile_name { + active_model.setting_profile_name = Set(Some(setting_profile_name)) + } + Ok(active_model) }, ) @@ -396,6 +405,13 @@ impl TauriLauncherApi for TauriLauncherApiImpl { api::setting_profiles::dao::get_profile_or_default(name.as_ref()).await } + async fn create_settings_profile(self, name: String) -> LauncherResult { + let mut profile = get_global_profile().await; + profile.name = name; + + api::setting_profiles::dao::insert_profile(profile.into()).await + } + // please kill this with fire async fn update_cluster_profile( self,