diff --git a/.vscode/launch.json b/.vscode/launch.json index 2054d910..a1d59c56 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "args": [ "build", "--profile=dev-debug", - "--manifest-path=./apps/desktop/Cargo.toml", + "--manifest-path=./apps/onelauncher/desktop/Cargo.toml", "--no-default-features" ], "problemMatcher": "$rustc" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d39bc396..43e09f85 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -41,7 +41,7 @@ }, "isBackground": true, "command": "pnpm", - "args": ["frontend", "dev", "--mode=development"], + "args": ["onelauncher:frontend", "dev", "--mode=development"], "runOptions": { "instanceLimit": 1 } diff --git a/Cargo.toml b/Cargo.toml index 033bcae0..70845882 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,6 +148,7 @@ sea-orm = { version = "1.1", features = [ "with-chrono", "with-json", "with-uuid", + "sqlite-use-returning-for-3_35", ] } sea-orm-migration = { version = "1.1", features = [ "sqlx-sqlite", diff --git a/apps/onelauncher/frontend/src/components/launcher/cluster/ClusterCreation.tsx b/apps/onelauncher/frontend/src/components/launcher/cluster/ClusterCreation.tsx new file mode 100644 index 00000000..7f805670 --- /dev/null +++ b/apps/onelauncher/frontend/src/components/launcher/cluster/ClusterCreation.tsx @@ -0,0 +1,212 @@ +import type { JSX } from 'react'; +import DefaultBanner from '@/assets/images/default_banner.png'; +import LauncherIcon from '@/components/content/LauncherIcon'; +import Modal from '@/components/overlay/Modal'; +import { bindings } from '@/main'; +import { LAUNCHER_IMPORT_TYPES } from '@/utils'; +import { useCommand } from '@onelauncher/common'; +import { Button, Dropdown, TextField } from '@onelauncher/common/components'; +import { Server01Icon, User03Icon } from '@untitled-theme/icons-react'; +import { useCallback, useState } from 'react'; + +const STEPS = [ + { + id: 'loader', + title: 'Select Provider', + }, + { + id: 'info', + title: 'Game Setup', + }, +] as const; + +export function NewClusterCreate() { + const [currentStepIndex, setCurrentStepIndex] = useState(0); + const [formData, setFormData] = useState({ + loader: '', + clusterName: '', + clusterVersion: '', + }); + + const handleNext = useCallback(async () => { + if (currentStepIndex < STEPS.length - 1) + setCurrentStepIndex(currentStepIndex + 1); + }, [currentStepIndex]); + + const handleBack = useCallback(() => { + if (currentStepIndex > 0) + setCurrentStepIndex(currentStepIndex - 1); + }, [currentStepIndex]); + + const currentStepConfig = STEPS[currentStepIndex]; + let stepContent: JSX.Element; + + switch (currentStepConfig.id) { + case 'loader': { + stepContent = ( + setFormData(prev => ({ ...prev, loader }))} + selectedLoader={formData.loader} + /> + ); + break; + } + + case 'info': { + stepContent = ( + setFormData(prev => ({ ...prev, clusterName: name }))} + onClusterVersionChange={version => + setFormData(prev => ({ ...prev, clusterVersion: version }))} + /> + ); + break; + } + } + + const isFirstStep = currentStepIndex === 0; + const isLastInputStep = currentStepIndex === STEPS.length - 1; + const nextButtonText = isLastInputStep ? 'Create Cluster' : 'Next'; + + let isNextDisabled = false; + if (currentStepConfig.id === 'loader' && !formData.loader) + isNextDisabled = true; + else if ( + currentStepConfig.id === 'info' + && (!formData.clusterName.trim() || !formData.clusterVersion.trim()) + ) + isNextDisabled = true; + + return ( + <> + + + {/* */} + + - - + + + + ); } diff --git a/apps/onelauncher/frontend/src/routes/onboarding/login.tsx b/apps/onelauncher/frontend/src/routes/onboarding/login.tsx index afa25829..1d7f3410 100644 --- a/apps/onelauncher/frontend/src/routes/onboarding/login.tsx +++ b/apps/onelauncher/frontend/src/routes/onboarding/login.tsx @@ -12,7 +12,6 @@ export const Route = createFileRoute('/onboarding/login')({ }); function RouteComponent() { - const ctx = useOnboardingContext(); const [profile, setProfile] = useState(); const [errorMessage, setErrorMessage] = useState(''); const result = useCommand('openMsaLogin', bindings.core.openMsaLogin, { @@ -33,8 +32,6 @@ function RouteComponent() { } setProfile(data.data); - - ctx.setIsForwardButtonEnabled(false); }); } diff --git a/apps/onelauncher/frontend/src/routes/onboarding/route.tsx b/apps/onelauncher/frontend/src/routes/onboarding/route.tsx index 11e68ae1..29df0956 100644 --- a/apps/onelauncher/frontend/src/routes/onboarding/route.tsx +++ b/apps/onelauncher/frontend/src/routes/onboarding/route.tsx @@ -239,7 +239,7 @@ function RouteComponent() { ]); return ( - +
- + ); } diff --git a/apps/onelauncher/frontend/src/routes/onboarding/summary.tsx b/apps/onelauncher/frontend/src/routes/onboarding/summary.tsx index 1206badd..9987684d 100644 --- a/apps/onelauncher/frontend/src/routes/onboarding/summary.tsx +++ b/apps/onelauncher/frontend/src/routes/onboarding/summary.tsx @@ -5,5 +5,17 @@ export const Route = createFileRoute('/onboarding/summary')({ }); function RouteComponent() { - return
Hello "/onboarding/summary"!
; + return ( +
+

+ Prepare OneLauncher +

+ +

Are you sure you want to proceed with the following tasks?

+ +
+
tasks
+
+
+ ); } diff --git a/eslint.config.js b/eslint.config.js index 5e70391c..5775a49d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -27,9 +27,10 @@ export default defineConfig( rules: { 'sort-imports': 'off', // Replaced by perfectionist/sort-named-imports', 'import/order': 'off', // Replaced by perfectionist/sort-named-imports', - 'style/jsx-max-props-per-line': ['error', { maximum: 3}], + 'style/jsx-max-props-per-line': ['error', { maximum: 3 }], 'ts/no-use-before-define': 'off', - 'style/function-paren-newline': ['error', 'consistent'] + 'style/function-paren-newline': ['error', 'consistent'], + 'react/no-context-provider': 'off', }, }, ); diff --git a/packages/core/src/api/java/mod.rs b/packages/core/src/api/java/mod.rs index 6b1051c1..1d284158 100644 --- a/packages/core/src/api/java/mod.rs +++ b/packages/core/src/api/java/mod.rs @@ -206,26 +206,57 @@ pub async fn locate_java() -> LauncherResult> { #[cfg(target_os = "windows")] fn internal_locate_java() -> Vec { - let mut found = Vec::new(); + let mut java_homes = Vec::new(); + + // epic common paths + let common_paths = [ + r"C:\Program Files\Java", + r"C:\Program Files (x86)\Java", + r"C:\Program Files\OpenJDK", + r"C:\Program Files\Eclipse Adoptium", + r"C:\Program Files\Zulu", + r"C:\Program Files\Amazon Corretto", + ]; + + for base_path in common_paths { + if let Ok(entries) = std::fs::read_dir(base_path) { + for entry in entries.flatten() { + if entry.file_type().map_or(false, |ft| ft.is_dir()) { + let java_exe = entry.path().join("bin").join("java.exe"); + if java_exe.exists() { + java_homes.push(java_exe); + } + } + } + } + } + + // env vars + if let Ok(java_home) = std::env::var("JAVA_HOME") { + let path_buf = PathBuf::from(java_home); + if path_buf.join("bin").join("java.exe").exists() { + java_homes.push(path_buf); + } + } - // TODO: Implement Registry scanning - // TODO(windows): More paths for Java installations - - let paths = vec![ - r"C:/Program Files/Java", - r"C:/Program Files/Eclipse Adoptium", - r"C:/Program Files (x86)/Java", - r"C:/Program Files (x86)/Eclipse Adoptium", - ]; - - for path in paths { - let path = PathBuf::from(path).join(get_java_bin()); - if path.exists() { - found.push(path); - } - } - - found + if let Ok(path) = std::env::var("PATH") { + for path_entry in path.split(';') { + let java_exe = PathBuf::from(path_entry).join("java.exe"); + if java_exe.exists() { + + if let Some(bin_dir) = java_exe.parent() { + if let Some(java_home) = bin_dir.parent() { + let java_home_path = java_home.to_path_buf(); + if !java_homes.contains(&java_home_path) { + java_homes.push(java_home_path); + } + } + } + } + } + } + + java_homes } #[cfg(target_os = "macos")] diff --git a/packages/core/src/api/tauri/commands.rs b/packages/core/src/api/tauri/commands.rs index 05455a7e..92e5a9da 100644 --- a/packages/core/src/api/tauri/commands.rs +++ b/packages/core/src/api/tauri/commands.rs @@ -1,5 +1,4 @@ -use interpulse::api::minecraft::Version; -use onelauncher_entity::{icon::Icon, loader::GameLoader}; +use onelauncher_entity::{icon::Icon, loader::GameLoader, prelude::entity}; use tauri::{AppHandle, Runtime}; use crate::{api::{self, cluster::dao::ClusterId}, error::LauncherResult, store::{credentials::MinecraftCredentials, Core}}; @@ -59,7 +58,6 @@ pub trait TauriLauncherApi { #[taurpc(alias = "openMsaLogin")] async fn open_msa_login(app_handle: AppHandle) -> LauncherResult>; - } @@ -224,5 +222,4 @@ impl TauriLauncherApi for TauriLauncherApiImpl { Ok(None) } - } diff --git a/packages/web_common/src/components/Dropdown.tsx b/packages/web_common/src/components/Dropdown.tsx index c6095d05..92b996ba 100644 --- a/packages/web_common/src/components/Dropdown.tsx +++ b/packages/web_common/src/components/Dropdown.tsx @@ -1,27 +1,28 @@ import type { - SelectProps as AriaComboBoxProps, ListBoxItemProps as AriaListBoxItemProps, + SelectProps as AriaSelectProps, } from 'react-aria-components'; import type { VariantProps } from 'tailwind-variants'; import type { ClassNameString } from '../types/global'; import { - Select as AriaComboBox, Label as AriaLabel, ListBox as AriaListBox, ListBoxItem as AriaListBoxItem, Popover as AriaPopover, + Select as AriaSelect, SelectValue, } from 'react-aria-components'; +import { twMerge } from 'tailwind-merge'; import { tv } from 'tailwind-variants'; import { Button } from '.'; -export interface ComboBoxWrapperProps extends Omit, 'className'>, ClassNameString { +export interface ComboBoxWrapperProps extends Omit, 'className'>, ClassNameString { label?: string; } const dropdownVariants = tv({ base: [ - 'flex flex-col border border-component-bg-hover w-fit rounded-lg focus:outline-none', + 'relative h-full', ], }); @@ -34,21 +35,18 @@ export function Dropdown({ ...rest }: ComboBoxWrapperProps) { return ( - + {label && {label}} -
+ -
- - + + + + {children} -
+ ); } @@ -59,7 +57,7 @@ Dropdown.Item = ({ ...rest }: AriaListBoxItemProps & ClassNameString) => ( );