import { invoke } from "@tauri-apps/api/core"; import { convertFileSrc } from "@tauri-apps/api/core"; 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 { settings = $state({ min_memory: 1024, max_memory: 2048, java_path: "java", width: 854, height: 480, download_threads: 32, enable_gpu_acceleration: false, enable_visual_effects: true, active_effect: "constellation", theme: "dark", custom_background_path: undefined, log_upload_service: "paste.rs", pastebin_api_key: undefined, }); // Convert background path to proper asset URL get backgroundUrl(): string | undefined { if (this.settings.custom_background_path) { return convertFileSrc(this.settings.custom_background_path); } return undefined; } javaInstallations = $state([]); isDetectingJava = $state(false); // Java download modal state showJavaDownloadModal = $state(false); 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 { const result = await invoke("get_settings"); this.settings = result; // Force dark mode if (this.settings.theme !== "dark") { this.settings.theme = "dark"; this.saveSettings(); } // Ensure custom_background_path is reactive if (!this.settings.custom_background_path) { this.settings.custom_background_path = undefined; } } catch (e) { console.error("Failed to load settings:", e); } } async saveSettings() { try { // Ensure we clean up any invalid paths before saving if (this.settings.custom_background_path === "") { this.settings.custom_background_path = undefined; } await invoke("save_settings", { config: this.settings }); uiState.setStatus("Settings saved!"); } catch (e) { console.error("Failed to save settings:", e); uiState.setStatus("Error saving settings: " + e); } } async detectJava() { this.isDetectingJava = true; try { this.javaInstallations = await invoke("detect_java"); if (this.javaInstallations.length === 0) { uiState.setStatus("No Java installations found"); } else { uiState.setStatus(`Found ${this.javaInstallations.length} Java installation(s)`); } } catch (e) { console.error("Failed to detect Java:", e); uiState.setStatus("Error detecting Java: " + e); } finally { this.isDetectingJava = false; } } selectJava(path: string) { this.settings.java_path = path; } 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 { 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 load Java catalog:", e); this.catalogError = `Failed to load Java catalog: ${e}`; } finally { this.isLoadingCatalog = 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 = "Starting download..."; this.downloadProgress = null; try { const result: JavaInstallation = await invoke("download_adoptium_java", { majorVersion: this.selectedMajorVersion, imageType: this.selectedImageType, customPath: null, }); this.settings.java_path = result.path; await this.detectJava(); setTimeout(() => { this.showJavaDownloadModal = false; uiState.setStatus(`Java ${this.selectedMajorVersion} is ready to use!`); }, 1500); } catch (e) { console.error("Failed to download Java:", 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();