diff options
Diffstat (limited to 'ui/src/stores')
| -rw-r--r-- | ui/src/stores/auth.svelte.ts | 141 | ||||
| -rw-r--r-- | ui/src/stores/game.svelte.ts | 48 | ||||
| -rw-r--r-- | ui/src/stores/settings.svelte.ts | 56 | ||||
| -rw-r--r-- | ui/src/stores/ui.svelte.ts | 34 |
4 files changed, 279 insertions, 0 deletions
diff --git a/ui/src/stores/auth.svelte.ts b/ui/src/stores/auth.svelte.ts new file mode 100644 index 0000000..9df8835 --- /dev/null +++ b/ui/src/stores/auth.svelte.ts @@ -0,0 +1,141 @@ +import { invoke } from "@tauri-apps/api/core"; +import { open } from "@tauri-apps/plugin-shell"; +import type { Account, DeviceCodeResponse } from "../types"; +import { uiState } from "./ui.svelte"; + +export class AuthState { + currentAccount = $state<Account | null>(null); + isLoginModalOpen = $state(false); + loginMode = $state<"select" | "offline" | "microsoft">("select"); + offlineUsername = $state(""); + deviceCodeData = $state<DeviceCodeResponse | null>(null); + msLoginLoading = $state(false); + msLoginStatus = $state("Waiting for authorization..."); + + private pollInterval: any; + private isPollingRequestActive = false; + + async checkAccount() { + try { + const acc = await invoke("get_active_account"); + this.currentAccount = acc as Account | null; + } catch (e) { + console.error("Failed to check account:", e); + } + } + + openLoginModal() { + if (this.currentAccount) { + if (confirm("Logout " + this.currentAccount.username + "?")) { + invoke("logout").then(() => (this.currentAccount = null)); + } + return; + } + this.resetLoginState(); + this.isLoginModalOpen = true; + } + + closeLoginModal() { + this.stopPolling(); + this.isLoginModalOpen = false; + } + + resetLoginState() { + this.loginMode = "select"; + this.offlineUsername = ""; + this.deviceCodeData = null; + this.msLoginLoading = false; + } + + async performOfflineLogin() { + if (!this.offlineUsername) return; + try { + this.currentAccount = (await invoke("login_offline", { + username: this.offlineUsername, + })) as Account; + this.isLoginModalOpen = false; + } catch (e) { + alert("Login failed: " + e); + } + } + + async startMicrosoftLogin() { + this.loginMode = "microsoft"; + this.msLoginLoading = true; + this.msLoginStatus = "Waiting for authorization..."; + this.stopPolling(); + + try { + this.deviceCodeData = (await invoke( + "start_microsoft_login" + )) as DeviceCodeResponse; + + if (this.deviceCodeData) { + try { + await navigator.clipboard.writeText(this.deviceCodeData.user_code); + } catch (e) { + console.error("Clipboard failed", e); + } + + open(this.deviceCodeData.verification_uri); + + console.log("Starting polling for token..."); + const intervalMs = (this.deviceCodeData.interval || 5) * 1000; + this.pollInterval = setInterval( + () => this.checkLoginStatus(this.deviceCodeData!.device_code), + intervalMs + ); + } + } catch (e) { + alert("Failed to start Microsoft login: " + e); + this.loginMode = "select"; + } finally { + this.msLoginLoading = false; + } + } + + stopPolling() { + if (this.pollInterval) { + clearInterval(this.pollInterval); + this.pollInterval = null; + } + } + + async checkLoginStatus(deviceCode: string) { + if (this.isPollingRequestActive) return; + this.isPollingRequestActive = true; + + console.log("Polling Microsoft API..."); + try { + this.currentAccount = (await invoke("complete_microsoft_login", { + deviceCode, + })) as Account; + + console.log("Login Successful!", this.currentAccount); + this.stopPolling(); + this.isLoginModalOpen = false; + uiState.setStatus("Welcome back, " + this.currentAccount.username); + } catch (e: any) { + const errStr = e.toString(); + if (errStr.includes("authorization_pending")) { + console.log("Status: Waiting for user to authorize..."); + } else { + console.error("Polling Error:", errStr); + this.msLoginStatus = "Error: " + errStr; + + if ( + errStr.includes("expired_token") || + errStr.includes("access_denied") + ) { + this.stopPolling(); + alert("Login failed: " + errStr); + this.loginMode = "select"; + } + } + } finally { + this.isPollingRequestActive = false; + } + } +} + +export const authState = new AuthState(); diff --git a/ui/src/stores/game.svelte.ts b/ui/src/stores/game.svelte.ts new file mode 100644 index 0000000..feaf8d6 --- /dev/null +++ b/ui/src/stores/game.svelte.ts @@ -0,0 +1,48 @@ +import { invoke } from "@tauri-apps/api/core"; +import type { Version } from "../types"; +import { uiState } from "./ui.svelte"; +import { authState } from "./auth.svelte"; + +export class GameState { + versions = $state<Version[]>([]); + selectedVersion = $state(""); + + async loadVersions() { + try { + this.versions = await invoke("get_versions"); + if (this.versions.length > 0) { + const latest = this.versions.find((v) => v.type === "release"); + this.selectedVersion = latest ? latest.id : this.versions[0].id; + } + } catch (e) { + console.error("Failed to fetch versions:", e); + uiState.setStatus("Error fetching versions: " + e); + } + } + + async startGame() { + if (!authState.currentAccount) { + alert("Please login first!"); + authState.openLoginModal(); + return; + } + + if (!this.selectedVersion) { + alert("Please select a version!"); + return; + } + + uiState.setStatus("Preparing to launch " + this.selectedVersion + "..."); + console.log("Invoking start_game for version:", this.selectedVersion); + try { + const msg = await invoke("start_game", { versionId: this.selectedVersion }); + console.log("Response:", msg); + uiState.setStatus(msg as string); + } catch (e) { + console.error(e); + uiState.setStatus("Error: " + e); + } + } +} + +export const gameState = new GameState(); diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts new file mode 100644 index 0000000..a1f687c --- /dev/null +++ b/ui/src/stores/settings.svelte.ts @@ -0,0 +1,56 @@ +import { invoke } from "@tauri-apps/api/core"; +import type { LauncherConfig, JavaInstallation } from "../types"; +import { uiState } from "./ui.svelte"; + +export class SettingsState { + settings = $state<LauncherConfig>({ + min_memory: 1024, + max_memory: 2048, + java_path: "java", + width: 854, + height: 480, + }); + javaInstallations = $state<JavaInstallation[]>([]); + isDetectingJava = $state(false); + + async loadSettings() { + try { + this.settings = await invoke("get_settings"); + } catch (e) { + console.error("Failed to load settings:", e); + } + } + + async saveSettings() { + try { + 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; + } +} + +export const settingsState = new SettingsState(); diff --git a/ui/src/stores/ui.svelte.ts b/ui/src/stores/ui.svelte.ts new file mode 100644 index 0000000..8fc339b --- /dev/null +++ b/ui/src/stores/ui.svelte.ts @@ -0,0 +1,34 @@ +export class UIState { + currentView = $state("home"); + status = $state("Ready"); + showConsole = $state(false); + appVersion = $state("..."); + + private statusTimeout: any; + + constructor() { + // Watch for status changes to auto-dismiss + $effect(() => { + if (this.status !== "Ready") { + if (this.statusTimeout) clearTimeout(this.statusTimeout); + this.statusTimeout = setTimeout(() => { + this.status = "Ready"; + }, 5000); + } + }); + } + + setStatus(msg: string) { + this.status = msg; + } + + toggleConsole() { + this.showConsole = !this.showConsole; + } + + setView(view: string) { + this.currentView = view; + } +} + +export const uiState = new UIState(); |