aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui-new/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/ui-new/src')
-rw-r--r--packages/ui-new/src/client.ts8
-rw-r--r--packages/ui-new/src/components/bottom-bar.tsx89
-rw-r--r--packages/ui-new/src/components/instance-creation-modal.tsx18
-rw-r--r--packages/ui-new/src/components/instance-editor-modal.tsx18
-rw-r--r--packages/ui-new/src/main.tsx11
-rw-r--r--packages/ui-new/src/models/instances.ts135
-rw-r--r--packages/ui-new/src/pages/assistant-view.tsx.bk (renamed from packages/ui-new/src/pages/assistant-view.tsx)0
-rw-r--r--packages/ui-new/src/pages/index-old.tsx187
-rw-r--r--packages/ui-new/src/pages/instances-view.tsx87
-rw-r--r--packages/ui-new/src/pages/settings-view.tsx.bk (renamed from packages/ui-new/src/pages/settings-view.tsx)0
-rw-r--r--packages/ui-new/src/pages/versions-view.tsx.bk (renamed from packages/ui-new/src/pages/versions-view.tsx)6
-rw-r--r--packages/ui-new/src/stores/game-store.ts4
-rw-r--r--packages/ui-new/src/stores/instances-store.ts149
-rw-r--r--packages/ui-new/src/stores/settings-store.ts14
-rw-r--r--packages/ui-new/src/types/bindings/java/core.ts2
15 files changed, 246 insertions, 482 deletions
diff --git a/packages/ui-new/src/client.ts b/packages/ui-new/src/client.ts
index 572cdd9..18d2377 100644
--- a/packages/ui-new/src/client.ts
+++ b/packages/ui-new/src/client.ts
@@ -223,8 +223,12 @@ export function getVersionMetadata(
});
}
-export function getVersions(instanceId: string): Promise<Version[]> {
- return invoke<Version[]>("get_versions", {
+export function getVersions(): Promise<Version[]> {
+ return invoke<Version[]>("get_versions");
+}
+
+export function getVersionsOfInstance(instanceId: string): Promise<Version[]> {
+ return invoke<Version[]>("get_versions_of_instance", {
instanceId,
});
}
diff --git a/packages/ui-new/src/components/bottom-bar.tsx b/packages/ui-new/src/components/bottom-bar.tsx
index 2653880..32eb852 100644
--- a/packages/ui-new/src/components/bottom-bar.tsx
+++ b/packages/ui-new/src/components/bottom-bar.tsx
@@ -1,13 +1,22 @@
-import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { Check, ChevronDown, Play, User } from "lucide-react";
-import { useCallback, useEffect, useRef, useState } from "react";
+import { Play, User } from "lucide-react";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { toast } from "sonner";
+import { listInstalledVersions, startGame } from "@/client";
import { cn } from "@/lib/utils";
import { useAuthStore } from "@/models/auth";
+import { useInstancesStore } from "@/models/instances";
import { useGameStore } from "@/stores/game-store";
-import { useInstancesStore } from "@/stores/instances-store";
import { LoginModal } from "./login-modal";
import { Button } from "./ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "./ui/select";
interface InstalledVersion {
id: string;
@@ -19,7 +28,7 @@ export function BottomBar() {
const gameStore = useGameStore();
const instancesStore = useInstancesStore();
- const [isVersionDropdownOpen, setIsVersionDropdownOpen] = useState(false);
+ const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
const [installedVersions, setInstalledVersions] = useState<
InstalledVersion[]
>([]);
@@ -27,7 +36,7 @@ export function BottomBar() {
const [showLoginModal, setShowLoginModal] = useState(false);
const loadInstalledVersions = useCallback(async () => {
- if (!instancesStore.activeInstanceId) {
+ if (!instancesStore.activeInstance) {
setInstalledVersions([]);
setIsLoadingVersions(false);
return;
@@ -35,9 +44,8 @@ export function BottomBar() {
setIsLoadingVersions(true);
try {
- const versions = await invoke<InstalledVersion[]>(
- "list_installed_versions",
- { instanceId: instancesStore.activeInstanceId },
+ const versions = await listInstalledVersions(
+ instancesStore.activeInstance.id,
);
const installed = versions || [];
@@ -53,7 +61,7 @@ export function BottomBar() {
setIsLoadingVersions(false);
}
}, [
- instancesStore.activeInstanceId,
+ instancesStore.activeInstance,
gameStore.selectedVersion,
gameStore.setSelectedVersion,
]);
@@ -100,20 +108,23 @@ export function BottomBar() {
};
}, [loadInstalledVersions]);
- const selectVersion = (id: string) => {
- if (id !== "loading" && id !== "empty") {
- gameStore.setSelectedVersion(id);
- setIsVersionDropdownOpen(false);
+ const handleStartGame = async () => {
+ if (!selectedVersion) {
+ toast.info("Please select a version!");
+ return;
}
- };
- const handleStartGame = async () => {
+ if (!instancesStore.activeInstance) {
+ toast.info("Please select an instance first!");
+ return;
+ }
// await gameStore.startGame(
// authStore.currentAccount,
// authStore.openLoginModal,
// instancesStore.activeInstanceId,
// uiStore.setView,
// );
+ await startGame(instancesStore.activeInstance?.id, selectedVersion);
};
const getVersionTypeColor = (type: string) => {
@@ -131,14 +142,15 @@ export function BottomBar() {
}
};
- const versionOptions = isLoadingVersions
- ? [{ id: "loading", type: "loading", label: "Loading..." }]
- : installedVersions.length === 0
- ? [{ id: "empty", type: "empty", label: "No versions installed" }]
- : installedVersions.map((v) => ({
- ...v,
- label: `${v.id}${v.type !== "release" ? ` (${v.type})` : ""}`,
- }));
+ const versionOptions = useMemo(
+ () =>
+ installedVersions.map((v) => ({
+ label: `${v.id}${v.type !== "release" ? ` (${v.type})` : ""}`,
+ value: v.id,
+ type: v.type,
+ })),
+ [installedVersions],
+ );
return (
<div className="absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/30 via-transparent to-transparent p-4 z-10">
@@ -153,6 +165,35 @@ export function BottomBar() {
{instancesStore.activeInstance?.name || "No instance selected"}
</span>
</div>
+
+ <Select
+ items={versionOptions}
+ onValueChange={setSelectedVersion}
+ disabled={isLoadingVersions}
+ >
+ <SelectTrigger className="max-w-48">
+ <SelectValue
+ placeholder={
+ isLoadingVersions
+ ? "Loading versions..."
+ : "Please select a version"
+ }
+ />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ {versionOptions.map((item) => (
+ <SelectItem
+ key={item.value}
+ value={item.value}
+ className={getVersionTypeColor(item.type)}
+ >
+ {item.label}
+ </SelectItem>
+ ))}
+ </SelectGroup>
+ </SelectContent>
+ </Select>
</div>
<div className="flex items-center gap-3">
diff --git a/packages/ui-new/src/components/instance-creation-modal.tsx b/packages/ui-new/src/components/instance-creation-modal.tsx
index bdc1a6f..8a2b1b4 100644
--- a/packages/ui-new/src/components/instance-creation-modal.tsx
+++ b/packages/ui-new/src/components/instance-creation-modal.tsx
@@ -13,8 +13,8 @@ import {
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
+import { useInstancesStore } from "@/models/instances";
import { useGameStore } from "@/stores/game-store";
-import { useInstancesStore } from "@/stores/instances-store";
import type { Version } from "@/types/bindings/manifest";
import type { FabricLoaderEntry } from "../types/bindings/fabric";
import type { ForgeVersion as ForgeVersionEntry } from "../types/bindings/forge";
@@ -25,20 +25,6 @@ interface Props {
onOpenChange: (open: boolean) => void;
}
-/**
- * InstanceCreationModal
- * 3-step wizard:
- * 1) Name
- * 2) Select base Minecraft version
- * 3) Optional: choose mod loader (vanilla/fabric/forge) and loader version
- *
- * Behavior:
- * - On Create: invoke("create_instance", { name })
- * - If a base version selected: invoke("install_version", { instanceId, versionId })
- * - If Fabric selected: invoke("install_fabric", { instanceId, gameVersion, loaderVersion })
- * - If Forge selected: invoke("install_forge", { instanceId, gameVersion, forgeVersion })
- * - Reload instances via instancesStore.loadInstances()
- */
export function InstanceCreationModal({ open, onOpenChange }: Props) {
const gameStore = useGameStore();
const instancesStore = useInstancesStore();
@@ -242,7 +228,7 @@ export function InstanceCreationModal({ open, onOpenChange }: Props) {
}
// Refresh instances list
- await instancesStore.loadInstances();
+ await instancesStore.refresh();
toast.success("Instance created successfully");
onOpenChange(false);
diff --git a/packages/ui-new/src/components/instance-editor-modal.tsx b/packages/ui-new/src/components/instance-editor-modal.tsx
index 74e0873..f880c20 100644
--- a/packages/ui-new/src/components/instance-editor-modal.tsx
+++ b/packages/ui-new/src/components/instance-editor-modal.tsx
@@ -16,8 +16,8 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { toNumber } from "@/lib/tsrs-utils";
-import { useInstancesStore } from "@/stores/instances-store";
-import { useSettingsStore } from "@/stores/settings-store";
+import { useInstancesStore } from "@/models/instances";
+import { useSettingsStore } from "@/models/settings";
import type { FileInfo } from "../types/bindings/core";
import type { Instance } from "../types/bindings/instance";
@@ -29,7 +29,7 @@ type Props = {
export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
const instancesStore = useInstancesStore();
- const { settings } = useSettingsStore();
+ const { config } = useSettingsStore();
const [activeTab, setActiveTab] = useState<
"info" | "version" | "files" | "settings"
@@ -67,19 +67,19 @@ export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
setEditNotes(instance.notes ?? "");
setEditMemoryMin(
(instance.memoryOverride && toNumber(instance.memoryOverride.min)) ??
- settings.minMemory ??
+ config?.minMemory ??
512,
);
setEditMemoryMax(
(instance.memoryOverride && toNumber(instance.memoryOverride.max)) ??
- settings.maxMemory ??
+ config?.maxMemory ??
2048,
);
setEditJavaArgs(instance.jvmArgsOverride ?? "");
setFileList([]);
setSelectedFileFolder("mods");
}
- }, [open, instance, settings.minMemory, settings.maxMemory]);
+ }, [open, instance, config?.minMemory, config?.maxMemory]);
// load files when switching to files tab
const loadFileList = useCallback(
@@ -178,7 +178,7 @@ export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
jvmArgsOverride: editJavaArgs.trim() ? editJavaArgs.trim() : null,
};
- await instancesStore.updateInstance(updatedInstance as Instance);
+ await instancesStore.update(updatedInstance as Instance);
toast.success("Instance saved");
onOpenChange(false);
} catch (err) {
@@ -471,7 +471,7 @@ export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
disabled={saving}
/>
<p className="text-xs text-zinc-400 mt-1">
- Default: {settings.minMemory} MB
+ Default: {config?.minMemory} MB
</p>
</div>
@@ -490,7 +490,7 @@ export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
disabled={saving}
/>
<p className="text-xs text-zinc-400 mt-1">
- Default: {settings.maxMemory} MB
+ Default: {config?.maxMemory} MB
</p>
</div>
diff --git a/packages/ui-new/src/main.tsx b/packages/ui-new/src/main.tsx
index bda693d..a3157bd 100644
--- a/packages/ui-new/src/main.tsx
+++ b/packages/ui-new/src/main.tsx
@@ -3,13 +3,10 @@ import { createRoot } from "react-dom/client";
import "./index.css";
import { createHashRouter, RouterProvider } from "react-router";
import { Toaster } from "./components/ui/sonner";
-import { AssistantView } from "./pages/assistant-view";
import { HomeView } from "./pages/home-view";
import { IndexPage } from "./pages/index";
import { InstancesView } from "./pages/instances-view";
import { SettingsPage } from "./pages/settings";
-import { SettingsView } from "./pages/settings-view";
-import { VersionsView } from "./pages/versions-view";
const router = createHashRouter([
{
@@ -25,17 +22,9 @@ const router = createHashRouter([
element: <InstancesView />,
},
{
- path: "versions",
- element: <VersionsView />,
- },
- {
path: "settings",
element: <SettingsPage />,
},
- // {
- // path: "guide",
- // element: <AssistantView />,
- // },
],
},
]);
diff --git a/packages/ui-new/src/models/instances.ts b/packages/ui-new/src/models/instances.ts
new file mode 100644
index 0000000..f434c7c
--- /dev/null
+++ b/packages/ui-new/src/models/instances.ts
@@ -0,0 +1,135 @@
+import { toast } from "sonner";
+import { create } from "zustand";
+import {
+ createInstance,
+ deleteInstance,
+ duplicateInstance,
+ getActiveInstance,
+ getInstance,
+ listInstances,
+ setActiveInstance,
+ updateInstance,
+} from "@/client";
+import type { Instance } from "@/types";
+
+interface InstancesState {
+ // State
+ instances: Instance[];
+ activeInstance: Instance | null;
+
+ // Actions
+ refresh: () => Promise<void>;
+ create: (name: string) => Promise<Instance | null>;
+ delete: (id: string) => Promise<void>;
+ update: (instance: Instance) => Promise<void>;
+ setActiveInstance: (instance: Instance) => Promise<void>;
+ duplicate: (id: string, newName: string) => Promise<Instance | null>;
+ getInstance: (id: string) => Promise<Instance | null>;
+}
+
+export const useInstancesStore = create<InstancesState>((set, get) => ({
+ // Initial state
+ instances: [],
+ activeInstance: null,
+
+ // Actions
+ refresh: async () => {
+ const { setActiveInstance } = get();
+ try {
+ const instances = await listInstances();
+ const active = await getActiveInstance();
+
+ if (!active && instances.length > 0) {
+ // If no active instance but instances exist, set the first one as active
+ await setActiveInstance(instances[0]);
+ }
+
+ set({ instances });
+ } catch (e) {
+ console.error("Failed to load instances:", e);
+ toast.error("Error loading instances");
+ }
+ },
+
+ create: async (name) => {
+ const { refresh } = get();
+ try {
+ const instance = await createInstance(name);
+ await refresh();
+ toast.success(`Instance "${name}" created successfully`);
+ return instance;
+ } catch (e) {
+ console.error("Failed to create instance:", e);
+ toast.error("Error creating instance");
+ return null;
+ }
+ },
+
+ delete: async (id) => {
+ const { refresh, instances, activeInstance, setActiveInstance } = get();
+ try {
+ await deleteInstance(id);
+ await refresh();
+
+ // If deleted instance was active, set another as active
+ if (activeInstance?.id === id) {
+ if (instances.length > 0) {
+ await setActiveInstance(instances[0]);
+ } else {
+ set({ activeInstance: null });
+ }
+ }
+
+ toast.success("Instance deleted successfully");
+ } catch (e) {
+ console.error("Failed to delete instance:", e);
+ toast.error("Error deleting instance");
+ }
+ },
+
+ update: async (instance) => {
+ const { refresh } = get();
+ try {
+ await updateInstance(instance);
+ await refresh();
+ toast.success("Instance updated successfully");
+ } catch (e) {
+ console.error("Failed to update instance:", e);
+ toast.error("Error updating instance");
+ }
+ },
+
+ setActiveInstance: async (instance) => {
+ try {
+ await setActiveInstance(instance.id);
+ set({ activeInstance: instance });
+ toast.success("Active instance changed");
+ } catch (e) {
+ console.error("Failed to set active instance:", e);
+ toast.error("Error setting active instance");
+ }
+ },
+
+ duplicate: async (id, newName) => {
+ const { refresh } = get();
+ try {
+ const instance = await duplicateInstance(id, newName);
+ await refresh();
+ toast.success(`Instance duplicated as "${newName}"`);
+ return instance;
+ } catch (e) {
+ console.error("Failed to duplicate instance:", e);
+ toast.error("Error duplicating instance");
+ return null;
+ }
+ },
+
+ getInstance: async (id) => {
+ try {
+ return await getInstance(id);
+ } catch (e) {
+ console.error("Failed to get instance:", e);
+ return null;
+ }
+ },
+}));
diff --git a/packages/ui-new/src/pages/assistant-view.tsx b/packages/ui-new/src/pages/assistant-view.tsx.bk
index 56f827b..56f827b 100644
--- a/packages/ui-new/src/pages/assistant-view.tsx
+++ b/packages/ui-new/src/pages/assistant-view.tsx.bk
diff --git a/packages/ui-new/src/pages/index-old.tsx b/packages/ui-new/src/pages/index-old.tsx
deleted file mode 100644
index a6626c9..0000000
--- a/packages/ui-new/src/pages/index-old.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-import { useEffect } from "react";
-import { Outlet } from "react-router";
-import { BottomBar } from "@/components/bottom-bar";
-import { DownloadMonitor } from "@/components/download-monitor";
-import { GameConsole } from "@/components/game-console";
-import { LoginModal } from "@/components/login-modal";
-import { ParticleBackground } from "@/components/particle-background";
-import { Sidebar } from "@/components/sidebar";
-
-import { useAuthStore } from "@/stores/auth-store";
-import { useGameStore } from "@/stores/game-store";
-import { useInstancesStore } from "@/stores/instances-store";
-import { useLogsStore } from "@/stores/logs-store";
-import { useSettingsStore } from "@/stores/settings-store";
-import { useUIStore } from "@/stores/ui-store";
-
-export function IndexPage() {
- const authStore = useAuthStore();
- const settingsStore = useSettingsStore();
- const uiStore = useUIStore();
- const instancesStore = useInstancesStore();
- const gameStore = useGameStore();
- const logsStore = useLogsStore();
- useEffect(() => {
- // ENFORCE DARK MODE: Always add 'dark' class and attribute
- document.documentElement.classList.add("dark");
- document.documentElement.setAttribute("data-theme", "dark");
- document.documentElement.classList.remove("light");
-
- // Initialize stores
- // Include store functions in the dependency array to satisfy hooks lint.
- // These functions are stable in our store implementation, so listing them
- // here is safe and prevents lint warnings.
- authStore.checkAccount();
- settingsStore.loadSettings();
- logsStore.init();
- settingsStore.detectJava();
- instancesStore.loadInstances();
- gameStore.loadVersions();
-
- // Note: getVersion() would need Tauri API setup
- // getVersion().then((v) => uiStore.setAppVersion(v));
- }, [
- authStore.checkAccount,
- settingsStore.loadSettings,
- logsStore.init,
- settingsStore.detectJava,
- instancesStore.loadInstances,
- gameStore.loadVersions,
- ]);
-
- // Refresh versions when active instance changes
- useEffect(() => {
- if (instancesStore.activeInstanceId) {
- gameStore.loadVersions();
- } else {
- gameStore.setVersions([]);
- }
- }, [
- instancesStore.activeInstanceId,
- gameStore.loadVersions,
- gameStore.setVersions,
- ]);
-
- return (
- <div className="relative h-screen w-screen overflow-hidden dark:text-white text-gray-900 font-sans selection:bg-indigo-500/30">
- {/* Modern Animated Background */}
- <div className="absolute inset-0 z-0 bg-gray-100 dark:bg-[#09090b] overflow-hidden">
- {settingsStore.settings.customBackgroundPath && (
- <img
- src={settingsStore.settings.customBackgroundPath}
- alt="Background"
- className="absolute inset-0 w-full h-full object-cover transition-transform duration-[20s] ease-linear"
- onError={(e) => console.error("Failed to load main background:", e)}
- />
- )}
-
- {/* Dimming Overlay for readability */}
- {settingsStore.settings.customBackgroundPath && (
- <div className="absolute inset-0 bg-black/50"></div>
- )}
-
- {!settingsStore.settings.customBackgroundPath && (
- <>
- {settingsStore.settings.theme === "dark" ? (
- <div className="absolute inset-0 opacity-60 bg-linear-to-br from-emerald-900 via-zinc-900 to-indigo-950"></div>
- ) : (
- <div className="absolute inset-0 opacity-100 bg-linear-to-br from-emerald-100 via-gray-100 to-indigo-100"></div>
- )}
-
- {uiStore.currentView === "home" && <ParticleBackground />}
-
- <div className="absolute inset-0 bg-linear-to-t from-zinc-900 via-transparent to-black/50 dark:from-zinc-900 dark:to-black/50"></div>
- </>
- )}
-
- {/* Subtle Grid Overlay */}
- <div
- className="absolute inset-0 z-0 dark:opacity-10 opacity-30 pointer-events-none"
- style={{
- backgroundImage: `linear-gradient(${
- settingsStore.settings.theme === "dark" ? "#ffffff" : "#000000"
- } 1px, transparent 1px), linear-gradient(90deg, ${
- settingsStore.settings.theme === "dark" ? "#ffffff" : "#000000"
- } 1px, transparent 1px)`,
- backgroundSize: "40px 40px",
- maskImage:
- "radial-gradient(circle at 50% 50%, black 30%, transparent 70%)",
- }}
- ></div>
- </div>
-
- {/* Content Wrapper */}
- <div className="relative z-10 flex h-full p-4 gap-4 text-gray-900 dark:text-white">
- {/* Floating Sidebar */}
- <Sidebar />
-
- {/* Main Content Area - Transparent & Flat */}
- <main className="flex-1 flex flex-col relative min-w-0 overflow-hidden transition-all duration-300">
- {/* Window Drag Region */}
- <div
- className="h-8 w-full absolute top-0 left-0 z-50 drag-region"
- data-tauri-drag-region
- ></div>
-
- {/* App Content */}
- <div className="flex-1 relative overflow-hidden flex flex-col">
- {/* Views Container */}
- <div className="flex-1 relative overflow-hidden">
- <Outlet />
- </div>
-
- {/* Download Monitor Overlay */}
- <div className="absolute bottom-20 left-4 right-4 pointer-events-none z-20">
- <div className="pointer-events-auto">
- <DownloadMonitor />
- </div>
- </div>
-
- {/* Bottom Bar */}
- {uiStore.currentView === "home" && <BottomBar />}
- </div>
- </main>
- </div>
-
- {/* Logout Confirmation Dialog */}
- {authStore.isLogoutConfirmOpen && (
- <div className="fixed inset-0 z-200 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4">
- <div className="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl p-6 max-w-sm w-full animate-in fade-in zoom-in-95 duration-200">
- <h3 className="text-lg font-bold text-white mb-2">Logout</h3>
- <p className="text-zinc-400 text-sm mb-6">
- Are you sure you want to logout{" "}
- <span className="text-white font-medium">
- {authStore.currentAccount?.username}
- </span>
- ?
- </p>
- <div className="flex gap-3 justify-end">
- <button
- type="button"
- onClick={() => authStore.cancelLogout()}
- className="px-4 py-2 text-sm font-medium text-zinc-300 hover:text-white bg-zinc-800 hover:bg-zinc-700 rounded-lg transition-colors"
- >
- Cancel
- </button>
- <button
- type="button"
- onClick={() => authStore.confirmLogout()}
- className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-500 rounded-lg transition-colors"
- >
- Logout
- </button>
- </div>
- </div>
- </div>
- )}
-
- {uiStore.showConsole && (
- <div className="fixed inset-0 z-100 bg-black/80 backdrop-blur-sm flex items-center justify-center p-8">
- <div className="w-full h-full max-w-6xl max-h-[85vh] bg-[#1e1e1e] rounded-lg overflow-hidden border border-zinc-700 shadow-2xl relative flex flex-col">
- <GameConsole />
- </div>
- </div>
- )}
- </div>
- );
-}
diff --git a/packages/ui-new/src/pages/instances-view.tsx b/packages/ui-new/src/pages/instances-view.tsx
index 0c511a1..ad6bd38 100644
--- a/packages/ui-new/src/pages/instances-view.tsx
+++ b/packages/ui-new/src/pages/instances-view.tsx
@@ -1,5 +1,6 @@
import { Copy, Edit2, Plus, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
+import InstanceCreationModal from "@/components/instance-creation-modal";
import InstanceEditorModal from "@/components/instance-editor-modal";
import { Button } from "@/components/ui/button";
import {
@@ -12,7 +13,7 @@ import {
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { toNumber } from "@/lib/tsrs-utils";
-import { useInstancesStore } from "@/stores/instances-store";
+import { useInstancesStore } from "@/models/instances";
import type { Instance } from "../types/bindings/instance";
export function InstancesView() {
@@ -31,19 +32,14 @@ export function InstancesView() {
const [editingInstance, setEditingInstance] = useState<Instance | null>(null);
// Form fields
- const [newInstanceName, setNewInstanceName] = useState("");
const [duplicateName, setDuplicateName] = useState("");
- // Load instances on mount (matches Svelte onMount behavior)
useEffect(() => {
- instancesStore.loadInstances();
- // instancesStore methods are stable (Zustand); do not add to deps to avoid spurious runs
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [instancesStore.loadInstances]);
+ instancesStore.refresh();
+ }, [instancesStore.refresh]);
// Handlers to open modals
const openCreate = () => {
- setNewInstanceName("");
setShowCreateModal(true);
};
@@ -63,25 +59,9 @@ export function InstancesView() {
setShowDuplicateModal(true);
};
- // Confirm actions
- const confirmCreate = async () => {
- const name = newInstanceName.trim();
- if (!name) return;
- await instancesStore.createInstance(name);
- setShowCreateModal(false);
- setNewInstanceName("");
- };
-
- const confirmEdit = async () => {
- if (!editingInstance) return;
- await instancesStore.updateInstance(editingInstance);
- setEditingInstance(null);
- setShowEditModal(false);
- };
-
const confirmDelete = async () => {
if (!selectedInstance) return;
- await instancesStore.deleteInstance(selectedInstance.id);
+ await instancesStore.delete(selectedInstance.id);
setSelectedInstance(null);
setShowDeleteConfirm(false);
};
@@ -90,16 +70,12 @@ export function InstancesView() {
if (!selectedInstance) return;
const name = duplicateName.trim();
if (!name) return;
- await instancesStore.duplicateInstance(selectedInstance.id, name);
+ await instancesStore.duplicate(selectedInstance.id, name);
setSelectedInstance(null);
setDuplicateName("");
setShowDuplicateModal(false);
};
- const setActiveInstance = async (id: string) => {
- await instancesStore.setActiveInstance(id);
- };
-
const formatDate = (timestamp: number): string =>
new Date(timestamp * 1000).toLocaleDateString();
@@ -124,7 +100,7 @@ export function InstancesView() {
<Button
type="button"
onClick={openCreate}
- className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
+ className="px-4 py-2 transition-colors"
>
<Plus size={18} />
Create Instance
@@ -141,16 +117,17 @@ export function InstancesView() {
) : (
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{instancesStore.instances.map((instance) => {
- const isActive = instancesStore.activeInstanceId === instance.id;
+ const isActive = instancesStore.activeInstance?.id === instance.id;
return (
<li
key={instance.id}
- onClick={() => setActiveInstance(instance.id)}
+ onClick={() => instancesStore.setActiveInstance(instance)}
onKeyDown={(e) =>
- e.key === "Enter" && setActiveInstance(instance.id)
+ e.key === "Enter" &&
+ instancesStore.setActiveInstance(instance)
}
- className={`relative p-4 text-left rounded-lg border-2 transition-all cursor-pointer hover:border-blue-500 ${
+ className={`relative p-4 text-left border-2 transition-all cursor-pointer hover:border-blue-500 ${
isActive ? "border-blue-500" : "border-transparent"
} bg-gray-100 dark:bg-gray-800`}
>
@@ -245,42 +222,10 @@ export function InstancesView() {
</ul>
)}
- {/* Create Modal */}
- <Dialog open={showCreateModal} onOpenChange={setShowCreateModal}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle>Create Instance</DialogTitle>
- <DialogDescription>
- Enter a name for the new instance.
- </DialogDescription>
- </DialogHeader>
-
- <div className="mt-4">
- <Input
- value={newInstanceName}
- onChange={(e) => setNewInstanceName(e.target.value)}
- placeholder="Instance name"
- />
- </div>
-
- <DialogFooter>
- <Button
- type="button"
- variant="outline"
- onClick={() => setShowCreateModal(false)}
- >
- Cancel
- </Button>
- <Button
- type="button"
- onClick={confirmCreate}
- disabled={!newInstanceName.trim()}
- >
- Create
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
+ <InstanceCreationModal
+ open={showCreateModal}
+ onOpenChange={setShowCreateModal}
+ />
<InstanceEditorModal
open={showEditModal}
diff --git a/packages/ui-new/src/pages/settings-view.tsx b/packages/ui-new/src/pages/settings-view.tsx.bk
index ac43d9b..ac43d9b 100644
--- a/packages/ui-new/src/pages/settings-view.tsx
+++ b/packages/ui-new/src/pages/settings-view.tsx.bk
diff --git a/packages/ui-new/src/pages/versions-view.tsx b/packages/ui-new/src/pages/versions-view.tsx.bk
index 7f44611..d54596d 100644
--- a/packages/ui-new/src/pages/versions-view.tsx
+++ b/packages/ui-new/src/pages/versions-view.tsx.bk
@@ -17,8 +17,8 @@ import {
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { useInstancesStore } from "../models/instances";
import { useGameStore } from "../stores/game-store";
-import { useInstancesStore } from "../stores/instances-store";
import type { Version } from "../types/bindings/manifest";
interface InstalledModdedVersion {
@@ -31,7 +31,7 @@ type TypeFilter = "all" | "release" | "snapshot" | "installed";
export function VersionsView() {
const { versions, selectedVersion, loadVersions, setSelectedVersion } =
useGameStore();
- const { activeInstanceId } = useInstancesStore();
+ const { activeInstance } = useInstancesStore();
const [searchQuery, setSearchQuery] = useState("");
const [typeFilter, setTypeFilter] = useState<TypeFilter>("all");
@@ -54,7 +54,7 @@ export function VersionsView() {
// Load installed modded versions with Java version info
const loadInstalledModdedVersions = useCallback(async () => {
- if (!activeInstanceId) {
+ if (!activeInstance) {
setInstalledModdedVersions([]);
setIsLoadingModded(false);
return;
diff --git a/packages/ui-new/src/stores/game-store.ts b/packages/ui-new/src/stores/game-store.ts
index 541b386..fa0f9f8 100644
--- a/packages/ui-new/src/stores/game-store.ts
+++ b/packages/ui-new/src/stores/game-store.ts
@@ -1,6 +1,6 @@
-import { invoke } from "@tauri-apps/api/core";
import { toast } from "sonner";
import { create } from "zustand";
+import { getVersions } from "@/client";
import type { Version } from "@/types/bindings/manifest";
interface GameState {
@@ -39,7 +39,7 @@ export const useGameStore = create<GameState>((set, get) => ({
try {
// Ask the backend for known versions (optionally scoped to an instance).
// The Tauri command `get_versions` is expected to return an array of `Version`.
- const versions = await invoke<Version[]>("get_versions", { instanceId });
+ const versions = await getVersions();
set({ versions: versions ?? [] });
} catch (e) {
console.error("Failed to load versions:", e);
diff --git a/packages/ui-new/src/stores/instances-store.ts b/packages/ui-new/src/stores/instances-store.ts
deleted file mode 100644
index 4636b79..0000000
--- a/packages/ui-new/src/stores/instances-store.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { toast } from "sonner";
-import { create } from "zustand";
-import type { Instance } from "../types/bindings/instance";
-
-interface InstancesState {
- // State
- instances: Instance[];
- activeInstanceId: string | null;
-
- // Computed property
- activeInstance: Instance | null;
-
- // Actions
- loadInstances: () => Promise<void>;
- createInstance: (name: string) => Promise<Instance | null>;
- deleteInstance: (id: string) => Promise<void>;
- updateInstance: (instance: Instance) => Promise<void>;
- setActiveInstance: (id: string) => Promise<void>;
- duplicateInstance: (id: string, newName: string) => Promise<Instance | null>;
- getInstance: (id: string) => Promise<Instance | null>;
- setInstances: (instances: Instance[]) => void;
- setActiveInstanceId: (id: string | null) => void;
-}
-
-export const useInstancesStore = create<InstancesState>((set, get) => ({
- // Initial state
- instances: [],
- activeInstanceId: null,
-
- // Computed property
- get activeInstance() {
- const { instances, activeInstanceId } = get();
- if (!activeInstanceId) return null;
- return instances.find((i) => i.id === activeInstanceId) || null;
- },
-
- // Actions
- loadInstances: async () => {
- try {
- const instances = await invoke<Instance[]>("list_instances");
- const active = await invoke<Instance | null>("get_active_instance");
-
- let newActiveInstanceId = null;
- if (active) {
- newActiveInstanceId = active.id;
- } else if (instances.length > 0) {
- // If no active instance but instances exist, set the first one as active
- await get().setActiveInstance(instances[0].id);
- newActiveInstanceId = instances[0].id;
- }
-
- set({ instances, activeInstanceId: newActiveInstanceId });
- } catch (e) {
- console.error("Failed to load instances:", e);
- toast.error("Error loading instances: " + String(e));
- }
- },
-
- createInstance: async (name) => {
- try {
- const instance = await invoke<Instance>("create_instance", { name });
- await get().loadInstances();
- toast.success(`Instance "${name}" created successfully`);
- return instance;
- } catch (e) {
- console.error("Failed to create instance:", e);
- toast.error("Error creating instance: " + String(e));
- return null;
- }
- },
-
- deleteInstance: async (id) => {
- try {
- await invoke("delete_instance", { instanceId: id });
- await get().loadInstances();
-
- // If deleted instance was active, set another as active
- const { instances, activeInstanceId } = get();
- if (activeInstanceId === id) {
- if (instances.length > 0) {
- await get().setActiveInstance(instances[0].id);
- } else {
- set({ activeInstanceId: null });
- }
- }
-
- toast.success("Instance deleted successfully");
- } catch (e) {
- console.error("Failed to delete instance:", e);
- toast.error("Error deleting instance: " + String(e));
- }
- },
-
- updateInstance: async (instance) => {
- try {
- await invoke("update_instance", { instance });
- await get().loadInstances();
- toast.success("Instance updated successfully");
- } catch (e) {
- console.error("Failed to update instance:", e);
- toast.error("Error updating instance: " + String(e));
- }
- },
-
- setActiveInstance: async (id) => {
- try {
- await invoke("set_active_instance", { instanceId: id });
- set({ activeInstanceId: id });
- toast.success("Active instance changed");
- } catch (e) {
- console.error("Failed to set active instance:", e);
- toast.error("Error setting active instance: " + String(e));
- }
- },
-
- duplicateInstance: async (id, newName) => {
- try {
- const instance = await invoke<Instance>("duplicate_instance", {
- instanceId: id,
- newName,
- });
- await get().loadInstances();
- toast.success(`Instance duplicated as "${newName}"`);
- return instance;
- } catch (e) {
- console.error("Failed to duplicate instance:", e);
- toast.error("Error duplicating instance: " + String(e));
- return null;
- }
- },
-
- getInstance: async (id) => {
- try {
- return await invoke<Instance>("get_instance", { instanceId: id });
- } catch (e) {
- console.error("Failed to get instance:", e);
- return null;
- }
- },
-
- setInstances: (instances) => {
- set({ instances });
- },
-
- setActiveInstanceId: (id) => {
- set({ activeInstanceId: id });
- },
-}));
diff --git a/packages/ui-new/src/stores/settings-store.ts b/packages/ui-new/src/stores/settings-store.ts
index 52da7fd..0bfc1e1 100644
--- a/packages/ui-new/src/stores/settings-store.ts
+++ b/packages/ui-new/src/stores/settings-store.ts
@@ -2,6 +2,7 @@ import { convertFileSrc, invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { toast } from "sonner";
import { create } from "zustand";
+import { downloadAdoptiumJava } from "@/client";
import type { ModelInfo } from "../types/bindings/assistant";
import type { LauncherConfig } from "../types/bindings/config";
import type {
@@ -10,7 +11,6 @@ import type {
} from "../types/bindings/downloader";
import type {
JavaCatalog,
- JavaDownloadInfo,
JavaInstallation,
JavaReleaseInfo,
} from "../types/bindings/java";
@@ -445,13 +445,13 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
if (!selectedMajorVersion) return;
set({ isDownloadingJava: true, javaDownloadStatus: "Starting..." });
try {
- const result = await invoke<JavaDownloadInfo>("download_java", {
- majorVersion: selectedMajorVersion,
- imageType: selectedImageType,
- source: selectedDownloadSource,
- });
+ const result = await downloadAdoptiumJava(
+ selectedMajorVersion,
+ selectedImageType,
+ selectedDownloadSource,
+ );
set({
- javaDownloadStatus: `Java ${selectedMajorVersion} download started: ${result.fileName}`,
+ javaDownloadStatus: `Java ${selectedMajorVersion} download started: ${result.path}`,
});
toast.success("Download started");
} catch (e) {
diff --git a/packages/ui-new/src/types/bindings/java/core.ts b/packages/ui-new/src/types/bindings/java/core.ts
index 8094c71..099dea9 100644
--- a/packages/ui-new/src/types/bindings/java/core.ts
+++ b/packages/ui-new/src/types/bindings/java/core.ts
@@ -23,7 +23,7 @@ export type JavaInstallation = {
arch: string;
vendor: string;
source: string;
- is_64bit: boolean;
+ is64bit: boolean;
};
export type JavaReleaseInfo = {