diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 32df5ee1..4ff6e459 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -232,9 +232,20 @@ export type VersionType = */ "old_beta" +<<<<<<< HEAD const ARGS_MAP = { 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'core':'{"getRunningProcessesByClusterId":["cluster_id"],"writeSettings":["setting"],"getPackage":["provider","slug"],"open":["input"],"updateClusterProfile":["name","profile"],"getScreenshots":["id"],"fetchMinecraftProfile":["uuid"],"launchCluster":["id","uuid"],"readSettings":[],"getMultiplePackages":["provider","slugs"],"getClusters":[],"updateClusterById":["id","request"],"getRunningProcesses":[],"getGlobalProfile":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getProfileOrDefault":["name"],"getUser":["uuid"],"createCluster":["options"],"killProcess":["pid"],"setDefaultUser":["uuid"],"openMsaLogin":[],"searchPackages":["provider","query"],"isClusterRunning":["cluster_id"],"getPackageBody":["provider","body"],"getLoadersForVersion":["mc_version"],"removeUser":["uuid"],"getClusterById":["id"],"removeCluster":["id"],"getGameVersions":[],"getUsers":[],"getUsersFromAuthor":["provider","author"],"getLogByName":["id","name"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getDefaultUser":["fallback"],"getLogs":["id"],"getWorlds":["id"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}' } export type Router = { 'oneclient': { openDevTools: () => Promise, getClustersGroupedByMajor: () => Promise> }, +======= +const ARGS_MAP = { 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[]}', 'core':'{"writeSettings":["setting"],"getWorlds":["id"],"getRunningProcessesByClusterId":["cluster_id"],"updateClusterProfile":["name","profile"],"getClusterById":["id"],"openMsaLogin":[],"getScreenshots":["id"],"getUsers":[],"getProfileOrDefault":["name"],"updateClusterById":["id","request"],"getDefaultUser":["fallback"],"removeCluster":["id"],"createCluster":["options"],"getPackage":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"open":["input"],"getMultiplePackages":["provider","slugs"],"getLogByName":["id","name"],"getLoadersForVersion":["mc_version"],"isClusterRunning":["cluster_id"],"searchPackages":["provider","query"],"setDefaultUser":["uuid"],"getUser":["uuid"],"killProcess":["pid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"launchCluster":["id","uuid"],"getClusters":[],"getRunningProcesses":[],"getGameVersions":[],"getGlobalProfile":[],"getPackageBody":["provider","body"],"removeUser":["uuid"],"fetchMinecraftProfile":["uuid"],"readSettings":[],"getLogs":["id"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}' } +export type Router = { 'oneclient': { openDevTools: () => Promise, +getClustersGroupedByMajor: () => Promise> }, +'events': { ingress: (event: IngressPayload) => Promise, +message: (event: MessagePayload) => Promise, +process: (event: ProcessPayload) => Promise }, +'folders': { fromCluster: (folderName: string) => Promise, +openCluster: (folderName: string) => Promise }, +>>>>>>> 035657cb791987107fa8d13da125fbb9d6d60825 'core': { getClusters: () => Promise, getClusterById: (id: number) => Promise, removeCluster: (id: number) => Promise, @@ -270,12 +281,16 @@ getPackageVersions: (provider: Provider, slug: string, mcVersion: string | null, downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: number, skipCompatibility: boolean | null) => Promise, getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise, fetchMinecraftProfile: (uuid: string) => Promise, +<<<<<<< HEAD open: (input: string) => Promise }, 'folders': { fromCluster: (folderName: string) => Promise, openCluster: (folderName: string) => Promise }, 'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, process: (event: ProcessPayload) => Promise } }; +======= +open: (input: string) => Promise } }; +>>>>>>> 035657cb791987107fa8d13da125fbb9d6d60825 export type { InferCommandOutput } diff --git a/apps/oneclient/frontend/src/components/ExternalLink.tsx b/apps/oneclient/frontend/src/components/ExternalLink.tsx index 0dd9dad5..ab64843f 100644 --- a/apps/oneclient/frontend/src/components/ExternalLink.tsx +++ b/apps/oneclient/frontend/src/components/ExternalLink.tsx @@ -45,7 +45,7 @@ export function ExternalLink({ {...rest} > {children} - {withIcon && } + {withIcon && } diff --git a/apps/oneclient/frontend/src/components/Navbar.tsx b/apps/oneclient/frontend/src/components/Navbar.tsx index de0d3734..1dfc6742 100644 --- a/apps/oneclient/frontend/src/components/Navbar.tsx +++ b/apps/oneclient/frontend/src/components/Navbar.tsx @@ -4,15 +4,16 @@ import LauncherLogo from '@/assets/logos/oneclient.svg?react'; import { bindings } from '@/main'; import { useCommand } from '@onelauncher/common'; import { Button, Popup } from '@onelauncher/common/components'; -import { Link } from '@tanstack/react-router'; +import { Link, useNavigate } from '@tanstack/react-router'; import { Window } from '@tauri-apps/api/window'; -import { MinusIcon, SquareIcon, XCloseIcon } from '@untitled-theme/icons-react'; +import { MinusIcon, Settings01Icon, Settings02Icon, SquareIcon, XCloseIcon } from '@untitled-theme/icons-react'; import { twMerge } from 'tailwind-merge'; import { AccountAvatar } from './AccountAvatar'; import { AccountPopup } from './overlay/AccountPopup'; export function Navbar() { const { data: currentAccount } = useCommand(['getDefaultUser'], () => bindings.core.getDefaultUser(true)); + const navigate = useNavigate(); const onMinimize = () => Window.getCurrent().minimize(); const onMaximize = () => Window.getCurrent().toggleMaximize(); @@ -30,37 +31,42 @@ export function Navbar() { Accounts -
- - - - +
+
+ } + onClick={() => navigate({ to: '/app/settings' })} + /> + + + + + + +
- - - - } - onClick={onMinimize} - /> - - } - onClick={onMaximize} - /> - - - )} - className="bg-transparent" - color="danger" - onClick={onClose} - /> +
+ } + onClick={onMinimize} + /> + } + onClick={onMaximize} + /> + + )} + className="bg-transparent" + color="danger" + onClick={onClose} + /> +
diff --git a/apps/oneclient/frontend/src/components/SettingSwitch.tsx b/apps/oneclient/frontend/src/components/SettingSwitch.tsx new file mode 100644 index 00000000..04a8b05a --- /dev/null +++ b/apps/oneclient/frontend/src/components/SettingSwitch.tsx @@ -0,0 +1,16 @@ +import type { useSettings } from '@/hooks/useSettings'; +import { Switch } from '@onelauncher/common/components'; + +type BooleanSetting = [ + boolean, + (value: boolean) => void, +]; + +export default function SettingsSwitch({ setting }: { setting: BooleanSetting }) { + return ( + setting[1](val)} + /> + ); +} diff --git a/apps/oneclient/frontend/src/components/SettingsRow.tsx b/apps/oneclient/frontend/src/components/SettingsRow.tsx new file mode 100644 index 00000000..4bfe7435 --- /dev/null +++ b/apps/oneclient/frontend/src/components/SettingsRow.tsx @@ -0,0 +1,45 @@ +import type { JSX } from 'react'; + +export interface SettingsRowProps { + title: JSX.Element | string; + description: JSX.Element | string; + icon: JSX.Element; + disabled?: boolean; + children?: JSX.Element; +} + +function SettingsRow({ disabled, description, icon, title, children }: SettingsRowProps) { + return ( +
+
+ {icon} +
+ +
+

{title}

+

{description}

+
+ +
+ {children} +
+
+ ); +} + +interface SettingsRowHeaderProps { + className?: string; + children?: React.ReactNode; +} + +SettingsRow.Header = ({ className, children }: SettingsRowHeaderProps) => { + const headerClasses = `mt-4 mb-1 ml-2 text-md text-fg-secondary uppercase ${className || ''}`; + return

{children}

; +}; + +export default SettingsRow; diff --git a/apps/oneclient/frontend/src/components/Tabs.tsx b/apps/oneclient/frontend/src/components/Tabs.tsx index 34f27446..081fd766 100644 --- a/apps/oneclient/frontend/src/components/Tabs.tsx +++ b/apps/oneclient/frontend/src/components/Tabs.tsx @@ -15,7 +15,7 @@ export function TabList({
bindings.core.getDefaultUser(true)); - const { data: accounts } = useCommandSuspense(['getUsers', 'accountPopup'], bindings.core.getUsers, { - select(data) { - data.sort((a, b) => { - if (currentAccount?.id === a.id) - return -1; - else if (currentAccount?.id === b.id) - return 1; - return a.username.localeCompare(b.username); - }); + const users = useCommand(['getUsers'], bindings.core.getUsers); + const defaultUser = useCommand(['getDefaultUser'], () => bindings.core.getDefaultUser(false)); - return data; - }, - }); - - const queryClient = useQueryClient(); - const { mutate: setDefaultUser } = useCommandMut(bindings.core.setDefaultUser, { - onSuccess() { - queryClient.invalidateQueries({ - queryKey: ['getDefaultUser'], - }); - }, - }); + const setDefaultUser = (user: MinecraftCredentials) => { + bindings.core.setDefaultUser(user.id); + }; return ( -
- {accounts.map(profile => ( - setDefaultUser(profile.id)} - profile={profile} - selected={profile.id === currentAccount?.id} - /> + +
+ {defaultUser.data && ( +
+ { }} + user={defaultUser.data} + /> +
+ )} + + {users.data?.filter(user => user.id !== defaultUser.data?.id).map(user => ( +
+ setDefaultUser(user)} + user={user} + /> +
))} + +
+
+ + + + + + + + +
+
+ + + +
+
); } -function AccountRow({ - selected = false, - profile, - onPress, +export default AccountPopup; + +function AccountEntry({ + onClick, + user, + loggedIn = false, }: { - selected?: boolean; - profile: MinecraftCredentials; - onPress: () => void; + onClick: () => void; + user: MinecraftCredentials; + loggedIn?: boolean; }) { return ( - - - -
-

{profile.username}

+
+ +
+
+

{user.username}

+ {loggedIn &&

Logged in

} +
+
- +
); } diff --git a/apps/oneclient/frontend/src/components/overlay/Popup.tsx b/apps/oneclient/frontend/src/components/overlay/Popup.tsx index 8003fe82..dd60f3ce 100644 --- a/apps/oneclient/frontend/src/components/overlay/Popup.tsx +++ b/apps/oneclient/frontend/src/components/overlay/Popup.tsx @@ -18,7 +18,7 @@ const popupVariants = tv({ ], dialog: [ 'flex flex-col items-center p-1 gap-2 relative', - 'border border-component-border-hover bg-page-elevated shadow-md rounded-lg', + 'border border-component-border-hover bg-page-elevated shadow-md rounded-xl', ], }, }); diff --git a/apps/oneclient/frontend/src/hooks/usePopState.ts b/apps/oneclient/frontend/src/hooks/usePopState.ts new file mode 100644 index 00000000..5714b47c --- /dev/null +++ b/apps/oneclient/frontend/src/hooks/usePopState.ts @@ -0,0 +1,20 @@ +import { useLocation } from '@tanstack/react-router'; +import { useEffect, useRef } from 'react'; + +export default function usePopState(onRouteChange: void | any) { + const location = useLocation(); + const prevLocationRef = useRef(location); + + useEffect(() => { + const prevLocation = prevLocationRef.current; + + // Only run if the pathname actually changed + if (prevLocation.pathname !== location.pathname) + onRouteChange?.({ + from: prevLocation, + to: location, + }); + + prevLocationRef.current = location; + }, [location, onRouteChange]); +} diff --git a/apps/oneclient/frontend/src/hooks/useSettings.ts b/apps/oneclient/frontend/src/hooks/useSettings.ts new file mode 100644 index 00000000..a8a2d315 --- /dev/null +++ b/apps/oneclient/frontend/src/hooks/useSettings.ts @@ -0,0 +1,25 @@ +import type { Settings } from '@/bindings.gen'; +import { bindings } from '@/main'; +import { useCommandSuspense } from '@onelauncher/common'; +import { useMemo } from 'react'; + +export function useSettings() { + const settings = useCommandSuspense(['readSettings'], bindings.core.readSettings); + const setting = useMemo(() => (key: TKey) => settings.data[key], [settings]); + + const setSetting = useMemo(() => (key: TKey, value: Settings[TKey]) => { + bindings.core.writeSettings({ + ...settings.data, + [key]: value, + }); + settings.refetch(); + }, [settings]); + + type Setter = (value: Settings[TKey]) => void; + + const createSetting = useMemo(() => (key: TKey): [Settings[TKey], Setter] => { + return [setting(key), value => setSetting(key, value)]; + }, [setting]); + + return { setting, setSetting, createSetting }; +} diff --git a/apps/oneclient/frontend/src/routes/app/cluster/browser/index.tsx b/apps/oneclient/frontend/src/routes/app/cluster/browser/index.tsx index 01e8f954..fc66e0b5 100644 --- a/apps/oneclient/frontend/src/routes/app/cluster/browser/index.tsx +++ b/apps/oneclient/frontend/src/routes/app/cluster/browser/index.tsx @@ -1,5 +1,5 @@ import type { Filters, PackageCategories, PackageType, Provider, Sort } from '@/bindings.gen'; -import { LoaderSuspense } from '@/components'; +import { LoaderSuspense, SheetPage } from '@/components'; import { PackageGrid } from '@/components/PackageItem'; import { bindings } from '@/main'; import { browserCategories, categoryNameFromId } from '@/utils/browser'; @@ -33,15 +33,16 @@ const PROVIDERS: Array = ['Modrinth', 'CurseForge', 'SkyClient']; function RouteComponent() { return ( + //
-
-
+
+

Browser

-
+ {/*
*/}
-
+
@@ -54,6 +55,7 @@ function RouteComponent() {
+ // ); } @@ -78,7 +80,7 @@ function BrowserCategories() { return (
-
+
Categories
{browserCategories[categoryNameFromId[packageType]].map(category => ( @@ -111,7 +113,7 @@ function BrowserToolbar() { const [searchBar, setSearchBar] = useState(query); return ( -
+
navigate({ search: { ...search, provider: id as Provider } })} diff --git a/apps/oneclient/frontend/src/routes/app/cluster/browser/package.tsx b/apps/oneclient/frontend/src/routes/app/cluster/browser/package.tsx index f325a871..3a1957e6 100644 --- a/apps/oneclient/frontend/src/routes/app/cluster/browser/package.tsx +++ b/apps/oneclient/frontend/src/routes/app/cluster/browser/package.tsx @@ -1,12 +1,12 @@ -import type { ClusterModel, ManagedPackage, ManagedUser, ManagedVersion, PackageDonationUrl, Provider } from '@/bindings.gen'; +import type { ManagedPackage, ManagedUser, ManagedVersion, PackageDonationUrl } from '@/bindings.gen'; import { LoaderSuspense } from '@/components'; import { ExternalLink } from '@/components/ExternalLink'; import { Markdown } from '@/components/Markdown'; import { bindings } from '@/main'; import { abbreviateNumber, formatAsRelative, upperFirst, useCommand, useCommandMut, useCommandSuspense } from '@onelauncher/common'; -import { Button, Tooltip } from '@onelauncher/common/components'; +import { Button, Show, Tooltip } from '@onelauncher/common/components'; import { createFileRoute, redirect } from '@tanstack/react-router'; -import { CalendarIcon, ClockRewindIcon, Download01Icon, File02Icon } from '@untitled-theme/icons-react'; +import { CalendarIcon, CheckIcon, ClockRewindIcon, Download01Icon, File02Icon, Loading02Icon, XIcon } from '@untitled-theme/icons-react'; import { useMemo } from 'react'; import { Cell, Column, Row, Tab, Table, TableBody, TableHeader, TabList, TabPanel, Tabs } from 'react-aria-components'; @@ -349,6 +349,7 @@ function Versions() { function VersionRow({ version }: { version: ManagedVersion }) { const { cluster } = Route.useRouteContext(); const { provider } = Route.useSearch(); + const download = useCommandMut(() => bindings.core.downloadPackage(provider, version.project_id, version.version_id, cluster.id, true)); return ( @@ -365,17 +366,19 @@ function VersionRow({ version }: { version: ManagedVersion }) { {version.downloads} ); } - -function downloadPackage(cluster: ClusterModel, provider: Provider, version: ManagedVersion, skipCompatibility = false) { - return bindings.core.downloadPackage(provider, version.project_id, version.version_id, cluster.id, skipCompatibility); -} diff --git a/apps/oneclient/frontend/src/routes/app/cluster/browser/route.tsx b/apps/oneclient/frontend/src/routes/app/cluster/browser/route.tsx index 55dbf88d..b9aa0929 100644 --- a/apps/oneclient/frontend/src/routes/app/cluster/browser/route.tsx +++ b/apps/oneclient/frontend/src/routes/app/cluster/browser/route.tsx @@ -1,4 +1,5 @@ import type { Provider } from '@/bindings.gen'; +import { SheetPage } from '@/components'; import { createFileRoute, Outlet } from '@tanstack/react-router'; export interface BrowserRouteSearchParams { @@ -16,6 +17,8 @@ export const Route = createFileRoute('/app/cluster/browser')({ function RouteComponent() { return ( - + + + ); } diff --git a/apps/oneclient/frontend/src/routes/app/settings/appearance.tsx b/apps/oneclient/frontend/src/routes/app/settings/appearance.tsx new file mode 100644 index 00000000..6b83f19e --- /dev/null +++ b/apps/oneclient/frontend/src/routes/app/settings/appearance.tsx @@ -0,0 +1,224 @@ +// import ScrollableContainer from '@/components/ScrollableContainer'; +import SettingsRow from '@/components/SettingsRow'; +import SettingsSwitch from '@/components/SettingSwitch'; +import { useSettings } from '@/hooks/useSettings'; +import { bindings } from '@/main'; +import { THEMES } from '@/utils/theming'; +import { Switch } from '@onelauncher/common/components'; +import { createFileRoute } from '@tanstack/react-router'; +import { Monitor01Icon, PackageIcon, Speedometer04Icon } from '@untitled-theme/icons-react'; +import Sidebar from './route'; + +export const Route = createFileRoute('/app/settings/appearance')({ + component: RouteComponent, +}); + +function RouteComponent() { + const { createSetting } = useSettings(); + + return ( + + {/* */} +
+

Appearance

+ +
+ +
+ {/* + {theme => ( + + {variant => ( + + )} + + )} + */} + + {Object.keys(THEMES).map((theme) => { + return THEMES[theme as keyof typeof THEMES].variants.map((variant) => { + return ( + { }} themeSelector={`${theme}-${variant.name}`} /> + ); + }); + })} +
+
+ + } + title="Package List Style" + > +

i hope i'll fix this

+
+ + } + title="Custom Window Frame" + > + + + + } + title="Disable Animations" + > + {/* */} + +
+ {/*
*/} +
+ ); +} + +interface ThemeCardProps { + themeSelector: string; + setTheme: (merged: string) => void; +}; + +function ThemeCard({ + themeSelector, + setTheme, +}: ThemeCardProps) { + return ( +
setTheme(themeSelector)}> + + + + + + + + + +
+ ); +} + +function PrimaryThemeCard() { + return ( +
+ + + + + + + + + + + + + +
+ ); +} diff --git a/apps/oneclient/frontend/src/routes/app/settings/index.tsx b/apps/oneclient/frontend/src/routes/app/settings/index.tsx new file mode 100644 index 00000000..ad6f400a --- /dev/null +++ b/apps/oneclient/frontend/src/routes/app/settings/index.tsx @@ -0,0 +1,77 @@ +// import DiscordIcon from '@/assets/logos/discord.svg'; +import SettingsRow from '@/components/SettingsRow'; +import SettingsSwitch from '@/components/SettingSwitch'; +import { useSettings } from '@/hooks/useSettings'; +import { bindings } from '@/main'; +// import useSettings from '@/hooks/useSettings'; +import { Button, Switch } from '@onelauncher/common/components'; +import { createFileRoute } from '@tanstack/react-router'; +import { dataDir, join } from '@tauri-apps/api/path'; +import { FolderIcon, Link03Icon, LinkExternal01Icon } from '@untitled-theme/icons-react'; +import { useEffect, useState } from 'react'; +import Sidebar from './route'; + +export const Route = createFileRoute('/app/settings/')({ + component: RouteComponent, +}); + +function RouteComponent() { + const { createSetting } = useSettings(); + const [launcherDir, setLauncherDir] = useState(''); + + useEffect(() => { + (async () => { + setLauncherDir(await join(await dataDir(), 'OneLauncher')); + })(); + }, []); + + const openLauncherDir = () => bindings.core.open(launcherDir); + + return ( + +
+

General

+ + } + title="Discord RPC" + > + + + + {/* } + title="Hide Close Dialog" + > + + + + } + title="Error Analytics" + > + !(settings().disable_analytics ?? false)} + onChecked={value => settings().disable_analytics = !value} + /> + */} + + Folders and Files + } + title="Launcher Folder" + > + + +
+
+ ); +} diff --git a/apps/oneclient/frontend/src/routes/app/settings/minecraft.tsx b/apps/oneclient/frontend/src/routes/app/settings/minecraft.tsx new file mode 100644 index 00000000..fc0358ca --- /dev/null +++ b/apps/oneclient/frontend/src/routes/app/settings/minecraft.tsx @@ -0,0 +1,191 @@ +import type { SettingProfileModel } from '@/bindings.gen'; +// import ScrollableContainer from '@/components/ScrollableContainer'; +import SettingsRow from '@/components/SettingsRow'; +import usePopState from '@/hooks/usePopState'; +import { useSettings } from '@/hooks/useSettings'; +import { bindings } from '@/main'; +import { useCommandMut, useCommandSuspense } from '@onelauncher/common'; +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'; +import { useMemo } from 'react'; +import Sidebar from './route'; + +export const Route = createFileRoute('/app/settings/minecraft')({ + component: RouteComponent, +}); + +function RouteComponent() { + const result = useCommandSuspense(['readSettings'], bindings.core.readSettings); + + const save = useCommandMut(() => bindings.core.writeSettings(result.data)); + + usePopState(() => { + save.mutate(); + }); + + return ( + + {/* */} +
+

Minecraft Settings

+ + + + {/* */} + + +
+ {/*
*/} +
+ ); +} + +export function GameSettings() { + const { createSetting } = useSettings(); + + const [gameSettings, setGameSettings] = createSetting('global_game_settings'); + + // const getSetting = useMemo(()=>(key: TKey)=>gameSettings[key], [gameSettings]) + + const setSetting = useMemo(() => (key: TKey, value: SettingProfileModel[TKey]) => { + setGameSettings({ + ...gameSettings, + [key]: value, + }); + }, [gameSettings, setGameSettings]); + + return ( + <> + Game + + } + title="Force Fullscreen" + > + setSetting('force_fullscreen', val)} + /> + + + } + title="Resolution" + > +
+ { + setSetting('res', { height: gameSettings.res?.height ?? 720, width: Number(e.currentTarget.value) }); + }} + type="number" + value={gameSettings.res?.width} + /> + + { + setSetting('res', { width: gameSettings.res?.height ?? 1280, height: Number(e.currentTarget.value) }); + }} + type="number" + value={gameSettings.res?.height} + /> +
+
+ + } + title="Memory" + > +
+
+ { + setSetting('mem_max', Number(e.currentTarget.value)); + }} + type="number" + value={gameSettings.mem_max ?? undefined} + /> +
+
+
+ + ); +} + +// function LauncherSettings() { +// return ( +// <> +// Launcher +// } +// title="Hide On Launch" +// > +// +// +// +// ); +// } + +export function ProcessSettings() { + const { createSetting } = useSettings(); + + const [gameSettings, setGameSettings] = createSetting('global_game_settings'); + + // const getSetting = useMemo(()=>(key: TKey)=>gameSettings[key], [gameSettings]) + + const setSetting = useMemo(() => (key: TKey, value: SettingProfileModel[TKey]) => { + setGameSettings({ + ...gameSettings, + [key]: value, + }); + }, [gameSettings, setGameSettings]); + + return ( + <> + Process + + } + title="Pre-Launch Command" + > + setSetting('hook_pre', e.currentTarget.value)} + placeholder="echo 'Game started'" + value={gameSettings.hook_pre ?? undefined} + /> + + + } + title="Wrapper Command" + > + setSetting('hook_wrapper', e.currentTarget.value)} + placeholder="gamescope" + value={gameSettings.hook_wrapper ?? undefined} + /> + + + } + title="Post-Exit Command" + > + setSetting('hook_post', e.currentTarget.value)} + placeholder="echo 'Game exited'" + value={gameSettings.hook_post ?? undefined} + /> + + + ); +} diff --git a/apps/oneclient/frontend/src/routes/app/settings/route.tsx b/apps/oneclient/frontend/src/routes/app/settings/route.tsx new file mode 100644 index 00000000..b51ab5ba --- /dev/null +++ b/apps/oneclient/frontend/src/routes/app/settings/route.tsx @@ -0,0 +1,135 @@ +import type { JSX } from 'react'; +import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'; +import { Brush01Icon, CodeSnippet02Icon, MessageTextSquare01Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon, Users01Icon } from '@untitled-theme/icons-react'; +import { useEffect } from 'react'; + +export const Route = createFileRoute('/app/settings')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+
+ , 'General', '/'], + [, 'Appearance', '/appearance'], + // [, 'APIs', '/apis'], + // [, 'Language', '/language'], + ], + 'Game Settings': [ + [, 'Minecraft settings', '/minecraft'], + ], + 'About': [ + [, 'Changelog', '/changelog'], + [, 'Feedback', '/feedback'], + [, 'Developer Options', '/developer'], + ], + }} + /> + {/* */} +
+ +
+ +
+
+ ); +} + +interface SidebarProps { + base: string; + links: Record>; +} + +function Sidebar(props: SidebarProps) { + const { base, links } = props; + + const navigate = useNavigate(); + const routerState = useRouterState(); + + const location = routerState.location.pathname; + + useEffect(() => { + if (base.endsWith('/')) + throw new Error('Base should not end with a slash'); + }); + + function goto(href: string, params?: URLSearchParams) { + const currParams = new URLSearchParams(location); + if (params) + for (const [key, value] of params) + currParams.set(key, value); + + const url = `${base}${href}`; + navigate({ to: url }); + } + + function isActive(link: string, params: URLSearchParams | undefined) { + const fullBasePath = `${base}${link}`; + const pathnameMatch = location.startsWith(fullBasePath) || `${location}/`.startsWith(fullBasePath); + + if (!params) + return pathnameMatch; + + const searchParams = new URLSearchParams(routerState.location.searchStr); + for (const [key, value] of params) + if (searchParams.get(key) !== value) + return false; + + return pathnameMatch; + } + + return ( +
+ {Object.keys(links).map(section => ( +
+
+

{section.toUpperCase()}

+
+ {links[section].map((entry) => { + if (!entry) + return ''; + + const [Component, label, link, params] = entry; + + return ( + goto(link, params)} + > + + {Component} + {label} + + + ); + })} +
+
+
+ ))} +
+ ); +} + +interface SidebarPageProps { + className?: string; + children: JSX.Element | Array; +} + +Sidebar.Page = function (props: SidebarPageProps) { + return ( +
+ {props.children} +
+ ); +}; + +export default Sidebar; diff --git a/packages/core/src/api/packages/provider/curseforge/mod.rs b/packages/core/src/api/packages/provider/curseforge/mod.rs index ac77a80e..ad4d7574 100644 --- a/packages/core/src/api/packages/provider/curseforge/mod.rs +++ b/packages/core/src/api/packages/provider/curseforge/mod.rs @@ -462,7 +462,7 @@ impl From for ManagedPackage { slug: value.slug, name: value.name, short_desc: value.summary, - body: ManagedPackageBody::Url(format!("/v1/mods/{}/description", value.id)), + body: ManagedPackageBody::Url(format!("/mods/{}/description", value.id)), version_ids: latest_version_ids, mc_versions, loaders, diff --git a/packages/core/src/api/packages/provider/mod.rs b/packages/core/src/api/packages/provider/mod.rs index 825c8c8a..20b6876f 100644 --- a/packages/core/src/api/packages/provider/mod.rs +++ b/packages/core/src/api/packages/provider/mod.rs @@ -130,4 +130,8 @@ impl ProviderExt for Provider { async fn get_versions(&self, slugs: &[String]) -> LauncherResult> { delegate!(self, get_versions(slugs)) } + + async fn get_body(&self, body: &ManagedPackageBody) -> LauncherResult { + delegate!(self, get_body(body)) + } }