From 43a3e9c285f3d5d04fef025041a06609a0d1c218 Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Thu, 15 Jan 2026 05:29:58 +0100 Subject: feat(java): Implement Java catalog management and download features - Added commands to fetch and refresh the Java catalog, cancel downloads, and manage pending downloads. - Enhanced the Java download modal in the UI to support version selection, download progress, and pending downloads. - Introduced new types for Java catalog, download progress, and pending downloads. - Updated settings store to handle Java catalog state, download progress, and pending downloads. - Improved user experience with loading states, error handling, and status notifications for Java installations. --- ui/src/stores/settings.svelte.ts | 294 +++++++++++++++++++++++++++++++++++---- 1 file changed, 268 insertions(+), 26 deletions(-) (limited to 'ui/src/stores') diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts index 5b87af2..cad466d 100644 --- a/ui/src/stores/settings.svelte.ts +++ b/ui/src/stores/settings.svelte.ts @@ -1,5 +1,14 @@ import { invoke } from "@tauri-apps/api/core"; -import type { JavaInstallation, LauncherConfig } from "../types"; +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; +import type { + JavaCatalog, + JavaDownloadProgress, + JavaDownloadSource, + JavaInstallation, + JavaReleaseInfo, + LauncherConfig, + PendingJavaDownload, +} from "../types"; import { uiState } from "./ui.svelte"; export class SettingsState { @@ -17,14 +26,101 @@ export class SettingsState { }); javaInstallations = $state([]); isDetectingJava = $state(false); - - // Java download state + + // Java download modal state showJavaDownloadModal = $state(false); - availableJavaVersions = $state([]); - selectedJavaVersion = $state(21); + selectedDownloadSource = $state("adoptium"); + + // Java catalog state + javaCatalog = $state(null); + isLoadingCatalog = $state(false); + catalogError = $state(""); + + // Version selection state + selectedMajorVersion = $state(null); selectedImageType = $state<"jre" | "jdk">("jre"); + showOnlyRecommended = $state(true); + searchQuery = $state(""); + + // Download progress state isDownloadingJava = $state(false); + downloadProgress = $state(null); javaDownloadStatus = $state(""); + + // Pending downloads + pendingDownloads = $state([]); + + // Event listener cleanup + private progressUnlisten: UnlistenFn | null = null; + + // Computed: filtered releases based on selection + get filteredReleases(): JavaReleaseInfo[] { + if (!this.javaCatalog) return []; + + let releases = this.javaCatalog.releases; + + // Filter by major version if selected + if (this.selectedMajorVersion !== null) { + releases = releases.filter(r => r.major_version === this.selectedMajorVersion); + } + + // Filter by image type + releases = releases.filter(r => r.image_type === this.selectedImageType); + + // Filter by recommended (LTS) versions + if (this.showOnlyRecommended) { + releases = releases.filter(r => r.is_lts); + } + + // Filter by search query + if (this.searchQuery.trim()) { + const query = this.searchQuery.toLowerCase(); + releases = releases.filter( + r => + r.release_name.toLowerCase().includes(query) || + r.version.toLowerCase().includes(query) || + r.major_version.toString().includes(query) + ); + } + + return releases; + } + + // Computed: available major versions for display + get availableMajorVersions(): number[] { + if (!this.javaCatalog) return []; + let versions = [...this.javaCatalog.available_major_versions]; + + // Filter by LTS if showOnlyRecommended is enabled + if (this.showOnlyRecommended) { + versions = versions.filter(v => this.javaCatalog!.lts_versions.includes(v)); + } + + // Sort descending (newest first) + return versions.sort((a, b) => b - a); + } + + // Get installation status for a release: 'installed' | 'download' + getInstallStatus(release: JavaReleaseInfo): 'installed' | 'download' { + // Find installed Java that matches the major version and image type (by path pattern) + const matchingInstallations = this.javaInstallations.filter(inst => { + // Check if this is a DropOut-managed Java (path contains temurin-XX-jre/jdk pattern) + const pathLower = inst.path.toLowerCase(); + const pattern = `temurin-${release.major_version}-${release.image_type}`; + return pathLower.includes(pattern); + }); + + // If any matching installation exists, it's installed + return matchingInstallations.length > 0 ? 'installed' : 'download'; + } + + // Computed: selected release details + get selectedRelease(): JavaReleaseInfo | null { + if (!this.javaCatalog || this.selectedMajorVersion === null) return null; + return this.javaCatalog.releases.find( + r => r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType + ) || null; + } async loadSettings() { try { @@ -32,8 +128,8 @@ export class SettingsState { this.settings = result; // Force dark mode if (this.settings.theme !== "dark") { - this.settings.theme = "dark"; - this.saveSettings(); + this.settings.theme = "dark"; + this.saveSettings(); } } catch (e) { console.error("Failed to load settings:", e); @@ -74,55 +170,201 @@ export class SettingsState { async openJavaDownloadModal() { this.showJavaDownloadModal = true; this.javaDownloadStatus = ""; + this.catalogError = ""; + this.downloadProgress = null; + + // Setup progress event listener + await this.setupProgressListener(); + + // Load catalog + await this.loadJavaCatalog(false); + + // Check for pending downloads + await this.loadPendingDownloads(); + } + + async closeJavaDownloadModal() { + if (!this.isDownloadingJava) { + this.showJavaDownloadModal = false; + // Cleanup listener + if (this.progressUnlisten) { + this.progressUnlisten(); + this.progressUnlisten = null; + } + } + } + + private async setupProgressListener() { + if (this.progressUnlisten) { + this.progressUnlisten(); + } + + this.progressUnlisten = await listen( + "java-download-progress", + (event) => { + this.downloadProgress = event.payload; + this.javaDownloadStatus = event.payload.status; + + if (event.payload.status === "Completed") { + this.isDownloadingJava = false; + setTimeout(async () => { + await this.detectJava(); + uiState.setStatus(`Java installed successfully!`); + }, 500); + } else if (event.payload.status === "Error") { + this.isDownloadingJava = false; + } + } + ); + } + + async loadJavaCatalog(forceRefresh: boolean) { + this.isLoadingCatalog = true; + this.catalogError = ""; + try { - this.availableJavaVersions = await invoke("fetch_available_java_versions"); - // Default selection logic - if (this.availableJavaVersions.includes(21)) { - this.selectedJavaVersion = 21; - } else if (this.availableJavaVersions.includes(17)) { - this.selectedJavaVersion = 17; - } else if (this.availableJavaVersions.length > 0) { - this.selectedJavaVersion = this.availableJavaVersions[this.availableJavaVersions.length - 1]; + const command = forceRefresh ? "refresh_java_catalog" : "fetch_java_catalog"; + this.javaCatalog = await invoke(command); + + // Auto-select first LTS version + if (this.selectedMajorVersion === null && this.javaCatalog.lts_versions.length > 0) { + // Select most recent LTS (21 or highest) + const ltsVersions = [...this.javaCatalog.lts_versions].sort((a, b) => b - a); + this.selectedMajorVersion = ltsVersions[0]; } } catch (e) { - console.error("Failed to fetch available Java versions:", e); - this.javaDownloadStatus = "Error fetching Java versions: " + e; + console.error("Failed to load Java catalog:", e); + this.catalogError = `Failed to load Java catalog: ${e}`; + } finally { + this.isLoadingCatalog = false; } } - closeJavaDownloadModal() { - if (!this.isDownloadingJava) { - this.showJavaDownloadModal = false; + async refreshCatalog() { + await this.loadJavaCatalog(true); + uiState.setStatus("Java catalog refreshed"); + } + + async loadPendingDownloads() { + try { + this.pendingDownloads = await invoke("get_pending_java_downloads"); + } catch (e) { + console.error("Failed to load pending downloads:", e); } } + selectMajorVersion(version: number) { + this.selectedMajorVersion = version; + } + async downloadJava() { + if (!this.selectedRelease || !this.selectedRelease.is_available) { + uiState.setStatus("Selected Java version is not available for this platform"); + return; + } + this.isDownloadingJava = true; - this.javaDownloadStatus = `Downloading Java ${this.selectedJavaVersion} ${this.selectedImageType.toUpperCase()}...`; + this.javaDownloadStatus = "Starting download..."; + this.downloadProgress = null; try { const result: JavaInstallation = await invoke("download_adoptium_java", { - majorVersion: this.selectedJavaVersion, + majorVersion: this.selectedMajorVersion, imageType: this.selectedImageType, customPath: null, }); - this.javaDownloadStatus = `Java ${this.selectedJavaVersion} installed at ${result.path}`; this.settings.java_path = result.path; - await this.detectJava(); setTimeout(() => { this.showJavaDownloadModal = false; - uiState.setStatus(`Java ${this.selectedJavaVersion} is ready to use!`); + uiState.setStatus(`Java ${this.selectedMajorVersion} is ready to use!`); }, 1500); } catch (e) { console.error("Failed to download Java:", e); - this.javaDownloadStatus = "Download failed: " + e; + this.javaDownloadStatus = `Download failed: ${e}`; } finally { this.isDownloadingJava = false; } } + + async cancelDownload() { + try { + await invoke("cancel_java_download"); + this.isDownloadingJava = false; + this.javaDownloadStatus = "Download cancelled"; + this.downloadProgress = null; + await this.loadPendingDownloads(); + } catch (e) { + console.error("Failed to cancel download:", e); + } + } + + async resumeDownloads() { + if (this.pendingDownloads.length === 0) return; + + this.isDownloadingJava = true; + this.javaDownloadStatus = "Resuming download..."; + + try { + const installed = await invoke("resume_java_downloads"); + if (installed.length > 0) { + this.settings.java_path = installed[0].path; + await this.detectJava(); + uiState.setStatus(`Resumed and installed ${installed.length} Java version(s)`); + } + await this.loadPendingDownloads(); + } catch (e) { + console.error("Failed to resume downloads:", e); + this.javaDownloadStatus = `Resume failed: ${e}`; + } finally { + this.isDownloadingJava = false; + } + } + + // Format bytes to human readable + formatBytes(bytes: number): string { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; + } + + // Format seconds to human readable + formatTime(seconds: number): string { + if (seconds === 0 || !isFinite(seconds)) return "--"; + if (seconds < 60) return `${Math.round(seconds)}s`; + if (seconds < 3600) { + const mins = Math.floor(seconds / 60); + const secs = Math.round(seconds % 60); + return `${mins}m ${secs}s`; + } + const hours = Math.floor(seconds / 3600); + const mins = Math.floor((seconds % 3600) / 60); + return `${hours}h ${mins}m`; + } + + // Format date string + formatDate(dateStr: string | null): string { + if (!dateStr) return "--"; + try { + const date = new Date(dateStr); + return date.toLocaleDateString("en-US", { + year: "2-digit", + month: "2-digit", + day: "2-digit", + }); + } catch { + return "--"; + } + } + + // Legacy compatibility + get availableJavaVersions(): number[] { + return this.availableMajorVersions; + } } export const settingsState = new SettingsState(); -- cgit v1.2.3-70-g09d2