feat: cluster settings
This commit is contained in:
parent
1eb6505c0c
commit
3d0658a950
5 changed files with 184 additions and 7 deletions
|
|
@ -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<string, never>
|
||||
|
||||
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<ClusterModel[]>,
|
||||
getClusterById: (id: number) => Promise<ClusterModel | null>,
|
||||
removeCluster: (id: number) => Promise<null>,
|
||||
|
|
@ -262,6 +262,7 @@ killProcess: (pid: number) => Promise<null>,
|
|||
getProfileOrDefault: (name: string | null) => Promise<SettingProfileModel>,
|
||||
getGlobalProfile: () => Promise<SettingProfileModel>,
|
||||
updateClusterProfile: (name: string, profile: ProfileUpdate) => Promise<SettingProfileModel>,
|
||||
createSettingsProfile: (name: string) => Promise<SettingProfileModel>,
|
||||
getGameVersions: () => Promise<Version[]>,
|
||||
getLoadersForVersion: (mcVersion: string) => Promise<GameLoader[]>,
|
||||
getUsers: () => Promise<MinecraftCredentials[]>,
|
||||
|
|
@ -282,11 +283,11 @@ getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise<Manag
|
|||
installModpack: (modpack: ModpackArchive, clusterId: number) => Promise<null>,
|
||||
fetchMinecraftProfile: (uuid: string) => Promise<MojangPlayerProfile>,
|
||||
open: (input: string) => Promise<null> },
|
||||
'folders': { fromCluster: (folderName: string) => Promise<string>,
|
||||
openCluster: (folderName: string) => Promise<null> },
|
||||
'oneclient': { openDevTools: () => Promise<void>,
|
||||
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
|
||||
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]> },
|
||||
'folders': { fromCluster: (folderName: string) => Promise<string>,
|
||||
openCluster: (folderName: string) => Promise<null> },
|
||||
'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> } };
|
||||
|
|
|
|||
|
|
@ -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<ProfileUpdate>) => {
|
||||
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<ProfileUpdate>) => setProfile({ ...profile.data, ...update }), [setProfile, profile.data]);
|
||||
return { profile, setProfile, updateProfile };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ function RouteComponent() {
|
|||
<Tab from={Route.id} search={search} to="/app/cluster/overview">Overview</Tab>
|
||||
<Tab from={Route.id} search={search} to="/app/cluster/logs">Logs</Tab>
|
||||
<Tab from={Route.id} search={{ ...search, packageType: 'mod' }} to="/app/cluster/browser">Browser</Tab>
|
||||
<Tab from={Route.id} search={search} to="/app/cluster/settings">Settings</Tab>
|
||||
{isRunning && <span className="flex-1"></span>}
|
||||
{isRunning && <Tab from={Route.id} search={search} to="/app/cluster/process">Process</Tab>}
|
||||
</TabList>
|
||||
|
|
|
|||
116
apps/oneclient/frontend/src/routes/app/cluster/settings.tsx
Normal file
116
apps/oneclient/frontend/src/routes/app/cluster/settings.tsx
Normal file
|
|
@ -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 (
|
||||
<SheetPage.Content>
|
||||
<h1>Settings</h1>
|
||||
<SettingsRow.Header>Process</SettingsRow.Header>
|
||||
|
||||
<SettingsRow
|
||||
description="Command to run before launching the game."
|
||||
icon={<FilePlus02Icon />}
|
||||
title="Pre-Launch Command"
|
||||
>
|
||||
<TextField
|
||||
onChange={e => updateProfile({ hook_pre: e.currentTarget.value })}
|
||||
placeholder="echo 'Game started'"
|
||||
value={profile.hook_pre ?? undefined}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
description="Command to run when launching the game."
|
||||
icon={<ParagraphWrapIcon />}
|
||||
title="Wrapper Command"
|
||||
>
|
||||
<TextField
|
||||
onChange={e => updateProfile({ hook_wrapper: e.currentTarget.value })}
|
||||
placeholder="gamescope"
|
||||
value={profile.hook_wrapper ?? undefined}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
description="Command to run after exiting the game."
|
||||
icon={<FileX02Icon />}
|
||||
title="Post-Exit Command"
|
||||
>
|
||||
<TextField
|
||||
onChange={e => updateProfile({ hook_post: e.currentTarget.value })}
|
||||
placeholder="echo 'Game exited'"
|
||||
value={profile.hook_post ?? undefined}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow.Header>Game</SettingsRow.Header>
|
||||
|
||||
<SettingsRow
|
||||
description="Force Minecraft to start in fullscreen mode."
|
||||
icon={<Maximize01Icon />}
|
||||
title="Force Fullscreen"
|
||||
>
|
||||
<Switch
|
||||
isSelected={profile.force_fullscreen ?? false}
|
||||
onChange={val => updateProfile({ force_fullscreen: val })}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
description="The game window resolution in pixels."
|
||||
icon={<LayoutTopIcon />}
|
||||
title="Resolution"
|
||||
>
|
||||
<div className="grid grid-cols-[70px_16px_70px] gap-2 grid-justify-center grid-items-center">
|
||||
<TextField
|
||||
className="text-center"
|
||||
onChange={(e) => {
|
||||
updateProfile({ res: { height: profile.res?.height ?? 720, width: Number(e.currentTarget.value) } });
|
||||
}}
|
||||
type="number"
|
||||
value={profile.res?.width}
|
||||
/>
|
||||
<XIcon className="size-4 self-center" />
|
||||
<TextField
|
||||
className="text-center"
|
||||
onChange={(e) => {
|
||||
updateProfile({ res: { width: profile.res?.height ?? 1280, height: Number(e.currentTarget.value) } });
|
||||
}}
|
||||
type="number"
|
||||
value={profile.res?.height}
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
description="The amount of memory in megabytes allocated for the game."
|
||||
icon={<Database01Icon />}
|
||||
title="Memory"
|
||||
>
|
||||
<div className="flex items-center gap-x-4 flex-justify-center">
|
||||
<div className="flex flex-row items-center gap-x-2">
|
||||
<TextField
|
||||
className="text-center"
|
||||
onChange={(e) => {
|
||||
updateProfile({ mem_max: Number(e.currentTarget.value) });
|
||||
}}
|
||||
type="number"
|
||||
value={profile.mem_max ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</SheetPage.Content>
|
||||
);
|
||||
}
|
||||
|
|
@ -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<SettingsProfile>;
|
||||
|
||||
#[taurpc(alias = "createSettingsProfile")]
|
||||
async fn create_settings_profile(name: String) -> LauncherResult<SettingsProfile>;
|
||||
// endregion: profiles
|
||||
|
||||
// MARK: API: metadata
|
||||
|
|
@ -215,6 +219,7 @@ pub struct CreateCluster {
|
|||
pub struct ClusterUpdate {
|
||||
name: Option<String>,
|
||||
icon_url: Option<Icon>,
|
||||
setting_profile_name: Option<String>,
|
||||
}
|
||||
|
||||
#[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<SettingsProfile> {
|
||||
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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue