diff --git a/apps/onelauncher/desktop/src/api/commands/cluster.rs b/apps/onelauncher/desktop/src/api/commands/cluster.rs index a7b6c03f..f339a12a 100644 --- a/apps/onelauncher/desktop/src/api/commands/cluster.rs +++ b/apps/onelauncher/desktop/src/api/commands/cluster.rs @@ -218,7 +218,7 @@ use std::ops::Deref; -use onelauncher_core::{api::cluster, entity::{clusters::Model, icon::Icon, loader::GameLoader}, error::LauncherResult}; +use onelauncher_core::{api::{cluster, setting_profiles}, entity::{clusters::Model, icon::Icon, loader::GameLoader}, error::LauncherResult, migration::sea_orm}; use serde::{Deserialize, Serialize}; use specta::Type; use crate::api::error::SerializableResult; @@ -237,7 +237,14 @@ pub struct CreateCluster { pub async fn create_cluster(options: CreateCluster) -> SerializableResult { let thing = cluster::create_cluster(&options.name, &options.mc_version, options.mc_loader, options.mc_loader_version.as_deref(), options.icon_url).await?; - cluster::prepare_cluster(&mut thing.clone(), Some(false)); + // cluster::prepare_cluster(&mut thing.clone(), Some(false)); + + if setting_profiles::dao::get_profile_by_name(&options.name).await?.is_none() { + setting_profiles::create_profile(&options.name, async |mut profile| { + profile.mem_max = sea_orm::ActiveValue::Set(Some(2048)); + Ok(profile) + }).await?; + } Ok(thing) } diff --git a/apps/onelauncher/desktop/src/api/commands/settings.rs b/apps/onelauncher/desktop/src/api/commands/settings.rs index 316b5346..3173c553 100644 --- a/apps/onelauncher/desktop/src/api/commands/settings.rs +++ b/apps/onelauncher/desktop/src/api/commands/settings.rs @@ -1,11 +1,11 @@ -use onelauncher_core::entity::setting_profiles::Model; +use onelauncher_core::entity::setting_profiles::Model as SettingsModel; use onelauncher_core::api::setting_profiles; use crate::api::error::SerializableResult; #[specta::specta] #[tauri::command] -pub async fn get_global_profile() -> SerializableResult { +pub async fn get_global_profile() -> SerializableResult { Ok(setting_profiles::get_global_profile().await) } @@ -13,6 +13,6 @@ pub async fn get_global_profile() -> SerializableResult { #[tauri::command] pub async fn get_profile_or_default( name: Option, -) -> SerializableResult { +) -> SerializableResult { Ok(setting_profiles::dao::get_profile_or_default(name.as_ref()).await?) } \ No newline at end of file diff --git a/apps/onelauncher/frontend/src/components/base/Switch.tsx b/apps/onelauncher/frontend/src/components/base/Switch.tsx index 31201cd6..b0a87e7c 100644 --- a/apps/onelauncher/frontend/src/components/base/Switch.tsx +++ b/apps/onelauncher/frontend/src/components/base/Switch.tsx @@ -5,26 +5,37 @@ import { Switch as AriaSwitch } from 'react-aria-components'; import { tv } from 'tailwind-variants'; export const switchVariants = tv({ - base: [ - 'w-[40px] h-[22px] p-3 flex flex-row relative rounded-full transition-colors overflow-hidden bg-brand', - ], -}) + base: [ + 'group inline-flex h-6 w-11 items-center rounded-full transition-colors', + 'bg-component-bg-pressed data-[selected]:bg-blue-600', + 'focus:outline-none', + 'disabled:cursor-not-allowed disabled:opacity-50', + ], +}); + +export const switchThumbVariants = tv({ + base: [ + 'pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow ring-0 transition-transform', + 'translate-x-1 group-data-[selected]:translate-x-6', + ], +}); export type SwitchProps = AriaSwitchProps & RefAttributes & { - className?: string; + className?: string; children?: React.ReactNode; }; function Switch({ - className, - ...props + className, + children, + ...props }: SwitchProps) { - return ( - -
- {props.children} + return ( + + + {children && {children}} - ); + ); } -export default Switch; +export default Switch; \ No newline at end of file diff --git a/apps/onelauncher/frontend/src/components/base/Tooltip.tsx b/apps/onelauncher/frontend/src/components/base/Tooltip.tsx new file mode 100644 index 00000000..f2b3dfd7 --- /dev/null +++ b/apps/onelauncher/frontend/src/components/base/Tooltip.tsx @@ -0,0 +1,59 @@ +import type React from 'react'; +import type { RefAttributes } from 'react'; +import type { TooltipTriggerComponentProps as AriaTooltipTriggerProps } from 'react-aria-components'; +import { + TooltipTrigger as AriaTooltipTrigger, + Tooltip as AriaTooltip, + OverlayArrow +} from 'react-aria-components'; +import { tv } from 'tailwind-variants'; + +export const tooltipVariants = tv({ + base: [ + 'px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-lg', + 'border border-gray-700 max-w-xs z-50', + 'data-[entering]:animate-in data-[entering]:fade-in data-[entering]:zoom-in-95', + 'data-[exiting]:animate-out data-[exiting]:fade-out data-[exiting]:zoom-out-95', + ], +}); + +export const tooltipArrowVariants = tv({ + base: [ + 'fill-gray-900 stroke-gray-700 stroke-1', + ], +}); + +export type TooltipProps = AriaTooltipTriggerProps & RefAttributes & { + text: string; + children: React.ReactElement; + placement?: 'top' | 'bottom' | 'left' | 'right'; + className?: string; + delay?: number; + closeDelay?: number; +}; + +function Tooltip({ + text, + children, + placement = 'top', + className, + delay = 700, + closeDelay = 0, + ...props +}: TooltipProps) { + return ( + + {children} + + + + + + + {text} + + + ); +} + +export default Tooltip; \ No newline at end of file diff --git a/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx b/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx index ac118e5f..00042c6d 100644 --- a/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx +++ b/apps/onelauncher/frontend/src/routes/app/cluster/index.tsx @@ -58,7 +58,7 @@ function RouteComponent() { Cluster Actions Edit + )} description="Edit the cluster name and cover image." icon={} diff --git a/apps/onelauncher/frontend/src/routes/app/cluster/settings.tsx b/apps/onelauncher/frontend/src/routes/app/cluster/settings.tsx index 0d1c47f2..dabbeaa3 100644 --- a/apps/onelauncher/frontend/src/routes/app/cluster/settings.tsx +++ b/apps/onelauncher/frontend/src/routes/app/cluster/settings.tsx @@ -3,6 +3,7 @@ import Sidebar from '../settings/route' import ScrollableContainer from '@/components/ScrollableContainer' import useCommand from '@/hooks/useCommand' import { bindings } from '@/main' +import { GameSettings, ProcessSettings } from '../settings/minecraft' export const Route = createFileRoute('/app/cluster/settings')({ component: RouteComponent, @@ -11,14 +12,22 @@ export const Route = createFileRoute('/app/cluster/settings')({ function RouteComponent() { const { id } = Route.useSearch() - const result = useCommand("getProfileOrDefault", () => bindings.commands.getProfileOrDefault(id.toString())) + const cluster = useCommand("getClusterById", () => bindings.commands.getClusterById(Number(id.toString()) as unknown as bigint)) + const result = useCommand("getProfileOrDefault", () => bindings.commands.getProfileOrDefault(cluster.data?.name as string), { + enabled: !!cluster.data?.name + }) + + console.log(result.data) return ( -

Settings

-
{JSON.stringify(result.data, null, 2)}
+

Minecraft Settings

+ + + +
diff --git a/apps/onelauncher/frontend/src/routes/app/index.tsx b/apps/onelauncher/frontend/src/routes/app/index.tsx index 9d283424..1fc42598 100644 --- a/apps/onelauncher/frontend/src/routes/app/index.tsx +++ b/apps/onelauncher/frontend/src/routes/app/index.tsx @@ -8,7 +8,8 @@ import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import DefaultInstancePhoto from "@/assets/images/default_instance_cover.jpg" import Modal from '@/components/overlay/Modal'; import { Server01Icon } from '@untitled-theme/icons-react'; -import HeaderImage from "@/assets/images/default_instance_cover.jpg"; +import HeaderImage from "@/assets/images/default_banner.png"; +import { TextField } from '@/components/base/TextField'; export const Route = createFileRoute('/app/')({ component: RouteComponent, @@ -150,13 +151,13 @@ function ClusterCreate() {

New Cluster

- asd + {/* asd */}
-
asdasdasdasds
+
diff --git a/apps/onelauncher/frontend/src/routes/app/settings/accounts.tsx b/apps/onelauncher/frontend/src/routes/app/settings/accounts.tsx new file mode 100644 index 00000000..8db5d86f --- /dev/null +++ b/apps/onelauncher/frontend/src/routes/app/settings/accounts.tsx @@ -0,0 +1,133 @@ +import { createFileRoute } from '@tanstack/react-router' +import Sidebar from './route' +import ScrollableContainer from '@/components/ScrollableContainer' +import Button from '@/components/base/Button' +import { InfoCircleIcon, Trash01Icon, UserPlus02Icon } from '@untitled-theme/icons-react' +import PlayerHead from '@/components/content/PlayerHead' +import { useState } from 'react' +import { Show } from '@/components/base/Show' +import Tooltip from '@/components/base/Tooltip' +import useCommand from '@/hooks/useCommand' +import { bindings } from '@/main' + +export const Route = createFileRoute('/app/settings/accounts')({ + component: RouteComponent, +}) + +interface Account { + id: string; + username: string; +} + +function RouteComponent() { + const { data: usersData, isLoading: usersLoading } = useCommand("getUsers", bindings.commands.getUsers) + const { data: defaultUserData, isLoading: defaultUserLoading } = useCommand("getDefaultUser", () => bindings.commands.getDefaultUser(false)) + + if (usersLoading || defaultUserLoading) { + return

Accounts

Loading accounts...

; + } + + return ( + +

Accounts

+ +
+ 0} fallback={

No accounts found.

}> + {usersData?.map((account) => ( + + ))} +
+
+
+ +
+ +
+
+ ) +} + +interface AccountRowProps { + account: Account; + isCurrent?: boolean; +}; + +function AccountRow({ account, isCurrent }: AccountRowProps) { + const [errored, setErrored] = useState(false); + + const setDefaultUserCommand = useCommand( + "setDefaultUser", + () => bindings.commands.setDefaultUser(account.id), + { + enabled: false, + subscribed: false, + } + ); + + const removeUserCommand = useCommand( + "removeUser", + () => bindings.commands.removeUser(account.id), + { + enabled: false, + subscribed: false, + } + ); + + const handleSetDefault = () => { + setDefaultUserCommand.refetch(); + }; + + const handleDelete = (e: React.MouseEvent) => { + e.stopPropagation(); + removeUserCommand.refetch(); + }; + + return ( +
{ if (e.key === 'Enter' || e.key === ' ') handleSetDefault(); }} + > +
+ setErrored(true)} + uuid={account.id} + /> +
+ +
+
+

{account.username}

+ + + + + +
+

{account.id}

+
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/apps/onelauncher/frontend/src/routes/app/settings/appearance.tsx b/apps/onelauncher/frontend/src/routes/app/settings/appearance.tsx new file mode 100644 index 00000000..362fcea3 --- /dev/null +++ b/apps/onelauncher/frontend/src/routes/app/settings/appearance.tsx @@ -0,0 +1,115 @@ +import { createFileRoute } from '@tanstack/react-router' +import Sidebar from './route' +import ScrollableContainer from '@/components/ScrollableContainer' +import { THEMES } from '@/utils/theming' +import SettingsRow from '@/components/SettingsRow' +import { Monitor01Icon, PackageIcon, Speedometer04Icon } from '@untitled-theme/icons-react' +import ToggleButton from '@/components/base/ToggleButton' +import Switch from '@/components/base/Switch' + +export const Route = createFileRoute('/app/settings/appearance')({ + component: RouteComponent, +}) + +function RouteComponent() { + return ( + + +
+

Appearance

+ +
+ +
+ {/* + {theme => ( + + {variant => ( + + )} + + )} + */} + + {Object.keys(THEMES).map((theme) => { + return THEMES[theme as keyof typeof THEMES].variants.map((variant, i: number) => { + return ( + { }} key={i} /> + ) + }) + })} +
+
+ + } + 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(props: ThemeCardProps) { + return ( +
props.setTheme(props.themeSelector)}> + + + + + + + + + +
+ ); +} + +function PrimaryThemeCard() { + return ( +
+ + + + + + + + + + + + + +
+ ); +} \ No newline at end of file diff --git a/apps/onelauncher/frontend/src/routes/app/settings/index.tsx b/apps/onelauncher/frontend/src/routes/app/settings/index.tsx index 5710f8e3..1579c9ca 100644 --- a/apps/onelauncher/frontend/src/routes/app/settings/index.tsx +++ b/apps/onelauncher/frontend/src/routes/app/settings/index.tsx @@ -4,8 +4,9 @@ import useCommand from '@/hooks/useCommand' import { bindings } from '@/main' import SettingsRow from '@/components/SettingsRow' import DiscordIcon from "@/assets/logos/discord.svg" -import ToggleButton from '@/components/base/ToggleButton' import Switch from '@/components/base/Switch' +import Button from '@/components/base/Button' +import { FolderIcon, LinkExternal01Icon, XIcon } from '@untitled-theme/icons-react' export const Route = createFileRoute('/app/settings/')({ component: RouteComponent, @@ -20,14 +21,42 @@ function RouteComponent() {

General Settings

} - title="Discord RPC" + description="Enable Discord Rich Presence." + icon={} + title="Discord RPC" + > + + + + } + title="Hide Close Dialog" + > + + + + {/* } + title="Error Analytics" > - Thing - - -
{JSON.stringify(result.data, null, 2)}
+ !(settings().disable_analytics ?? false)} + onChecked={value => settings().disable_analytics = !value} + /> + */} + + Folders and Files + } + title="Launcher Folder" + > + +
) diff --git a/apps/onelauncher/frontend/src/routes/app/settings/minecraft.tsx b/apps/onelauncher/frontend/src/routes/app/settings/minecraft.tsx index c0545744..b6bbff8a 100644 --- a/apps/onelauncher/frontend/src/routes/app/settings/minecraft.tsx +++ b/apps/onelauncher/frontend/src/routes/app/settings/minecraft.tsx @@ -1,8 +1,14 @@ import { createFileRoute } from '@tanstack/react-router' import Sidebar from './route' import SettingsRow from '@/components/SettingsRow' -import { Maximize01Icon } from '@untitled-theme/icons-react' +import { Database01Icon, EyeIcon, FilePlus02Icon, FileX02Icon, LayoutTopIcon, Maximize01Icon, ParagraphWrapIcon, XIcon } from '@untitled-theme/icons-react' import Button from '@/components/base/Button' +import useCommand from '@/hooks/useCommand' +import { bindings } from '@/main' +import { TextField } from '@/components/base/TextField' +import ToggleButton from '@/components/base/ToggleButton' +import ScrollableContainer from '@/components/ScrollableContainer' +import Switch from '@/components/base/Switch' export const Route = createFileRoute('/app/settings/minecraft')({ component: RouteComponent, @@ -11,19 +17,126 @@ export const Route = createFileRoute('/app/settings/minecraft')({ function RouteComponent() { return ( -
-

Minecraft Settings

+ +
+

Minecraft Settings

- Game + - } - title="Force Fullscreen" - > - - -
+ {/* */} + + +
+
) } + +export function GameSettings() { + return <> + Game + + } + title="Force Fullscreen" + > + + + + } + title="Resolution" + > +
+ + + +
+
+ + } + title="Memory" + > +
+
+ Min: + +
+ +
+ Max: + +
+
+
+ +} + +function LauncherSettings() { + return <> + Launcher + } + title="Hide On Launch" + > + + + +} + +export function ProcessSettings() { + return <> + Process + + } + title="Pre-Launch Command" + > + + + + } + title="Wrapper Command" + > + + + + } + title="Post-Exit Command" + > + + + +} \ No newline at end of file diff --git a/apps/onelauncher/frontend/src/utils/theming.ts b/apps/onelauncher/frontend/src/utils/theming.ts new file mode 100644 index 00000000..d9fb26bb --- /dev/null +++ b/apps/onelauncher/frontend/src/utils/theming.ts @@ -0,0 +1,26 @@ +export const DEFAULT_THEME = 'OneLauncher-Dark'; +export const THEMES = { + OneLauncher: { + variants: [ + { + name: 'Dark', + type: 'dark', + }, + { + name: 'Light', + type: 'light', + }, + ], + }, +} as const satisfies Record; + +export interface Theme { + variants: ThemeVariant[]; +} + +export type ThemeTypes = 'dark' | 'light'; + +export interface ThemeVariant { + type: ThemeTypes; + name: string; +}; \ No newline at end of file diff --git a/packages/macro/src/specta.rs b/packages/macro/src/specta.rs index 1650fdd6..3529898b 100644 --- a/packages/macro/src/specta.rs +++ b/packages/macro/src/specta.rs @@ -16,9 +16,16 @@ pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> p proc_macro2::TokenStream::new() }; - let specta_type = quote! { - #[cfg_attr(feature = "specta", derive(specta::Type))] - }; + let specta_type = if let Some(rename) = attrs.rename { + quote! { + #[cfg_attr(feature = "specta", derive(specta::Type))] + #[cfg_attr(feature = "specta", specta(rename = #rename))] + } + } else { + quote! { + #[cfg_attr(feature = "specta", derive(specta::Type))] + } + }; let expanded = quote! { #specta_event_type @@ -32,12 +39,15 @@ pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> p #[derive(Default)] struct SpectaAttributes { event: bool, + rename: Option, } impl SpectaAttributes { fn parse(&mut self, meta: ParseNestedMeta) -> syn::Result<()> { if meta.path.is_ident("event") { self.event = true; + } else if meta.path.is_ident("rename") { + self.rename = Some(meta.value()?.to_string()); } Ok(()) }