diff options
| author | 2026-01-14 22:05:25 +0100 | |
|---|---|---|
| committer | 2026-01-14 22:05:25 +0100 | |
| commit | b473aa744e1382e946a92a116707b93151558888 (patch) | |
| tree | a8957a732caac948412c78ac7a443771f7ee12d0 /ui/src/App.svelte | |
| parent | 2cb21f2bbc601ae134095cf0e68b5bcc6966d227 (diff) | |
| parent | 18111ef323a81e399e3b907c9046170afcb8e0eb (diff) | |
| download | DropOut-b473aa744e1382e946a92a116707b93151558888.tar.gz DropOut-b473aa744e1382e946a92a116707b93151558888.zip | |
Merge main into feat/download-java-rt
- Integrate latest main branch changes (Fabric, Forge support, new UI)
- Keep Adoptium Java download feature with SHA256 support
- Merge improved download progress tracking with checksum verification
- Update dependencies and build configuration
Diffstat (limited to 'ui/src/App.svelte')
| -rw-r--r-- | ui/src/App.svelte | 919 |
1 files changed, 131 insertions, 788 deletions
diff --git a/ui/src/App.svelte b/ui/src/App.svelte index b637512..1c465b1 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -1,98 +1,9 @@ <script lang="ts"> import { getVersion } from "@tauri-apps/api/app"; - import { onMount } from "svelte"; + import { onMount, onDestroy } from "svelte"; + import { convertFileSrc } from "@tauri-apps/api/core"; import DownloadMonitor from "./lib/DownloadMonitor.svelte"; import GameConsole from "./lib/GameConsole.svelte"; - - let status = "Ready"; - let showConsole = false; - let currentView = "home"; - let statusTimeout: any; - let appVersion = "..."; - - // Watch for status changes to auto-dismiss - $: if (status !== "Ready") { - if (statusTimeout) clearTimeout(statusTimeout); - statusTimeout = setTimeout(() => { - status = "Ready"; - }, 5000); - } - - interface Version { - id: string; - type: string; - url: string; - time: string; - releaseTime: string; - } - - interface Account { - type: "Offline" | "Microsoft"; - username: string; - uuid: string; - } - - interface DeviceCodeResponse { - user_code: string; - device_code: string; - verification_uri: string; - expires_in: number; - interval: number; - message?: string; - } - - interface LauncherConfig { - min_memory: number; - max_memory: number; - java_path: string; - width: number; - height: number; - } - - interface JavaInstallation { - path: string; - version: string; - is_64bit: boolean; - } - - interface JavaDownloadInfo { - version: string; - release_name: string; - download_url: string; - file_name: string; - file_size: number; - checksum: string | null; - image_type: string; - } - - let versions: Version[] = []; - let selectedVersion = ""; - let currentAccount: Account | null = null; - let settings: LauncherConfig = { - min_memory: 1024, - max_memory: 2048, - java_path: "java", - width: 854, - height: 480, - }; - let javaInstallations: JavaInstallation[] = []; - let isDetectingJava = false; - - let availableJavaVersions: number[] = []; - let selectedJavaVersion = 21; - let selectedImageType: "jre" | "jdk" = "jre"; - let isDownloadingJava = false; - let javaDownloadStatus = ""; - let showJavaDownloadModal = false; - - // Login UI State - let isLoginModalOpen = false; - let loginMode: "select" | "offline" | "microsoft" = "select"; - let offlineUsername = ""; - let deviceCodeData: DeviceCodeResponse | null = null; - let msLoginLoading = false; - let msLoginStatus = "Waiting for authorization..."; - let isPollingRequestActive = false; // Components import Sidebar from "./components/Sidebar.svelte"; @@ -102,6 +13,7 @@ import BottomBar from "./components/BottomBar.svelte"; import LoginModal from "./components/LoginModal.svelte"; import StatusToast from "./components/StatusToast.svelte"; + import ParticleBackground from "./components/ParticleBackground.svelte"; // Stores import { uiState } from "./stores/ui.svelte"; @@ -109,726 +21,157 @@ import { settingsState } from "./stores/settings.svelte"; import { gameState } from "./stores/game.svelte"; + let mouseX = $state(0); + let mouseY = $state(0); + + function handleMouseMove(e: MouseEvent) { + mouseX = (e.clientX / window.innerWidth) * 2 - 1; + mouseY = (e.clientY / window.innerHeight) * 2 - 1; + } + onMount(async () => { authState.checkAccount(); - settingsState.loadSettings(); + await settingsState.loadSettings(); gameState.loadVersions(); getVersion().then((v) => (uiState.appVersion = v)); + window.addEventListener("mousemove", handleMouseMove); }); - - async function checkAccount() { - try { - const acc = await invoke("get_active_account"); - currentAccount = acc as Account | null; - } catch (e) { - console.error("Failed to check account:", e); - } - } - - async function loadSettings() { - try { - settings = await invoke("get_settings"); - } catch (e) { - console.error("Failed to load settings:", e); - } - } - - async function saveSettings() { - try { - await invoke("save_settings", { config: settings }); - status = "Settings saved!"; - } catch (e) { - console.error("Failed to save settings:", e); - status = "Error saving settings: " + e; - } - } - - async function detectJava() { - isDetectingJava = true; - try { - javaInstallations = await invoke("detect_java"); - if (javaInstallations.length === 0) { - status = "No Java installations found"; - } else { - status = `Found ${javaInstallations.length} Java installation(s)`; - } - } catch (e) { - console.error("Failed to detect Java:", e); - status = "Error detecting Java: " + e; - } finally { - isDetectingJava = false; - } - } - - function selectJava(path: string) { - settings.java_path = path; - } - - async function openJavaDownloadModal() { - showJavaDownloadModal = true; - javaDownloadStatus = ""; - try { - availableJavaVersions = await invoke("fetch_available_java_versions"); - // Default selection logic - if (availableJavaVersions.includes(21)) { - selectedJavaVersion = 21; - } else if (availableJavaVersions.includes(17)) { - selectedJavaVersion = 17; - } else if (availableJavaVersions.length > 0) { - selectedJavaVersion = availableJavaVersions[availableJavaVersions.length - 1]; - } - } catch (e) { - console.error("Failed to fetch available Java versions:", e); - javaDownloadStatus = "Error fetching Java versions: " + e; - } - } - - function closeJavaDownloadModal() { - if (!isDownloadingJava) { - showJavaDownloadModal = false; - } - } - - async function downloadJava() { - isDownloadingJava = true; - javaDownloadStatus = `Downloading Java ${selectedJavaVersion} ${selectedImageType.toUpperCase()}...`; + + $effect(() => { + // ENFORCE DARK MODE: Always add 'dark' class and attribute + // This combined with the @variant dark in app.css ensures dark mode is always active + // regardless of system preference settings. + document.documentElement.classList.add('dark'); + document.documentElement.setAttribute('data-theme', 'dark'); - try { - const result: JavaInstallation = await invoke("download_adoptium_java", { - majorVersion: selectedJavaVersion, - imageType: selectedImageType, - customPath: null, - }); - - javaDownloadStatus = `Java ${selectedJavaVersion} installed at ${result.path}`; - settings.java_path = result.path; - - await detectJava(); - - setTimeout(() => { - showJavaDownloadModal = false; - status = `Java ${selectedJavaVersion} is ready to use!`; - }, 1500); - } catch (e) { - console.error("Failed to download Java:", e); - javaDownloadStatus = "Download failed: " + e; - } finally { - isDownloadingJava = false; - } - } - - // --- Auth Functions --- - - function openLoginModal() { - if (currentAccount) { - if (confirm("Logout " + currentAccount.username + "?")) { - invoke("logout").then(() => (currentAccount = null)); - } - return; - } - // Reset state - isLoginModalOpen = true; - loginMode = "select"; - offlineUsername = ""; - deviceCodeData = null; - msLoginLoading = false; - } - - function closeLoginModal() { - stopPolling(); - isLoginModalOpen = false; - } - - async function performOfflineLogin() { - if (!offlineUsername) return; - try { - currentAccount = (await invoke("login_offline", { - username: offlineUsername, - })) as Account; - isLoginModalOpen = false; - } catch (e) { - alert("Login failed: " + e); - } - } - - let pollInterval: any; - - // Cleanup on destroy/close - function stopPolling() { - if (pollInterval) { - clearInterval(pollInterval); - pollInterval = null; - } - } - - async function startMicrosoftLogin() { - loginMode = "microsoft"; - msLoginLoading = true; - msLoginStatus = "Waiting for authorization..."; - stopPolling(); // Ensure no duplicates - - try { - deviceCodeData = (await invoke( - "start_microsoft_login" - )) as DeviceCodeResponse; - - // UX Improvements: Auto Copy & Auto Open - if (deviceCodeData) { - try { - await navigator.clipboard.writeText(deviceCodeData.user_code); - } catch (e) { - console.error("Clipboard failed", e); - } - - openLink(deviceCodeData.verification_uri); - - // Start Polling - console.log("Starting polling for token..."); - const intervalMs = (deviceCodeData.interval || 5) * 1000; - pollInterval = setInterval( - () => checkLoginStatus(deviceCodeData!.device_code), - intervalMs - ); - } - } catch (e) { - alert("Failed to start Microsoft login: " + e); - loginMode = "select"; // Go back - } finally { - msLoginLoading = false; - } - } - - async function checkLoginStatus(deviceCode: string) { - if (isPollingRequestActive) return; - isPollingRequestActive = true; - - console.log("Polling Microsoft API..."); - try { - // This will fail with "authorization_pending" until user logs in - currentAccount = (await invoke("complete_microsoft_login", { - deviceCode, - })) as Account; - - // If success: - console.log("Login Successful!", currentAccount); - stopPolling(); - isLoginModalOpen = false; - status = "Welcome back, " + currentAccount.username; - } catch (e: any) { - const errStr = e.toString(); - if (errStr.includes("authorization_pending")) { - console.log("Status: Waiting for user to authorize..."); - // Keep checking - } else { - // Real error - console.error("Polling Error:", errStr); - msLoginStatus = "Error: " + errStr; - - // Optional: Stop polling on fatal errors? - // expired_token should stop it. - if ( - errStr.includes("expired_token") || - errStr.includes("access_denied") - ) { - stopPolling(); - alert("Login failed: " + errStr); - loginMode = "select"; - } - } - } finally { - isPollingRequestActive = false; - } - } - - // Clean up manual button to just be a status indicator or 'Retry Now' - async function completeMicrosoftLogin() { - if (deviceCodeData) checkLoginStatus(deviceCodeData.device_code); - } - - function openLink(url: string) { - open(url); - } - - async function startGame() { - if (!currentAccount) { - alert("Please login first!"); - openLoginModal(); - return; - } - - if (!selectedVersion) { - alert("Please select a version!"); - return; - } + // Ensure 'light' class is never present + document.documentElement.classList.remove('light'); + }); - status = "Preparing to launch " + selectedVersion + "..."; - console.log("Invoking start_game for version:", selectedVersion); - try { - const msg = await invoke("start_game", { versionId: selectedVersion }); - console.log("Response:", msg); - status = msg as string; - } catch (e) { - console.error(e); - status = "Error: " + e; - } - } + onDestroy(() => { + if (typeof window !== 'undefined') + window.removeEventListener("mousemove", handleMouseMove); + }); </script> <div - class="flex h-screen bg-zinc-900 text-white font-sans overflow-hidden select-none" + class="relative h-screen w-screen overflow-hidden dark:text-white text-gray-900 font-sans selection:bg-indigo-500/30" > - <Sidebar /> - - <!-- Main Content --> - <main class="flex-1 flex flex-col relative min-w-0"> - <DownloadMonitor /> - <!-- Top Bar (Window Controls Placeholder) --> - <div - class="h-8 w-full bg-zinc-900/50 absolute top-0 left-0 z-50 drag-region" - data-tauri-drag-region - > - <!-- Windows/macOS controls would go here or be handled by OS --> - </div> - - <!-- Background / Poster area --> - <div class="flex-1 relative overflow-hidden group"> - {#if currentView === "home"} - <!-- Background Image - Using gradient fallback --> - <div - class="absolute inset-0 z-0 opacity-60 bg-gradient-to-br from-emerald-900 via-zinc-900 to-indigo-950 transition-transform duration-[10s] ease-linear group-hover:scale-105" - ></div> - <div - class="absolute inset-0 z-0 bg-gradient-to-t from-zinc-900 via-transparent to-black/50" - ></div> - - <div class="absolute bottom-24 left-8 z-10 p-4"> - <h1 - class="text-6xl font-black mb-2 tracking-tight text-white drop-shadow-lg" - > - MINECRAFT - </h1> - <div class="flex items-center gap-2 text-zinc-300"> - <span - class="bg-zinc-800 text-xs px-2 py-1 rounded border border-zinc-600" - >JAVA EDITION</span - > - <span class="text-lg">Release 1.20.4</span> - </div> - </div> - {:else if currentView === "versions"} - <div class="p-8 h-full overflow-y-auto bg-zinc-900"> - <h2 class="text-3xl font-bold mb-6">Versions</h2> - <div class="grid gap-2"> - {#if versions.length === 0} - <div class="text-zinc-500">Loading versions...</div> - {:else} - {#each versions as version} - <button - class="flex items-center justify-between p-4 bg-zinc-800 rounded hover:bg-zinc-700 transition text-left border border-zinc-700 {selectedVersion === - version.id - ? 'border-green-500 bg-zinc-800/80 ring-1 ring-green-500' - : ''}" - onclick={() => (selectedVersion = version.id)} - > - <div> - <div class="font-bold font-mono text-lg">{version.id}</div> - <div class="text-xs text-zinc-400 capitalize"> - {version.type} • {new Date( - version.releaseTime - ).toLocaleDateString()} - </div> - </div> - {#if selectedVersion === version.id} - <div class="text-green-500 font-bold text-sm">SELECTED</div> - {/if} - </button> - {/each} - {/if} - </div> - </div> - {:else if currentView === "settings"} - <div class="p-8 bg-zinc-900 h-full overflow-y-auto"> - <h2 class="text-3xl font-bold mb-8">Settings</h2> - - <div class="space-y-6 max-w-2xl"> - <!-- Java Path --> - <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700"> - <label - class="block text-sm font-bold text-zinc-400 mb-2 uppercase tracking-wide" - >Java Executable Path</label - > - <div class="flex gap-2"> - <input - bind:value={settings.java_path} - type="text" - class="bg-zinc-950 text-white flex-1 p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none font-mono text-sm" - placeholder="e.g. java, /usr/bin/java" - /> - <button - onclick={detectJava} - disabled={isDetectingJava} - class="bg-zinc-700 hover:bg-zinc-600 disabled:opacity-50 text-white px-4 py-2 rounded transition-colors whitespace-nowrap" - > - {isDetectingJava ? "Detecting..." : "Auto Detect"} - </button> - <button - onclick={openJavaDownloadModal} - class="bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 rounded transition-colors whitespace-nowrap" - > - Download Java - </button> - </div> - - {#if javaInstallations.length > 0} - <div class="mt-4 space-y-2"> - <p class="text-xs text-zinc-400 uppercase font-bold">Detected Java Installations:</p> - {#each javaInstallations as java} - <button - onclick={() => selectJava(java.path)} - class="w-full text-left p-3 bg-zinc-950 rounded border transition-colors {settings.java_path === java.path ? 'border-indigo-500 bg-indigo-950/30' : 'border-zinc-700 hover:border-zinc-500'}" - > - <div class="flex justify-between items-center"> - <div> - <span class="text-white font-mono text-sm">{java.version}</span> - <span class="text-zinc-500 text-xs ml-2">{java.is_64bit ? "64-bit" : "32-bit"}</span> - </div> - {#if settings.java_path === java.path} - <span class="text-indigo-400 text-xs">Selected</span> - {/if} - </div> - <div class="text-zinc-500 text-xs font-mono truncate mt-1">{java.path}</div> - </button> - {/each} - </div> - {/if} - - <p class="text-xs text-zinc-500 mt-2"> - The command or path to the Java Runtime Environment. Click "Auto Detect" to find installed Java versions. - </p> - </div> - - <!-- Memory --> - <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700"> - <label - class="block text-sm font-bold text-zinc-400 mb-4 uppercase tracking-wide" - >Memory Allocation (RAM)</label - > - - <div class="grid grid-cols-2 gap-6"> - <div> - <label class="block text-xs text-zinc-500 mb-1" - >Minimum (MB)</label - > - <input - bind:value={settings.min_memory} - type="number" - class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none" - /> - </div> - <div> - <label class="block text-xs text-zinc-500 mb-1" - >Maximum (MB)</label - > - <input - bind:value={settings.max_memory} - type="number" - class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none" - /> - </div> - </div> - </div> - - <!-- Resolution --> - <div class="bg-zinc-800/50 p-6 rounded-lg border border-zinc-700"> - <label - class="block text-sm font-bold text-zinc-400 mb-4 uppercase tracking-wide" - >Game Window Size</label - > - <div class="grid grid-cols-2 gap-6"> - <div> - <label class="block text-xs text-zinc-500 mb-1">Width</label> - <input - bind:value={settings.width} - type="number" - class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none" - /> - </div> - <div> - <label class="block text-xs text-zinc-500 mb-1">Height</label> - <input - bind:value={settings.height} - type="number" - class="bg-zinc-950 text-white w-full p-3 rounded border border-zinc-700 focus:border-indigo-500 outline-none" - /> - </div> - </div> - </div> + <!-- Modern Animated Background --> + <div class="absolute inset-0 z-0 bg-[#09090b] dark:bg-[#09090b] bg-gray-100 overflow-hidden"> + {#if settingsState.settings.custom_background_path} + <img + src={convertFileSrc(settingsState.settings.custom_background_path)} + alt="Background" + class="absolute inset-0 w-full h-full object-cover transition-transform duration-[20s] ease-linear hover:scale-105" + /> + <!-- Dimming Overlay for readability --> + <div class="absolute inset-0 bg-black/50 "></div> + {:else if settingsState.settings.enable_visual_effects} + <!-- Original Gradient (Dark Only / or Adjusted for Light) --> + {#if settingsState.settings.theme === 'dark'} + <div + class="absolute inset-0 opacity-60 bg-gradient-to-br from-emerald-900 via-zinc-900 to-indigo-950" + ></div> + {:else} + <!-- Light Mode Gradient --> + <div + class="absolute inset-0 opacity-100 bg-gradient-to-br from-emerald-100 via-gray-100 to-indigo-100" + ></div> + {/if} - <div class="pt-4"> - <button - onclick={saveSettings} - class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-8 rounded shadow-lg transition-transform active:scale-95" - > - Save Settings - </button> - </div> - </div> - </div> {#if uiState.currentView === "home"} - <HomeView /> - {:else if uiState.currentView === "versions"} - <VersionsView /> - {:else if uiState.currentView === "settings"} - <SettingsView /> + <ParticleBackground /> {/if} - </div> - - <BottomBar /> - </main> - - <!-- Login Modal --> - {#if isLoginModalOpen} - <div - class="fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4" - > - <div - class="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl p-6 w-full max-w-md animate-in fade-in zoom-in-0 duration-200" - > - <div class="flex justify-between items-center mb-6"> - <h2 class="text-2xl font-bold text-white">Login</h2> - <button - onclick={closeLoginModal} - class="text-zinc-500 hover:text-white transition group" - > - ✕ - </button> - </div> - - {#if loginMode === "select"} - <div class="space-y-4"> - <button - onclick={startMicrosoftLogin} - class="w-full flex items-center justify-center gap-3 bg-[#2F2F2F] hover:bg-[#3F3F3F] text-white p-4 rounded-lg font-bold border border-transparent hover:border-zinc-500 transition-all group" - > - <!-- Microsoft Logo SVG --> - <svg - class="w-5 h-5" - viewBox="0 0 23 23" - fill="none" - xmlns="http://www.w3.org/2000/svg" - ><path fill="#f35325" d="M1 1h10v10H1z" /><path - fill="#81bc06" - d="M12 1h10v10H12z" - /><path fill="#05a6f0" d="M1 12h10v10H1z" /><path - fill="#ffba08" - d="M12 12h10v10H12z" - /></svg - > - Microsoft Account - </button> - - <div class="relative py-2"> - <div class="absolute inset-0 flex items-center"> - <div class="w-full border-t border-zinc-700"></div> - </div> - <div class="relative flex justify-center text-xs uppercase"> - <span class="bg-zinc-900 px-2 text-zinc-500">OR</span> - </div> - </div> - <div class="space-y-2"> - <input - type="text" - bind:value={offlineUsername} - placeholder="Offline Username" - class="w-full bg-zinc-950 border border-zinc-700 rounded p-3 text-white focus:border-indigo-500 outline-none" - onkeydown={(e) => e.key === "Enter" && performOfflineLogin()} - /> - <button - onclick={performOfflineLogin} - class="w-full bg-zinc-800 hover:bg-zinc-700 text-zinc-300 p-3 rounded font-medium transition-colors" - > - Offline Login - </button> - </div> - </div> - {:else if loginMode === "microsoft"} - <div class="text-center"> - {#if msLoginLoading && !deviceCodeData} - <div class="py-8 text-zinc-400 animate-pulse"> - Starting login flow... - </div> - {:else if deviceCodeData} - <div class="space-y-4"> - <p class="text-sm text-zinc-400">1. Go to this URL:</p> - <button - onclick={() => - deviceCodeData && openLink(deviceCodeData.verification_uri)} - class="text-indigo-400 hover:text-indigo-300 underline break-all font-mono text-sm" - > - {deviceCodeData.verification_uri} - </button> - - <p class="text-sm text-zinc-400 mt-2">2. Enter this code:</p> - <div - class="bg-zinc-950 p-4 rounded border border-zinc-700 font-mono text-2xl tracking-widest text-center select-all cursor-pointer hover:border-indigo-500 transition-colors" - onclick={() => - navigator.clipboard.writeText( - deviceCodeData?.user_code || "" - )} - > - {deviceCodeData.user_code} - </div> - <p class="text-xs text-zinc-500">Click code to copy</p> - - <div class="pt-6 space-y-3"> - <div class="flex flex-col items-center gap-3"> - <div class="animate-spin rounded-full h-6 w-6 border-2 border-zinc-600 border-t-indigo-500"></div> - <span class="text-sm text-zinc-400 font-medium break-all text-center">{msLoginStatus}</span> - </div> - <p class="text-xs text-zinc-600">This window will update automatically.</p> - </div> - - <button - onclick={() => { stopPolling(); loginMode = "select"; }} - class="text-xs text-zinc-500 hover:text-zinc-300 mt-6 underline" - >Cancel</button - > - </div> - {/if} - </div> - {/if} - </div> + <div + class="absolute inset-0 bg-gradient-to-t from-zinc-900 via-transparent to-black/50 dark:from-zinc-900 dark:to-black/50 from-gray-100 to-transparent" + ></div> + {/if} + + <!-- Subtle Grid Overlay --> + <div class="absolute inset-0 z-0 opacity-10 dark:opacity-10 opacity-30 pointer-events-none" + style="background-image: linear-gradient({settingsState.settings.theme === 'dark' ? '#ffffff' : '#000000'} 1px, transparent 1px), linear-gradient(90deg, {settingsState.settings.theme === 'dark' ? '#ffffff' : '#000000'} 1px, transparent 1px); background-size: 40px 40px; mask-image: radial-gradient(circle at 50% 50%, black 30%, transparent 70%);"> </div> - {/if} + </div> - <!-- Overlay Status (Toast) --> - {#if status !== "Ready"} - <div - class="absolute top-12 right-12 bg-zinc-800/90 backdrop-blur border border-zinc-600 p-4 rounded-lg shadow-2xl max-w-sm animate-in fade-in slide-in-from-top-4 duration-300 z-50 group" - > - <div class="flex justify-between items-start mb-1"> - <div class="text-xs text-zinc-400 uppercase font-bold">Status</div> - <button - onclick={() => (status = "Ready")} - class="text-zinc-500 hover:text-white transition -mt-1 -mr-1 p-1" - > - ✕ - </button> - </div> - <div class="font-mono text-sm whitespace-pre-wrap mb-2">{status}</div> - <div class="w-full bg-zinc-700/50 h-1 rounded-full overflow-hidden"> - <div - class="h-full bg-indigo-500 animate-[progress_5s_linear_forwards] origin-left w-full" - ></div> - </div> - </div> - {/if} + <!-- Content Wrapper --> + <div class="relative z-10 flex h-full p-4 gap-4 text-gray-900 dark:text-white"> + <!-- Floating Sidebar --> + <Sidebar /> - {#if showJavaDownloadModal} - <div - class="fixed inset-0 bg-black/70 flex items-center justify-center z-50 backdrop-blur-sm" - onclick={closeJavaDownloadModal} - > + <!-- Main Content Area - Transparent & Flat --> + <main class="flex-1 flex flex-col relative min-w-0 overflow-hidden transition-all duration-300"> + + <!-- Window Drag Region --> <div - class="bg-zinc-900 border border-zinc-700 rounded-xl p-6 w-full max-w-md shadow-2xl" - onclick={(e) => e.stopPropagation()} - > - <div class="flex justify-between items-center mb-6"> - <h3 class="text-xl font-bold">Download Java (Adoptium)</h3> - {#if !isDownloadingJava} - <button - onclick={closeJavaDownloadModal} - class="text-zinc-500 hover:text-white transition text-xl" - > - ✕ - </button> - {/if} - </div> - <div class="space-y-4"> - <!-- Version Selection --> - <div> - <label class="block text-sm font-bold text-zinc-400 mb-2">Java Version</label> - <select - bind:value={selectedJavaVersion} - disabled={isDownloadingJava} - class="w-full bg-zinc-950 border border-zinc-700 rounded p-3 text-white focus:border-indigo-500 outline-none disabled:opacity-50" - > - {#each availableJavaVersions as ver} - <option value={ver}> - Java {ver} {ver === 21 ? "(Recommended)" : ver === 17 ? "(LTS)" : ver === 8 ? "(Legacy)" : ""} - </option> - {/each} - </select> - <p class="text-xs text-zinc-500 mt-1"> - MC 1.20.5+ requires Java 21, MC 1.17-1.20.4 requires Java 17, older versions require Java 8 - </p> + class="h-8 w-full absolute top-0 left-0 z-50 drag-region" + data-tauri-drag-region + ></div> + + <!-- App Content --> + <div class="flex-1 relative overflow-hidden flex flex-col"> + <!-- Views Container --> + <div class="flex-1 relative overflow-hidden"> + {#if uiState.currentView === "home"} + <HomeView mouseX={mouseX} mouseY={mouseY} /> + {:else if uiState.currentView === "versions"} + <VersionsView /> + {:else if uiState.currentView === "settings"} + <SettingsView /> + {/if} </div> - - <!-- Image Type Selection --> - <div> - <label class="block text-sm font-bold text-zinc-400 mb-2">Type</label> - <div class="flex gap-3"> - <button - onclick={() => selectedImageType = "jre"} - disabled={isDownloadingJava} - class="flex-1 p-3 rounded border transition-colors disabled:opacity-50 {selectedImageType === 'jre' ? 'border-indigo-500 bg-indigo-950/30 text-white' : 'border-zinc-700 bg-zinc-950 text-zinc-400 hover:border-zinc-500'}" - > - <div class="font-bold">JRE</div> - <div class="text-xs opacity-70">runtime environment</div> - </button> - <button - onclick={() => selectedImageType = "jdk"} - disabled={isDownloadingJava} - class="flex-1 p-3 rounded border transition-colors disabled:opacity-50 {selectedImageType === 'jdk' ? 'border-indigo-500 bg-indigo-950/30 text-white' : 'border-zinc-700 bg-zinc-950 text-zinc-400 hover:border-zinc-500'}" - > - <div class="font-bold">JDK</div> - <div class="text-xs opacity-70">development kit</div> - </button> - </div> + + <!-- Download Monitor Overlay --> + <div class="absolute bottom-20 left-4 right-4 pointer-events-none z-20"> + <div class="pointer-events-auto"> + <DownloadMonitor /> + </div> </div> - - <!-- Status --> - {#if javaDownloadStatus} - <div class="p-3 rounded {javaDownloadStatus.startsWith('✓') ? 'bg-green-950/50 border border-green-700 text-green-400' : javaDownloadStatus.includes('failed') || javaDownloadStatus.includes('Failed') ? 'bg-red-950/50 border border-red-700 text-red-400' : 'bg-zinc-800 border border-zinc-700 text-zinc-300'}"> - <p class="text-sm">{javaDownloadStatus}</p> - </div> - {/if} - - <!-- Download Button --> - <button - onclick={downloadJava} - disabled={isDownloadingJava || availableJavaVersions.length === 0} - class="w-full bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed text-white p-3 rounded font-bold transition-colors flex items-center justify-center gap-2" - > - {#if isDownloadingJava} - <div class="animate-spin rounded-full h-5 w-5 border-2 border-white/30 border-t-white"></div> - Downloading... - {:else} - Download Java {selectedJavaVersion} {selectedImageType.toUpperCase()} - {/if} - </button> - - <p class="text-xs text-zinc-500 text-center"> - Provided by <a href="https://adoptium.net" class="text-indigo-400 hover:underline" onclick={(e) => { e.preventDefault(); openLink("https://adoptium.net"); }}>Eclipse Adoptium</a> - </p> - </div> + + <!-- Bottom Bar --> + <BottomBar /> </div> - </div> - {/if} + </main> + </div> - <style> - @keyframes progress { - from { - transform: scaleX(1); - } - to { - transform: scaleX(0); - } - } - </style> <LoginModal /> <StatusToast /> - - <GameConsole visible={uiState.showConsole} /> + + {#if uiState.showConsole} + <!-- Assuming GameConsole handles its own display mode or overlay --> + <div class="fixed inset-0 z-[100] bg-black/80 flex items-center justify-center p-10"> + <div class="w-full h-full bg-[#1e1e1e] rounded-xl overflow-hidden border border-white/10 shadow-2xl relative"> + <button class="absolute top-4 right-4 text-white hover:text-red-400 z-10" onclick={() => uiState.toggleConsole()}>✕</button> + <GameConsole /> + </div> + </div> + {/if} </div> + +<style> + :global(body) { + margin: 0; + padding: 0; + background: #000; + } + + /* Modern Scrollbar */ + :global(*::-webkit-scrollbar) { + width: 6px; + height: 6px; + } + + :global(*::-webkit-scrollbar-track) { + background: transparent; + } + + :global(*::-webkit-scrollbar-thumb) { + background: rgba(255, 255, 255, 0.1); + border-radius: 999px; + } + + :global(*::-webkit-scrollbar-thumb:hover) { + background: rgba(255, 255, 255, 0.25); + } +</style> |