aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src/stores
diff options
context:
space:
mode:
authorNatsuu <natsukawa247@outlook.com>2026-01-14 03:41:18 +0000
committerNatsuu <natsukawa247@outlook.com>2026-01-14 03:41:18 +0000
commit64b939e6ac0b196d18ee183a37a40b0bf7927a80 (patch)
tree54b366819e9f3fd8694092c0053dd5e706da59f9 /ui/src/stores
parent8aeadd2c2203540b93eabc6ba53b7b4ceaff7eb7 (diff)
downloadDropOut-64b939e6ac0b196d18ee183a37a40b0bf7927a80.tar.gz
DropOut-64b939e6ac0b196d18ee183a37a40b0bf7927a80.zip
refactor: split App.svelte into components
Diffstat (limited to 'ui/src/stores')
-rw-r--r--ui/src/stores/auth.svelte.ts141
-rw-r--r--ui/src/stores/game.svelte.ts48
-rw-r--r--ui/src/stores/settings.svelte.ts56
-rw-r--r--ui/src/stores/ui.svelte.ts34
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();