aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui-new/src
diff options
context:
space:
mode:
author苏向夜 <fu050409@163.com>2026-02-25 01:32:51 +0800
committer苏向夜 <fu050409@163.com>2026-02-25 01:32:51 +0800
commit66668d85d603c5841d755a6023aa1925559fc6d4 (patch)
tree485464148c76b0021efb55b7d2afd1c3004ceee0 /packages/ui-new/src
parenta6773bd092db654360c599ca6b0108ea0e456e8c (diff)
downloadDropOut-66668d85d603c5841d755a6023aa1925559fc6d4.tar.gz
DropOut-66668d85d603c5841d755a6023aa1925559fc6d4.zip
chore(workspace): replace legacy codes
Diffstat (limited to 'packages/ui-new/src')
-rw-r--r--packages/ui-new/src/client.ts400
-rw-r--r--packages/ui-new/src/components/bottom-bar.tsx231
-rw-r--r--packages/ui-new/src/components/config-editor.tsx111
-rw-r--r--packages/ui-new/src/components/download-monitor.tsx62
-rw-r--r--packages/ui-new/src/components/game-console.tsx290
-rw-r--r--packages/ui-new/src/components/instance-creation-modal.tsx552
-rw-r--r--packages/ui-new/src/components/instance-editor-modal.tsx548
-rw-r--r--packages/ui-new/src/components/login-modal.tsx188
-rw-r--r--packages/ui-new/src/components/particle-background.tsx63
-rw-r--r--packages/ui-new/src/components/sidebar.tsx185
-rw-r--r--packages/ui-new/src/components/ui/avatar.tsx107
-rw-r--r--packages/ui-new/src/components/ui/badge.tsx52
-rw-r--r--packages/ui-new/src/components/ui/button.tsx56
-rw-r--r--packages/ui-new/src/components/ui/card.tsx103
-rw-r--r--packages/ui-new/src/components/ui/checkbox.tsx27
-rw-r--r--packages/ui-new/src/components/ui/dialog.tsx155
-rw-r--r--packages/ui-new/src/components/ui/dropdown-menu.tsx269
-rw-r--r--packages/ui-new/src/components/ui/field.tsx238
-rw-r--r--packages/ui-new/src/components/ui/input.tsx20
-rw-r--r--packages/ui-new/src/components/ui/label.tsx19
-rw-r--r--packages/ui-new/src/components/ui/scroll-area.tsx53
-rw-r--r--packages/ui-new/src/components/ui/select.tsx199
-rw-r--r--packages/ui-new/src/components/ui/separator.tsx25
-rw-r--r--packages/ui-new/src/components/ui/sonner.tsx43
-rw-r--r--packages/ui-new/src/components/ui/spinner.tsx10
-rw-r--r--packages/ui-new/src/components/ui/switch.tsx32
-rw-r--r--packages/ui-new/src/components/ui/tabs.tsx80
-rw-r--r--packages/ui-new/src/components/ui/textarea.tsx18
-rw-r--r--packages/ui-new/src/components/user-avatar.tsx23
-rw-r--r--packages/ui-new/src/index.css126
-rw-r--r--packages/ui-new/src/lib/effects/SaturnEffect.ts299
-rw-r--r--packages/ui-new/src/lib/tsrs-utils.ts67
-rw-r--r--packages/ui-new/src/lib/utils.ts6
-rw-r--r--packages/ui-new/src/main.tsx38
-rw-r--r--packages/ui-new/src/models/auth.ts142
-rw-r--r--packages/ui-new/src/models/instances.ts135
-rw-r--r--packages/ui-new/src/models/settings.ts75
-rw-r--r--packages/ui-new/src/pages/assistant-view.tsx.bk485
-rw-r--r--packages/ui-new/src/pages/home-view.tsx174
-rw-r--r--packages/ui-new/src/pages/index.tsx76
-rw-r--r--packages/ui-new/src/pages/instances-view.tsx315
-rw-r--r--packages/ui-new/src/pages/settings-view.tsx.bk1158
-rw-r--r--packages/ui-new/src/pages/settings.tsx310
-rw-r--r--packages/ui-new/src/pages/versions-view.tsx.bk662
-rw-r--r--packages/ui-new/src/stores/assistant-store.ts201
-rw-r--r--packages/ui-new/src/stores/auth-store.ts296
-rw-r--r--packages/ui-new/src/stores/game-store.ts101
-rw-r--r--packages/ui-new/src/stores/logs-store.ts200
-rw-r--r--packages/ui-new/src/stores/releases-store.ts63
-rw-r--r--packages/ui-new/src/stores/settings-store.ts568
-rw-r--r--packages/ui-new/src/stores/ui-store.ts42
-rw-r--r--packages/ui-new/src/types/bindings/account.ts28
-rw-r--r--packages/ui-new/src/types/bindings/assistant.ts25
-rw-r--r--packages/ui-new/src/types/bindings/auth.ts32
-rw-r--r--packages/ui-new/src/types/bindings/config.ts61
-rw-r--r--packages/ui-new/src/types/bindings/core.ts47
-rw-r--r--packages/ui-new/src/types/bindings/downloader.ts73
-rw-r--r--packages/ui-new/src/types/bindings/fabric.ts74
-rw-r--r--packages/ui-new/src/types/bindings/forge.ts21
-rw-r--r--packages/ui-new/src/types/bindings/game-version.ts89
-rw-r--r--packages/ui-new/src/types/bindings/index.ts12
-rw-r--r--packages/ui-new/src/types/bindings/instance.ts33
-rw-r--r--packages/ui-new/src/types/bindings/java/core.ts41
-rw-r--r--packages/ui-new/src/types/bindings/java/index.ts3
-rw-r--r--packages/ui-new/src/types/bindings/java/persistence.ts7
-rw-r--r--packages/ui-new/src/types/bindings/java/providers/adoptium.ts37
-rw-r--r--packages/ui-new/src/types/bindings/java/providers/index.ts1
-rw-r--r--packages/ui-new/src/types/bindings/manifest.ts22
-rw-r--r--packages/ui-new/src/types/index.ts1
69 files changed, 0 insertions, 10305 deletions
diff --git a/packages/ui-new/src/client.ts b/packages/ui-new/src/client.ts
deleted file mode 100644
index 18d2377..0000000
--- a/packages/ui-new/src/client.ts
+++ /dev/null
@@ -1,400 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import type {
- Account,
- DeviceCodeResponse,
- FabricGameVersion,
- FabricLoaderEntry,
- FabricLoaderVersion,
- FileInfo,
- ForgeVersion,
- GithubRelease,
- InstalledFabricVersion,
- InstalledForgeVersion,
- InstalledVersion,
- Instance,
- JavaCatalog,
- JavaDownloadInfo,
- JavaInstallation,
- LauncherConfig,
- Message,
- MigrationResult,
- ModelInfo,
- PastebinResponse,
- PendingJavaDownload,
- Version,
- VersionMetadata,
-} from "@/types";
-
-export function assistantChat(messages: Message[]): Promise<Message> {
- return invoke<Message>("assistant_chat", {
- messages,
- });
-}
-
-export function assistantChatStream(messages: Message[]): Promise<string> {
- return invoke<string>("assistant_chat_stream", {
- messages,
- });
-}
-
-export function assistantCheckHealth(): Promise<boolean> {
- return invoke<boolean>("assistant_check_health");
-}
-
-export function cancelJavaDownload(): Promise<void> {
- return invoke<void>("cancel_java_download");
-}
-
-export function checkVersionInstalled(
- instanceId: string,
- versionId: string,
-): Promise<boolean> {
- return invoke<boolean>("check_version_installed", {
- instanceId,
- versionId,
- });
-}
-
-export function completeMicrosoftLogin(deviceCode: string): Promise<Account> {
- return invoke<Account>("complete_microsoft_login", {
- deviceCode,
- });
-}
-
-export function createInstance(name: string): Promise<Instance> {
- return invoke<Instance>("create_instance", {
- name,
- });
-}
-
-export function deleteInstance(instanceId: string): Promise<void> {
- return invoke<void>("delete_instance", {
- instanceId,
- });
-}
-
-export function deleteInstanceFile(path: string): Promise<void> {
- return invoke<void>("delete_instance_file", {
- path,
- });
-}
-
-export function deleteVersion(
- instanceId: string,
- versionId: string,
-): Promise<void> {
- return invoke<void>("delete_version", {
- instanceId,
- versionId,
- });
-}
-
-export function detectAllJavaInstallations(): Promise<JavaInstallation[]> {
- return invoke<JavaInstallation[]>("detect_all_java_installations");
-}
-
-export function detectJava(): Promise<JavaInstallation[]> {
- return invoke<JavaInstallation[]>("detect_java");
-}
-
-export function downloadAdoptiumJava(
- majorVersion: number,
- imageType: string,
- customPath: string | null,
-): Promise<JavaInstallation> {
- return invoke<JavaInstallation>("download_adoptium_java", {
- majorVersion,
- imageType,
- customPath,
- });
-}
-
-export function duplicateInstance(
- instanceId: string,
- newName: string,
-): Promise<Instance> {
- return invoke<Instance>("duplicate_instance", {
- instanceId,
- newName,
- });
-}
-
-export function fetchAdoptiumJava(
- majorVersion: number,
- imageType: string,
-): Promise<JavaDownloadInfo> {
- return invoke<JavaDownloadInfo>("fetch_adoptium_java", {
- majorVersion,
- imageType,
- });
-}
-
-export function fetchAvailableJavaVersions(): Promise<number[]> {
- return invoke<number[]>("fetch_available_java_versions");
-}
-
-export function fetchJavaCatalog(): Promise<JavaCatalog> {
- return invoke<JavaCatalog>("fetch_java_catalog");
-}
-
-export function getActiveAccount(): Promise<Account | null> {
- return invoke<Account | null>("get_active_account");
-}
-
-export function getActiveInstance(): Promise<Instance | null> {
- return invoke<Instance | null>("get_active_instance");
-}
-
-export function getConfigPath(): Promise<string> {
- return invoke<string>("get_config_path");
-}
-
-export function getFabricGameVersions(): Promise<FabricGameVersion[]> {
- return invoke<FabricGameVersion[]>("get_fabric_game_versions");
-}
-
-export function getFabricLoaderVersions(): Promise<FabricLoaderVersion[]> {
- return invoke<FabricLoaderVersion[]>("get_fabric_loader_versions");
-}
-
-export function getFabricLoadersForVersion(
- gameVersion: string,
-): Promise<FabricLoaderEntry[]> {
- return invoke<FabricLoaderEntry[]>("get_fabric_loaders_for_version", {
- gameVersion,
- });
-}
-
-export function getForgeGameVersions(): Promise<string[]> {
- return invoke<string[]>("get_forge_game_versions");
-}
-
-export function getForgeVersionsForGame(
- gameVersion: string,
-): Promise<ForgeVersion[]> {
- return invoke<ForgeVersion[]>("get_forge_versions_for_game", {
- gameVersion,
- });
-}
-
-export function getGithubReleases(): Promise<GithubRelease[]> {
- return invoke<GithubRelease[]>("get_github_releases");
-}
-
-export function getInstance(instanceId: string): Promise<Instance> {
- return invoke<Instance>("get_instance", {
- instanceId,
- });
-}
-
-export function getPendingJavaDownloads(): Promise<PendingJavaDownload[]> {
- return invoke<PendingJavaDownload[]>("get_pending_java_downloads");
-}
-
-export function getRecommendedJava(
- requiredMajorVersion: number | null,
-): Promise<JavaInstallation | null> {
- return invoke<JavaInstallation | null>("get_recommended_java", {
- requiredMajorVersion,
- });
-}
-
-export function getSettings(): Promise<LauncherConfig> {
- return invoke<LauncherConfig>("get_settings");
-}
-
-export function getVersionJavaVersion(
- instanceId: string,
- versionId: string,
-): Promise<number | null> {
- return invoke<number | null>("get_version_java_version", {
- instanceId,
- versionId,
- });
-}
-
-export function getVersionMetadata(
- instanceId: string,
- versionId: string,
-): Promise<VersionMetadata> {
- return invoke<VersionMetadata>("get_version_metadata", {
- instanceId,
- versionId,
- });
-}
-
-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,
- });
-}
-
-export function installFabric(
- instanceId: string,
- gameVersion: string,
- loaderVersion: string,
-): Promise<InstalledFabricVersion> {
- return invoke<InstalledFabricVersion>("install_fabric", {
- instanceId,
- gameVersion,
- loaderVersion,
- });
-}
-
-export function installForge(
- instanceId: string,
- gameVersion: string,
- forgeVersion: string,
-): Promise<InstalledForgeVersion> {
- return invoke<InstalledForgeVersion>("install_forge", {
- instanceId,
- gameVersion,
- forgeVersion,
- });
-}
-
-export function installVersion(
- instanceId: string,
- versionId: string,
-): Promise<void> {
- return invoke<void>("install_version", {
- instanceId,
- versionId,
- });
-}
-
-export function isFabricInstalled(
- instanceId: string,
- gameVersion: string,
- loaderVersion: string,
-): Promise<boolean> {
- return invoke<boolean>("is_fabric_installed", {
- instanceId,
- gameVersion,
- loaderVersion,
- });
-}
-
-export function listInstalledFabricVersions(
- instanceId: string,
-): Promise<string[]> {
- return invoke<string[]>("list_installed_fabric_versions", {
- instanceId,
- });
-}
-
-export function listInstalledVersions(
- instanceId: string,
-): Promise<InstalledVersion[]> {
- return invoke<InstalledVersion[]>("list_installed_versions", {
- instanceId,
- });
-}
-
-export function listInstanceDirectory(
- instanceId: string,
- folder: string,
-): Promise<FileInfo[]> {
- return invoke<FileInfo[]>("list_instance_directory", {
- instanceId,
- folder,
- });
-}
-
-export function listInstances(): Promise<Instance[]> {
- return invoke<Instance[]>("list_instances");
-}
-
-export function listOllamaModels(endpoint: string): Promise<ModelInfo[]> {
- return invoke<ModelInfo[]>("list_ollama_models", {
- endpoint,
- });
-}
-
-export function listOpenaiModels(): Promise<ModelInfo[]> {
- return invoke<ModelInfo[]>("list_openai_models");
-}
-
-export function loginOffline(username: string): Promise<Account> {
- return invoke<Account>("login_offline", {
- username,
- });
-}
-
-export function logout(): Promise<void> {
- return invoke<void>("logout");
-}
-
-export function migrateSharedCaches(): Promise<MigrationResult> {
- return invoke<MigrationResult>("migrate_shared_caches");
-}
-
-export function openFileExplorer(path: string): Promise<void> {
- return invoke<void>("open_file_explorer", {
- path,
- });
-}
-
-export function readRawConfig(): Promise<string> {
- return invoke<string>("read_raw_config");
-}
-
-export function refreshAccount(): Promise<Account> {
- return invoke<Account>("refresh_account");
-}
-
-export function refreshJavaCatalog(): Promise<JavaCatalog> {
- return invoke<JavaCatalog>("refresh_java_catalog");
-}
-
-export function resumeJavaDownloads(): Promise<JavaInstallation[]> {
- return invoke<JavaInstallation[]>("resume_java_downloads");
-}
-
-export function saveRawConfig(content: string): Promise<void> {
- return invoke<void>("save_raw_config", {
- content,
- });
-}
-
-export function saveSettings(config: LauncherConfig): Promise<void> {
- return invoke<void>("save_settings", {
- config,
- });
-}
-
-export function setActiveInstance(instanceId: string): Promise<void> {
- return invoke<void>("set_active_instance", {
- instanceId,
- });
-}
-
-export function startGame(
- instanceId: string,
- versionId: string,
-): Promise<string> {
- return invoke<string>("start_game", {
- instanceId,
- versionId,
- });
-}
-
-export function startMicrosoftLogin(): Promise<DeviceCodeResponse> {
- return invoke<DeviceCodeResponse>("start_microsoft_login");
-}
-
-export function updateInstance(instance: Instance): Promise<void> {
- return invoke<void>("update_instance", {
- instance,
- });
-}
-
-export function uploadToPastebin(content: string): Promise<PastebinResponse> {
- return invoke<PastebinResponse>("upload_to_pastebin", {
- content,
- });
-}
diff --git a/packages/ui-new/src/components/bottom-bar.tsx b/packages/ui-new/src/components/bottom-bar.tsx
deleted file mode 100644
index 32eb852..0000000
--- a/packages/ui-new/src/components/bottom-bar.tsx
+++ /dev/null
@@ -1,231 +0,0 @@
-import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-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 { LoginModal } from "./login-modal";
-import { Button } from "./ui/button";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "./ui/select";
-
-interface InstalledVersion {
- id: string;
- type: string;
-}
-
-export function BottomBar() {
- const authStore = useAuthStore();
- const gameStore = useGameStore();
- const instancesStore = useInstancesStore();
-
- const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
- const [installedVersions, setInstalledVersions] = useState<
- InstalledVersion[]
- >([]);
- const [isLoadingVersions, setIsLoadingVersions] = useState(true);
- const [showLoginModal, setShowLoginModal] = useState(false);
-
- const loadInstalledVersions = useCallback(async () => {
- if (!instancesStore.activeInstance) {
- setInstalledVersions([]);
- setIsLoadingVersions(false);
- return;
- }
-
- setIsLoadingVersions(true);
- try {
- const versions = await listInstalledVersions(
- instancesStore.activeInstance.id,
- );
-
- const installed = versions || [];
- setInstalledVersions(installed);
-
- // If no version is selected but we have installed versions, select the first one
- if (!gameStore.selectedVersion && installed.length > 0) {
- gameStore.setSelectedVersion(installed[0].id);
- }
- } catch (error) {
- console.error("Failed to load installed versions:", error);
- } finally {
- setIsLoadingVersions(false);
- }
- }, [
- instancesStore.activeInstance,
- gameStore.selectedVersion,
- gameStore.setSelectedVersion,
- ]);
-
- useEffect(() => {
- loadInstalledVersions();
-
- // Listen for backend events that should refresh installed versions.
- let unlistenDownload: UnlistenFn | null = null;
- let unlistenVersionDeleted: UnlistenFn | null = null;
-
- (async () => {
- try {
- unlistenDownload = await listen("download-complete", () => {
- loadInstalledVersions();
- });
- } catch (err) {
- // best-effort: do not break UI if listening fails
- // eslint-disable-next-line no-console
- console.warn("Failed to attach download-complete listener:", err);
- }
-
- try {
- unlistenVersionDeleted = await listen("version-deleted", () => {
- loadInstalledVersions();
- });
- } catch (err) {
- // eslint-disable-next-line no-console
- console.warn("Failed to attach version-deleted listener:", err);
- }
- })();
-
- return () => {
- try {
- if (unlistenDownload) unlistenDownload();
- } catch {
- // ignore
- }
- try {
- if (unlistenVersionDeleted) unlistenVersionDeleted();
- } catch {
- // ignore
- }
- };
- }, [loadInstalledVersions]);
-
- const handleStartGame = async () => {
- if (!selectedVersion) {
- toast.info("Please select a version!");
- return;
- }
-
- 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) => {
- switch (type) {
- case "release":
- return "bg-emerald-500";
- case "snapshot":
- return "bg-amber-500";
- case "old_beta":
- return "bg-rose-500";
- case "old_alpha":
- return "bg-violet-500";
- default:
- return "bg-gray-500";
- }
- };
-
- 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">
- <div className="max-w-7xl mx-auto">
- <div className="flex items-center justify-between bg-white/5 dark:bg-black/20 backdrop-blur-xl border border-white/10 dark:border-white/5 p-3 shadow-lg">
- <div className="flex items-center gap-4">
- <div className="flex flex-col">
- <span className="text-xs font-mono text-zinc-400 uppercase tracking-wider">
- Active Instance
- </span>
- <span className="text-sm font-medium text-white">
- {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">
- {authStore.account ? (
- <Button
- className={cn(
- "px-4 py-2 shadow-xl",
- "bg-emerald-600! hover:bg-emerald-500!",
- )}
- size="lg"
- onClick={handleStartGame}
- >
- <Play />
- Start
- </Button>
- ) : (
- <Button
- className="px-4 py-2"
- size="lg"
- onClick={() => setShowLoginModal(true)}
- >
- <User /> Login
- </Button>
- )}
- </div>
- </div>
- </div>
-
- <LoginModal
- open={showLoginModal}
- onOpenChange={() => setShowLoginModal(false)}
- />
- </div>
- );
-}
diff --git a/packages/ui-new/src/components/config-editor.tsx b/packages/ui-new/src/components/config-editor.tsx
deleted file mode 100644
index 129b8f7..0000000
--- a/packages/ui-new/src/components/config-editor.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import type React from "react";
-import { useEffect, useState } from "react";
-import { type ZodType, z } from "zod";
-import { useSettingsStore } from "@/models/settings";
-import type { LauncherConfig } from "@/types";
-import { Button } from "./ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "./ui/dialog";
-import { FieldError } from "./ui/field";
-import { Spinner } from "./ui/spinner";
-import { Textarea } from "./ui/textarea";
-
-const launcherConfigSchema: ZodType<LauncherConfig> = z.object({
- minMemory: z.number(),
- maxMemory: z.number(),
- javaPath: z.string(),
- width: z.number(),
- height: z.number(),
- downloadThreads: z.number(),
- customBackgroundPath: z.string().nullable(),
- enableGpuAcceleration: z.boolean(),
- enableVisualEffects: z.boolean(),
- activeEffect: z.string(),
- theme: z.string(),
- logUploadService: z.string(),
- pastebinApiKey: z.string().nullable(),
- assistant: z.any(), // TODO: AssistantConfig schema
- useSharedCaches: z.boolean(),
- keepLegacyPerInstanceStorage: z.boolean(),
- featureFlags: z.any(), // TODO: FeatureFlags schema
-});
-
-export interface ConfigEditorProps
- extends Omit<React.ComponentPropsWithoutRef<typeof Dialog>, "onOpenChange"> {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function ConfigEditor({ onOpenChange, ...props }: ConfigEditorProps) {
- const settings = useSettingsStore();
-
- const [errorMessage, setErrorMessage] = useState<string | null>(null);
- const [rawConfigContent, setRawConfigContent] = useState(
- JSON.stringify(settings.config, null, 2),
- );
- const [isSaving, setIsSaving] = useState(false);
-
- useEffect(() => {
- setRawConfigContent(JSON.stringify(settings.config, null, 2));
- }, [settings.config]);
-
- const handleSave = async () => {
- setIsSaving(true);
- setErrorMessage(null);
- try {
- const validatedConfig = launcherConfigSchema.parse(
- JSON.parse(rawConfigContent),
- );
- settings.config = validatedConfig;
- await settings.save();
- onOpenChange?.(false);
- } catch (error) {
- setErrorMessage(error instanceof Error ? error.message : String(error));
- } finally {
- setIsSaving(false);
- }
- };
-
- return (
- <Dialog onOpenChange={onOpenChange} {...props}>
- <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden">
- <DialogHeader>
- <DialogTitle>Edit Configuration</DialogTitle>
- <DialogDescription>
- Edit the raw JSON configuration file.
- </DialogDescription>
- </DialogHeader>
-
- <Textarea
- value={rawConfigContent}
- onChange={(e) => setRawConfigContent(e.target.value)}
- className="font-mono text-sm h-100 resize-none"
- spellCheck={false}
- aria-invalid={!!errorMessage}
- />
-
- {errorMessage && <FieldError errors={[{ message: errorMessage }]} />}
-
- <DialogFooter>
- <Button
- variant="outline"
- onClick={() => onOpenChange?.(false)}
- disabled={isSaving}
- >
- Cancel
- </Button>
- <Button onClick={handleSave} disabled={isSaving}>
- {isSaving && <Spinner />}
- Save Changes
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
diff --git a/packages/ui-new/src/components/download-monitor.tsx b/packages/ui-new/src/components/download-monitor.tsx
deleted file mode 100644
index f3902d9..0000000
--- a/packages/ui-new/src/components/download-monitor.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { X } from "lucide-react";
-import { useState } from "react";
-
-export function DownloadMonitor() {
- const [isVisible, setIsVisible] = useState(true);
-
- if (!isVisible) return null;
-
- return (
- <div className="bg-zinc-900/80 backdrop-blur-md border border-zinc-700 rounded-lg shadow-2xl overflow-hidden">
- {/* Header */}
- <div className="flex items-center justify-between px-4 py-3 bg-zinc-800/50 border-b border-zinc-700">
- <div className="flex items-center gap-2">
- <div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
- <span className="text-sm font-medium text-white">Downloads</span>
- </div>
- <button
- type="button"
- onClick={() => setIsVisible(false)}
- className="text-zinc-400 hover:text-white transition-colors p-1"
- >
- <X size={16} />
- </button>
- </div>
-
- {/* Content */}
- <div className="p-4">
- <div className="space-y-3">
- {/* Download Item */}
- <div className="space-y-1">
- <div className="flex justify-between text-xs">
- <span className="text-zinc-300">Minecraft 1.20.4</span>
- <span className="text-zinc-400">65%</span>
- </div>
- <div className="h-1.5 bg-zinc-800 rounded-full overflow-hidden">
- <div
- className="h-full bg-emerald-500 rounded-full transition-all duration-300"
- style={{ width: "65%" }}
- ></div>
- </div>
- <div className="flex justify-between text-[10px] text-zinc-500">
- <span>142 MB / 218 MB</span>
- <span>2.1 MB/s • 36s remaining</span>
- </div>
- </div>
-
- {/* Download Item */}
- <div className="space-y-1">
- <div className="flex justify-between text-xs">
- <span className="text-zinc-300">Java 17</span>
- <span className="text-zinc-400">100%</span>
- </div>
- <div className="h-1.5 bg-zinc-800 rounded-full overflow-hidden">
- <div className="h-full bg-emerald-500 rounded-full"></div>
- </div>
- <div className="text-[10px] text-emerald-400">Completed</div>
- </div>
- </div>
- </div>
- </div>
- );
-}
diff --git a/packages/ui-new/src/components/game-console.tsx b/packages/ui-new/src/components/game-console.tsx
deleted file mode 100644
index 6980c8c..0000000
--- a/packages/ui-new/src/components/game-console.tsx
+++ /dev/null
@@ -1,290 +0,0 @@
-import { Copy, Download, Filter, Search, Trash2, X } from "lucide-react";
-import { useEffect, useRef, useState } from "react";
-import { useLogsStore } from "@/stores/logs-store";
-import { useUIStore } from "@/stores/ui-store";
-
-export function GameConsole() {
- const uiStore = useUIStore();
- const logsStore = useLogsStore();
-
- const [searchTerm, setSearchTerm] = useState("");
- const [selectedLevels, setSelectedLevels] = useState<Set<string>>(
- new Set(["info", "warn", "error", "debug", "fatal"]),
- );
- const [autoScroll, setAutoScroll] = useState(true);
- const consoleEndRef = useRef<HTMLDivElement>(null);
- const logsContainerRef = useRef<HTMLDivElement>(null);
-
- const levelColors: Record<string, string> = {
- info: "text-blue-400",
- warn: "text-amber-400",
- error: "text-red-400",
- debug: "text-purple-400",
- fatal: "text-rose-400",
- };
-
- const levelBgColors: Record<string, string> = {
- info: "bg-blue-400/10",
- warn: "bg-amber-400/10",
- error: "bg-red-400/10",
- debug: "bg-purple-400/10",
- fatal: "bg-rose-400/10",
- };
-
- // Filter logs based on search term and selected levels
- const filteredLogs = logsStore.logs.filter((log) => {
- const matchesSearch =
- searchTerm === "" ||
- log.message.toLowerCase().includes(searchTerm.toLowerCase()) ||
- log.source.toLowerCase().includes(searchTerm.toLowerCase());
-
- const matchesLevel = selectedLevels.has(log.level);
-
- return matchesSearch && matchesLevel;
- });
-
- // Auto-scroll to bottom when new logs arrive or autoScroll is enabled
- useEffect(() => {
- if (autoScroll && consoleEndRef.current && filteredLogs.length > 0) {
- consoleEndRef.current.scrollIntoView({ behavior: "smooth" });
- }
- }, [filteredLogs, autoScroll]);
-
- // Handle keyboard shortcuts
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- // Ctrl/Cmd + K to focus search
- if ((e.ctrlKey || e.metaKey) && e.key === "k") {
- e.preventDefault();
- // Focus search input
- const searchInput = document.querySelector(
- 'input[type="text"]',
- ) as HTMLInputElement;
- if (searchInput) searchInput.focus();
- }
- // Escape to close console
- if (e.key === "Escape") {
- uiStore.toggleConsole();
- }
- };
-
- window.addEventListener("keydown", handleKeyDown);
- return () => window.removeEventListener("keydown", handleKeyDown);
- }, [uiStore.toggleConsole]);
-
- const toggleLevel = (level: string) => {
- const newLevels = new Set(selectedLevels);
- if (newLevels.has(level)) {
- newLevels.delete(level);
- } else {
- newLevels.add(level);
- }
- setSelectedLevels(newLevels);
- };
-
- const handleCopyAll = () => {
- const logsText = logsStore.exportLogs(filteredLogs);
- navigator.clipboard.writeText(logsText);
- };
-
- const handleExport = () => {
- const logsText = logsStore.exportLogs(filteredLogs);
- const blob = new Blob([logsText], { type: "text/plain" });
- const url = URL.createObjectURL(blob);
- const a = document.createElement("a");
- a.href = url;
- a.download = `dropout_logs_${new Date().toISOString().replace(/[:.]/g, "-")}.txt`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- };
-
- const handleClear = () => {
- logsStore.clear();
- };
-
- return (
- <>
- {/* Header */}
- <div className="flex items-center justify-between p-4 border-b border-zinc-700 bg-[#252526]">
- <div className="flex items-center gap-3">
- <h2 className="text-lg font-bold text-white">Game Console</h2>
- <div className="flex items-center gap-1">
- <span className="text-xs text-zinc-400">Logs:</span>
- <span className="text-xs font-medium text-emerald-400">
- {filteredLogs.length}
- </span>
- <span className="text-xs text-zinc-400">/</span>
- <span className="text-xs text-zinc-400">
- {logsStore.logs.length}
- </span>
- </div>
- </div>
- <button
- type="button"
- onClick={() => uiStore.toggleConsole()}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- >
- <X size={20} />
- </button>
- </div>
-
- {/* Toolbar */}
- <div className="flex items-center gap-3 p-3 border-b border-zinc-700 bg-[#2D2D30]">
- {/* Search */}
- <div className="relative flex-1">
- <Search
- className="absolute left-3 top-1/2 transform -translate-y-1/2 text-zinc-500"
- size={16}
- />
- <input
- type="text"
- value={searchTerm}
- onChange={(e) => setSearchTerm(e.target.value)}
- placeholder="Search logs..."
- className="w-full pl-10 pr-4 py-2 bg-[#3E3E42] border border-zinc-600 rounded text-sm text-white placeholder:text-zinc-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
- />
- {searchTerm && (
- <button
- type="button"
- onClick={() => setSearchTerm("")}
- className="absolute right-3 top-1/2 transform -translate-y-1/2 text-zinc-400 hover:text-white"
- >
- ×
- </button>
- )}
- </div>
-
- {/* Level Filters */}
- <div className="flex items-center gap-1">
- {Object.entries(levelColors).map(([level, colorClass]) => (
- <button
- type="button"
- key={level}
- onClick={() => toggleLevel(level)}
- className={`px-3 py-1.5 text-xs font-medium rounded transition-colors ${
- selectedLevels.has(level)
- ? `${levelBgColors[level]} ${colorClass}`
- : "bg-[#3E3E42] text-zinc-400 hover:text-white"
- }`}
- >
- {level.toUpperCase()}
- </button>
- ))}
- </div>
-
- {/* Actions */}
- <div className="flex items-center gap-1">
- <button
- type="button"
- onClick={handleCopyAll}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- title="Copy all logs"
- >
- <Copy size={16} />
- </button>
- <button
- type="button"
- onClick={handleExport}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- title="Export logs"
- >
- <Download size={16} />
- </button>
- <button
- type="button"
- onClick={handleClear}
- className="p-2 text-zinc-400 hover:text-white transition-colors"
- title="Clear logs"
- >
- <Trash2 size={16} />
- </button>
- </div>
-
- {/* Auto-scroll Toggle */}
- <div className="flex items-center gap-2 pl-2 border-l border-zinc-700">
- <label className="inline-flex items-center cursor-pointer">
- <input
- type="checkbox"
- checked={autoScroll}
- onChange={(e) => setAutoScroll(e.target.checked)}
- className="sr-only peer"
- />
- <div className="relative w-9 h-5 bg-zinc-700 peer-focus:outline-none peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-0.5 after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"></div>
- <span className="ml-2 text-xs text-zinc-400">Auto-scroll</span>
- </label>
- </div>
- </div>
-
- {/* Logs Container */}
- <div
- ref={logsContainerRef}
- className="flex-1 overflow-y-auto font-mono text-sm bg-[#1E1E1E]"
- style={{ fontFamily: "'Cascadia Code', 'Consolas', monospace" }}
- >
- {filteredLogs.length === 0 ? (
- <div className="flex items-center justify-center h-full">
- <div className="text-center text-zinc-500">
- <Filter className="mx-auto mb-2" size={24} />
- <p>No logs match the current filters</p>
- </div>
- </div>
- ) : (
- <div className="p-4 space-y-1">
- {filteredLogs.map((log) => (
- <div
- key={log.id}
- className="group hover:bg-white/5 p-2 rounded transition-colors"
- >
- <div className="flex items-start gap-3">
- <div
- className={`px-2 py-0.5 rounded text-xs font-medium ${levelBgColors[log.level]} ${levelColors[log.level]}`}
- >
- {log.level.toUpperCase()}
- </div>
- <div className="text-zinc-400 text-xs shrink-0">
- {log.timestamp}
- </div>
- <div className="text-amber-300 text-xs shrink-0">
- [{log.source}]
- </div>
- <div className="text-gray-300 flex-1">{log.message}</div>
- </div>
- </div>
- ))}
- <div ref={consoleEndRef} />
- </div>
- )}
- </div>
-
- {/* Footer */}
- <div className="flex items-center justify-between p-3 border-t border-zinc-700 bg-[#252526] text-xs text-zinc-400">
- <div className="flex items-center gap-4">
- <div>
- <span>Total: </span>
- <span className="text-white">{logsStore.logs.length}</span>
- <span> | Filtered: </span>
- <span className="text-emerald-400">{filteredLogs.length}</span>
- </div>
- <div className="flex items-center gap-2">
- <kbd className="px-1.5 py-0.5 bg-[#3E3E42] rounded text-xs">
- Ctrl+K
- </kbd>
- <span>to search</span>
- </div>
- </div>
- <div>
- <span>Updated: </span>
- <span>
- {new Date().toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- })}
- </span>
- </div>
- </div>
- </>
- );
-}
diff --git a/packages/ui-new/src/components/instance-creation-modal.tsx b/packages/ui-new/src/components/instance-creation-modal.tsx
deleted file mode 100644
index 8a2b1b4..0000000
--- a/packages/ui-new/src/components/instance-creation-modal.tsx
+++ /dev/null
@@ -1,552 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { Loader2, Search } from "lucide-react";
-import { useCallback, useEffect, useMemo, useState } from "react";
-import { toast } from "sonner";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} 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 type { Version } from "@/types/bindings/manifest";
-import type { FabricLoaderEntry } from "../types/bindings/fabric";
-import type { ForgeVersion as ForgeVersionEntry } from "../types/bindings/forge";
-import type { Instance } from "../types/bindings/instance";
-
-interface Props {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function InstanceCreationModal({ open, onOpenChange }: Props) {
- const gameStore = useGameStore();
- const instancesStore = useInstancesStore();
-
- // Steps: 1 = name, 2 = version, 3 = mod loader
- const [step, setStep] = useState<number>(1);
-
- // Step 1
- const [instanceName, setInstanceName] = useState<string>("");
-
- // Step 2
- const [versionSearch, setVersionSearch] = useState<string>("");
- const [versionFilter, setVersionFilter] = useState<
- "all" | "release" | "snapshot"
- >("release");
- const [selectedVersionUI, setSelectedVersionUI] = useState<Version | null>(
- null,
- );
-
- // Step 3
- const [modLoaderType, setModLoaderType] = useState<
- "vanilla" | "fabric" | "forge"
- >("vanilla");
- const [fabricLoaders, setFabricLoaders] = useState<FabricLoaderEntry[]>([]);
- const [forgeVersions, setForgeVersions] = useState<ForgeVersionEntry[]>([]);
- const [selectedFabricLoader, setSelectedFabricLoader] = useState<string>("");
- const [selectedForgeLoader, setSelectedForgeLoader] = useState<string>("");
- const [loadingLoaders, setLoadingLoaders] = useState(false);
-
- const loadModLoaders = useCallback(async () => {
- if (!selectedVersionUI) return;
- setLoadingLoaders(true);
- setFabricLoaders([]);
- setForgeVersions([]);
- try {
- if (modLoaderType === "fabric") {
- const loaders = await invoke<FabricLoaderEntry[]>(
- "get_fabric_loaders_for_version",
- {
- gameVersion: selectedVersionUI.id,
- },
- );
- setFabricLoaders(loaders || []);
- if (loaders && loaders.length > 0) {
- setSelectedFabricLoader(loaders[0].loader.version);
- } else {
- setSelectedFabricLoader("");
- }
- } else if (modLoaderType === "forge") {
- const versions = await invoke<ForgeVersionEntry[]>(
- "get_forge_versions_for_game",
- {
- gameVersion: selectedVersionUI.id,
- },
- );
- setForgeVersions(versions || []);
- if (versions && versions.length > 0) {
- // Binding `ForgeVersion` uses `version` (not `id`) — use `.version` here.
- setSelectedForgeLoader(versions[0].version);
- } else {
- setSelectedForgeLoader("");
- }
- }
- } catch (e) {
- console.error("Failed to load mod loaders:", e);
- toast.error("Failed to fetch mod loader versions");
- } finally {
- setLoadingLoaders(false);
- }
- }, [modLoaderType, selectedVersionUI]);
-
- // When entering step 3 and a base version exists, fetch loaders if needed
- useEffect(() => {
- if (step === 3 && modLoaderType !== "vanilla" && selectedVersionUI) {
- loadModLoaders();
- }
- }, [step, modLoaderType, selectedVersionUI, loadModLoaders]);
-
- // Creating state
- const [creating, setCreating] = useState(false);
- const [errorMessage, setErrorMessage] = useState<string>("");
-
- // Derived filtered versions
- const filteredVersions = useMemo(() => {
- const all = gameStore.versions || [];
- let list = all.slice();
- if (versionFilter !== "all") {
- list = list.filter((v) => v.type === versionFilter);
- }
- if (versionSearch.trim()) {
- const q = versionSearch.trim().toLowerCase().replace(/。/g, ".");
- list = list.filter((v) => v.id.toLowerCase().includes(q));
- }
- return list;
- }, [gameStore.versions, versionFilter, versionSearch]);
-
- // Reset when opened/closed
- useEffect(() => {
- if (open) {
- // ensure versions are loaded
- gameStore.loadVersions();
- setStep(1);
- setInstanceName("");
- setVersionSearch("");
- setVersionFilter("release");
- setSelectedVersionUI(null);
- setModLoaderType("vanilla");
- setFabricLoaders([]);
- setForgeVersions([]);
- setSelectedFabricLoader("");
- setSelectedForgeLoader("");
- setErrorMessage("");
- setCreating(false);
- }
- }, [open, gameStore.loadVersions]);
-
- function validateStep1(): boolean {
- if (!instanceName.trim()) {
- setErrorMessage("Please enter an instance name");
- return false;
- }
- setErrorMessage("");
- return true;
- }
-
- function validateStep2(): boolean {
- if (!selectedVersionUI) {
- setErrorMessage("Please select a Minecraft version");
- return false;
- }
- setErrorMessage("");
- return true;
- }
-
- async function handleNext() {
- setErrorMessage("");
- if (step === 1) {
- if (!validateStep1()) return;
- setStep(2);
- } else if (step === 2) {
- if (!validateStep2()) return;
- setStep(3);
- }
- }
-
- function handleBack() {
- setErrorMessage("");
- setStep((s) => Math.max(1, s - 1));
- }
-
- async function handleCreate() {
- if (!validateStep1() || !validateStep2()) return;
- setCreating(true);
- setErrorMessage("");
-
- try {
- // Step 1: create instance
- const instance = await invoke<Instance>("create_instance", {
- name: instanceName.trim(),
- });
-
- // If selectedVersion provided, install it
- if (selectedVersionUI) {
- try {
- await invoke("install_version", {
- instanceId: instance.id,
- versionId: selectedVersionUI.id,
- });
- } catch (err) {
- console.error("Failed to install base version:", err);
- // continue - instance created but version install failed
- toast.error(
- `Failed to install version ${selectedVersionUI.id}: ${String(err)}`,
- );
- }
- }
-
- // If mod loader selected, install it
- if (modLoaderType === "fabric" && selectedFabricLoader) {
- try {
- await invoke("install_fabric", {
- instanceId: instance.id,
- gameVersion: selectedVersionUI?.id ?? "",
- loaderVersion: selectedFabricLoader,
- });
- } catch (err) {
- console.error("Failed to install Fabric:", err);
- toast.error(`Failed to install Fabric: ${String(err)}`);
- }
- } else if (modLoaderType === "forge" && selectedForgeLoader) {
- try {
- await invoke("install_forge", {
- instanceId: instance.id,
- gameVersion: selectedVersionUI?.id ?? "",
- installerVersion: selectedForgeLoader,
- });
- } catch (err) {
- console.error("Failed to install Forge:", err);
- toast.error(`Failed to install Forge: ${String(err)}`);
- }
- }
-
- // Refresh instances list
- await instancesStore.refresh();
-
- toast.success("Instance created successfully");
- onOpenChange(false);
- } catch (e) {
- console.error("Failed to create instance:", e);
- setErrorMessage(String(e));
- toast.error(`Failed to create instance: ${e}`);
- } finally {
- setCreating(false);
- }
- }
-
- // UI pieces
- const StepIndicator = () => (
- <div className="flex gap-2 w-full">
- <div
- className={`flex-1 h-1 rounded ${step >= 1 ? "bg-indigo-500" : "bg-zinc-700"}`}
- />
- <div
- className={`flex-1 h-1 rounded ${step >= 2 ? "bg-indigo-500" : "bg-zinc-700"}`}
- />
- <div
- className={`flex-1 h-1 rounded ${step >= 3 ? "bg-indigo-500" : "bg-zinc-700"}`}
- />
- </div>
- );
-
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="w-full max-w-3xl max-h-[90vh] overflow-hidden">
- <DialogHeader>
- <DialogTitle>Create New Instance</DialogTitle>
- <DialogDescription>
- Multi-step wizard — create an instance and optionally install a
- version or mod loader.
- </DialogDescription>
- </DialogHeader>
-
- <div className="px-6">
- <div className="pt-4 pb-6">
- <StepIndicator />
- </div>
-
- {/* Step 1 - Name */}
- {step === 1 && (
- <div className="space-y-4">
- <div>
- <label
- htmlFor="instance-name"
- className="block text-sm font-medium mb-2"
- >
- Instance Name
- </label>
- <Input
- id="instance-name"
- placeholder="My Minecraft Instance"
- value={instanceName}
- onChange={(e) => setInstanceName(e.target.value)}
- disabled={creating}
- />
- </div>
- <p className="text-xs text-muted-foreground">
- Give your instance a memorable name.
- </p>
- </div>
- )}
-
- {/* Step 2 - Version selection */}
- {step === 2 && (
- <div className="space-y-4">
- <div className="flex gap-3">
- <div className="relative flex-1">
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
- <Input
- value={versionSearch}
- onChange={(e) => setVersionSearch(e.target.value)}
- placeholder="Search versions..."
- className="pl-9"
- />
- </div>
-
- <div className="flex gap-2">
- <Button
- type="button"
- variant={versionFilter === "all" ? "default" : "outline"}
- onClick={() => setVersionFilter("all")}
- >
- All
- </Button>
- <Button
- type="button"
- variant={
- versionFilter === "release" ? "default" : "outline"
- }
- onClick={() => setVersionFilter("release")}
- >
- Release
- </Button>
- <Button
- type="button"
- variant={
- versionFilter === "snapshot" ? "default" : "outline"
- }
- onClick={() => setVersionFilter("snapshot")}
- >
- Snapshot
- </Button>
- </div>
- </div>
-
- <ScrollArea className="max-h-[36vh]">
- <div className="space-y-2 py-2">
- {gameStore.versions.length === 0 ? (
- <div className="flex items-center justify-center py-8 text-muted-foreground">
- <Loader2 className="animate-spin mr-2" />
- Loading versions...
- </div>
- ) : filteredVersions.length === 0 ? (
- <div className="text-center py-8 text-muted-foreground">
- No matching versions found
- </div>
- ) : (
- filteredVersions.map((v) => {
- const isSelected = selectedVersionUI?.id === v.id;
- return (
- <button
- key={v.id}
- type="button"
- onClick={() => setSelectedVersionUI(v)}
- className={`w-full text-left p-3 rounded-lg border transition-colors ${
- isSelected
- ? "bg-indigo-50 dark:bg-indigo-600/20 border-indigo-200"
- : "bg-white/40 dark:bg-white/5 border-black/5 dark:border-white/5 hover:bg-white/60"
- }`}
- >
- <div className="flex items-center justify-between">
- <div>
- <div className="font-mono font-bold">{v.id}</div>
- <div className="text-xs text-muted-foreground mt-1">
- {v.type}{" "}
- {v.releaseTime
- ? ` • ${new Date(v.releaseTime).toLocaleDateString()}`
- : ""}
- </div>
- </div>
- {v.javaVersion && (
- <div className="text-sm">
- Java {v.javaVersion}
- </div>
- )}
- </div>
- </button>
- );
- })
- )}
- </div>
- </ScrollArea>
- </div>
- )}
-
- {/* Step 3 - Mod loader */}
- {step === 3 && (
- <div className="space-y-4">
- <div>
- <div className="text-sm font-medium mb-2">Mod Loader Type</div>
- <div className="flex gap-3">
- <Button
- type="button"
- variant={
- modLoaderType === "vanilla" ? "default" : "outline"
- }
- onClick={() => setModLoaderType("vanilla")}
- >
- Vanilla
- </Button>
- <Button
- type="button"
- variant={modLoaderType === "fabric" ? "default" : "outline"}
- onClick={() => setModLoaderType("fabric")}
- >
- Fabric
- </Button>
- <Button
- type="button"
- variant={modLoaderType === "forge" ? "default" : "outline"}
- onClick={() => setModLoaderType("forge")}
- >
- Forge
- </Button>
- </div>
- </div>
-
- {modLoaderType === "fabric" && (
- <div>
- {loadingLoaders ? (
- <div className="flex items-center gap-2">
- <Loader2 className="animate-spin" />
- Loading Fabric versions...
- </div>
- ) : fabricLoaders.length > 0 ? (
- <div className="space-y-2">
- <select
- value={selectedFabricLoader}
- onChange={(e) =>
- setSelectedFabricLoader(e.target.value)
- }
- className="w-full px-3 py-2 rounded border bg-transparent"
- >
- {fabricLoaders.map((f) => (
- <option
- key={f.loader.version}
- value={f.loader.version}
- >
- {f.loader.version}{" "}
- {f.loader.stable ? "(Stable)" : "(Beta)"}
- </option>
- ))}
- </select>
- </div>
- ) : (
- <p className="text-sm text-muted-foreground">
- No Fabric loaders available for this version
- </p>
- )}
- </div>
- )}
-
- {modLoaderType === "forge" && (
- <div>
- {loadingLoaders ? (
- <div className="flex items-center gap-2">
- <Loader2 className="animate-spin" />
- Loading Forge versions...
- </div>
- ) : forgeVersions.length > 0 ? (
- <div className="space-y-2">
- <select
- value={selectedForgeLoader}
- onChange={(e) => setSelectedForgeLoader(e.target.value)}
- className="w-full px-3 py-2 rounded border bg-transparent"
- >
- {forgeVersions.map((f) => (
- // binding ForgeVersion uses `version` as the identifier
- <option key={f.version} value={f.version}>
- {f.version}
- </option>
- ))}
- </select>
- </div>
- ) : (
- <p className="text-sm text-muted-foreground">
- No Forge versions available for this version
- </p>
- )}
- </div>
- )}
- </div>
- )}
-
- {errorMessage && (
- <div className="text-sm text-red-400 mt-3">{errorMessage}</div>
- )}
- </div>
-
- <DialogFooter>
- <div className="w-full flex justify-between items-center">
- <div>
- <Button
- type="button"
- variant="ghost"
- onClick={() => {
- // cancel
- onOpenChange(false);
- }}
- disabled={creating}
- >
- Cancel
- </Button>
- </div>
-
- <div className="flex gap-2">
- {step > 1 && (
- <Button
- type="button"
- variant="outline"
- onClick={handleBack}
- disabled={creating}
- >
- Back
- </Button>
- )}
-
- {step < 3 ? (
- <Button type="button" onClick={handleNext} disabled={creating}>
- Next
- </Button>
- ) : (
- <Button
- type="button"
- onClick={handleCreate}
- disabled={creating}
- >
- {creating ? (
- <>
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
- Creating...
- </>
- ) : (
- "Create"
- )}
- </Button>
- )}
- </div>
- </div>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
-
-export default InstanceCreationModal;
diff --git a/packages/ui-new/src/components/instance-editor-modal.tsx b/packages/ui-new/src/components/instance-editor-modal.tsx
deleted file mode 100644
index f880c20..0000000
--- a/packages/ui-new/src/components/instance-editor-modal.tsx
+++ /dev/null
@@ -1,548 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { Folder, Loader2, Save, Trash2, X } from "lucide-react";
-import { useCallback, useEffect, useState } from "react";
-import { toast } from "sonner";
-
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { Textarea } from "@/components/ui/textarea";
-
-import { toNumber } from "@/lib/tsrs-utils";
-import { useInstancesStore } from "@/models/instances";
-import { useSettingsStore } from "@/models/settings";
-import type { FileInfo } from "../types/bindings/core";
-import type { Instance } from "../types/bindings/instance";
-
-type Props = {
- open: boolean;
- instance: Instance | null;
- onOpenChange: (open: boolean) => void;
-};
-
-export function InstanceEditorModal({ open, instance, onOpenChange }: Props) {
- const instancesStore = useInstancesStore();
- const { config } = useSettingsStore();
-
- const [activeTab, setActiveTab] = useState<
- "info" | "version" | "files" | "settings"
- >("info");
- const [saving, setSaving] = useState(false);
- const [errorMessage, setErrorMessage] = useState("");
-
- // Info tab fields
- const [editName, setEditName] = useState("");
- const [editNotes, setEditNotes] = useState("");
-
- // Files tab state
- const [selectedFileFolder, setSelectedFileFolder] = useState<
- "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots"
- >("mods");
- const [fileList, setFileList] = useState<FileInfo[]>([]);
- const [loadingFiles, setLoadingFiles] = useState(false);
- const [deletingPath, setDeletingPath] = useState<string | null>(null);
-
- // Version tab state (placeholder - the Svelte implementation used a ModLoaderSelector component)
- // React versions-view/instance-creation handle mod loader installs; here we show basic current info.
-
- // Settings tab fields
- const [editMemoryMin, setEditMemoryMin] = useState<number>(0);
- const [editMemoryMax, setEditMemoryMax] = useState<number>(0);
- const [editJavaArgs, setEditJavaArgs] = useState<string>("");
-
- // initialize when open & instance changes
- useEffect(() => {
- if (open && instance) {
- setActiveTab("info");
- setSaving(false);
- setErrorMessage("");
- setEditName(instance.name || "");
- setEditNotes(instance.notes ?? "");
- setEditMemoryMin(
- (instance.memoryOverride && toNumber(instance.memoryOverride.min)) ??
- config?.minMemory ??
- 512,
- );
- setEditMemoryMax(
- (instance.memoryOverride && toNumber(instance.memoryOverride.max)) ??
- config?.maxMemory ??
- 2048,
- );
- setEditJavaArgs(instance.jvmArgsOverride ?? "");
- setFileList([]);
- setSelectedFileFolder("mods");
- }
- }, [open, instance, config?.minMemory, config?.maxMemory]);
-
- // load files when switching to files tab
- const loadFileList = useCallback(
- async (
- folder:
- | "mods"
- | "resourcepacks"
- | "shaderpacks"
- | "saves"
- | "screenshots",
- ) => {
- if (!instance) return;
- setLoadingFiles(true);
- try {
- const files = await invoke<FileInfo[]>("list_instance_directory", {
- instanceId: instance.id,
- folder,
- });
- setFileList(files || []);
- } catch (err) {
- console.error("Failed to load files:", err);
- toast.error("Failed to load files: " + String(err));
- setFileList([]);
- } finally {
- setLoadingFiles(false);
- }
- },
- [instance],
- );
-
- useEffect(() => {
- if (open && instance && activeTab === "files") {
- // explicitly pass the selected folder so loadFileList doesn't rely on stale closures
- loadFileList(selectedFileFolder);
- }
- }, [activeTab, open, instance, selectedFileFolder, loadFileList]);
-
- async function changeFolder(
- folder: "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots",
- ) {
- setSelectedFileFolder(folder);
- // reload the list for the newly selected folder
- if (open && instance) await loadFileList(folder);
- }
-
- async function deleteFile(filePath: string) {
- if (
- !confirm(
- `Are you sure you want to delete "${filePath.split("/").pop()}"?`,
- )
- ) {
- return;
- }
- setDeletingPath(filePath);
- try {
- await invoke("delete_instance_file", { path: filePath });
- // refresh the currently selected folder
- await loadFileList(selectedFileFolder);
- toast.success("Deleted");
- } catch (err) {
- console.error("Failed to delete file:", err);
- toast.error("Failed to delete file: " + String(err));
- } finally {
- setDeletingPath(null);
- }
- }
-
- async function openInExplorer(filePath: string) {
- try {
- await invoke("open_file_explorer", { path: filePath });
- } catch (err) {
- console.error("Failed to open in explorer:", err);
- toast.error("Failed to open file explorer: " + String(err));
- }
- }
-
- async function saveChanges() {
- if (!instance) return;
- if (!editName.trim()) {
- setErrorMessage("Instance name cannot be empty");
- return;
- }
- setSaving(true);
- setErrorMessage("");
- try {
- // Build updated instance shape compatible with backend
- const updatedInstance: Instance = {
- ...instance,
- name: editName.trim(),
- // some bindings may use camelCase; set optional string fields to null when empty
- notes: editNotes.trim() ? editNotes.trim() : null,
- memoryOverride: {
- min: editMemoryMin,
- max: editMemoryMax,
- },
- jvmArgsOverride: editJavaArgs.trim() ? editJavaArgs.trim() : null,
- };
-
- await instancesStore.update(updatedInstance as Instance);
- toast.success("Instance saved");
- onOpenChange(false);
- } catch (err) {
- console.error("Failed to save instance:", err);
- setErrorMessage(String(err));
- toast.error("Failed to save instance: " + String(err));
- } finally {
- setSaving(false);
- }
- }
-
- function formatFileSize(bytesBig: FileInfo["size"]): string {
- const bytes = Number(bytesBig ?? 0);
- if (bytes === 0) return "0 B";
- const k = 1024;
- const sizes = ["B", "KB", "MB", "GB", "TB"];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;
- }
-
- function formatDate(
- tsBig?:
- | FileInfo["modified"]
- | Instance["createdAt"]
- | Instance["lastPlayed"],
- ) {
- if (tsBig === undefined || tsBig === null) return "";
- const n = toNumber(tsBig);
- // tsrs bindings often use seconds for createdAt/lastPlayed; if value looks like seconds use *1000
- const maybeMs = n > 1e12 ? n : n * 1000;
- return new Date(maybeMs).toLocaleDateString();
- }
-
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="w-full max-w-4xl max-h-[90vh] overflow-hidden">
- <DialogHeader>
- <div className="flex items-center justify-between gap-4">
- <div>
- <DialogTitle>Edit Instance</DialogTitle>
- <DialogDescription>{instance?.name ?? ""}</DialogDescription>
- </div>
- <div className="flex items-center gap-2">
- <button
- type="button"
- onClick={() => onOpenChange(false)}
- disabled={saving}
- className="p-2 rounded hover:bg-zinc-800 text-zinc-400"
- aria-label="Close"
- >
- <X />
- </button>
- </div>
- </div>
- </DialogHeader>
-
- {/* Tab Navigation */}
- <div className="flex gap-1 px-6 pt-2 border-b border-zinc-700">
- {[
- { id: "info", label: "Info" },
- { id: "version", label: "Version" },
- { id: "files", label: "Files" },
- { id: "settings", label: "Settings" },
- ].map((tab) => (
- <button
- type="button"
- key={tab.id}
- onClick={() =>
- setActiveTab(
- tab.id as "info" | "version" | "files" | "settings",
- )
- }
- className={`px-4 py-2 text-sm font-medium transition-colors rounded-t-lg ${
- activeTab === tab.id
- ? "bg-indigo-600 text-white"
- : "bg-zinc-800 text-zinc-400 hover:text-white"
- }`}
- >
- {tab.label}
- </button>
- ))}
- </div>
-
- {/* Content */}
- <div className="p-6 overflow-y-auto max-h-[60vh]">
- {activeTab === "info" && (
- <div className="space-y-4">
- <div>
- <label
- htmlFor="instance-name-edit"
- className="block text-sm font-medium mb-2"
- >
- Instance Name
- </label>
- <Input
- id="instance-name-edit"
- value={editName}
- onChange={(e) => setEditName(e.target.value)}
- disabled={saving}
- />
- </div>
-
- <div>
- <label
- htmlFor="instance-notes-edit"
- className="block text-sm font-medium mb-2"
- >
- Notes
- </label>
- <Textarea
- id="instance-notes-edit"
- value={editNotes}
- onChange={(e) => setEditNotes(e.target.value)}
- rows={4}
- disabled={saving}
- />
- </div>
-
- <div className="grid grid-cols-2 gap-4 text-sm">
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Created</p>
- <p className="text-white font-medium">
- {instance?.createdAt ? formatDate(instance.createdAt) : "-"}
- </p>
- </div>
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Last Played</p>
- <p className="text-white font-medium">
- {instance?.lastPlayed
- ? formatDate(instance.lastPlayed)
- : "Never"}
- </p>
- </div>
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Game Directory</p>
- <p
- className="text-white font-medium text-xs truncate"
- title={instance?.gameDir ?? ""}
- >
- {instance?.gameDir
- ? String(instance.gameDir).split("/").pop()
- : ""}
- </p>
- </div>
- <div className="p-3 bg-zinc-800 rounded-lg">
- <p className="text-zinc-400">Current Version</p>
- <p className="text-white font-medium">
- {instance?.versionId ?? "None"}
- </p>
- </div>
- </div>
- </div>
- )}
-
- {activeTab === "version" && (
- <div className="space-y-4">
- {instance?.versionId ? (
- <div className="p-4 bg-indigo-500/10 border border-indigo-500/30 rounded-lg">
- <p className="text-sm text-indigo-400">
- Currently playing:{" "}
- <span className="font-medium">{instance.versionId}</span>
- {instance.modLoader && (
- <>
- {" "}
- with{" "}
- <span className="capitalize">{instance.modLoader}</span>
- {instance.modLoaderVersion
- ? ` ${instance.modLoaderVersion}`
- : ""}
- </>
- )}
- </p>
- </div>
- ) : (
- <div className="text-sm text-zinc-400">
- No version selected for this instance
- </div>
- )}
-
- <div>
- <p className="text-sm font-medium mb-2">
- Change Version / Mod Loader
- </p>
- <p className="text-xs text-zinc-400">
- Use the Versions page to install new game versions or mod
- loaders, then set them here.
- </p>
- </div>
- </div>
- )}
-
- {activeTab === "files" && (
- <div className="space-y-4">
- <div className="flex gap-2 flex-wrap">
- {(
- [
- "mods",
- "resourcepacks",
- "shaderpacks",
- "saves",
- "screenshots",
- ] as const
- ).map((folder) => (
- <button
- type="button"
- key={folder}
- onClick={() => changeFolder(folder)}
- className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
- selectedFileFolder === folder
- ? "bg-indigo-600 text-white"
- : "bg-zinc-800 text-zinc-400 hover:text-white"
- }`}
- >
- {folder}
- </button>
- ))}
- </div>
-
- {loadingFiles ? (
- <div className="flex items-center gap-2 text-zinc-400 py-8 justify-center">
- <Loader2 className="animate-spin" />
- Loading files...
- </div>
- ) : fileList.length === 0 ? (
- <div className="text-center py-8 text-zinc-500">
- No files in this folder
- </div>
- ) : (
- <div className="space-y-2">
- {fileList.map((file) => (
- <div
- key={file.path}
- className="flex items-center justify-between p-3 bg-zinc-800 rounded-lg hover:bg-zinc-700 transition-colors"
- >
- <div className="flex-1 min-w-0">
- <p className="font-medium text-white truncate">
- {file.name}
- </p>
- <p className="text-xs text-zinc-400">
- {file.isDirectory
- ? "Folder"
- : formatFileSize(file.size)}{" "}
- • {formatDate(file.modified)}
- </p>
- </div>
- <div className="flex gap-2 ml-4">
- <button
- type="button"
- onClick={() => openInExplorer(file.path)}
- title="Open in explorer"
- className="p-2 rounded-lg hover:bg-zinc-600 text-zinc-400 hover:text-white transition-colors"
- >
- <Folder />
- </button>
- <button
- type="button"
- onClick={() => deleteFile(file.path)}
- disabled={deletingPath === file.path}
- title="Delete"
- className="p-2 rounded-lg hover:bg-red-600/20 text-red-400 hover:text-red-300 transition-colors disabled:opacity-50"
- >
- {deletingPath === file.path ? (
- <Loader2 className="animate-spin" />
- ) : (
- <Trash2 />
- )}
- </button>
- </div>
- </div>
- ))}
- </div>
- )}
- </div>
- )}
-
- {activeTab === "settings" && (
- <div className="space-y-4">
- <div>
- <label
- htmlFor="min-memory-edit"
- className="block text-sm font-medium mb-2"
- >
- Minimum Memory (MB)
- </label>
- <Input
- id="min-memory-edit"
- type="number"
- value={String(editMemoryMin)}
- onChange={(e) => setEditMemoryMin(Number(e.target.value))}
- disabled={saving}
- />
- <p className="text-xs text-zinc-400 mt-1">
- Default: {config?.minMemory} MB
- </p>
- </div>
-
- <div>
- <label
- htmlFor="max-memory-edit"
- className="block text-sm font-medium mb-2"
- >
- Maximum Memory (MB)
- </label>
- <Input
- id="max-memory-edit"
- type="number"
- value={String(editMemoryMax)}
- onChange={(e) => setEditMemoryMax(Number(e.target.value))}
- disabled={saving}
- />
- <p className="text-xs text-zinc-400 mt-1">
- Default: {config?.maxMemory} MB
- </p>
- </div>
-
- <div>
- <label
- htmlFor="jvm-args-edit"
- className="block text-sm font-medium mb-2"
- >
- JVM Arguments (Advanced)
- </label>
- <Textarea
- id="jvm-args-edit"
- value={editJavaArgs}
- onChange={(e) => setEditJavaArgs(e.target.value)}
- rows={4}
- disabled={saving}
- />
- </div>
- </div>
- )}
- </div>
-
- {errorMessage && (
- <div className="px-6 text-sm text-red-400">{errorMessage}</div>
- )}
-
- <DialogFooter>
- <div className="flex items-center justify-between w-full">
- <div />
- <div className="flex gap-2">
- <Button
- variant="outline"
- onClick={() => {
- onOpenChange(false);
- }}
- >
- Cancel
- </Button>
- <Button onClick={saveChanges} disabled={saving}>
- {saving ? (
- <Loader2 className="animate-spin mr-2" />
- ) : (
- <Save className="mr-2" />
- )}
- Save
- </Button>
- </div>
- </div>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
-
-export default InstanceEditorModal;
diff --git a/packages/ui-new/src/components/login-modal.tsx b/packages/ui-new/src/components/login-modal.tsx
deleted file mode 100644
index 49596da..0000000
--- a/packages/ui-new/src/components/login-modal.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import { Mail, User } from "lucide-react";
-import { useCallback, useState } from "react";
-import { toast } from "sonner";
-import { useAuthStore } from "@/models/auth";
-import { Button } from "./ui/button";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "./ui/dialog";
-import {
- Field,
- FieldDescription,
- FieldError,
- FieldGroup,
- FieldLabel,
-} from "./ui/field";
-import { Input } from "./ui/input";
-
-export interface LoginModalProps
- extends Omit<React.ComponentPropsWithoutRef<typeof Dialog>, "onOpenChange"> {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function LoginModal({ onOpenChange, ...props }: LoginModalProps) {
- const authStore = useAuthStore();
-
- const [offlineUsername, setOfflineUsername] = useState<string>("");
- const [errorMessage, setErrorMessage] = useState<string>("");
- const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
-
- const handleMicrosoftLogin = useCallback(async () => {
- setIsLoggingIn(true);
- authStore.setLoginMode("microsoft");
- try {
- await authStore.loginOnline(() => onOpenChange?.(false));
- } catch (error) {
- const err = error as Error;
- console.error("Failed to login with Microsoft:", err);
- setErrorMessage(err.message);
- } finally {
- setIsLoggingIn(false);
- }
- }, [authStore.loginOnline, authStore.setLoginMode, onOpenChange]);
-
- const handleOfflineLogin = useCallback(async () => {
- setIsLoggingIn(true);
- try {
- await authStore.loginOffline(offlineUsername);
- toast.success("Logged in offline successfully");
- onOpenChange?.(false);
- } catch (error) {
- const err = error as Error;
- console.error("Failed to login offline:", err);
- setErrorMessage(err.message);
- } finally {
- setIsLoggingIn(false);
- }
- }, [authStore, offlineUsername, onOpenChange]);
-
- return (
- <Dialog onOpenChange={onOpenChange} {...props}>
- <DialogContent className="md:max-w-md">
- <DialogHeader>
- <DialogTitle>Login</DialogTitle>
- <DialogDescription>
- Login to your Minecraft account or play offline
- </DialogDescription>
- </DialogHeader>
- <div className="p-4 w-full overflow-hidden">
- {!authStore.loginMode && (
- <div className="flex flex-col space-y-4">
- <Button size="lg" onClick={handleMicrosoftLogin}>
- <Mail />
- Login with Microsoft
- </Button>
- <Button
- variant="secondary"
- onClick={() => authStore.setLoginMode("offline")}
- size="lg"
- >
- <User />
- Login Offline
- </Button>
- </div>
- )}
- {authStore.loginMode === "microsoft" && (
- <div className="flex flex-col space-y-4">
- <button
- type="button"
- className="text-4xl font-bold text-center bg-accent p-4 cursor-pointer"
- onClick={() => {
- if (authStore.deviceCode?.userCode) {
- navigator.clipboard?.writeText(
- authStore.deviceCode?.userCode,
- );
- toast.success("Copied to clipboard");
- }
- }}
- >
- {authStore.deviceCode?.userCode}
- </button>
- <span className="text-muted-foreground w-full overflow-hidden text-ellipsis">
- To sign in, use a web browser to open the page{" "}
- <a href={authStore.deviceCode?.verificationUri}>
- {authStore.deviceCode?.verificationUri}
- </a>{" "}
- and enter the code{" "}
- <code
- className="font-semibold cursor-pointer"
- onClick={() => {
- if (authStore.deviceCode?.userCode) {
- navigator.clipboard?.writeText(
- authStore.deviceCode?.userCode,
- );
- }
- }}
- onKeyDown={() => {
- if (authStore.deviceCode?.userCode) {
- navigator.clipboard?.writeText(
- authStore.deviceCode?.userCode,
- );
- }
- }}
- >
- {authStore.deviceCode?.userCode}
- </code>{" "}
- to authenticate, this code will be expired in{" "}
- {authStore.deviceCode?.expiresIn} seconds.
- </span>
- <FieldError>{errorMessage}</FieldError>
- </div>
- )}
- {authStore.loginMode === "offline" && (
- <FieldGroup>
- <Field>
- <FieldLabel>Username</FieldLabel>
- <FieldDescription>
- Enter a username to play offline
- </FieldDescription>
- <Input
- value={offlineUsername}
- onChange={(e) => {
- setOfflineUsername(e.target.value);
- setErrorMessage("");
- }}
- aria-invalid={!!errorMessage}
- />
- <FieldError>{errorMessage}</FieldError>
- </Field>
- </FieldGroup>
- )}
- </div>
- <DialogFooter>
- <div className="flex flex-col justify-center items-center">
- <span className="text-xs text-muted-foreground ">
- {authStore.statusMessage}
- </span>
- </div>
- <Button
- variant="outline"
- onClick={() => {
- if (authStore.loginMode) {
- if (authStore.loginMode === "microsoft") {
- authStore.cancelLoginOnline();
- }
- authStore.setLoginMode(null);
- } else {
- onOpenChange?.(false);
- }
- }}
- >
- Cancel
- </Button>
- {authStore.loginMode === "offline" && (
- <Button onClick={handleOfflineLogin} disabled={isLoggingIn}>
- Login
- </Button>
- )}
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
diff --git a/packages/ui-new/src/components/particle-background.tsx b/packages/ui-new/src/components/particle-background.tsx
deleted file mode 100644
index 2e0b15a..0000000
--- a/packages/ui-new/src/components/particle-background.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { useEffect, useRef } from "react";
-import { SaturnEffect } from "../lib/effects/SaturnEffect";
-
-export function ParticleBackground() {
- const canvasRef = useRef<HTMLCanvasElement | null>(null);
- const effectRef = useRef<SaturnEffect | null>(null);
-
- useEffect(() => {
- const canvas = canvasRef.current;
- if (!canvas) return;
-
- // Instantiate SaturnEffect and attach to canvas
- let effect: SaturnEffect | null = null;
- try {
- effect = new SaturnEffect(canvas);
- effectRef.current = effect;
- } catch (err) {
- // If effect fails, silently degrade (keep background blank)
- // eslint-disable-next-line no-console
- console.warn("SaturnEffect initialization failed:", err);
- }
-
- const resizeHandler = () => {
- if (effectRef.current) {
- try {
- effectRef.current.resize(window.innerWidth, window.innerHeight);
- } catch {
- // ignore
- }
- }
- };
-
- window.addEventListener("resize", resizeHandler);
-
- // Expose getter for HomeView interactions (getSaturnEffect)
- // HomeView will call window.getSaturnEffect()?.handleMouseDown/Move/Up
- (
- window as unknown as { getSaturnEffect?: () => SaturnEffect | null }
- ).getSaturnEffect = () => effectRef.current;
-
- return () => {
- window.removeEventListener("resize", resizeHandler);
- if (effectRef.current) {
- try {
- effectRef.current.destroy();
- } catch {
- // ignore
- }
- }
- effectRef.current = null;
- (
- window as unknown as { getSaturnEffect?: () => SaturnEffect | null }
- ).getSaturnEffect = undefined;
- };
- }, []);
-
- return (
- <canvas
- ref={canvasRef}
- className="absolute inset-0 z-0 pointer-events-none"
- />
- );
-}
diff --git a/packages/ui-new/src/components/sidebar.tsx b/packages/ui-new/src/components/sidebar.tsx
deleted file mode 100644
index 0147b0a..0000000
--- a/packages/ui-new/src/components/sidebar.tsx
+++ /dev/null
@@ -1,185 +0,0 @@
-import { Folder, Home, LogOutIcon, Settings } from "lucide-react";
-import { useLocation, useNavigate } from "react-router";
-import { cn } from "@/lib/utils";
-import { useAuthStore } from "@/models/auth";
-import { Button } from "./ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "./ui/dropdown-menu";
-import { UserAvatar } from "./user-avatar";
-
-interface NavItemProps {
- Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
- label: string;
- to: string;
-}
-
-function NavItem({ Icon, label, to }: NavItemProps) {
- const navigate = useNavigate();
- const location = useLocation();
- const isActive = location.pathname === to;
-
- const handleClick = () => {
- navigate(to);
- };
-
- return (
- <Button
- variant="ghost"
- className={cn(
- "w-fit lg:w-full justify-center lg:justify-start",
- isActive && "relative bg-accent",
- )}
- size="lg"
- onClick={handleClick}
- >
- <Icon className="size-5" strokeWidth={isActive ? 2.5 : 2} />
- <span className="hidden lg:block text-sm relative z-10">{label}</span>
- {isActive && (
- <div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-4 bg-black dark:bg-white rounded-r-full hidden lg:block"></div>
- )}
- </Button>
- );
-}
-
-export function Sidebar() {
- const authStore = useAuthStore();
-
- return (
- <aside
- className={cn(
- "flex flex-col items-center lg:items-start",
- "bg-sidebar transition-all duration-300",
- "w-20 lg:w-64 shrink-0 py-6 h-full",
- )}
- >
- {/* Logo Area */}
- <div className="h-16 w-full flex items-center justify-center lg:justify-start lg:px-6 mb-6">
- {/* Icon Logo (Small) */}
- <div className="lg:hidden text-black dark:text-white">
- <svg
- width="32"
- height="32"
- viewBox="0 0 100 100"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <title>Logo</title>
- <path
- d="M25 25 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M25 75 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M50 50 L75 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <circle cx="25" cy="25" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="50" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="75" r="8" fill="currentColor" stroke="none" />
- <circle cx="50" cy="50" r="10" fill="currentColor" stroke="none" />
- <circle cx="75" cy="50" r="8" fill="currentColor" stroke="none" />
- </svg>
- </div>
- {/* Full Logo (Large) */}
- <div className="hidden lg:flex items-center gap-3 font-bold text-xl tracking-tighter dark:text-white text-black">
- <svg
- width="42"
- height="42"
- viewBox="0 0 100 100"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- className="shrink-0"
- >
- <title>Logo</title>
- <path
- d="M25 25 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M25 75 L50 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
- <path
- d="M50 50 L75 50"
- stroke="currentColor"
- strokeWidth="4"
- strokeLinecap="round"
- />
-
- <circle cx="25" cy="25" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="50" r="8" fill="currentColor" stroke="none" />
- <circle cx="25" cy="75" r="8" fill="currentColor" stroke="none" />
-
- <circle
- cx="50"
- cy="25"
- r="7"
- stroke="currentColor"
- strokeWidth="2"
- strokeDasharray="4 2"
- fill="none"
- className="opacity-30"
- />
- <circle
- cx="50"
- cy="75"
- r="7"
- stroke="currentColor"
- strokeWidth="2"
- strokeDasharray="4 2"
- fill="none"
- className="opacity-30"
- />
- <circle cx="50" cy="50" r="10" fill="currentColor" stroke="none" />
- <circle cx="75" cy="50" r="8" fill="currentColor" stroke="none" />
- </svg>
-
- <span>DROPOUT</span>
- </div>
- </div>
-
- <nav className="w-full flex flex-col space-y-1 px-3 items-center">
- <NavItem Icon={Home} label="Overview" to="/" />
- <NavItem Icon={Folder} label="Instances" to="/instances" />
- <NavItem Icon={Settings} label="Settings" to="/settings" />
- </nav>
-
- <div className="flex-1 flex flex-col justify-end">
- <DropdownMenu>
- <DropdownMenuTrigger render={<UserAvatar />}>
- Open
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end" side="right" sideOffset={20}>
- <DropdownMenuGroup>
- <DropdownMenuItem
- variant="destructive"
- onClick={authStore.logout}
- >
- <LogOutIcon />
- Logout
- </DropdownMenuItem>
- </DropdownMenuGroup>
- </DropdownMenuContent>
- </DropdownMenu>
- </div>
- </aside>
- );
-}
diff --git a/packages/ui-new/src/components/ui/avatar.tsx b/packages/ui-new/src/components/ui/avatar.tsx
deleted file mode 100644
index 9fd72a2..0000000
--- a/packages/ui-new/src/components/ui/avatar.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Avatar({
- className,
- size = "default",
- ...props
-}: AvatarPrimitive.Root.Props & {
- size?: "default" | "sm" | "lg";
-}) {
- return (
- <AvatarPrimitive.Root
- data-slot="avatar"
- data-size={size}
- className={cn(
- "size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
- return (
- <AvatarPrimitive.Image
- data-slot="avatar-image"
- className={cn(
- "rounded-full aspect-square size-full object-cover",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarFallback({
- className,
- ...props
-}: AvatarPrimitive.Fallback.Props) {
- return (
- <AvatarPrimitive.Fallback
- data-slot="avatar-fallback"
- className={cn(
- "bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
- return (
- <span
- data-slot="avatar-badge"
- className={cn(
- "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
- "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
- "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
- "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="avatar-group"
- className={cn(
- "*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
- className,
- )}
- {...props}
- />
- );
-}
-
-function AvatarGroupCount({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="avatar-group-count"
- className={cn(
- "bg-muted text-muted-foreground size-8 rounded-full text-xs group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- Avatar,
- AvatarImage,
- AvatarFallback,
- AvatarGroup,
- AvatarGroupCount,
- AvatarBadge,
-};
diff --git a/packages/ui-new/src/components/ui/badge.tsx b/packages/ui-new/src/components/ui/badge.tsx
deleted file mode 100644
index 425ab9e..0000000
--- a/packages/ui-new/src/components/ui/badge.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { mergeProps } from "@base-ui/react/merge-props";
-import { useRender } from "@base-ui/react/use-render";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const badgeVariants = cva(
- "h-5 gap-1 rounded-none border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
- secondary:
- "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
- destructive:
- "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
- outline:
- "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
- ghost:
- "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
- link: "text-primary underline-offset-4 hover:underline",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-function Badge({
- className,
- variant = "default",
- render,
- ...props
-}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
- return useRender({
- defaultTagName: "span",
- props: mergeProps<"span">(
- {
- className: cn(badgeVariants({ variant }), className),
- },
- props,
- ),
- render,
- state: {
- slot: "badge",
- variant,
- },
- });
-}
-
-export { Badge, badgeVariants };
diff --git a/packages/ui-new/src/components/ui/button.tsx b/packages/ui-new/src/components/ui/button.tsx
deleted file mode 100644
index 7dee494..0000000
--- a/packages/ui-new/src/components/ui/button.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Button as ButtonPrimitive } from "@base-ui/react/button";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const buttonVariants = cva(
- "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
- outline:
- "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
- secondary:
- "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
- ghost:
- "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
- destructive:
- "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default:
- "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
- xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
- sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
- lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
- icon: "size-8",
- "icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
- "icon-sm": "size-7 rounded-none",
- "icon-lg": "size-9",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-function Button({
- className,
- variant = "default",
- size = "default",
- ...props
-}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
- return (
- <ButtonPrimitive
- data-slot="button"
- className={cn(buttonVariants({ variant, size, className }))}
- {...props}
- />
- );
-}
-
-export { Button, buttonVariants };
diff --git a/packages/ui-new/src/components/ui/card.tsx b/packages/ui-new/src/components/ui/card.tsx
deleted file mode 100644
index b7084a0..0000000
--- a/packages/ui-new/src/components/ui/card.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Card({
- className,
- size = "default",
- ...props
-}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
- return (
- <div
- data-slot="card"
- data-size={size}
- className={cn(
- "ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-header"
- className={cn(
- "gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-title"
- className={cn(
- "text-sm font-medium group-data-[size=sm]/card:text-sm",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-description"
- className={cn("text-muted-foreground text-xs/relaxed", className)}
- {...props}
- />
- );
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-action"
- className={cn(
- "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
- className,
- )}
- {...props}
- />
- );
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-content"
- className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
- {...props}
- />
- );
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="card-footer"
- className={cn(
- "rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardAction,
- CardDescription,
- CardContent,
-};
diff --git a/packages/ui-new/src/components/ui/checkbox.tsx b/packages/ui-new/src/components/ui/checkbox.tsx
deleted file mode 100644
index 9f22cea..0000000
--- a/packages/ui-new/src/components/ui/checkbox.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-"use client";
-
-import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
-import { CheckIcon } from "lucide-react";
-import { cn } from "@/lib/utils";
-
-function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
- return (
- <CheckboxPrimitive.Root
- data-slot="checkbox"
- className={cn(
- "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-none border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-1 aria-invalid:ring-1 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- {...props}
- >
- <CheckboxPrimitive.Indicator
- data-slot="checkbox-indicator"
- className="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
- >
- <CheckIcon />
- </CheckboxPrimitive.Indicator>
- </CheckboxPrimitive.Root>
- );
-}
-
-export { Checkbox };
diff --git a/packages/ui-new/src/components/ui/dialog.tsx b/packages/ui-new/src/components/ui/dialog.tsx
deleted file mode 100644
index 033b47c..0000000
--- a/packages/ui-new/src/components/ui/dialog.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-"use client";
-
-import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
-import { XIcon } from "lucide-react";
-import type * as React from "react";
-import { Button } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
-
-function Dialog({ ...props }: DialogPrimitive.Root.Props) {
- return <DialogPrimitive.Root data-slot="dialog" {...props} />;
-}
-
-function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
-}
-
-function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
-}
-
-function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
-}
-
-function DialogOverlay({
- className,
- ...props
-}: DialogPrimitive.Backdrop.Props) {
- return (
- <DialogPrimitive.Backdrop
- data-slot="dialog-overlay"
- className={cn(
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
- className,
- )}
- {...props}
- />
- );
-}
-
-function DialogContent({
- className,
- children,
- showCloseButton = true,
- ...props
-}: DialogPrimitive.Popup.Props & {
- showCloseButton?: boolean;
-}) {
- return (
- <DialogPortal>
- <DialogOverlay />
- <DialogPrimitive.Popup
- data-slot="dialog-content"
- className={cn(
- "bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-none p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
- className,
- )}
- {...props}
- >
- {children}
- {showCloseButton && (
- <DialogPrimitive.Close
- data-slot="dialog-close"
- render={
- <Button
- variant="ghost"
- className="absolute top-2 right-2"
- size="icon-sm"
- />
- }
- >
- <XIcon />
- <span className="sr-only">Close</span>
- </DialogPrimitive.Close>
- )}
- </DialogPrimitive.Popup>
- </DialogPortal>
- );
-}
-
-function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="dialog-header"
- className={cn("gap-1 text-left flex flex-col", className)}
- {...props}
- />
- );
-}
-
-function DialogFooter({
- className,
- showCloseButton = false,
- children,
- ...props
-}: React.ComponentProps<"div"> & {
- showCloseButton?: boolean;
-}) {
- return (
- <div
- data-slot="dialog-footer"
- className={cn(
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
- className,
- )}
- {...props}
- >
- {children}
- {showCloseButton && (
- <DialogPrimitive.Close render={<Button variant="outline" />}>
- Close
- </DialogPrimitive.Close>
- )}
- </div>
- );
-}
-
-function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
- return (
- <DialogPrimitive.Title
- data-slot="dialog-title"
- className={cn("text-sm font-medium", className)}
- {...props}
- />
- );
-}
-
-function DialogDescription({
- className,
- ...props
-}: DialogPrimitive.Description.Props) {
- return (
- <DialogPrimitive.Description
- data-slot="dialog-description"
- className={cn(
- "text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogOverlay,
- DialogPortal,
- DialogTitle,
- DialogTrigger,
-};
diff --git a/packages/ui-new/src/components/ui/dropdown-menu.tsx b/packages/ui-new/src/components/ui/dropdown-menu.tsx
deleted file mode 100644
index ee97374..0000000
--- a/packages/ui-new/src/components/ui/dropdown-menu.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import { Menu as MenuPrimitive } from "@base-ui/react/menu";
-import { CheckIcon, ChevronRightIcon } from "lucide-react";
-import type * as React from "react";
-import { cn } from "@/lib/utils";
-
-function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
- return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
-}
-
-function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
- return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
-}
-
-function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
- return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
-}
-
-function DropdownMenuContent({
- align = "start",
- alignOffset = 0,
- side = "bottom",
- sideOffset = 4,
- className,
- ...props
-}: MenuPrimitive.Popup.Props &
- Pick<
- MenuPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset"
- >) {
- return (
- <MenuPrimitive.Portal>
- <MenuPrimitive.Positioner
- className="isolate z-50 outline-none"
- align={align}
- alignOffset={alignOffset}
- side={side}
- sideOffset={sideOffset}
- >
- <MenuPrimitive.Popup
- data-slot="dropdown-menu-content"
- className={cn(
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
- className,
- )}
- {...props}
- />
- </MenuPrimitive.Positioner>
- </MenuPrimitive.Portal>
- );
-}
-
-function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
- return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
-}
-
-function DropdownMenuLabel({
- className,
- inset,
- ...props
-}: MenuPrimitive.GroupLabel.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.GroupLabel
- data-slot="dropdown-menu-label"
- data-inset={inset}
- className={cn(
- "text-muted-foreground px-2 py-2 text-xs data-inset:pl-7",
- className,
- )}
- {...props}
- />
- );
-}
-
-function DropdownMenuItem({
- className,
- inset,
- variant = "default",
- ...props
-}: MenuPrimitive.Item.Props & {
- inset?: boolean;
- variant?: "default" | "destructive";
-}) {
- return (
- <MenuPrimitive.Item
- data-slot="dropdown-menu-item"
- data-inset={inset}
- data-variant={variant}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- />
- );
-}
-
-function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
- return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />;
-}
-
-function DropdownMenuSubTrigger({
- className,
- inset,
- children,
- ...props
-}: MenuPrimitive.SubmenuTrigger.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.SubmenuTrigger
- data-slot="dropdown-menu-sub-trigger"
- data-inset={inset}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 data-popup-open:bg-accent data-popup-open:text-accent-foreground flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- {children}
- <ChevronRightIcon className="ml-auto" />
- </MenuPrimitive.SubmenuTrigger>
- );
-}
-
-function DropdownMenuSubContent({
- align = "start",
- alignOffset = -3,
- side = "right",
- sideOffset = 0,
- className,
- ...props
-}: React.ComponentProps<typeof DropdownMenuContent>) {
- return (
- <DropdownMenuContent
- data-slot="dropdown-menu-sub-content"
- className={cn(
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-24 rounded-none shadow-lg ring-1 duration-100 w-auto",
- className,
- )}
- align={align}
- alignOffset={alignOffset}
- side={side}
- sideOffset={sideOffset}
- {...props}
- />
- );
-}
-
-function DropdownMenuCheckboxItem({
- className,
- children,
- checked,
- inset,
- ...props
-}: MenuPrimitive.CheckboxItem.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.CheckboxItem
- data-slot="dropdown-menu-checkbox-item"
- data-inset={inset}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- checked={checked}
- {...props}
- >
- <span
- className="absolute right-2 flex items-center justify-center pointer-events-none"
- data-slot="dropdown-menu-checkbox-item-indicator"
- >
- <MenuPrimitive.CheckboxItemIndicator>
- <CheckIcon />
- </MenuPrimitive.CheckboxItemIndicator>
- </span>
- {children}
- </MenuPrimitive.CheckboxItem>
- );
-}
-
-function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
- return (
- <MenuPrimitive.RadioGroup
- data-slot="dropdown-menu-radio-group"
- {...props}
- />
- );
-}
-
-function DropdownMenuRadioItem({
- className,
- children,
- inset,
- ...props
-}: MenuPrimitive.RadioItem.Props & {
- inset?: boolean;
-}) {
- return (
- <MenuPrimitive.RadioItem
- data-slot="dropdown-menu-radio-item"
- data-inset={inset}
- className={cn(
- "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- <span
- className="absolute right-2 flex items-center justify-center pointer-events-none"
- data-slot="dropdown-menu-radio-item-indicator"
- >
- <MenuPrimitive.RadioItemIndicator>
- <CheckIcon />
- </MenuPrimitive.RadioItemIndicator>
- </span>
- {children}
- </MenuPrimitive.RadioItem>
- );
-}
-
-function DropdownMenuSeparator({
- className,
- ...props
-}: MenuPrimitive.Separator.Props) {
- return (
- <MenuPrimitive.Separator
- data-slot="dropdown-menu-separator"
- className={cn("bg-border -mx-1 h-px", className)}
- {...props}
- />
- );
-}
-
-function DropdownMenuShortcut({
- className,
- ...props
-}: React.ComponentProps<"span">) {
- return (
- <span
- data-slot="dropdown-menu-shortcut"
- className={cn(
- "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
- className,
- )}
- {...props}
- />
- );
-}
-
-export {
- DropdownMenu,
- DropdownMenuPortal,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuLabel,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioGroup,
- DropdownMenuRadioItem,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuSub,
- DropdownMenuSubTrigger,
- DropdownMenuSubContent,
-};
diff --git a/packages/ui-new/src/components/ui/field.tsx b/packages/ui-new/src/components/ui/field.tsx
deleted file mode 100644
index ab9fb71..0000000
--- a/packages/ui-new/src/components/ui/field.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import { cva, type VariantProps } from "class-variance-authority";
-import { useMemo } from "react";
-import { Label } from "@/components/ui/label";
-import { Separator } from "@/components/ui/separator";
-import { cn } from "@/lib/utils";
-
-function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
- return (
- <fieldset
- data-slot="field-set"
- className={cn(
- "gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldLegend({
- className,
- variant = "legend",
- ...props
-}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
- return (
- <legend
- data-slot="field-legend"
- data-variant={variant}
- className={cn(
- "mb-2.5 font-medium data-[variant=label]:text-xs data-[variant=legend]:text-sm",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="field-group"
- className={cn(
- "gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4 group/field-group @container/field-group flex w-full flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-const fieldVariants = cva(
- "data-[invalid=true]:text-destructive gap-2 group/field flex w-full",
- {
- variants: {
- orientation: {
- vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
- horizontal:
- "flex-row items-center *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
- responsive:
- "flex-col *:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:*:data-[slot=field-label]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
- },
- },
- defaultVariants: {
- orientation: "vertical",
- },
- },
-);
-
-function Field({
- className,
- orientation = "vertical",
- ...props
-}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
- return (
- <div
- data-slot="field"
- data-orientation={orientation}
- className={cn(fieldVariants({ orientation }), className)}
- {...props}
- />
- );
-}
-
-function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="field-content"
- className={cn(
- "gap-0.5 group/field-content flex flex-1 flex-col leading-snug",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldLabel({
- className,
- ...props
-}: React.ComponentProps<typeof Label>) {
- return (
- <Label
- data-slot="field-label"
- className={cn(
- "has-data-checked:bg-primary/5 has-data-checked:border-primary/30 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10 gap-2 group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-none has-[>[data-slot=field]]:border *:data-[slot=field]:p-2 group/field-label peer/field-label flex w-fit leading-snug",
- "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
- <div
- data-slot="field-label"
- className={cn(
- "gap-2 text-xs/relaxed group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
- return (
- <p
- data-slot="field-description"
- className={cn(
- "text-muted-foreground text-left text-xs/relaxed [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-horizontal/field:text-balance",
- "last:mt-0 nth-last-2:-mt-1",
- "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
- className,
- )}
- {...props}
- />
- );
-}
-
-function FieldSeparator({
- children,
- className,
- ...props
-}: React.ComponentProps<"div"> & {
- children?: React.ReactNode;
-}) {
- return (
- <div
- data-slot="field-separator"
- data-content={!!children}
- className={cn(
- "-my-2 h-5 text-xs group-data-[variant=outline]/field-group:-mb-2 relative",
- className,
- )}
- {...props}
- >
- <Separator className="absolute inset-0 top-1/2" />
- {children && (
- <span
- className="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
- data-slot="field-separator-content"
- >
- {children}
- </span>
- )}
- </div>
- );
-}
-
-function FieldError({
- className,
- children,
- errors,
- ...props
-}: React.ComponentProps<"div"> & {
- errors?: Array<{ message?: string } | undefined>;
-}) {
- const content = useMemo(() => {
- if (children) {
- return children;
- }
-
- if (!errors?.length) {
- return null;
- }
-
- const uniqueErrors = [
- ...new Map(errors.map((error) => [error?.message, error])).values(),
- ];
-
- if (uniqueErrors?.length === 1) {
- return uniqueErrors[0]?.message;
- }
-
- return (
- <ul className="ml-4 flex list-disc flex-col gap-1">
- {uniqueErrors.map(
- (error, index) =>
- error?.message && (
- <li key={`${error.message.slice(6)}-${index}`}>
- {error.message}
- </li>
- ),
- )}
- </ul>
- );
- }, [children, errors]);
-
- if (!content) {
- return null;
- }
-
- return (
- <div
- role="alert"
- data-slot="field-error"
- className={cn("text-destructive text-xs font-normal", className)}
- {...props}
- >
- {content}
- </div>
- );
-}
-
-export {
- Field,
- FieldLabel,
- FieldDescription,
- FieldError,
- FieldGroup,
- FieldLegend,
- FieldSeparator,
- FieldSet,
- FieldContent,
- FieldTitle,
-};
diff --git a/packages/ui-new/src/components/ui/input.tsx b/packages/ui-new/src/components/ui/input.tsx
deleted file mode 100644
index bb0390a..0000000
--- a/packages/ui-new/src/components/ui/input.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Input as InputPrimitive } from "@base-ui/react/input";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Input({ className, type, ...props }: React.ComponentProps<"input">) {
- return (
- <InputPrimitive
- type={type}
- data-slot="input"
- className={cn(
- "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors file:h-6 file:text-xs file:font-medium focus-visible:ring-1 aria-invalid:ring-1 md:text-xs file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Input };
diff --git a/packages/ui-new/src/components/ui/label.tsx b/packages/ui-new/src/components/ui/label.tsx
deleted file mode 100644
index 9a998c7..0000000
--- a/packages/ui-new/src/components/ui/label.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Label({ className, ...props }: React.ComponentProps<"label">) {
- return (
- // biome-ignore lint/a11y/noLabelWithoutControl: shadcn component
- <label
- data-slot="label"
- className={cn(
- "gap-2 text-xs leading-none group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Label };
diff --git a/packages/ui-new/src/components/ui/scroll-area.tsx b/packages/ui-new/src/components/ui/scroll-area.tsx
deleted file mode 100644
index 4a68eb2..0000000
--- a/packages/ui-new/src/components/ui/scroll-area.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-
-import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
-import { cn } from "@/lib/utils";
-
-function ScrollArea({
- className,
- children,
- ...props
-}: ScrollAreaPrimitive.Root.Props) {
- return (
- <ScrollAreaPrimitive.Root
- data-slot="scroll-area"
- className={cn("relative", className)}
- {...props}
- >
- <ScrollAreaPrimitive.Viewport
- data-slot="scroll-area-viewport"
- className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
- >
- {children}
- </ScrollAreaPrimitive.Viewport>
- <ScrollBar />
- <ScrollAreaPrimitive.Corner />
- </ScrollAreaPrimitive.Root>
- );
-}
-
-function ScrollBar({
- className,
- orientation = "vertical",
- ...props
-}: ScrollAreaPrimitive.Scrollbar.Props) {
- return (
- <ScrollAreaPrimitive.Scrollbar
- data-slot="scroll-area-scrollbar"
- data-orientation={orientation}
- orientation={orientation}
- className={cn(
- "data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none",
- className,
- )}
- {...props}
- >
- <ScrollAreaPrimitive.Thumb
- data-slot="scroll-area-thumb"
- className="rounded-none bg-border relative flex-1"
- />
- </ScrollAreaPrimitive.Scrollbar>
- );
-}
-
-export { ScrollArea, ScrollBar };
diff --git a/packages/ui-new/src/components/ui/select.tsx b/packages/ui-new/src/components/ui/select.tsx
deleted file mode 100644
index 210adba..0000000
--- a/packages/ui-new/src/components/ui/select.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-import { Select as SelectPrimitive } from "@base-ui/react/select";
-import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
-import type * as React from "react";
-import { cn } from "@/lib/utils";
-
-const Select = SelectPrimitive.Root;
-
-function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
- return (
- <SelectPrimitive.Group
- data-slot="select-group"
- className={cn("scroll-my-1", className)}
- {...props}
- />
- );
-}
-
-function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
- return (
- <SelectPrimitive.Value
- data-slot="select-value"
- className={cn("flex flex-1 text-left", className)}
- {...props}
- />
- );
-}
-
-function SelectTrigger({
- className,
- size = "default",
- children,
- ...props
-}: SelectPrimitive.Trigger.Props & {
- size?: "sm" | "default";
-}) {
- return (
- <SelectPrimitive.Trigger
- data-slot="select-trigger"
- data-size={size}
- className={cn(
- "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-none border bg-transparent py-2 pr-2 pl-2.5 text-xs transition-colors select-none focus-visible:ring-1 aria-invalid:ring-1 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- {children}
- <SelectPrimitive.Icon
- render={
- <ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" />
- }
- />
- </SelectPrimitive.Trigger>
- );
-}
-
-function SelectContent({
- className,
- children,
- side = "bottom",
- sideOffset = 4,
- align = "center",
- alignOffset = 0,
- alignItemWithTrigger = true,
- ...props
-}: SelectPrimitive.Popup.Props &
- Pick<
- SelectPrimitive.Positioner.Props,
- "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
- >) {
- return (
- <SelectPrimitive.Portal>
- <SelectPrimitive.Positioner
- side={side}
- sideOffset={sideOffset}
- align={align}
- alignOffset={alignOffset}
- alignItemWithTrigger={alignItemWithTrigger}
- className="isolate z-50"
- >
- <SelectPrimitive.Popup
- data-slot="select-content"
- data-align-trigger={alignItemWithTrigger}
- className={cn(
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-none shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none",
- className,
- )}
- {...props}
- >
- <SelectScrollUpButton />
- <SelectPrimitive.List>{children}</SelectPrimitive.List>
- <SelectScrollDownButton />
- </SelectPrimitive.Popup>
- </SelectPrimitive.Positioner>
- </SelectPrimitive.Portal>
- );
-}
-
-function SelectLabel({
- className,
- ...props
-}: SelectPrimitive.GroupLabel.Props) {
- return (
- <SelectPrimitive.GroupLabel
- data-slot="select-label"
- className={cn("text-muted-foreground px-2 py-2 text-xs", className)}
- {...props}
- />
- );
-}
-
-function SelectItem({
- className,
- children,
- ...props
-}: SelectPrimitive.Item.Props) {
- return (
- <SelectPrimitive.Item
- data-slot="select-item"
- className={cn(
- "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- className,
- )}
- {...props}
- >
- <SelectPrimitive.ItemText className="flex flex-1 gap-2 shrink-0 whitespace-nowrap">
- {children}
- </SelectPrimitive.ItemText>
- <SelectPrimitive.ItemIndicator
- render={
- <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
- }
- >
- <CheckIcon className="pointer-events-none" />
- </SelectPrimitive.ItemIndicator>
- </SelectPrimitive.Item>
- );
-}
-
-function SelectSeparator({
- className,
- ...props
-}: SelectPrimitive.Separator.Props) {
- return (
- <SelectPrimitive.Separator
- data-slot="select-separator"
- className={cn("bg-border -mx-1 h-px pointer-events-none", className)}
- {...props}
- />
- );
-}
-
-function SelectScrollUpButton({
- className,
- ...props
-}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
- return (
- <SelectPrimitive.ScrollUpArrow
- data-slot="select-scroll-up-button"
- className={cn(
- "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full",
- className,
- )}
- {...props}
- >
- <ChevronUpIcon />
- </SelectPrimitive.ScrollUpArrow>
- );
-}
-
-function SelectScrollDownButton({
- className,
- ...props
-}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
- return (
- <SelectPrimitive.ScrollDownArrow
- data-slot="select-scroll-down-button"
- className={cn(
- "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full",
- className,
- )}
- {...props}
- >
- <ChevronDownIcon />
- </SelectPrimitive.ScrollDownArrow>
- );
-}
-
-export {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectScrollDownButton,
- SelectScrollUpButton,
- SelectSeparator,
- SelectTrigger,
- SelectValue,
-};
diff --git a/packages/ui-new/src/components/ui/separator.tsx b/packages/ui-new/src/components/ui/separator.tsx
deleted file mode 100644
index e91a862..0000000
--- a/packages/ui-new/src/components/ui/separator.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-"use client";
-
-import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
-
-import { cn } from "@/lib/utils";
-
-function Separator({
- className,
- orientation = "horizontal",
- ...props
-}: SeparatorPrimitive.Props) {
- return (
- <SeparatorPrimitive
- data-slot="separator"
- orientation={orientation}
- className={cn(
- "bg-border shrink-0 data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Separator };
diff --git a/packages/ui-new/src/components/ui/sonner.tsx b/packages/ui-new/src/components/ui/sonner.tsx
deleted file mode 100644
index d6e293d..0000000
--- a/packages/ui-new/src/components/ui/sonner.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import {
- CircleCheckIcon,
- InfoIcon,
- Loader2Icon,
- OctagonXIcon,
- TriangleAlertIcon,
-} from "lucide-react";
-import { useTheme } from "next-themes";
-import { Toaster as Sonner, type ToasterProps } from "sonner";
-
-const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme();
-
- return (
- <Sonner
- theme={theme as ToasterProps["theme"]}
- className="toaster group"
- icons={{
- success: <CircleCheckIcon className="size-4" />,
- info: <InfoIcon className="size-4" />,
- warning: <TriangleAlertIcon className="size-4" />,
- error: <OctagonXIcon className="size-4" />,
- loading: <Loader2Icon className="size-4 animate-spin" />,
- }}
- style={
- {
- "--normal-bg": "var(--popover)",
- "--normal-text": "var(--popover-foreground)",
- "--normal-border": "var(--border)",
- "--border-radius": "var(--radius)",
- } as React.CSSProperties
- }
- toastOptions={{
- classNames: {
- toast: "cn-toast",
- },
- }}
- {...props}
- />
- );
-};
-
-export { Toaster };
diff --git a/packages/ui-new/src/components/ui/spinner.tsx b/packages/ui-new/src/components/ui/spinner.tsx
deleted file mode 100644
index 91f6a63..0000000
--- a/packages/ui-new/src/components/ui/spinner.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { cn } from "@/lib/utils"
-import { Loader2Icon } from "lucide-react"
-
-function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
- return (
- <Loader2Icon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
- )
-}
-
-export { Spinner }
diff --git a/packages/ui-new/src/components/ui/switch.tsx b/packages/ui-new/src/components/ui/switch.tsx
deleted file mode 100644
index fef14e3..0000000
--- a/packages/ui-new/src/components/ui/switch.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-"use client";
-
-import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
-
-import { cn } from "@/lib/utils";
-
-function Switch({
- className,
- size = "default",
- ...props
-}: SwitchPrimitive.Root.Props & {
- size?: "sm" | "default";
-}) {
- return (
- <SwitchPrimitive.Root
- data-slot="switch"
- data-size={size}
- className={cn(
- "data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent focus-visible:ring-1 aria-invalid:ring-1 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50",
- className,
- )}
- {...props}
- >
- <SwitchPrimitive.Thumb
- data-slot="switch-thumb"
- className="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform"
- />
- </SwitchPrimitive.Root>
- );
-}
-
-export { Switch };
diff --git a/packages/ui-new/src/components/ui/tabs.tsx b/packages/ui-new/src/components/ui/tabs.tsx
deleted file mode 100644
index c66893f..0000000
--- a/packages/ui-new/src/components/ui/tabs.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-function Tabs({
- className,
- orientation = "horizontal",
- ...props
-}: TabsPrimitive.Root.Props) {
- return (
- <TabsPrimitive.Root
- data-slot="tabs"
- data-orientation={orientation}
- className={cn(
- "gap-2 group/tabs flex data-horizontal:flex-col",
- className,
- )}
- {...props}
- />
- );
-}
-
-const tabsListVariants = cva(
- "rounded-none p-0.75 group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
- {
- variants: {
- variant: {
- default: "bg-muted",
- line: "gap-1 bg-transparent",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-function TabsList({
- className,
- variant = "default",
- ...props
-}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
- return (
- <TabsPrimitive.List
- data-slot="tabs-list"
- data-variant={variant}
- className={cn(tabsListVariants({ variant }), className)}
- {...props}
- />
- );
-}
-
-function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
- return (
- <TabsPrimitive.Tab
- data-slot="tabs-trigger"
- className={cn(
- "gap-1.5 rounded-none border border-transparent px-1.5 py-0.5 text-xs font-medium group-data-vertical/tabs:py-[calc(--spacing(1.25))] [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
- "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
- "data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
- "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:-bottom-1.25 group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
- className,
- )}
- {...props}
- />
- );
-}
-
-function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
- return (
- <TabsPrimitive.Panel
- data-slot="tabs-content"
- className={cn("text-xs/relaxed flex-1 outline-none", className)}
- {...props}
- />
- );
-}
-
-export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
diff --git a/packages/ui-new/src/components/ui/textarea.tsx b/packages/ui-new/src/components/ui/textarea.tsx
deleted file mode 100644
index 3c3e5d0..0000000
--- a/packages/ui-new/src/components/ui/textarea.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
- return (
- <textarea
- data-slot="textarea"
- className={cn(
- "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 rounded-none border bg-transparent px-2.5 py-2 text-xs transition-colors focus-visible:ring-1 aria-invalid:ring-1 md:text-xs placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Textarea };
diff --git a/packages/ui-new/src/components/user-avatar.tsx b/packages/ui-new/src/components/user-avatar.tsx
deleted file mode 100644
index bbdb84c..0000000
--- a/packages/ui-new/src/components/user-avatar.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useAuthStore } from "@/models/auth";
-import { Avatar, AvatarBadge, AvatarFallback, AvatarImage } from "./ui/avatar";
-
-export function UserAvatar({
- className,
- ...props
-}: React.ComponentProps<typeof Avatar>) {
- const authStore = useAuthStore();
-
- if (!authStore.account) {
- return null;
- }
-
- return (
- <Avatar {...props}>
- <AvatarImage
- src={`https://minotar.net/helm/${authStore.account.username}/100.png`}
- />
- <AvatarFallback>{authStore.account.username.slice(0, 2)}</AvatarFallback>
- <AvatarBadge />
- </Avatar>
- );
-}
diff --git a/packages/ui-new/src/index.css b/packages/ui-new/src/index.css
deleted file mode 100644
index 8803e5e..0000000
--- a/packages/ui-new/src/index.css
+++ /dev/null
@@ -1,126 +0,0 @@
-@import "tailwindcss";
-@import "tw-animate-css";
-@import "shadcn/tailwind.css";
-
-@custom-variant dark (&:is(.dark *));
-
-@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --radius-2xl: calc(var(--radius) + 8px);
- --radius-3xl: calc(var(--radius) + 12px);
- --radius-4xl: calc(var(--radius) + 16px);
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
-}
-
-:root {
- --radius: 0.625rem;
- --background: #f3f4f6; /* bg-gray-100 */
- --foreground: #18181b; /* zinc-900 */
- --card: #ffffff;
- --card-foreground: #18181b;
- --popover: #ffffff;
- --popover-foreground: #18181b;
- --primary: #4f46e5; /* indigo-600 */
- --primary-foreground: #ffffff;
- --secondary: #f4f4f5; /* zinc-100 */
- --secondary-foreground: #18181b;
- --muted: #f4f4f5; /* zinc-100 */
- --muted-foreground: #71717a; /* zinc-500 */
- --accent: #f4f4f5; /* zinc-100 */
- --accent-foreground: #18181b;
- --destructive: #ef4444; /* red-500 */
- --destructive-foreground: #ffffff;
- --border: #e4e4e7; /* zinc-200 */
- --input: #ffffff;
- --ring: #6366f1; /* indigo-500 */
- --chart-1: #059669; /* emerald-600 */
- --chart-2: #0d9488; /* teal-600 */
- --chart-3: #4f46e5; /* indigo-600 */
- --chart-4: #7c3aed; /* violet-600 */
- --chart-5: #dc2626; /* red-600 */
- --sidebar: #ffffff;
- --sidebar-foreground: #18181b;
- --sidebar-primary: #4f46e5; /* indigo-600 */
- --sidebar-primary-foreground: #ffffff;
- --sidebar-accent: #f4f4f5; /* zinc-100 */
- --sidebar-accent-foreground: #18181b;
- --sidebar-border: #e4e4e7; /* zinc-200 */
- --sidebar-ring: #6366f1; /* indigo-500 */
-}
-
-.dark {
- --background: #09090b;
- --foreground: #fafafa; /* zinc-50 */
- --card: #18181b; /* zinc-900 */
- --card-foreground: #fafafa;
- --popover: #18181b;
- --popover-foreground: #fafafa;
- --primary: #6366f1; /* indigo-500 */
- --primary-foreground: #ffffff;
- --secondary: #27272a; /* zinc-800 */
- --secondary-foreground: #fafafa;
- --muted: #27272a; /* zinc-800 */
- --muted-foreground: #a1a1aa; /* zinc-400 */
- --accent: #27272a; /* zinc-800 */
- --accent-foreground: #fafafa;
- --destructive: #f87171; /* red-400 */
- --destructive-foreground: #ffffff;
- --border: #3f3f46; /* zinc-700 */
- --input: rgba(255, 255, 255, 0.15);
- --ring: #6366f1; /* indigo-500 */
- --chart-1: #10b981; /* emerald-500 */
- --chart-2: #06b6d4; /* cyan-500 */
- --chart-3: #6366f1; /* indigo-500 */
- --chart-4: #8b5cf6; /* violet-500 */
- --chart-5: #f87171; /* red-400 */
- --sidebar: #09090b;
- --sidebar-foreground: #fafafa;
- --sidebar-primary: #6366f1; /* indigo-500 */
- --sidebar-primary-foreground: #ffffff;
- --sidebar-accent: #27272a; /* zinc-800 */
- --sidebar-accent-foreground: #fafafa;
- --sidebar-border: #3f3f46; /* zinc-700 */
- --sidebar-ring: #6366f1; /* indigo-500 */
-}
-
-@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
diff --git a/packages/ui-new/src/lib/effects/SaturnEffect.ts b/packages/ui-new/src/lib/effects/SaturnEffect.ts
deleted file mode 100644
index 497a340..0000000
--- a/packages/ui-new/src/lib/effects/SaturnEffect.ts
+++ /dev/null
@@ -1,299 +0,0 @@
-/**
- * Ported SaturnEffect for the React UI (ui-new).
- * Adapted from the original Svelte implementation but written as a standalone
- * TypeScript class that manages a 2D canvas particle effect resembling a
- * rotating "Saturn" with rings. Designed to be instantiated and controlled
- * from a React component (e.g. ParticleBackground).
- *
- * Usage:
- * const effect = new SaturnEffect(canvasElement);
- * effect.handleMouseDown(clientX);
- * effect.handleMouseMove(clientX);
- * effect.handleMouseUp();
- * // on resize:
- * effect.resize(width, height);
- * // on unmount:
- * effect.destroy();
- */
-
-export class SaturnEffect {
- private canvas: HTMLCanvasElement;
- private ctx: CanvasRenderingContext2D;
- private width = 0;
- private height = 0;
-
- // Particle storage
- private xyz: Float32Array | null = null; // interleaved x,y,z
- private types: Uint8Array | null = null; // 0 = planet, 1 = ring
- private count = 0;
-
- // Animation
- private animationId = 0;
- private angle = 0;
- private scaleFactor = 1;
-
- // Interaction
- private isDragging = false;
- private lastMouseX = 0;
- private lastMouseTime = 0;
- private mouseVelocities: number[] = [];
-
- // Speed control
- private readonly baseSpeed = 0.005;
- private currentSpeed = 0.005;
- private rotationDirection = 1;
- private readonly speedDecayRate = 0.992;
- private readonly minSpeedMultiplier = 1;
- private readonly maxSpeedMultiplier = 50;
- private isStopped = false;
-
- constructor(canvas: HTMLCanvasElement) {
- this.canvas = canvas;
- const ctx = canvas.getContext("2d", { alpha: true, desynchronized: false });
- if (!ctx) {
- throw new Error("Failed to get 2D context for SaturnEffect");
- }
- this.ctx = ctx;
-
- // Initialize size & particles
- this.resize(window.innerWidth, window.innerHeight);
- this.initParticles();
-
- this.animate = this.animate.bind(this);
- this.animate();
- }
-
- // External interaction handlers (accept clientX)
- handleMouseDown(clientX: number) {
- this.isDragging = true;
- this.lastMouseX = clientX;
- this.lastMouseTime = performance.now();
- this.mouseVelocities = [];
- }
-
- handleMouseMove(clientX: number) {
- if (!this.isDragging) return;
- const now = performance.now();
- const dt = now - this.lastMouseTime;
- if (dt > 0) {
- const dx = clientX - this.lastMouseX;
- const velocity = dx / dt;
- this.mouseVelocities.push(velocity);
- if (this.mouseVelocities.length > 5) this.mouseVelocities.shift();
- // Rotate directly while dragging for immediate feedback
- this.angle += dx * 0.002;
- }
- this.lastMouseX = clientX;
- this.lastMouseTime = now;
- }
-
- handleMouseUp() {
- if (this.isDragging && this.mouseVelocities.length > 0) {
- this.applyFlingVelocity();
- }
- this.isDragging = false;
- }
-
- handleTouchStart(clientX: number) {
- this.handleMouseDown(clientX);
- }
-
- handleTouchMove(clientX: number) {
- this.handleMouseMove(clientX);
- }
-
- handleTouchEnd() {
- this.handleMouseUp();
- }
-
- // Resize canvas & scale (call on window resize)
- resize(width: number, height: number) {
- const dpr = window.devicePixelRatio || 1;
- this.width = width;
- this.height = height;
-
- // Update canvas pixel size and CSS size
- this.canvas.width = Math.max(1, Math.floor(width * dpr));
- this.canvas.height = Math.max(1, Math.floor(height * dpr));
- this.canvas.style.width = `${width}px`;
- this.canvas.style.height = `${height}px`;
-
- // Reset transform and scale for devicePixelRatio
- this.ctx.setTransform(1, 0, 0, 1, 0, 0); // reset
- this.ctx.scale(dpr, dpr);
-
- const minDim = Math.min(width, height);
- this.scaleFactor = Math.max(1, minDim * 0.45);
- }
-
- // Initialize particle arrays with reduced counts to keep performance reasonable
- private initParticles() {
- // Tuned particle counts for reasonable performance across platforms
- const planetCount = 1000;
- const ringCount = 2500;
- this.count = planetCount + ringCount;
-
- this.xyz = new Float32Array(this.count * 3);
- this.types = new Uint8Array(this.count);
-
- let idx = 0;
-
- // Planet points
- for (let i = 0; i < planetCount; i++, idx++) {
- const theta = Math.random() * Math.PI * 2;
- const phi = Math.acos(Math.random() * 2 - 1);
- const r = 1.0;
-
- this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta);
- this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
- this.xyz[idx * 3 + 2] = r * Math.cos(phi);
-
- this.types[idx] = 0;
- }
-
- // Ring points
- const ringInner = 1.4;
- const ringOuter = 2.3;
- for (let i = 0; i < ringCount; i++, idx++) {
- const angle = Math.random() * Math.PI * 2;
- const dist = Math.sqrt(
- Math.random() * (ringOuter * ringOuter - ringInner * ringInner) +
- ringInner * ringInner,
- );
-
- this.xyz[idx * 3] = dist * Math.cos(angle);
- this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05;
- this.xyz[idx * 3 + 2] = dist * Math.sin(angle);
-
- this.types[idx] = 1;
- }
- }
-
- // Map fling/velocity samples to a rotation speed and direction
- private applyFlingVelocity() {
- if (this.mouseVelocities.length === 0) return;
- const avg =
- this.mouseVelocities.reduce((a, b) => a + b, 0) /
- this.mouseVelocities.length;
- const flingThreshold = 0.3;
- const stopThreshold = 0.1;
-
- if (Math.abs(avg) > flingThreshold) {
- this.isStopped = false;
- const newDir = avg > 0 ? 1 : -1;
- if (newDir !== this.rotationDirection) this.rotationDirection = newDir;
- const multiplier = Math.min(
- this.maxSpeedMultiplier,
- this.minSpeedMultiplier + Math.abs(avg) * 10,
- );
- this.currentSpeed = this.baseSpeed * multiplier;
- } else if (Math.abs(avg) < stopThreshold) {
- this.isStopped = true;
- this.currentSpeed = 0;
- }
- }
-
- // Main render loop
- private animate() {
- // Clear with full alpha to allow layering over background
- this.ctx.clearRect(0, 0, this.width, this.height);
-
- // Standard composition
- this.ctx.globalCompositeOperation = "source-over";
-
- // Update rotation speed (decay)
- if (!this.isDragging && !this.isStopped) {
- if (this.currentSpeed > this.baseSpeed) {
- this.currentSpeed =
- this.baseSpeed +
- (this.currentSpeed - this.baseSpeed) * this.speedDecayRate;
- if (this.currentSpeed - this.baseSpeed < 0.00001) {
- this.currentSpeed = this.baseSpeed;
- }
- }
- this.angle += this.currentSpeed * this.rotationDirection;
- }
-
- // Center positions
- const cx = this.width * 0.6;
- const cy = this.height * 0.5;
-
- // Pre-calc rotations
- const rotationY = this.angle;
- const rotationX = 0.4;
- const rotationZ = 0.15;
-
- const sinY = Math.sin(rotationY);
- const cosY = Math.cos(rotationY);
- const sinX = Math.sin(rotationX);
- const cosX = Math.cos(rotationX);
- const sinZ = Math.sin(rotationZ);
- const cosZ = Math.cos(rotationZ);
-
- const fov = 1500;
- const scaleFactor = this.scaleFactor;
-
- if (!this.xyz || !this.types) {
- this.animationId = requestAnimationFrame(this.animate);
- return;
- }
-
- // Loop particles
- for (let i = 0; i < this.count; i++) {
- const x = this.xyz[i * 3];
- const y = this.xyz[i * 3 + 1];
- const z = this.xyz[i * 3 + 2];
-
- // Scale to screen
- const px = x * scaleFactor;
- const py = y * scaleFactor;
- const pz = z * scaleFactor;
-
- // Rotate Y then X then Z
- const x1 = px * cosY - pz * sinY;
- const z1 = pz * cosY + px * sinY;
- const y2 = py * cosX - z1 * sinX;
- const z2 = z1 * cosX + py * sinX;
- const x3 = x1 * cosZ - y2 * sinZ;
- const y3 = y2 * cosZ + x1 * sinZ;
- const z3 = z2;
-
- const scale = fov / (fov + z3);
-
- if (z3 > -fov) {
- const x2d = cx + x3 * scale;
- const y2d = cy + y3 * scale;
-
- const type = this.types[i];
- const sizeBase = type === 0 ? 2.4 : 1.5;
- const size = sizeBase * scale;
-
- let alpha = scale * scale * scale;
- if (alpha > 1) alpha = 1;
- if (alpha < 0.15) continue;
-
- if (type === 0) {
- // Planet: warm-ish
- this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`;
- } else {
- // Ring: cool-ish
- this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`;
- }
-
- // Render as small rectangles (faster than arc)
- this.ctx.fillRect(x2d, y2d, size, size);
- }
- }
-
- this.animationId = requestAnimationFrame(this.animate);
- }
-
- // Stop animations and release resources
- destroy() {
- if (this.animationId) {
- cancelAnimationFrame(this.animationId);
- this.animationId = 0;
- }
- // Intentionally do not null out arrays to allow reuse if desired.
- }
-}
diff --git a/packages/ui-new/src/lib/tsrs-utils.ts b/packages/ui-new/src/lib/tsrs-utils.ts
deleted file mode 100644
index f48f851..0000000
--- a/packages/ui-new/src/lib/tsrs-utils.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-export type Maybe<T> = T | null | undefined;
-
-export function toNumber(
- value: Maybe<number | bigint | string>,
- fallback = 0,
-): number {
- if (value === null || value === undefined) return fallback;
-
- if (typeof value === "number") {
- if (Number.isFinite(value)) return value;
- return fallback;
- }
-
- if (typeof value === "bigint") {
- // safe conversion for typical values (timestamps, sizes). Might overflow for huge bigint.
- return Number(value);
- }
-
- if (typeof value === "string") {
- const n = Number(value);
- return Number.isFinite(n) ? n : fallback;
- }
-
- return fallback;
-}
-
-/**
- * Like `toNumber` but ensures non-negative result (clamps at 0).
- */
-export function toNonNegativeNumber(
- value: Maybe<number | bigint | string>,
- fallback = 0,
-): number {
- const n = toNumber(value, fallback);
- return n < 0 ? 0 : n;
-}
-
-export function toDate(
- value: Maybe<number | bigint | string>,
- opts?: { isSeconds?: boolean },
-): Date | null {
- if (value === null || value === undefined) return null;
-
- const isSeconds = opts?.isSeconds ?? true;
-
- // accept bigint, number, numeric string
- const n = toNumber(value, NaN);
- if (Number.isNaN(n)) return null;
-
- const ms = isSeconds ? Math.floor(n) * 1000 : Math.floor(n);
- return new Date(ms);
-}
-
-/**
- * Convert a binding boolean-ish value (0/1, "true"/"false", boolean) to boolean.
- */
-export function toBoolean(value: unknown, fallback = false): boolean {
- if (value === null || value === undefined) return fallback;
- if (typeof value === "boolean") return value;
- if (typeof value === "number") return value !== 0;
- if (typeof value === "string") {
- const s = value.toLowerCase().trim();
- if (s === "true" || s === "1") return true;
- if (s === "false" || s === "0") return false;
- }
- return fallback;
-}
diff --git a/packages/ui-new/src/lib/utils.ts b/packages/ui-new/src/lib/utils.ts
deleted file mode 100644
index 365058c..0000000
--- a/packages/ui-new/src/lib/utils.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { type ClassValue, clsx } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs));
-}
diff --git a/packages/ui-new/src/main.tsx b/packages/ui-new/src/main.tsx
deleted file mode 100644
index a3157bd..0000000
--- a/packages/ui-new/src/main.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import "./index.css";
-import { createHashRouter, RouterProvider } from "react-router";
-import { Toaster } from "./components/ui/sonner";
-import { HomeView } from "./pages/home-view";
-import { IndexPage } from "./pages/index";
-import { InstancesView } from "./pages/instances-view";
-import { SettingsPage } from "./pages/settings";
-
-const router = createHashRouter([
- {
- path: "/",
- element: <IndexPage />,
- children: [
- {
- index: true,
- element: <HomeView />,
- },
- {
- path: "instances",
- element: <InstancesView />,
- },
- {
- path: "settings",
- element: <SettingsPage />,
- },
- ],
- },
-]);
-
-const root = createRoot(document.getElementById("root") as HTMLElement);
-root.render(
- <StrictMode>
- <RouterProvider router={router} />
- <Toaster />
- </StrictMode>,
-);
diff --git a/packages/ui-new/src/models/auth.ts b/packages/ui-new/src/models/auth.ts
deleted file mode 100644
index 10b2a0d..0000000
--- a/packages/ui-new/src/models/auth.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { open } from "@tauri-apps/plugin-shell";
-import { Mutex } from "es-toolkit";
-import { toString as stringify } from "es-toolkit/compat";
-import { toast } from "sonner";
-import { create } from "zustand";
-import {
- completeMicrosoftLogin,
- getActiveAccount,
- loginOffline,
- logout,
- startMicrosoftLogin,
-} from "@/client";
-import type { Account, DeviceCodeResponse } from "@/types";
-
-export interface AuthState {
- account: Account | null;
- loginMode: Account["type"] | null;
- deviceCode: DeviceCodeResponse | null;
- _pollingInterval: number | null;
- _mutex: Mutex;
- statusMessage: string | null;
- _progressUnlisten: UnlistenFn | null;
-
- init: () => Promise<void>;
- setLoginMode: (mode: Account["type"] | null) => void;
- loginOnline: (onSuccess?: () => void | Promise<void>) => Promise<void>;
- _pollLoginStatus: (
- deviceCode: string,
- onSuccess?: () => void | Promise<void>,
- ) => Promise<void>;
- cancelLoginOnline: () => Promise<void>;
- loginOffline: (username: string) => Promise<void>;
- logout: () => Promise<void>;
-}
-
-export const useAuthStore = create<AuthState>((set, get) => ({
- account: null,
- loginMode: null,
- deviceCode: null,
- _pollingInterval: null,
- statusMessage: null,
- _progressUnlisten: null,
- _mutex: new Mutex(),
-
- init: async () => {
- try {
- const account = await getActiveAccount();
- set({ account });
- } catch (error) {
- console.error("Failed to initialize auth store:", error);
- }
- },
- setLoginMode: (mode) => set({ loginMode: mode }),
- loginOnline: async (onSuccess) => {
- const { _pollLoginStatus } = get();
-
- set({ statusMessage: "Waiting for authorization..." });
-
- try {
- const unlisten = await listen("auth-progress", (event) => {
- const message = event.payload;
- console.log(message);
- set({ statusMessage: stringify(message), _progressUnlisten: unlisten });
- });
- } catch (error) {
- console.warn("Failed to attch auth-progress listener:", error);
- toast.warning("Failed to attch auth-progress listener");
- }
-
- const deviceCode = await startMicrosoftLogin();
- navigator.clipboard?.writeText(deviceCode.userCode).catch((err) => {
- console.error("Failed to copy to clipboard:", err);
- });
- open(deviceCode.verificationUri).catch((err) => {
- console.error("Failed to open browser:", err);
- });
- const ms = Number(deviceCode.interval) * 1000;
- const interval = setInterval(() => {
- _pollLoginStatus(deviceCode.deviceCode, onSuccess);
- }, ms);
- set({ _pollingInterval: interval, deviceCode });
- },
- _pollLoginStatus: async (deviceCode, onSuccess) => {
- const { _pollingInterval, _mutex: mutex, _progressUnlisten } = get();
- if (mutex.isLocked) return;
- mutex.acquire();
- try {
- const account = await completeMicrosoftLogin(deviceCode);
- clearInterval(_pollingInterval ?? undefined);
- _progressUnlisten?.();
- onSuccess?.();
- set({ account, loginMode: "microsoft" });
- } catch (error) {
- if (error === "authorization_pending") {
- console.log("Authorization pending...");
- } else {
- console.error("Failed to poll login status:", error);
- toast.error("Failed to poll login status");
- }
- } finally {
- mutex.release();
- }
- },
- cancelLoginOnline: async () => {
- const { account, logout, _pollingInterval, _progressUnlisten } = get();
- clearInterval(_pollingInterval ?? undefined);
- _progressUnlisten?.();
- if (account) {
- await logout();
- }
- set({
- loginMode: null,
- _pollingInterval: null,
- statusMessage: null,
- _progressUnlisten: null,
- });
- },
- loginOffline: async (username: string) => {
- const trimmedUsername = username.trim();
- if (trimmedUsername.length === 0) {
- throw new Error("Username cannot be empty");
- }
-
- try {
- const account = await loginOffline(trimmedUsername);
- set({ account, loginMode: "offline" });
- } catch (error) {
- console.error("Failed to login offline:", error);
- toast.error("Failed to login offline");
- }
- },
- logout: async () => {
- try {
- await logout();
- set({ account: null });
- } catch (error) {
- console.error("Failed to logout:", error);
- toast.error("Failed to logout");
- }
- },
-}));
diff --git a/packages/ui-new/src/models/instances.ts b/packages/ui-new/src/models/instances.ts
deleted file mode 100644
index f434c7c..0000000
--- a/packages/ui-new/src/models/instances.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-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/models/settings.ts b/packages/ui-new/src/models/settings.ts
deleted file mode 100644
index 9f4119c..0000000
--- a/packages/ui-new/src/models/settings.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { toast } from "sonner";
-import { create } from "zustand/react";
-import { getConfigPath, getSettings, saveSettings } from "@/client";
-import type { LauncherConfig } from "@/types";
-
-export interface SettingsState {
- config: LauncherConfig | null;
- configPath: string | null;
-
- /* Theme getter */
- get theme(): string;
- /* Apply theme to the document */
- applyTheme: (theme?: string) => void;
-
- /* Refresh settings from the backend */
- refresh: () => Promise<void>;
- /* Save settings to the backend */
- save: () => Promise<void>;
- /* Update settings in the backend */
- update: (config: LauncherConfig) => Promise<void>;
- /* Merge settings with the current config without saving */
- merge: (config: Partial<LauncherConfig>) => void;
-}
-
-export const useSettingsStore = create<SettingsState>((set, get) => ({
- config: null,
- configPath: null,
-
- get theme() {
- const { config } = get();
- return config?.theme || "dark";
- },
- applyTheme: (theme?: string) => {
- const { config } = get();
- if (!config) return;
- if (!theme) theme = config.theme;
- let themeValue = theme;
- if (theme === "system") {
- themeValue = window.matchMedia("(prefers-color-scheme: dark)").matches
- ? "dark"
- : "light";
- }
- document.documentElement.classList.remove("light", "dark");
- document.documentElement.setAttribute("data-theme", themeValue);
- document.documentElement.classList.add(themeValue);
- set({ config: { ...config, theme } });
- },
-
- refresh: async () => {
- const { applyTheme } = get();
- try {
- const settings = await getSettings();
- const path = await getConfigPath();
- set({ config: settings, configPath: path });
- applyTheme(settings.theme);
- } catch (error) {
- console.error("Failed to load settings:", error);
- toast.error("Failed to load settings");
- }
- },
- save: async () => {
- const { config } = get();
- if (!config) return;
- await saveSettings(config);
- },
- update: async (config) => {
- await saveSettings(config);
- set({ config });
- },
- merge: (config) => {
- const { config: currentConfig } = get();
- if (!currentConfig) throw new Error("Settings not loaded");
- set({ config: { ...currentConfig, ...config } });
- },
-}));
diff --git a/packages/ui-new/src/pages/assistant-view.tsx.bk b/packages/ui-new/src/pages/assistant-view.tsx.bk
deleted file mode 100644
index 56f827b..0000000
--- a/packages/ui-new/src/pages/assistant-view.tsx.bk
+++ /dev/null
@@ -1,485 +0,0 @@
-import {
- AlertTriangle,
- Bot,
- Brain,
- ChevronDown,
- Loader2,
- RefreshCw,
- Send,
- Settings,
- Trash2,
-} from "lucide-react";
-import { marked } from "marked";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent } from "@/components/ui/card";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { Separator } from "@/components/ui/separator";
-import { Textarea } from "@/components/ui/textarea";
-import { toNumber } from "@/lib/tsrs-utils";
-import { type Message, useAssistantStore } from "../stores/assistant-store";
-import { useSettingsStore } from "../stores/settings-store";
-import { useUiStore } from "../stores/ui-store";
-
-interface ParsedMessage {
- thinking: string | null;
- content: string;
- isThinking: boolean;
-}
-
-function parseMessageContent(content: string): ParsedMessage {
- if (!content) return { thinking: null, content: "", isThinking: false };
-
- // Support both <thinking> and <think> (DeepSeek uses <think>)
- let startTag = "<thinking>";
- let endTag = "</thinking>";
- let startIndex = content.indexOf(startTag);
-
- if (startIndex === -1) {
- startTag = "<think>";
- endTag = "</think>";
- startIndex = content.indexOf(startTag);
- }
-
- // Also check for encoded tags if they weren't decoded properly
- if (startIndex === -1) {
- startTag = "\u003cthink\u003e";
- endTag = "\u003c/think\u003e";
- startIndex = content.indexOf(startTag);
- }
-
- if (startIndex !== -1) {
- const endIndex = content.indexOf(endTag, startIndex);
-
- if (endIndex !== -1) {
- // Completed thinking block
- const before = content.substring(0, startIndex);
- const thinking = content
- .substring(startIndex + startTag.length, endIndex)
- .trim();
- const after = content.substring(endIndex + endTag.length);
-
- return {
- thinking,
- content: (before + after).trim(),
- isThinking: false,
- };
- } else {
- // Incomplete thinking block (still streaming)
- const before = content.substring(0, startIndex);
- const thinking = content.substring(startIndex + startTag.length).trim();
-
- return {
- thinking,
- content: before.trim(),
- isThinking: true,
- };
- }
- }
-
- return { thinking: null, content, isThinking: false };
-}
-
-function renderMarkdown(content: string): string {
- if (!content) return "";
- try {
- return marked(content, { breaks: true, gfm: true }) as string;
- } catch {
- return content;
- }
-}
-
-export function AssistantView() {
- const {
- messages,
- isProcessing,
- isProviderHealthy,
- streamingContent,
- init,
- checkHealth,
- sendMessage,
- clearHistory,
- } = useAssistantStore();
- const { settings } = useSettingsStore();
- const { setView } = useUiStore();
-
- const [input, setInput] = useState("");
- const messagesEndRef = useRef<HTMLDivElement>(null);
- const messagesContainerRef = useRef<HTMLDivElement>(null);
-
- const provider = settings.assistant.llmProvider;
- const endpoint =
- provider === "ollama"
- ? settings.assistant.ollamaEndpoint
- : settings.assistant.openaiEndpoint;
- const model =
- provider === "ollama"
- ? settings.assistant.ollamaModel
- : settings.assistant.openaiModel;
-
- const getProviderName = (): string => {
- if (provider === "ollama") {
- return `Ollama (${model})`;
- } else if (provider === "openai") {
- return `OpenAI (${model})`;
- }
- return provider;
- };
-
- const getProviderHelpText = (): string => {
- if (provider === "ollama") {
- return `Please ensure Ollama is installed and running at ${endpoint}.`;
- } else if (provider === "openai") {
- return "Please check your OpenAI API key in Settings > AI Assistant.";
- }
- return "";
- };
-
- const scrollToBottom = useCallback(() => {
- if (messagesContainerRef.current) {
- setTimeout(() => {
- if (messagesContainerRef.current) {
- messagesContainerRef.current.scrollTop =
- messagesContainerRef.current.scrollHeight;
- }
- }, 0);
- }
- }, []);
-
- useEffect(() => {
- init();
- }, [init]);
-
- useEffect(() => {
- if (messages.length > 0 || isProcessing) {
- scrollToBottom();
- }
- }, [messages.length, isProcessing, scrollToBottom]);
-
- const handleSubmit = async () => {
- if (!input.trim() || isProcessing) return;
- const text = input;
- setInput("");
- await sendMessage(text, settings.assistant.enabled, provider, endpoint);
- };
-
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- handleSubmit();
- }
- };
-
- const renderMessage = (message: Message, index: number) => {
- const isUser = message.role === "user";
- const parsed = parseMessageContent(message.content);
-
- return (
- <div
- key={index}
- className={`flex ${isUser ? "justify-end" : "justify-start"} mb-4`}
- >
- <div
- className={`max-w-[80%] rounded-2xl px-4 py-3 ${
- isUser
- ? "bg-indigo-500 text-white rounded-br-none"
- : "bg-zinc-800 text-zinc-100 rounded-bl-none"
- }`}
- >
- {!isUser && parsed.thinking && (
- <div className="mb-3 max-w-full overflow-hidden">
- <details className="group" open={parsed.isThinking}>
- <summary className="list-none cursor-pointer flex items-center gap-2 text-zinc-500 hover:text-zinc-300 transition-colors text-xs font-medium select-none bg-black/20 p-2 rounded-lg border border-white/5 w-fit mb-2 outline-none">
- <Brain className="h-3 w-3" />
- <span>Thinking Process</span>
- <ChevronDown className="h-3 w-3 transition-transform duration-200 group-open:rotate-180" />
- </summary>
- <div className="pl-3 border-l-2 border-zinc-700 text-zinc-500 text-xs italic leading-relaxed whitespace-pre-wrap font-mono max-h-96 overflow-y-auto custom-scrollbar bg-black/10 p-2 rounded-r-md">
- {parsed.thinking}
- {parsed.isThinking && (
- <span className="inline-block w-1.5 h-3 bg-zinc-500 ml-1 animate-pulse align-middle" />
- )}
- </div>
- </details>
- </div>
- )}
- <div
- className="prose prose-invert max-w-none"
- dangerouslySetInnerHTML={{
- __html: renderMarkdown(parsed.content),
- }}
- />
- {!isUser && message.stats && (
- <div className="mt-2 pt-2 border-t border-zinc-700/50">
- <div className="text-xs text-zinc-400">
- {message.stats.evalCount} tokens ·{" "}
- {Math.round(toNumber(message.stats.totalDuration) / 1000000)}
- ms
- </div>
- </div>
- )}
- </div>
- </div>
- );
- };
-
- return (
- <div className="h-full w-full flex flex-col gap-4 p-4 lg:p-8">
- <div className="flex items-center justify-between mb-2">
- <div className="flex items-center gap-3">
- <div className="p-2 bg-indigo-500/20 rounded-lg text-indigo-400">
- <Bot size={24} />
- </div>
- <div>
- <h2 className="text-2xl font-bold">Game Assistant</h2>
- <p className="text-zinc-400 text-sm">
- Powered by {getProviderName()}
- </p>
- </div>
- </div>
-
- <div className="flex items-center gap-2">
- {!settings.assistant.enabled ? (
- <Badge
- variant="outline"
- className="bg-zinc-500/10 text-zinc-400 border-zinc-500/20"
- >
- <AlertTriangle className="h-3 w-3 mr-1" />
- Disabled
- </Badge>
- ) : !isProviderHealthy ? (
- <Badge
- variant="outline"
- className="bg-red-500/10 text-red-400 border-red-500/20"
- >
- <AlertTriangle className="h-3 w-3 mr-1" />
- Offline
- </Badge>
- ) : (
- <Badge
- variant="outline"
- className="bg-emerald-500/10 text-emerald-400 border-emerald-500/20"
- >
- <div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse mr-1" />
- Online
- </Badge>
- )}
-
- <Button
- variant="ghost"
- size="icon"
- onClick={checkHealth}
- title="Check Connection"
- disabled={isProcessing}
- >
- <RefreshCw
- className={`h-4 w-4 ${isProcessing ? "animate-spin" : ""}`}
- />
- </Button>
-
- <Button
- variant="ghost"
- size="icon"
- onClick={clearHistory}
- title="Clear History"
- disabled={isProcessing}
- >
- <Trash2 className="h-4 w-4" />
- </Button>
-
- <Button
- variant="ghost"
- size="icon"
- onClick={() => setView("settings")}
- title="Settings"
- >
- <Settings className="h-4 w-4" />
- </Button>
- </div>
- </div>
-
- {/* Chat Area */}
- <div className="flex-1 bg-black/20 border border-white/5 rounded-xl overflow-hidden flex flex-col relative">
- {/* Warning when assistant is disabled */}
- {!settings.assistant.enabled && (
- <div className="absolute top-4 left-1/2 transform -translate-x-1/2 z-10">
- <Card className="bg-yellow-500/10 border-yellow-500/20">
- <CardContent className="p-3 flex items-center gap-2">
- <AlertTriangle className="h-4 w-4 text-yellow-500" />
- <span className="text-yellow-500 text-sm font-medium">
- Assistant is disabled. Enable it in Settings &gt; AI
- Assistant.
- </span>
- </CardContent>
- </Card>
- </div>
- )}
-
- {/* Provider offline warning */}
- {settings.assistant.enabled && !isProviderHealthy && (
- <div className="absolute top-4 left-1/2 transform -translate-x-1/2 z-10">
- <Card className="bg-red-500/10 border-red-500/20">
- <CardContent className="p-3 flex items-center gap-2">
- <AlertTriangle className="h-4 w-4 text-red-500" />
- <div className="flex flex-col">
- <span className="text-red-500 text-sm font-medium">
- Assistant is offline
- </span>
- <span className="text-red-400 text-xs">
- {getProviderHelpText()}
- </span>
- </div>
- </CardContent>
- </Card>
- </div>
- )}
-
- {/* Messages Container */}
- <ScrollArea className="flex-1 p-4 lg:p-6" ref={messagesContainerRef}>
- {messages.length === 0 ? (
- <div className="flex flex-col items-center justify-center h-full text-zinc-400 gap-4 mt-8">
- <div className="p-4 bg-zinc-800/50 rounded-full">
- <Bot className="h-12 w-12" />
- </div>
- <h3 className="text-xl font-medium">How can I help you today?</h3>
- <p className="text-center max-w-md text-sm">
- I can analyze your game logs, diagnose crashes, or explain mod
- features.
- {!settings.assistant.enabled && (
- <span className="block mt-2 text-yellow-500">
- Assistant is disabled. Enable it in{" "}
- <button
- type="button"
- onClick={() => setView("settings")}
- className="text-indigo-400 hover:underline"
- >
- Settings &gt; AI Assistant
- </button>
- .
- </span>
- )}
- </p>
- <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-2 max-w-lg">
- <Button
- variant="outline"
- className="text-left h-auto py-3"
- onClick={() =>
- setInput("How do I fix Minecraft crashing on launch?")
- }
- disabled={isProcessing}
- >
- <div className="text-sm">
- How do I fix Minecraft crashing on launch?
- </div>
- </Button>
- <Button
- variant="outline"
- className="text-left h-auto py-3"
- onClick={() =>
- setInput("What's the best way to improve FPS?")
- }
- disabled={isProcessing}
- >
- <div className="text-sm">
- What's the best way to improve FPS?
- </div>
- </Button>
- <Button
- variant="outline"
- className="text-left h-auto py-3"
- onClick={() =>
- setInput(
- "Can you help me install Fabric for Minecraft 1.20.4?",
- )
- }
- disabled={isProcessing}
- >
- <div className="text-sm">
- Can you help me install Fabric for 1.20.4?
- </div>
- </Button>
- <Button
- variant="outline"
- className="text-left h-auto py-3"
- onClick={() =>
- setInput("What mods do you recommend for performance?")
- }
- disabled={isProcessing}
- >
- <div className="text-sm">
- What mods do you recommend for performance?
- </div>
- </Button>
- </div>
- </div>
- ) : (
- <>
- {messages.map((message, index) => renderMessage(message, index))}
- {isProcessing && streamingContent && (
- <div className="flex justify-start mb-4">
- <div className="max-w-[80%] bg-zinc-800 text-zinc-100 rounded-2xl rounded-bl-none px-4 py-3">
- <div
- className="prose prose-invert max-w-none"
- dangerouslySetInnerHTML={{
- __html: renderMarkdown(streamingContent),
- }}
- />
- <div className="flex items-center gap-1 mt-2 text-xs text-zinc-400">
- <Loader2 className="h-3 w-3 animate-spin" />
- <span>Assistant is typing...</span>
- </div>
- </div>
- </div>
- )}
- </>
- )}
- <div ref={messagesEndRef} />
- </ScrollArea>
-
- <Separator />
-
- {/* Input Area */}
- <div className="p-3 lg:p-4">
- <div className="flex gap-2">
- <Textarea
- placeholder={
- settings.assistant.enabled
- ? "Ask about your game..."
- : "Assistant is disabled. Enable it in Settings to use."
- }
- value={input}
- onChange={(e) => setInput(e.target.value)}
- onKeyDown={handleKeyDown}
- className="min-h-11 max-h-50 resize-none border-zinc-700 bg-zinc-900/50 focus:bg-zinc-900/80"
- disabled={!settings.assistant.enabled || isProcessing}
- />
- <Button
- onClick={handleSubmit}
- disabled={
- !settings.assistant.enabled || !input.trim() || isProcessing
- }
- className="px-6 bg-indigo-600 hover:bg-indigo-700 text-white"
- >
- {isProcessing ? (
- <Loader2 className="h-4 w-4 animate-spin" />
- ) : (
- <Send className="h-4 w-4" />
- )}
- </Button>
- </div>
- <div className="mt-2 flex items-center justify-between">
- <div className="text-xs text-zinc-500">
- {settings.assistant.enabled
- ? "Press Enter to send, Shift+Enter for new line"
- : "Enable the assistant in Settings to use"}
- </div>
- <div className="text-xs text-zinc-500">
- Model: {model} • Provider: {provider}
- </div>
- </div>
- </div>
- </div>
- </div>
- );
-}
diff --git a/packages/ui-new/src/pages/home-view.tsx b/packages/ui-new/src/pages/home-view.tsx
deleted file mode 100644
index 4f80cb0..0000000
--- a/packages/ui-new/src/pages/home-view.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-import { useEffect, useState } from "react";
-import { BottomBar } from "@/components/bottom-bar";
-import type { SaturnEffect } from "@/lib/effects/SaturnEffect";
-import { useGameStore } from "../stores/game-store";
-import { useReleasesStore } from "../stores/releases-store";
-
-export function HomeView() {
- const gameStore = useGameStore();
- const releasesStore = useReleasesStore();
- const [mouseX, setMouseX] = useState(0);
- const [mouseY, setMouseY] = useState(0);
-
- useEffect(() => {
- releasesStore.loadReleases();
- }, [releasesStore.loadReleases]);
-
- const handleMouseMove = (e: React.MouseEvent) => {
- const x = (e.clientX / window.innerWidth) * 2 - 1;
- const y = (e.clientY / window.innerHeight) * 2 - 1;
- setMouseX(x);
- setMouseY(y);
-
- // Forward mouse move to SaturnEffect (if available) for parallax/rotation interactions
- try {
- const saturn = (
- window as unknown as {
- getSaturnEffect?: () => SaturnEffect;
- }
- ).getSaturnEffect?.();
- if (saturn?.handleMouseMove) {
- saturn.handleMouseMove(e.clientX);
- }
- } catch {
- /* best-effort, ignore errors from effect */
- }
- };
-
- const handleSaturnMouseDown = (e: React.MouseEvent) => {
- try {
- const saturn = (window as any).getSaturnEffect?.();
- if (saturn?.handleMouseDown) {
- saturn.handleMouseDown(e.clientX);
- }
- } catch {
- /* ignore */
- }
- };
-
- const handleSaturnMouseUp = () => {
- try {
- const saturn = (window as any).getSaturnEffect?.();
- if (saturn?.handleMouseUp) {
- saturn.handleMouseUp();
- }
- } catch {
- /* ignore */
- }
- };
-
- const handleSaturnMouseLeave = () => {
- // Treat leaving the area as mouse-up for the effect
- try {
- const saturn = (window as any).getSaturnEffect?.();
- if (saturn?.handleMouseUp) {
- saturn.handleMouseUp();
- }
- } catch {
- /* ignore */
- }
- };
-
- const handleSaturnTouchStart = (e: React.TouchEvent) => {
- if (e.touches && e.touches.length === 1) {
- try {
- const clientX = e.touches[0].clientX;
- const saturn = (window as any).getSaturnEffect?.();
- if (saturn?.handleTouchStart) {
- saturn.handleTouchStart(clientX);
- }
- } catch {
- /* ignore */
- }
- }
- };
-
- const handleSaturnTouchMove = (e: React.TouchEvent) => {
- if (e.touches && e.touches.length === 1) {
- try {
- const clientX = e.touches[0].clientX;
- const saturn = (window as any).getSaturnEffect?.();
- if (saturn?.handleTouchMove) {
- saturn.handleTouchMove(clientX);
- }
- } catch {
- /* ignore */
- }
- }
- };
-
- const handleSaturnTouchEnd = () => {
- try {
- const saturn = (window as any).getSaturnEffect?.();
- if (saturn?.handleTouchEnd) {
- saturn.handleTouchEnd();
- }
- } catch {
- /* ignore */
- }
- };
-
- return (
- <div
- className="relative z-10 h-full overflow-y-auto custom-scrollbar scroll-smooth"
- style={{
- overflow: releasesStore.isLoading ? "hidden" : "auto",
- }}
- >
- {/* Hero Section (Full Height) - Interactive area */}
- <div
- role="tab"
- className="min-h-full flex flex-col justify-end p-12 pb-32 cursor-grab active:cursor-grabbing select-none"
- onMouseDown={handleSaturnMouseDown}
- onMouseMove={handleMouseMove}
- onMouseUp={handleSaturnMouseUp}
- onMouseLeave={handleSaturnMouseLeave}
- onTouchStart={handleSaturnTouchStart}
- onTouchMove={handleSaturnTouchMove}
- onTouchEnd={handleSaturnTouchEnd}
- tabIndex={0}
- >
- {/* 3D Floating Hero Text */}
- <div
- className="transition-transform duration-200 ease-out origin-bottom-left"
- style={{
- transform: `perspective(1000px) rotateX(${mouseY * -1}deg) rotateY(${mouseX * 1}deg)`,
- }}
- >
- <div className="flex items-center gap-3 mb-6">
- <div className="h-px w-12 bg-white/50"></div>
- <span className="text-xs font-mono font-bold tracking-[0.2em] text-white/50 uppercase">
- Launcher Active
- </span>
- </div>
-
- <h1 className="text-8xl font-black tracking-tighter text-white mb-6 leading-none">
- MINECRAFT
- </h1>
-
- <div className="flex items-center gap-4">
- <div className="bg-white/10 backdrop-blur-md border border-white/10 px-3 py-1 rounded-sm text-xs font-bold uppercase tracking-widest text-white shadow-sm">
- Java Edition
- </div>
- <div className="h-4 w-px bg-white/20"></div>
- <div className="text-sm text-zinc-400">
- Latest Release{" "}
- <span className="text-white font-medium">
- {gameStore.latestRelease?.id || "..."}
- </span>
- </div>
- </div>
- </div>
-
- {/* Action Area */}
- <div className="mt-8 flex gap-4">
- <div className="text-zinc-500 text-sm font-mono">
- &gt; Ready to launch session.
- </div>
- </div>
-
- <BottomBar />
- </div>
- </div>
- );
-}
diff --git a/packages/ui-new/src/pages/index.tsx b/packages/ui-new/src/pages/index.tsx
deleted file mode 100644
index 54cfc1e..0000000
--- a/packages/ui-new/src/pages/index.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { useEffect } from "react";
-import { Outlet, useLocation } from "react-router";
-import { ParticleBackground } from "@/components/particle-background";
-import { Sidebar } from "@/components/sidebar";
-import { useAuthStore } from "@/models/auth";
-import { useSettingsStore } from "@/models/settings";
-
-export function IndexPage() {
- const authStore = useAuthStore();
- const settingsStore = useSettingsStore();
-
- const location = useLocation();
-
- useEffect(() => {
- authStore.init();
- settingsStore.refresh();
- }, [authStore.init, settingsStore.refresh]);
-
- return (
- <div className="relative h-screen w-full overflow-hidden bg-background font-sans">
- <div className="absolute inset-0 z-0 bg-gray-100 dark:bg-[#09090b] overflow-hidden">
- {settingsStore.config?.customBackgroundPath && (
- <>
- <img
- src={settingsStore.config?.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 */}
- <div className="absolute inset-0 bg-black/50" />
- </>
- )}
-
- {!settingsStore.config?.customBackgroundPath && (
- <>
- {settingsStore.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>
- )}
-
- {location.pathname === "/" && <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.config?.theme === "dark" ? "#ffffff" : "#000000"
- } 1px, transparent 1px), linear-gradient(90deg, ${
- settingsStore.config?.theme === "dark" ? "#ffffff" : "#000000"
- } 1px, transparent 1px)`,
- backgroundSize: "40px 40px",
- maskImage:
- "radial-gradient(circle at 50% 50%, black 30%, transparent 70%)",
- }}
- />
- </div>
-
- <div className="size-full flex flex-row p-4 space-x-4 z-20 relative">
- <Sidebar />
-
- <main className="size-full overflow-hidden">
- <Outlet />
- </main>
- </div>
- </div>
- );
-}
diff --git a/packages/ui-new/src/pages/instances-view.tsx b/packages/ui-new/src/pages/instances-view.tsx
deleted file mode 100644
index ad6bd38..0000000
--- a/packages/ui-new/src/pages/instances-view.tsx
+++ /dev/null
@@ -1,315 +0,0 @@
-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 {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { toNumber } from "@/lib/tsrs-utils";
-import { useInstancesStore } from "@/models/instances";
-import type { Instance } from "../types/bindings/instance";
-
-export function InstancesView() {
- const instancesStore = useInstancesStore();
-
- // Modal / UI state
- const [showCreateModal, setShowCreateModal] = useState(false);
- const [showEditModal, setShowEditModal] = useState(false);
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
- const [showDuplicateModal, setShowDuplicateModal] = useState(false);
-
- // Selected / editing instance state
- const [selectedInstance, setSelectedInstance] = useState<Instance | null>(
- null,
- );
- const [editingInstance, setEditingInstance] = useState<Instance | null>(null);
-
- // Form fields
- const [duplicateName, setDuplicateName] = useState("");
-
- useEffect(() => {
- instancesStore.refresh();
- }, [instancesStore.refresh]);
-
- // Handlers to open modals
- const openCreate = () => {
- setShowCreateModal(true);
- };
-
- const openEdit = (instance: Instance) => {
- setEditingInstance({ ...instance });
- setShowEditModal(true);
- };
-
- const openDelete = (instance: Instance) => {
- setSelectedInstance(instance);
- setShowDeleteConfirm(true);
- };
-
- const openDuplicate = (instance: Instance) => {
- setSelectedInstance(instance);
- setDuplicateName(`${instance.name} (Copy)`);
- setShowDuplicateModal(true);
- };
-
- const confirmDelete = async () => {
- if (!selectedInstance) return;
- await instancesStore.delete(selectedInstance.id);
- setSelectedInstance(null);
- setShowDeleteConfirm(false);
- };
-
- const confirmDuplicate = async () => {
- if (!selectedInstance) return;
- const name = duplicateName.trim();
- if (!name) return;
- await instancesStore.duplicate(selectedInstance.id, name);
- setSelectedInstance(null);
- setDuplicateName("");
- setShowDuplicateModal(false);
- };
-
- const formatDate = (timestamp: number): string =>
- new Date(timestamp * 1000).toLocaleDateString();
-
- const formatLastPlayed = (timestamp: number): string => {
- const date = new Date(timestamp * 1000);
- const now = new Date();
- const diff = now.getTime() - date.getTime();
- const days = Math.floor(diff / (1000 * 60 * 60 * 24));
-
- if (days === 0) return "Today";
- if (days === 1) return "Yesterday";
- if (days < 7) return `${days} days ago`;
- return date.toLocaleDateString();
- };
-
- return (
- <div className="h-full flex flex-col gap-4 p-6 overflow-y-auto">
- <div className="flex items-center justify-between">
- <h1 className="text-2xl font-bold text-gray-900 dark:text-white">
- Instances
- </h1>
- <Button
- type="button"
- onClick={openCreate}
- className="px-4 py-2 transition-colors"
- >
- <Plus size={18} />
- Create Instance
- </Button>
- </div>
-
- {instancesStore.instances.length === 0 ? (
- <div className="flex-1 flex items-center justify-center">
- <div className="text-center text-gray-500 dark:text-gray-400">
- <p className="text-lg mb-2">No instances yet</p>
- <p className="text-sm">Create your first instance to get started</p>
- </div>
- </div>
- ) : (
- <ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- {instancesStore.instances.map((instance) => {
- const isActive = instancesStore.activeInstance?.id === instance.id;
-
- return (
- <li
- key={instance.id}
- onClick={() => instancesStore.setActiveInstance(instance)}
- onKeyDown={(e) =>
- e.key === "Enter" &&
- instancesStore.setActiveInstance(instance)
- }
- 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`}
- >
- {/* Instance Icon */}
- {instance.iconPath ? (
- <div className="w-12 h-12 mb-3 rounded overflow-hidden">
- <img
- src={instance.iconPath}
- alt={instance.name}
- className="w-full h-full object-cover"
- />
- </div>
- ) : (
- <div className="w-12 h-12 mb-3 rounded bg-linear-to-br from-blue-500 to-purple-600 flex items-center justify-center">
- <span className="text-white font-bold text-lg">
- {instance.name.charAt(0).toUpperCase()}
- </span>
- </div>
- )}
-
- <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-1">
- {instance.name}
- </h3>
-
- <div className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
- {instance.versionId ? (
- <p className="truncate">Version: {instance.versionId}</p>
- ) : (
- <p className="text-gray-400">No version selected</p>
- )}
-
- {instance.modLoader && (
- <p className="truncate">
- Mod Loader:{" "}
- <span className="capitalize">{instance.modLoader}</span>
- </p>
- )}
-
- <p className="truncate">
- Created: {formatDate(toNumber(instance.createdAt))}
- </p>
-
- {instance.lastPlayed && (
- <p className="truncate">
- Last played:{" "}
- {formatLastPlayed(toNumber(instance.lastPlayed))}
- </p>
- )}
- </div>
-
- {/* Action Buttons */}
- <div className="mt-4 flex gap-2">
- <button
- type="button"
- onClick={(e) => {
- e.stopPropagation();
- openEdit(instance);
- }}
- className="flex-1 flex items-center justify-center gap-1 px-3 py-1.5 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded text-sm transition-colors"
- >
- <Edit2 size={14} />
- Edit
- </button>
-
- <button
- type="button"
- onClick={(e) => {
- e.stopPropagation();
- openDuplicate(instance);
- }}
- className="flex-1 flex items-center justify-center gap-1 px-3 py-1.5 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded text-sm transition-colors"
- >
- <Copy size={14} />
- Duplicate
- </button>
-
- <button
- type="button"
- onClick={(e) => {
- e.stopPropagation();
- openDelete(instance);
- }}
- className="flex-1 flex items-center justify-center gap-1 px-3 py-1.5 bg-red-500 hover:bg-red-600 text-white rounded text-sm transition-colors"
- >
- <Trash2 size={14} />
- Delete
- </button>
- </div>
- </li>
- );
- })}
- </ul>
- )}
-
- <InstanceCreationModal
- open={showCreateModal}
- onOpenChange={setShowCreateModal}
- />
-
- <InstanceEditorModal
- open={showEditModal}
- instance={editingInstance}
- onOpenChange={(open) => {
- setShowEditModal(open);
- if (!open) setEditingInstance(null);
- }}
- />
-
- {/* Delete Confirmation */}
- <Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle>Delete Instance</DialogTitle>
- <DialogDescription>
- Are you sure you want to delete "{selectedInstance?.name}"? This
- action cannot be undone.
- </DialogDescription>
- </DialogHeader>
-
- <DialogFooter>
- <Button
- type="button"
- variant="outline"
- onClick={() => {
- setShowDeleteConfirm(false);
- setSelectedInstance(null);
- }}
- >
- Cancel
- </Button>
- <Button
- type="button"
- onClick={confirmDelete}
- className="bg-red-600 text-white hover:bg-red-500"
- >
- Delete
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
-
- {/* Duplicate Modal */}
- <Dialog open={showDuplicateModal} onOpenChange={setShowDuplicateModal}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle>Duplicate Instance</DialogTitle>
- <DialogDescription>
- Provide a name for the duplicated instance.
- </DialogDescription>
- </DialogHeader>
-
- <div className="mt-4">
- <Input
- value={duplicateName}
- onChange={(e) => setDuplicateName(e.target.value)}
- placeholder="New instance name"
- onKeyDown={(e) => e.key === "Enter" && confirmDuplicate()}
- />
- </div>
-
- <DialogFooter>
- <Button
- type="button"
- variant="outline"
- onClick={() => {
- setShowDuplicateModal(false);
- setSelectedInstance(null);
- setDuplicateName("");
- }}
- >
- Cancel
- </Button>
- <Button
- type="button"
- onClick={confirmDuplicate}
- disabled={!duplicateName.trim()}
- >
- Duplicate
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- </div>
- );
-}
diff --git a/packages/ui-new/src/pages/settings-view.tsx.bk b/packages/ui-new/src/pages/settings-view.tsx.bk
deleted file mode 100644
index ac43d9b..0000000
--- a/packages/ui-new/src/pages/settings-view.tsx.bk
+++ /dev/null
@@ -1,1158 +0,0 @@
-import { open } from "@tauri-apps/plugin-dialog";
-import {
- Coffee,
- Download,
- FileJson,
- Loader2,
- RefreshCw,
- Upload,
-} from "lucide-react";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Checkbox } from "@/components/ui/checkbox";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { Separator } from "@/components/ui/separator";
-import { Switch } from "@/components/ui/switch";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { Textarea } from "@/components/ui/textarea";
-import { useSettingsStore } from "../stores/settings-store";
-
-const effectOptions = [
- { value: "saturn", label: "Saturn" },
- { value: "constellation", label: "Network (Constellation)" },
-];
-
-const logServiceOptions = [
- { value: "paste.rs", label: "paste.rs (Free, No Account)" },
- { value: "pastebin.com", label: "pastebin.com (Requires API Key)" },
-];
-
-const llmProviderOptions = [
- { value: "ollama", label: "Ollama (Local)" },
- { value: "openai", label: "OpenAI (Remote)" },
-];
-
-const languageOptions = [
- { value: "auto", label: "Auto (Match User)" },
- { value: "English", label: "English" },
- { value: "Chinese", label: "中文" },
- { value: "Japanese", label: "日本語" },
- { value: "Korean", label: "한국어" },
- { value: "Spanish", label: "Español" },
- { value: "French", label: "Français" },
- { value: "German", label: "Deutsch" },
- { value: "Russian", label: "Русский" },
-];
-
-const ttsProviderOptions = [
- { value: "disabled", label: "Disabled" },
- { value: "piper", label: "Piper TTS (Local)" },
- { value: "edge", label: "Edge TTS (Online)" },
-];
-
-const personas = [
- {
- value: "default",
- label: "Minecraft Expert (Default)",
- prompt:
- "You are a helpful Minecraft expert assistant. You help players with game issues, mod installation, performance optimization, and gameplay tips. Analyze any game logs provided and give concise, actionable advice.",
- },
- {
- value: "technical",
- label: "Technical Debugger",
- prompt:
- "You are a technical support specialist for Minecraft. Focus strictly on analyzing logs, identifying crash causes, and providing technical solutions. Be precise and avoid conversational filler.",
- },
- {
- value: "concise",
- label: "Concise Helper",
- prompt:
- "You are a direct and concise assistant. Provide answers in as few words as possible while remaining accurate. Use bullet points for lists.",
- },
- {
- value: "explain",
- label: "Teacher / Explainer",
- prompt:
- "You are a patient teacher. Explain Minecraft concepts, redstone mechanics, and mod features in simple, easy-to-understand terms suitable for beginners.",
- },
- {
- value: "pirate",
- label: "Pirate Captain",
- prompt:
- "You are a salty Minecraft Pirate Captain! Yarr! Speak like a pirate while helping the crew (the user) with their blocky adventures. Use terms like 'matey', 'landlubber', and 'treasure'.",
- },
-];
-
-export function SettingsView() {
- const {
- settings,
- backgroundUrl,
- javaInstallations,
- isDetectingJava,
- showJavaDownloadModal,
- selectedDownloadSource,
- javaCatalog,
- isLoadingCatalog,
- catalogError,
- selectedMajorVersion,
- selectedImageType,
- showOnlyRecommended,
- searchQuery,
- isDownloadingJava,
- downloadProgress,
- javaDownloadStatus,
- pendingDownloads,
- ollamaModels,
- openaiModels,
- isLoadingOllamaModels,
- isLoadingOpenaiModels,
- ollamaModelsError,
- openaiModelsError,
- showConfigEditor,
- rawConfigContent,
- configFilePath,
- configEditorError,
- filteredReleases,
- availableMajorVersions,
- installStatus,
- selectedRelease,
- currentModelOptions,
- loadSettings,
- saveSettings,
- detectJava,
- selectJava,
- openJavaDownloadModal,
- closeJavaDownloadModal,
- loadJavaCatalog,
- refreshCatalog,
- loadPendingDownloads,
- selectMajorVersion,
- downloadJava,
- cancelDownload,
- resumeDownloads,
- openConfigEditor,
- closeConfigEditor,
- saveRawConfig,
- loadOllamaModels,
- loadOpenaiModels,
- set,
- setSetting,
- setAssistantSetting,
- setFeatureFlag,
- } = useSettingsStore();
-
- // Mark potentially-unused variables as referenced so TypeScript does not report
- // them as unused in this file (they are part of the store API and used elsewhere).
- // This is a no-op but satisfies the compiler.
- void selectedDownloadSource;
- void javaCatalog;
- void javaDownloadStatus;
- void pendingDownloads;
- void ollamaModels;
- void openaiModels;
- void isLoadingOllamaModels;
- void isLoadingOpenaiModels;
- void ollamaModelsError;
- void openaiModelsError;
- void selectedRelease;
- void loadJavaCatalog;
- void loadPendingDownloads;
- void cancelDownload;
- void resumeDownloads;
- void setFeatureFlag;
- const [selectedPersona, setSelectedPersona] = useState("default");
- const [migrating, setMigrating] = useState(false);
- const [activeTab, setActiveTab] = useState("appearance");
-
- useEffect(() => {
- loadSettings();
- detectJava();
- }, [loadSettings, detectJava]);
-
- useEffect(() => {
- if (activeTab === "assistant") {
- if (settings.assistant.llmProvider === "ollama") {
- loadOllamaModels();
- } else if (settings.assistant.llmProvider === "openai") {
- loadOpenaiModels();
- }
- }
- }, [
- activeTab,
- settings.assistant.llmProvider,
- loadOllamaModels,
- loadOpenaiModels,
- ]);
-
- const handleSelectBackground = async () => {
- try {
- const selected = await open({
- multiple: false,
- filters: [
- {
- name: "Images",
- extensions: ["png", "jpg", "jpeg", "webp", "gif"],
- },
- ],
- });
-
- if (selected && typeof selected === "string") {
- setSetting("customBackgroundPath", selected);
- saveSettings();
- }
- } catch (e) {
- console.error("Failed to select background:", e);
- toast.error("Failed to select background");
- }
- };
-
- const handleClearBackground = () => {
- setSetting("customBackgroundPath", null);
- saveSettings();
- };
-
- const handleApplyPersona = (value: string) => {
- const persona = personas.find((p) => p.value === value);
- if (persona) {
- setAssistantSetting("systemPrompt", persona.prompt);
- setSelectedPersona(value);
- saveSettings();
- }
- };
-
- const handleResetSystemPrompt = () => {
- const defaultPersona = personas.find((p) => p.value === "default");
- if (defaultPersona) {
- setAssistantSetting("systemPrompt", defaultPersona.prompt);
- setSelectedPersona("default");
- saveSettings();
- }
- };
-
- const handleRunMigration = async () => {
- if (migrating) return;
- setMigrating(true);
- try {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- toast.success("Migration complete! Files migrated successfully");
- } catch (e) {
- console.error("Migration failed:", e);
- toast.error(`Migration failed: ${e}`);
- } finally {
- setMigrating(false);
- }
- };
-
- return (
- <div className="h-full flex flex-col p-6 overflow-hidden">
- <div className="flex items-center justify-between mb-6">
- <h2 className="text-3xl font-black bg-clip-text text-transparent bg-linear-to-r dark:from-white dark:to-white/60 from-gray-900 to-gray-600">
- Settings
- </h2>
-
- <Button
- variant="outline"
- size="sm"
- onClick={openConfigEditor}
- className="gap-2"
- >
- <FileJson className="h-4 w-4" />
- <span className="hidden sm:inline">Open JSON</span>
- </Button>
- </div>
-
- <Tabs
- value={activeTab}
- onValueChange={setActiveTab}
- className="flex-1 overflow-hidden"
- >
- <TabsList className="grid grid-cols-4 mb-6">
- <TabsTrigger value="appearance">Appearance</TabsTrigger>
- <TabsTrigger value="java">Java</TabsTrigger>
- <TabsTrigger value="advanced">Advanced</TabsTrigger>
- <TabsTrigger value="assistant">Assistant</TabsTrigger>
- </TabsList>
-
- <ScrollArea className="flex-1 pr-2">
- <TabsContent value="appearance" className="space-y-6">
- <Card className="border-border">
- <CardHeader>
- <CardTitle className="text-lg">Appearance</CardTitle>
- </CardHeader>
- <CardContent className="space-y-6">
- <div>
- <Label className="mb-3">Custom Background Image</Label>
- <div className="flex items-center gap-6">
- <div className="w-40 h-24 rounded-xl overflow-hidden bg-secondary border relative group shadow-lg">
- {backgroundUrl ? (
- <img
- src={backgroundUrl}
- alt="Background Preview"
- className="w-full h-full object-cover"
- onError={(e) => {
- console.error("Failed to load image");
- e.currentTarget.style.display = "none";
- }}
- />
- ) : (
- <div className="w-full h-full bg-linear-to-br from-emerald-900 via-zinc-900 to-indigo-950" />
- )}
- {!backgroundUrl && (
- <div className="absolute inset-0 flex items-center justify-center text-xs text-white/50 bg-black/20">
- Default Gradient
- </div>
- )}
- </div>
-
- <div className="flex flex-col gap-2">
- <Button
- variant="outline"
- onClick={handleSelectBackground}
- >
- Select Image
- </Button>
- {backgroundUrl && (
- <Button
- variant="ghost"
- className="text-red-500"
- onClick={handleClearBackground}
- >
- Reset to Default
- </Button>
- )}
- </div>
- </div>
- <p className="text-sm text-muted-foreground mt-3">
- Select an image from your computer to replace the default
- gradient background.
- </p>
- </div>
-
- <Separator />
-
- <div className="space-y-4">
- <div className="flex items-center justify-between">
- <div>
- <Label className="text-base">Visual Effects</Label>
- <p className="text-sm text-muted-foreground">
- Enable particle effects and animated gradients.
- </p>
- </div>
- <Switch
- checked={settings.enableVisualEffects}
- onCheckedChange={(checked) => {
- setSetting("enableVisualEffects", checked);
- saveSettings();
- }}
- />
- </div>
-
- {settings.enableVisualEffects && (
- <div className="pl-4 border-l-2 border-border">
- <div className="space-y-2">
- <Label>Theme Effect</Label>
- <Select
- value={settings.activeEffect}
- onValueChange={(value) => {
- setSetting("activeEffect", value);
- saveSettings();
- }}
- >
- <SelectTrigger className="w-52">
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- {effectOptions.map((option) => (
- <SelectItem
- key={option.value}
- value={option.value}
- >
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- <p className="text-sm text-muted-foreground">
- Select the active visual theme.
- </p>
- </div>
- </div>
- )}
-
- <div className="flex items-center justify-between">
- <div>
- <Label className="text-base">GPU Acceleration</Label>
- <p className="text-sm text-muted-foreground">
- Enable GPU acceleration for the interface.
- </p>
- </div>
- <Switch
- checked={settings.enableGpuAcceleration}
- onCheckedChange={(checked) => {
- setSetting("enableGpuAcceleration", checked);
- saveSettings();
- }}
- />
- </div>
- </div>
- </CardContent>
- </Card>
- </TabsContent>
-
- <TabsContent value="java" className="space-y-6">
- <Card className="border-border">
- <CardHeader>
- <CardTitle className="text-lg">Java Environment</CardTitle>
- </CardHeader>
- <CardContent className="space-y-6">
- <div>
- <Label className="mb-2">Java Path</Label>
- <div className="flex gap-2">
- <Input
- value={settings.javaPath}
- onChange={(e) => setSetting("javaPath", e.target.value)}
- className="flex-1"
- placeholder="java or full path to java executable"
- />
- <Button
- variant="outline"
- onClick={() => detectJava()}
- disabled={isDetectingJava}
- >
- {isDetectingJava ? (
- <Loader2 className="h-4 w-4 animate-spin" />
- ) : (
- "Detect"
- )}
- </Button>
- </div>
- <p className="text-sm text-muted-foreground mt-2">
- Path to Java executable.
- </p>
- </div>
-
- <div>
- <Label className="mb-2">Memory Settings (MB)</Label>
- <div className="grid grid-cols-2 gap-4">
- <div>
- <Label htmlFor="min-memory" className="text-sm">
- Minimum Memory
- </Label>
- <Input
- id="min-memory"
- type="number"
- value={settings.minMemory}
- onChange={(e) =>
- setSetting(
- "minMemory",
- parseInt(e.target.value, 10) || 1024,
- )
- }
- min={512}
- step={256}
- />
- </div>
- <div>
- <Label htmlFor="max-memory" className="text-sm">
- Maximum Memory
- </Label>
- <Input
- id="max-memory"
- type="number"
- value={settings.maxMemory}
- onChange={(e) =>
- setSetting(
- "maxMemory",
- parseInt(e.target.value, 10) || 2048,
- )
- }
- min={1024}
- step={256}
- />
- </div>
- </div>
- <p className="text-sm text-muted-foreground mt-2">
- Memory allocation for Minecraft.
- </p>
- </div>
-
- <Separator />
-
- <div>
- <div className="flex items-center justify-between mb-4">
- <Label className="text-base">
- Detected Java Installations
- </Label>
- <Button
- variant="outline"
- size="sm"
- onClick={() => detectJava()}
- disabled={isDetectingJava}
- >
- <RefreshCw
- className={`h-4 w-4 mr-2 ${isDetectingJava ? "animate-spin" : ""}`}
- />
- Rescan
- </Button>
- </div>
-
- {javaInstallations.length === 0 ? (
- <div className="text-center py-8 text-muted-foreground border rounded-lg">
- <Coffee className="h-12 w-12 mx-auto mb-4 opacity-30" />
- <p>No Java installations detected</p>
- </div>
- ) : (
- <div className="space-y-2">
- {javaInstallations.map((installation) => (
- <Card
- key={installation.path}
- className={`p-3 cursor-pointer transition-colors ${
- settings.javaPath === installation.path
- ? "border-primary bg-primary/5"
- : ""
- }`}
- onClick={() => selectJava(installation.path)}
- >
- <div className="flex items-center justify-between">
- <div>
- <div className="font-medium flex items-center gap-2">
- <Coffee className="h-4 w-4" />
- {installation.version}
- </div>
- <div className="text-sm text-muted-foreground font-mono">
- {installation.path}
- </div>
- </div>
- {settings.javaPath === installation.path && (
- <div className="h-5 w-5 text-primary">✓</div>
- )}
- </div>
- </Card>
- ))}
- </div>
- )}
-
- <div className="mt-4">
- <Button
- variant="default"
- className="w-full"
- onClick={openJavaDownloadModal}
- >
- <Download className="h-4 w-4 mr-2" />
- Download Java
- </Button>
- </div>
- </div>
- </CardContent>
- </Card>
- </TabsContent>
-
- <TabsContent value="advanced" className="space-y-6">
- <Card className="border-border">
- <CardHeader>
- <CardTitle className="text-lg">Advanced Settings</CardTitle>
- </CardHeader>
- <CardContent className="space-y-6">
- <div>
- <Label className="mb-2">Download Threads</Label>
- <Input
- type="number"
- value={settings.downloadThreads}
- onChange={(e) =>
- setSetting(
- "downloadThreads",
- parseInt(e.target.value, 10) || 32,
- )
- }
- min={1}
- max={64}
- />
- <p className="text-sm text-muted-foreground mt-2">
- Number of concurrent downloads.
- </p>
- </div>
-
- <div>
- <Label className="mb-2">Log Upload Service</Label>
- <Select
- value={settings.logUploadService}
- onValueChange={(value) => {
- setSetting("logUploadService", value as any);
- saveSettings();
- }}
- >
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- {logServiceOptions.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
- {settings.logUploadService === "pastebin.com" && (
- <div>
- <Label className="mb-2">Pastebin API Key</Label>
- <Input
- type="password"
- value={settings.pastebinApiKey || ""}
- onChange={(e) =>
- setSetting("pastebinApiKey", e.target.value || null)
- }
- placeholder="Enter your Pastebin API key"
- />
- </div>
- )}
-
- <Separator />
-
- <div className="space-y-4">
- <div className="flex items-center justify-between">
- <div>
- <Label className="text-base">Use Shared Caches</Label>
- <p className="text-sm text-muted-foreground">
- Share downloaded assets between instances.
- </p>
- </div>
- <Switch
- checked={settings.useSharedCaches}
- onCheckedChange={(checked) => {
- setSetting("useSharedCaches", checked);
- saveSettings();
- }}
- />
- </div>
-
- {!settings.useSharedCaches && (
- <div className="flex items-center justify-between">
- <div>
- <Label className="text-base">
- Keep Legacy Per-Instance Storage
- </Label>
- <p className="text-sm text-muted-foreground">
- Maintain separate cache folders for compatibility.
- </p>
- </div>
- <Switch
- checked={settings.keepLegacyPerInstanceStorage}
- onCheckedChange={(checked) => {
- setSetting("keepLegacyPerInstanceStorage", checked);
- saveSettings();
- }}
- />
- </div>
- )}
-
- {settings.useSharedCaches && (
- <div className="mt-4">
- <Button
- variant="outline"
- className="w-full"
- onClick={handleRunMigration}
- disabled={migrating}
- >
- {migrating ? (
- <Loader2 className="h-4 w-4 mr-2 animate-spin" />
- ) : (
- <Upload className="h-4 w-4 mr-2" />
- )}
- {migrating
- ? "Migrating..."
- : "Migrate to Shared Caches"}
- </Button>
- </div>
- )}
- </div>
- </CardContent>
- </Card>
- </TabsContent>
-
- <TabsContent value="assistant" className="space-y-6">
- <Card className="border-border">
- <CardHeader>
- <CardTitle className="text-lg">AI Assistant</CardTitle>
- </CardHeader>
- <CardContent className="space-y-6">
- <div className="flex items-center justify-between">
- <div>
- <Label className="text-base">Enable Assistant</Label>
- <p className="text-sm text-muted-foreground">
- Enable the AI assistant for help with Minecraft issues.
- </p>
- </div>
- <Switch
- checked={settings.assistant.enabled}
- onCheckedChange={(checked) => {
- setAssistantSetting("enabled", checked);
- saveSettings();
- }}
- />
- </div>
-
- {settings.assistant.enabled && (
- <>
- <div>
- <Label className="mb-2">LLM Provider</Label>
- <Select
- value={settings.assistant.llmProvider}
- onValueChange={(value) => {
- setAssistantSetting("llmProvider", value as any);
- saveSettings();
- }}
- >
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- {llmProviderOptions.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
- <div>
- <Label className="mb-2">Model</Label>
- <Select
- value={
- settings.assistant.llmProvider === "ollama"
- ? settings.assistant.ollamaModel
- : settings.assistant.openaiModel
- }
- onValueChange={(value) => {
- if (settings.assistant.llmProvider === "ollama") {
- setAssistantSetting("ollamaModel", value);
- } else {
- setAssistantSetting("openaiModel", value);
- }
- saveSettings();
- }}
- >
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- {currentModelOptions.map((model) => (
- <SelectItem key={model.value} value={model.value}>
- {model.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
- {settings.assistant.llmProvider === "ollama" && (
- <div>
- <Label className="mb-2">Ollama Endpoint</Label>
- <Input
- value={settings.assistant.ollamaEndpoint}
- onChange={(e) => {
- setAssistantSetting(
- "ollamaEndpoint",
- e.target.value,
- );
- saveSettings();
- }}
- placeholder="http://localhost:11434"
- />
- </div>
- )}
-
- {settings.assistant.llmProvider === "openai" && (
- <>
- <div>
- <Label className="mb-2">OpenAI API Key</Label>
- <Input
- type="password"
- value={settings.assistant.openaiApiKey || ""}
- onChange={(e) => {
- setAssistantSetting(
- "openaiApiKey",
- e.target.value || null,
- );
- saveSettings();
- }}
- placeholder="Enter your OpenAI API key"
- />
- </div>
- <div>
- <Label className="mb-2">OpenAI Endpoint</Label>
- <Input
- value={settings.assistant.openaiEndpoint}
- onChange={(e) => {
- setAssistantSetting(
- "openaiEndpoint",
- e.target.value,
- );
- saveSettings();
- }}
- placeholder="https://api.openai.com/v1"
- />
- </div>
- </>
- )}
-
- <div>
- <Label className="mb-2">Response Language</Label>
- <Select
- value={settings.assistant.responseLanguage}
- onValueChange={(value) => {
- setAssistantSetting("responseLanguage", value);
- saveSettings();
- }}
- >
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- {languageOptions.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
- <div>
- <Label className="mb-2">Assistant Persona</Label>
- <Select
- value={selectedPersona}
- onValueChange={handleApplyPersona}
- >
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- {personas.map((persona) => (
- <SelectItem
- key={persona.value}
- value={persona.value}
- >
- {persona.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- <div className="mt-2">
- <Button
- variant="outline"
- size="sm"
- onClick={handleResetSystemPrompt}
- >
- Reset to Default
- </Button>
- </div>
- </div>
-
- <div>
- <Label className="mb-2">System Prompt</Label>
-
- <Textarea
- value={settings.assistant.systemPrompt}
- onChange={(e) => {
- setAssistantSetting("systemPrompt", e.target.value);
- saveSettings();
- }}
- rows={6}
- className="font-mono text-sm"
- />
- </div>
-
- <div>
- <Label className="mb-2">Text-to-Speech</Label>
-
- <Select
- value={settings.assistant.ttsProvider}
- onValueChange={(value) => {
- setAssistantSetting("ttsProvider", value);
- saveSettings();
- }}
- >
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
-
- <SelectContent>
- {ttsProviderOptions.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- </>
- )}
- </CardContent>
- </Card>
- </TabsContent>
- </ScrollArea>
- </Tabs>
-
- {/* Java Download Modal */}
- <Dialog
- open={showJavaDownloadModal}
- onOpenChange={closeJavaDownloadModal}
- >
- <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden">
- <DialogHeader>
- <DialogTitle>Download Java</DialogTitle>
- <DialogDescription>
- Download and install Java for Minecraft.
- </DialogDescription>
- </DialogHeader>
-
- <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
- <div className="space-y-4">
- <div>
- <Label className="mb-2">Java Version</Label>
- <Select
- value={selectedMajorVersion?.toString() || ""}
- onValueChange={(v) => selectMajorVersion(parseInt(v, 10))}
- >
- <SelectTrigger>
- <SelectValue placeholder="Select version" />
- </SelectTrigger>
- <SelectContent>
- {availableMajorVersions.map((version) => (
- <SelectItem key={version} value={version.toString()}>
- Java {version}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
-
- <div>
- <Label className="mb-2">Type</Label>
- <Select
- value={selectedImageType}
- onValueChange={(v) => set({ selectedImageType: v as any })}
- >
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="jre">JRE (Runtime)</SelectItem>
- <SelectItem value="jdk">JDK (Development)</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="flex items-center space-x-2">
- <Checkbox
- id="recommended"
- checked={showOnlyRecommended}
- onCheckedChange={(checked) =>
- set({ showOnlyRecommended: !!checked })
- }
- />
- <Label htmlFor="recommended">Show only LTS/Recommended</Label>
- </div>
-
- <div>
- <Label className="mb-2">Search</Label>
- <Input
- placeholder="Search versions..."
- value={searchQuery}
- onChange={(e) => set({ searchQuery: e.target.value })}
- />
- </div>
-
- <Button
- variant="outline"
- size="sm"
- onClick={refreshCatalog}
- disabled={isLoadingCatalog}
- >
- <RefreshCw
- className={`h-4 w-4 mr-2 ${isLoadingCatalog ? "animate-spin" : ""}`}
- />
- Refresh Catalog
- </Button>
- </div>
-
- <div className="md:col-span-2">
- <ScrollArea className="h-75 pr-4">
- {isLoadingCatalog ? (
- <div className="flex items-center justify-center h-full">
- <Loader2 className="h-8 w-8 animate-spin" />
- </div>
- ) : catalogError ? (
- <div className="text-red-500 p-4">{catalogError}</div>
- ) : filteredReleases.length === 0 ? (
- <div className="text-muted-foreground p-4 text-center">
- No Java versions found
- </div>
- ) : (
- <div className="space-y-2">
- {filteredReleases.map((release) => {
- const status = installStatus(
- release.majorVersion,
- release.imageType,
- );
- return (
- <Card
- key={`${release.majorVersion}-${release.imageType}`}
- className="p-3 cursor-pointer hover:bg-accent"
- onClick={() =>
- selectMajorVersion(release.majorVersion)
- }
- >
- <div className="flex items-center justify-between">
- <div>
- <div className="font-medium">
- Java {release.majorVersion}{" "}
- {release.imageType.toUpperCase()}
- </div>
- <div className="text-sm text-muted-foreground">
- {release.releaseName} • {release.architecture}{" "}
- {release.architecture}
- </div>
- </div>
- <div className="flex items-center gap-2">
- {release.isLts && (
- <Badge variant="secondary">LTS</Badge>
- )}
- {status === "installed" && (
- <Badge variant="default">Installed</Badge>
- )}
- {status === "available" && (
- <Button
- variant="ghost"
- size="sm"
- onClick={(e) => {
- e.stopPropagation();
- selectMajorVersion(release.majorVersion);
- downloadJava();
- }}
- >
- <Download className="h-3 w-3 mr-1" />
- Download
- </Button>
- )}
- </div>
- </div>
- </Card>
- );
- })}
- </div>
- )}
- </ScrollArea>
- </div>
- </div>
-
- {isDownloadingJava && downloadProgress && (
- <div className="mt-4 p-4 border rounded-lg">
- <div className="flex justify-between items-center mb-2">
- <span className="text-sm font-medium">
- {downloadProgress.fileName}
- </span>
- <span className="text-sm text-muted-foreground">
- {Math.round(downloadProgress.percentage)}%
- </span>
- </div>
- <div className="w-full bg-secondary h-2 rounded-full overflow-hidden">
- <div
- className="bg-primary h-full transition-all duration-300"
- style={{ width: `${downloadProgress.percentage}%` }}
- />
- </div>
- </div>
- )}
-
- <DialogFooter>
- <Button
- variant="outline"
- onClick={closeJavaDownloadModal}
- disabled={isDownloadingJava}
- >
- Cancel
- </Button>
- {selectedMajorVersion && (
- <Button
- onClick={() => downloadJava()}
- disabled={isDownloadingJava}
- >
- {isDownloadingJava ? (
- <>
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
- Downloading...
- </>
- ) : (
- <>
- <Download className="mr-2 h-4 w-4" />
- Download Java {selectedMajorVersion}
- </>
- )}
- </Button>
- )}
- </DialogFooter>
- </DialogContent>
- </Dialog>
-
- {/* Config Editor Modal */}
- <Dialog open={showConfigEditor} onOpenChange={closeConfigEditor}>
- <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden">
- <DialogHeader>
- <DialogTitle>Edit Configuration</DialogTitle>
- <DialogDescription>
- Edit the raw JSON configuration file.
- </DialogDescription>
- </DialogHeader>
-
- <div className="text-sm text-muted-foreground mb-2">
- File: {configFilePath}
- </div>
-
- {configEditorError && (
- <div className="text-red-500 p-3 bg-red-50 dark:bg-red-950/30 rounded-md">
- {configEditorError}
- </div>
- )}
-
- <Textarea
- value={rawConfigContent}
- onChange={(e) => set({ rawConfigContent: e.target.value })}
- className="font-mono text-sm h-100 resize-none"
- spellCheck={false}
- />
-
- <DialogFooter>
- <Button variant="outline" onClick={closeConfigEditor}>
- Cancel
- </Button>
- <Button onClick={() => saveRawConfig()}>Save Changes</Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- </div>
- );
-}
diff --git a/packages/ui-new/src/pages/settings.tsx b/packages/ui-new/src/pages/settings.tsx
deleted file mode 100644
index 440a5dc..0000000
--- a/packages/ui-new/src/pages/settings.tsx
+++ /dev/null
@@ -1,310 +0,0 @@
-import { toNumber } from "es-toolkit/compat";
-import { FileJsonIcon } from "lucide-react";
-import { useEffect, useState } from "react";
-import { migrateSharedCaches } from "@/client";
-import { ConfigEditor } from "@/components/config-editor";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import {
- Field,
- FieldContent,
- FieldDescription,
- FieldGroup,
- FieldLabel,
- FieldLegend,
- FieldSet,
-} from "@/components/ui/field";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { Spinner } from "@/components/ui/spinner";
-import { Switch } from "@/components/ui/switch";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { useSettingsStore } from "@/models/settings";
-
-export type SettingsTab = "general" | "appearance" | "advanced";
-
-export function SettingsPage() {
- const { config, ...settings } = useSettingsStore();
- const [showConfigEditor, setShowConfigEditor] = useState<boolean>(false);
- const [activeTab, setActiveTab] = useState<SettingsTab>("general");
-
- useEffect(() => {
- if (!config) settings.refresh();
- }, [config, settings.refresh]);
-
- const renderScrollArea = () => {
- if (!config) {
- return (
- <div className="size-full justify-center items-center">
- <Spinner />
- </div>
- );
- }
- return (
- <ScrollArea className="size-full pr-2">
- <TabsContent value="general" className="size-full">
- <Card className="size-full">
- <CardHeader>
- <CardTitle className="font-bold text-xl">General</CardTitle>
- </CardHeader>
- <CardContent>
- <FieldGroup>
- <FieldSet>
- <FieldLegend>Window Options</FieldLegend>
- <FieldDescription>
- May not work on some platforms like Linux Niri.
- </FieldDescription>
- <FieldGroup>
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
- <Field>
- <FieldLabel htmlFor="width">
- Window Default Width
- </FieldLabel>
- <Input
- type="number"
- name="width"
- value={config?.width}
- onChange={(e) => {
- settings.merge({
- width: toNumber(e.target.value),
- });
- }}
- onBlur={() => {
- settings.save();
- }}
- min={800}
- max={3840}
- />
- </Field>
- <Field>
- <FieldLabel htmlFor="height">
- Window Default Height
- </FieldLabel>
- <Input
- type="number"
- name="height"
- value={config?.height}
- onChange={(e) => {
- settings.merge({
- height: toNumber(e.target.value),
- });
- }}
- onBlur={() => {
- settings.save();
- }}
- min={600}
- max={2160}
- />
- </Field>
- </div>
- <Field className="flex flex-row items-center justify-between">
- <FieldContent>
- <FieldLabel htmlFor="gpu-acceleration">
- GPU Acceleration
- </FieldLabel>
- <FieldDescription>
- Enable GPU acceleration for the interface.
- </FieldDescription>
- </FieldContent>
- <Switch
- checked={config?.enableGpuAcceleration}
- onCheckedChange={(checked) => {
- settings.merge({
- enableGpuAcceleration: checked,
- });
- settings.save();
- }}
- />
- </Field>
- </FieldGroup>
- </FieldSet>
- <FieldSet>
- <FieldLegend>Network Options</FieldLegend>
- <Field>
- <Label htmlFor="download-threads">Download Threads</Label>
- <Input
- type="number"
- name="download-threads"
- value={config?.downloadThreads}
- onChange={(e) => {
- settings.merge({
- downloadThreads: toNumber(e.target.value),
- });
- }}
- onBlur={() => {
- settings.save();
- }}
- min={1}
- max={64}
- />
- </Field>
- </FieldSet>
- </FieldGroup>
- </CardContent>
- </Card>
- </TabsContent>
- <TabsContent value="java" className="size-full">
- <Card className="size-full">
- <CardHeader>
- <CardTitle className="font-bold text-xl">
- Java Installations
- </CardTitle>
- <CardContent></CardContent>
- </CardHeader>
- </Card>
- </TabsContent>
- <TabsContent value="appearance" className="size-full">
- <Card className="size-full">
- <CardHeader>
- <CardTitle className="font-bold text-xl">Appearance</CardTitle>
- </CardHeader>
- <CardContent>
- <FieldGroup>
- <Field className="flex flex-row">
- <FieldContent>
- <FieldLabel htmlFor="theme">Theme</FieldLabel>
- <FieldDescription>
- Select your prefered theme.
- </FieldDescription>
- </FieldContent>
- <Select
- items={[
- { label: "Dark", value: "dark" },
- { label: "Light", value: "light" },
- { label: "System", value: "system" },
- ]}
- value={config.theme}
- onValueChange={async (value) => {
- if (
- value === "system" ||
- value === "light" ||
- value === "dark"
- ) {
- settings.merge({ theme: value });
- await settings.save();
- settings.applyTheme(value);
- }
- }}
- >
- <SelectTrigger className="w-full max-w-48">
- <SelectValue placeholder="Please select a prefered theme" />
- </SelectTrigger>
- <SelectContent alignItemWithTrigger={false}>
- <SelectGroup>
- <SelectItem value="system">System</SelectItem>
- <SelectItem value="light">Light</SelectItem>
- <SelectItem value="dark">Dark</SelectItem>
- </SelectGroup>
- </SelectContent>
- </Select>
- </Field>
- </FieldGroup>
- </CardContent>
- </Card>
- </TabsContent>
- <TabsContent value="advanced" className="size-full">
- <Card className="size-full">
- <CardHeader>
- <CardTitle className="font-bold text-xl">Advanced</CardTitle>
- </CardHeader>
- <CardContent>
- <FieldGroup>
- <FieldSet>
- <FieldLegend>Advanced Options</FieldLegend>
- <FieldGroup>
- <Field className="flex flex-row items-center justify-between">
- <FieldContent>
- <FieldLabel htmlFor="use-shared-caches">
- Use Shared Caches
- </FieldLabel>
- <FieldDescription>
- Share downloaded assets between instances.
- </FieldDescription>
- </FieldContent>
- <Switch
- checked={config?.useSharedCaches}
- onCheckedChange={async (checked) => {
- checked && (await migrateSharedCaches());
- settings.merge({
- useSharedCaches: checked,
- });
- settings.save();
- }}
- />
- </Field>
- <Field className="flex flex-row items-center justify-between">
- <FieldContent>
- <FieldLabel htmlFor="keep-per-instance-storage">
- Keep Legacy Per-Instance Storage
- </FieldLabel>
- <FieldDescription>
- Maintain separate cache folders for compatibility.
- </FieldDescription>
- </FieldContent>
- <Switch
- checked={config?.keepLegacyPerInstanceStorage}
- onCheckedChange={(checked) => {
- settings.merge({
- keepLegacyPerInstanceStorage: checked,
- });
- settings.save();
- }}
- />
- </Field>
- </FieldGroup>
- </FieldSet>
- </FieldGroup>
- </CardContent>
- </Card>
- </TabsContent>
- </ScrollArea>
- );
- };
-
- return (
- <div className="size-full flex flex-col p-6 space-y-6">
- <div className="flex items-center justify-between">
- <h2 className="text-3xl font-black bg-clip-text text-transparent bg-linear-to-r dark:from-white dark:to-white/60 from-gray-900 to-gray-600">
- Settings
- </h2>
-
- <Button
- variant="outline"
- size="sm"
- onClick={() => setShowConfigEditor(true)}
- >
- <FileJsonIcon />
- <span className="hidden sm:inline">Open JSON</span>
- </Button>
- </div>
-
- <Tabs
- value={activeTab}
- onValueChange={setActiveTab}
- className="size-full flex flex-col gap-6"
- >
- <TabsList>
- <TabsTrigger value="general">General</TabsTrigger>
- <TabsTrigger value="java">Java</TabsTrigger>
- <TabsTrigger value="appearance">Appearance</TabsTrigger>
- <TabsTrigger value="advanced">Advanced</TabsTrigger>
- </TabsList>
- {renderScrollArea()}
- </Tabs>
-
- <ConfigEditor
- open={showConfigEditor}
- onOpenChange={() => setShowConfigEditor(false)}
- />
- </div>
- );
-}
diff --git a/packages/ui-new/src/pages/versions-view.tsx.bk b/packages/ui-new/src/pages/versions-view.tsx.bk
deleted file mode 100644
index d54596d..0000000
--- a/packages/ui-new/src/pages/versions-view.tsx.bk
+++ /dev/null
@@ -1,662 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { Coffee, Loader2, Search, Trash2 } from "lucide-react";
-import { useCallback, useEffect, useState } from "react";
-import { toast } from "sonner";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-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 type { Version } from "../types/bindings/manifest";
-
-interface InstalledModdedVersion {
- id: string;
- javaVersion?: number;
-}
-
-type TypeFilter = "all" | "release" | "snapshot" | "installed";
-
-export function VersionsView() {
- const { versions, selectedVersion, loadVersions, setSelectedVersion } =
- useGameStore();
- const { activeInstance } = useInstancesStore();
-
- const [searchQuery, setSearchQuery] = useState("");
- const [typeFilter, setTypeFilter] = useState<TypeFilter>("all");
- const [installedModdedVersions, setInstalledModdedVersions] = useState<
- InstalledModdedVersion[]
- >([]);
- const [, setIsLoadingModded] = useState(false);
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
- const [versionToDelete, setVersionToDelete] = useState<string | null>(null);
- const [isDeleting, setIsDeleting] = useState(false);
- const [selectedVersionMetadata, setSelectedVersionMetadata] = useState<{
- id: string;
- javaVersion?: number;
- isInstalled: boolean;
- } | null>(null);
- const [isLoadingMetadata, setIsLoadingMetadata] = useState(false);
- const [showModLoaderSelector, setShowModLoaderSelector] = useState(false);
-
- const normalizedQuery = searchQuery.trim().toLowerCase().replace(/。/g, ".");
-
- // Load installed modded versions with Java version info
- const loadInstalledModdedVersions = useCallback(async () => {
- if (!activeInstance) {
- setInstalledModdedVersions([]);
- setIsLoadingModded(false);
- return;
- }
-
- setIsLoadingModded(true);
- try {
- const allInstalled = await invoke<Array<{ id: string; type: string }>>(
- "list_installed_versions",
- { instanceId: activeInstanceId },
- );
-
- const moddedIds = allInstalled
- .filter((v) => v.type === "fabric" || v.type === "forge")
- .map((v) => v.id);
-
- const versionsWithJava = await Promise.all(
- moddedIds.map(async (id) => {
- try {
- const javaVersion = await invoke<number | null>(
- "get_version_java_version",
- {
- instanceId: activeInstanceId,
- versionId: id,
- },
- );
- return {
- id,
- javaVersion: javaVersion ?? undefined,
- };
- } catch (e) {
- console.error(`Failed to get Java version for ${id}:`, e);
- return { id, javaVersion: undefined };
- }
- }),
- );
-
- setInstalledModdedVersions(versionsWithJava);
- } catch (e) {
- console.error("Failed to load installed modded versions:", e);
- toast.error("Error loading modded versions");
- } finally {
- setIsLoadingModded(false);
- }
- }, [activeInstanceId]);
-
- // Combined versions list (vanilla + modded)
- const allVersions = (() => {
- const moddedVersions: Version[] = installedModdedVersions.map((v) => {
- const versionType = v.id.startsWith("fabric-loader-")
- ? "fabric"
- : v.id.includes("-forge-")
- ? "forge"
- : "fabric";
- return {
- id: v.id,
- type: versionType,
- url: "",
- time: "",
- releaseTime: new Date().toISOString(),
- javaVersion: BigInt(v.javaVersion ?? 0),
- isInstalled: true,
- };
- });
- return [...moddedVersions, ...versions];
- })();
-
- // Filter versions based on search and type filter
- const filteredVersions = allVersions.filter((version) => {
- if (typeFilter === "release" && version.type !== "release") return false;
- if (typeFilter === "snapshot" && version.type !== "snapshot") return false;
- if (typeFilter === "installed" && !version.isInstalled) return false;
-
- if (
- normalizedQuery &&
- !version.id.toLowerCase().includes(normalizedQuery)
- ) {
- return false;
- }
-
- return true;
- });
-
- // Get version badge styling
- const getVersionBadge = (type: string) => {
- switch (type) {
- case "release":
- return {
- text: "Release",
- variant: "default" as const,
- className: "bg-emerald-500 hover:bg-emerald-600",
- };
- case "snapshot":
- return {
- text: "Snapshot",
- variant: "secondary" as const,
- className: "bg-amber-500 hover:bg-amber-600",
- };
- case "fabric":
- return {
- text: "Fabric",
- variant: "outline" as const,
- className: "border-indigo-500 text-indigo-700 dark:text-indigo-300",
- };
- case "forge":
- return {
- text: "Forge",
- variant: "outline" as const,
- className: "border-orange-500 text-orange-700 dark:text-orange-300",
- };
- case "modpack":
- return {
- text: "Modpack",
- variant: "outline" as const,
- className: "border-purple-500 text-purple-700 dark:text-purple-300",
- };
- default:
- return {
- text: type,
- variant: "outline" as const,
- className: "border-gray-500 text-gray-700 dark:text-gray-300",
- };
- }
- };
-
- // Load version metadata
- const loadVersionMetadata = useCallback(
- async (versionId: string) => {
- if (!versionId || !activeInstanceId) {
- setSelectedVersionMetadata(null);
- return;
- }
-
- setIsLoadingMetadata(true);
- try {
- const metadata = await invoke<{
- id: string;
- javaVersion?: number;
- isInstalled: boolean;
- }>("get_version_metadata", {
- instanceId: activeInstanceId,
- versionId,
- });
- setSelectedVersionMetadata(metadata);
- } catch (e) {
- console.error("Failed to load version metadata:", e);
- setSelectedVersionMetadata(null);
- } finally {
- setIsLoadingMetadata(false);
- }
- },
- [activeInstanceId],
- );
-
- // Get base version for mod loader selector
- const selectedBaseVersion = (() => {
- if (!selectedVersion) return "";
-
- if (selectedVersion.startsWith("fabric-loader-")) {
- const parts = selectedVersion.split("-");
- return parts[parts.length - 1];
- }
- if (selectedVersion.includes("-forge-")) {
- return selectedVersion.split("-forge-")[0];
- }
-
- const version = versions.find((v) => v.id === selectedVersion);
- return version ? selectedVersion : "";
- })();
-
- // Handle version deletion
- const handleDeleteVersion = async () => {
- if (!versionToDelete || !activeInstanceId) return;
-
- setIsDeleting(true);
- try {
- await invoke("delete_version", {
- instanceId: activeInstanceId,
- versionId: versionToDelete,
- });
-
- if (selectedVersion === versionToDelete) {
- setSelectedVersion("");
- }
-
- setShowDeleteDialog(false);
- setVersionToDelete(null);
- toast.success("Version deleted successfully");
-
- await loadVersions(activeInstanceId);
- await loadInstalledModdedVersions();
- } catch (e) {
- console.error("Failed to delete version:", e);
- toast.error(`Failed to delete version: ${e}`);
- } finally {
- setIsDeleting(false);
- }
- };
-
- // Show delete confirmation dialog
- const showDeleteConfirmation = (versionId: string, e: React.MouseEvent) => {
- e.stopPropagation();
- setVersionToDelete(versionId);
- setShowDeleteDialog(true);
- };
-
- // Setup event listeners for version updates
- useEffect(() => {
- let unlisteners: UnlistenFn[] = [];
-
- const setupEventListeners = async () => {
- try {
- const versionDeletedUnlisten = await listen(
- "version-deleted",
- async () => {
- await loadVersions(activeInstanceId ?? undefined);
- await loadInstalledModdedVersions();
- },
- );
-
- const downloadCompleteUnlisten = await listen(
- "download-complete",
- async () => {
- await loadVersions(activeInstanceId ?? undefined);
- await loadInstalledModdedVersions();
- },
- );
-
- const versionInstalledUnlisten = await listen(
- "version-installed",
- async () => {
- await loadVersions(activeInstanceId ?? undefined);
- await loadInstalledModdedVersions();
- },
- );
-
- const fabricInstalledUnlisten = await listen(
- "fabric-installed",
- async () => {
- await loadVersions(activeInstanceId ?? undefined);
- await loadInstalledModdedVersions();
- },
- );
-
- const forgeInstalledUnlisten = await listen(
- "forge-installed",
- async () => {
- await loadVersions(activeInstanceId ?? undefined);
- await loadInstalledModdedVersions();
- },
- );
-
- unlisteners = [
- versionDeletedUnlisten,
- downloadCompleteUnlisten,
- versionInstalledUnlisten,
- fabricInstalledUnlisten,
- forgeInstalledUnlisten,
- ];
- } catch (e) {
- console.error("Failed to setup event listeners:", e);
- }
- };
-
- setupEventListeners();
- loadInstalledModdedVersions();
-
- return () => {
- unlisteners.forEach((unlisten) => {
- unlisten();
- });
- };
- }, [activeInstanceId, loadVersions, loadInstalledModdedVersions]);
-
- // Load metadata when selected version changes
- useEffect(() => {
- if (selectedVersion) {
- loadVersionMetadata(selectedVersion);
- } else {
- setSelectedVersionMetadata(null);
- }
- }, [selectedVersion, loadVersionMetadata]);
-
- return (
- <div className="h-full flex flex-col p-6 overflow-hidden">
- <div className="flex items-center justify-between mb-6">
- <h2 className="text-3xl font-black bg-clip-text text-transparent bg-linear-to-r from-gray-900 to-gray-600 dark:from-white dark:to-white/60">
- Version Manager
- </h2>
- <div className="text-sm dark:text-white/40 text-black/50">
- Select a version to play or modify
- </div>
- </div>
-
- <div className="flex-1 grid grid-cols-1 lg:grid-cols-3 gap-6 overflow-hidden">
- {/* Left: Version List */}
- <div className="lg:col-span-2 flex flex-col gap-4 overflow-hidden">
- {/* Search and Filters */}
- <div className="flex gap-3">
- <div className="relative flex-1">
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
- <Input
- type="text"
- placeholder="Search versions..."
- className="pl-9 bg-white/60 dark:bg-black/20 border-black/10 dark:border-white/10 dark:text-white text-gray-900 placeholder-black/30 dark:placeholder-white/30 focus:border-indigo-500/50 dark:focus:bg-black/40 focus:bg-white/80 backdrop-blur-sm"
- value={searchQuery}
- onChange={(e) => setSearchQuery(e.target.value)}
- />
- </div>
- </div>
-
- {/* Type Filter Tabs */}
- <Tabs
- value={typeFilter}
- onValueChange={(v) => setTypeFilter(v as TypeFilter)}
- className="w-full"
- >
- <TabsList className="grid grid-cols-4 bg-white/60 dark:bg-black/20 border-black/5 dark:border-white/5">
- <TabsTrigger value="all">All</TabsTrigger>
- <TabsTrigger value="release">Release</TabsTrigger>
- <TabsTrigger value="snapshot">Snapshot</TabsTrigger>
- <TabsTrigger value="installed">Installed</TabsTrigger>
- </TabsList>
- </Tabs>
-
- {/* Version List */}
- <ScrollArea className="flex-1 pr-2">
- {versions.length === 0 ? (
- <div className="flex items-center justify-center h-40 dark:text-white/30 text-black/30 italic animate-pulse">
- Loading versions...
- </div>
- ) : filteredVersions.length === 0 ? (
- <div className="flex flex-col items-center justify-center h-40 dark:text-white/30 text-black/30 gap-2">
- <span className="text-2xl">👻</span>
- <span>No matching versions found</span>
- </div>
- ) : (
- <div className="space-y-2">
- {filteredVersions.map((version) => {
- const badge = getVersionBadge(version.type);
- const isSelected = selectedVersion === version.id;
-
- return (
- <Card
- key={version.id}
- className={`w-full cursor-pointer transition-all duration-200 relative overflow-hidden group ${
- isSelected
- ? "border-indigo-200 dark:border-indigo-500/50 bg-indigo-50 dark:bg-indigo-600/20 shadow-[0_0_20px_rgba(99,102,241,0.2)]"
- : "border-black/5 dark:border-white/5 bg-white/40 dark:bg-white/5 hover:bg-white/60 dark:hover:bg-white/10 hover:border-black/10 dark:hover:border-white/10 hover:translate-x-1"
- }`}
- onClick={() => setSelectedVersion(version.id)}
- >
- {isSelected && (
- <div className="absolute inset-0 bg-linear-to-r from-indigo-500/10 to-transparent pointer-events-none" />
- )}
-
- <CardContent className="p-4">
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-4 flex-1">
- <Badge
- variant={badge.variant}
- className={badge.className}
- >
- {badge.text}
- </Badge>
- <div className="flex-1">
- <div
- className={`font-bold font-mono text-lg tracking-tight ${
- isSelected
- ? "text-black dark:text-white"
- : "text-gray-700 dark:text-zinc-300 group-hover:text-black dark:group-hover:text-white"
- }`}
- >
- {version.id}
- </div>
- <div className="flex items-center gap-2 mt-0.5">
- {version.releaseTime &&
- version.type !== "fabric" &&
- version.type !== "forge" && (
- <div className="text-xs dark:text-white/30 text-black/30">
- {new Date(
- version.releaseTime,
- ).toLocaleDateString()}
- </div>
- )}
- {version.javaVersion && (
- <div className="flex items-center gap-1 text-xs dark:text-white/40 text-black/40">
- <Coffee className="h-3 w-3 opacity-60" />
- <span className="font-medium">
- Java {version.javaVersion}
- </span>
- </div>
- )}
- </div>
- </div>
- </div>
-
- <div className="flex items-center gap-2">
- {version.isInstalled && (
- <Button
- variant="ghost"
- size="icon"
- className="opacity-0 group-hover:opacity-100 transition-opacity text-red-500 dark:text-red-400 hover:bg-red-500/10 dark:hover:bg-red-500/20"
- onClick={(e) =>
- showDeleteConfirmation(version.id, e)
- }
- title="Delete version"
- >
- <Trash2 className="h-4 w-4" />
- </Button>
- )}
- </div>
- </div>
- </CardContent>
- </Card>
- );
- })}
- </div>
- )}
- </ScrollArea>
- </div>
-
- {/* Right: Version Details */}
- <div className="flex flex-col gap-6">
- <Card className="border-black/5 dark:border-white/5 bg-white/40 dark:bg-white/5 backdrop-blur-sm">
- <CardHeader>
- <CardTitle className="text-lg">Version Details</CardTitle>
- </CardHeader>
- <CardContent className="space-y-4">
- {selectedVersion ? (
- <>
- <div>
- <div className="text-sm text-muted-foreground mb-1">
- Selected Version
- </div>
- <div className="font-mono text-xl font-bold">
- {selectedVersion}
- </div>
- </div>
-
- {isLoadingMetadata ? (
- <div className="flex items-center gap-2">
- <Loader2 className="h-4 w-4 animate-spin" />
- <span className="text-sm">Loading metadata...</span>
- </div>
- ) : selectedVersionMetadata ? (
- <div className="space-y-3">
- <div>
- <div className="text-sm text-muted-foreground mb-1">
- Installation Status
- </div>
- <Badge
- variant={
- selectedVersionMetadata.isInstalled
- ? "default"
- : "outline"
- }
- >
- {selectedVersionMetadata.isInstalled
- ? "Installed"
- : "Not Installed"}
- </Badge>
- </div>
-
- {selectedVersionMetadata.javaVersion && (
- <div>
- <div className="text-sm text-muted-foreground mb-1">
- Java Version
- </div>
- <div className="flex items-center gap-2">
- <Coffee className="h-4 w-4" />
- <span>
- Java {selectedVersionMetadata.javaVersion}
- </span>
- </div>
- </div>
- )}
-
- {!selectedVersionMetadata.isInstalled && (
- <Button
- className="w-full"
- onClick={() => setShowModLoaderSelector(true)}
- >
- Install with Mod Loader
- </Button>
- )}
- </div>
- ) : null}
- </>
- ) : (
- <div className="text-center py-8 text-muted-foreground">
- Select a version to view details
- </div>
- )}
- </CardContent>
- </Card>
-
- {/* Mod Loader Installation */}
- {showModLoaderSelector && selectedBaseVersion && (
- <Card className="border-black/5 dark:border-white/5 bg-white/40 dark:bg-white/5 backdrop-blur-sm">
- <CardHeader>
- <CardTitle className="text-lg">Install Mod Loader</CardTitle>
- </CardHeader>
- <CardContent className="space-y-4">
- <div className="text-sm text-muted-foreground">
- Install {selectedBaseVersion} with Fabric or Forge
- </div>
- <div className="flex gap-2">
- <Button
- variant="outline"
- className="flex-1"
- onClick={async () => {
- if (!activeInstanceId) return;
- try {
- await invoke("install_fabric", {
- instanceId: activeInstanceId,
- gameVersion: selectedBaseVersion,
- loaderVersion: "latest",
- });
- toast.success("Fabric installation started");
- setShowModLoaderSelector(false);
- } catch (e) {
- console.error("Failed to install Fabric:", e);
- toast.error(`Failed to install Fabric: ${e}`);
- }
- }}
- >
- Install Fabric
- </Button>
- <Button
- variant="outline"
- className="flex-1"
- onClick={async () => {
- if (!activeInstanceId) return;
- try {
- await invoke("install_forge", {
- instanceId: activeInstanceId,
- gameVersion: selectedBaseVersion,
- installerVersion: "latest",
- });
- toast.success("Forge installation started");
- setShowModLoaderSelector(false);
- } catch (e) {
- console.error("Failed to install Forge:", e);
- toast.error(`Failed to install Forge: ${e}`);
- }
- }}
- >
- Install Forge
- </Button>
- </div>
- <Button
- variant="ghost"
- size="sm"
- onClick={() => setShowModLoaderSelector(false)}
- >
- Cancel
- </Button>
- </CardContent>
- </Card>
- )}
- </div>
- </div>
-
- {/* Delete Confirmation Dialog */}
- <Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle>Delete Version</DialogTitle>
- <DialogDescription>
- Are you sure you want to delete version "{versionToDelete}"? This
- action cannot be undone.
- </DialogDescription>
- </DialogHeader>
- <DialogFooter>
- <Button
- variant="outline"
- onClick={() => {
- setShowDeleteDialog(false);
- setVersionToDelete(null);
- }}
- disabled={isDeleting}
- >
- Cancel
- </Button>
- <Button
- variant="destructive"
- onClick={handleDeleteVersion}
- disabled={isDeleting}
- >
- {isDeleting ? (
- <>
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
- Deleting...
- </>
- ) : (
- "Delete"
- )}
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- </div>
- );
-}
diff --git a/packages/ui-new/src/stores/assistant-store.ts b/packages/ui-new/src/stores/assistant-store.ts
deleted file mode 100644
index 180031b..0000000
--- a/packages/ui-new/src/stores/assistant-store.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { create } from "zustand";
-import type { GenerationStats, StreamChunk } from "@/types/bindings/assistant";
-
-export interface Message {
- role: "user" | "assistant" | "system";
- content: string;
- stats?: GenerationStats;
-}
-
-interface AssistantState {
- // State
- messages: Message[];
- isProcessing: boolean;
- isProviderHealthy: boolean | undefined;
- streamingContent: string;
- initialized: boolean;
- streamUnlisten: UnlistenFn | null;
-
- // Actions
- init: () => Promise<void>;
- checkHealth: () => Promise<void>;
- sendMessage: (
- content: string,
- isEnabled: boolean,
- provider: string,
- endpoint: string,
- ) => Promise<void>;
- finishStreaming: () => void;
- clearHistory: () => void;
- setMessages: (messages: Message[]) => void;
- setIsProcessing: (isProcessing: boolean) => void;
- setIsProviderHealthy: (isProviderHealthy: boolean | undefined) => void;
- setStreamingContent: (streamingContent: string) => void;
-}
-
-export const useAssistantStore = create<AssistantState>((set, get) => ({
- // Initial state
- messages: [],
- isProcessing: false,
- isProviderHealthy: false,
- streamingContent: "",
- initialized: false,
- streamUnlisten: null,
-
- // Actions
- init: async () => {
- const { initialized } = get();
- if (initialized) return;
- set({ initialized: true });
- await get().checkHealth();
- },
-
- checkHealth: async () => {
- try {
- const isHealthy = await invoke<boolean>("assistant_check_health");
- set({ isProviderHealthy: isHealthy });
- } catch (e) {
- console.error("Failed to check provider health:", e);
- set({ isProviderHealthy: false });
- }
- },
-
- finishStreaming: () => {
- const { streamUnlisten } = get();
- set({ isProcessing: false, streamingContent: "" });
-
- if (streamUnlisten) {
- streamUnlisten();
- set({ streamUnlisten: null });
- }
- },
-
- sendMessage: async (content, isEnabled, provider, endpoint) => {
- if (!content.trim()) return;
-
- const { messages } = get();
-
- if (!isEnabled) {
- const newMessage: Message = {
- role: "assistant",
- content: "Assistant is disabled. Enable it in Settings > AI Assistant.",
- };
- set({ messages: [...messages, { role: "user", content }, newMessage] });
- return;
- }
-
- // Add user message
- const userMessage: Message = { role: "user", content };
- const updatedMessages = [...messages, userMessage];
- set({
- messages: updatedMessages,
- isProcessing: true,
- streamingContent: "",
- });
-
- // Add empty assistant message for streaming
- const assistantMessage: Message = { role: "assistant", content: "" };
- const withAssistantMessage = [...updatedMessages, assistantMessage];
- set({ messages: withAssistantMessage });
-
- try {
- // Set up stream listener
- const unlisten = await listen<StreamChunk>(
- "assistant-stream",
- (event) => {
- const chunk = event.payload;
- const currentState = get();
-
- if (chunk.content) {
- const newStreamingContent =
- currentState.streamingContent + chunk.content;
- const currentMessages = [...currentState.messages];
- const lastIdx = currentMessages.length - 1;
-
- if (lastIdx >= 0 && currentMessages[lastIdx].role === "assistant") {
- currentMessages[lastIdx] = {
- ...currentMessages[lastIdx],
- content: newStreamingContent,
- };
- set({
- streamingContent: newStreamingContent,
- messages: currentMessages,
- });
- }
- }
-
- if (chunk.done) {
- const finalMessages = [...currentState.messages];
- const lastIdx = finalMessages.length - 1;
-
- if (
- chunk.stats &&
- lastIdx >= 0 &&
- finalMessages[lastIdx].role === "assistant"
- ) {
- finalMessages[lastIdx] = {
- ...finalMessages[lastIdx],
- stats: chunk.stats,
- };
- set({ messages: finalMessages });
- }
-
- get().finishStreaming();
- }
- },
- );
-
- set({ streamUnlisten: unlisten });
-
- // Start streaming chat
- await invoke<string>("assistant_chat_stream", {
- messages: withAssistantMessage.slice(0, -1), // Exclude the empty assistant message
- });
- } catch (e) {
- console.error("Failed to send message:", e);
- const errorMessage = e instanceof Error ? e.message : String(e);
-
- let helpText = "";
- if (provider === "ollama") {
- helpText = `\n\nPlease ensure Ollama is running at ${endpoint}.`;
- } else if (provider === "openai") {
- helpText = "\n\nPlease check your OpenAI API key in Settings.";
- }
-
- // Update the last message with error
- const currentMessages = [...get().messages];
- const lastIdx = currentMessages.length - 1;
- if (lastIdx >= 0 && currentMessages[lastIdx].role === "assistant") {
- currentMessages[lastIdx] = {
- role: "assistant",
- content: `Error: ${errorMessage}${helpText}`,
- };
- set({ messages: currentMessages });
- }
-
- get().finishStreaming();
- }
- },
-
- clearHistory: () => {
- set({ messages: [], streamingContent: "" });
- },
-
- setMessages: (messages) => {
- set({ messages });
- },
-
- setIsProcessing: (isProcessing) => {
- set({ isProcessing });
- },
-
- setIsProviderHealthy: (isProviderHealthy) => {
- set({ isProviderHealthy });
- },
-
- setStreamingContent: (streamingContent) => {
- set({ streamingContent });
- },
-}));
diff --git a/packages/ui-new/src/stores/auth-store.ts b/packages/ui-new/src/stores/auth-store.ts
deleted file mode 100644
index bf7e3c5..0000000
--- a/packages/ui-new/src/stores/auth-store.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { open } from "@tauri-apps/plugin-shell";
-import { toast } from "sonner";
-import { create } from "zustand";
-import type { Account, DeviceCodeResponse } from "../types/bindings/auth";
-
-interface AuthState {
- // State
- currentAccount: Account | null;
- isLoginModalOpen: boolean;
- isLogoutConfirmOpen: boolean;
- loginMode: "select" | "offline" | "microsoft";
- offlineUsername: string;
- deviceCodeData: DeviceCodeResponse | null;
- msLoginLoading: boolean;
- msLoginStatus: string;
-
- // Private state
- pollInterval: ReturnType<typeof setInterval> | null;
- isPollingRequestActive: boolean;
- authProgressUnlisten: UnlistenFn | null;
-
- // Actions
- checkAccount: () => Promise<void>;
- openLoginModal: () => void;
- openLogoutConfirm: () => void;
- cancelLogout: () => void;
- confirmLogout: () => Promise<void>;
- closeLoginModal: () => void;
- resetLoginState: () => void;
- performOfflineLogin: () => Promise<void>;
- startMicrosoftLogin: () => Promise<void>;
- checkLoginStatus: (deviceCode: string) => Promise<void>;
- stopPolling: () => void;
- cancelMicrosoftLogin: () => void;
- setLoginMode: (mode: "select" | "offline" | "microsoft") => void;
- setOfflineUsername: (username: string) => void;
-}
-
-export const useAuthStore = create<AuthState>((set, get) => ({
- // Initial state
- currentAccount: null,
- isLoginModalOpen: false,
- isLogoutConfirmOpen: false,
- loginMode: "select",
- offlineUsername: "",
- deviceCodeData: null,
- msLoginLoading: false,
- msLoginStatus: "Waiting for authorization...",
-
- // Private state
- pollInterval: null,
- isPollingRequestActive: false,
- authProgressUnlisten: null,
-
- // Actions
- checkAccount: async () => {
- try {
- const acc = await invoke<Account | null>("get_active_account");
- set({ currentAccount: acc });
- } catch (error) {
- console.error("Failed to check account:", error);
- }
- },
-
- openLoginModal: () => {
- const { currentAccount } = get();
- if (currentAccount) {
- // Show custom logout confirmation dialog
- set({ isLogoutConfirmOpen: true });
- return;
- }
- get().resetLoginState();
- set({ isLoginModalOpen: true });
- },
-
- openLogoutConfirm: () => {
- set({ isLogoutConfirmOpen: true });
- },
-
- cancelLogout: () => {
- set({ isLogoutConfirmOpen: false });
- },
-
- confirmLogout: async () => {
- set({ isLogoutConfirmOpen: false });
- try {
- await invoke("logout");
- set({ currentAccount: null });
- } catch (error) {
- console.error("Logout failed:", error);
- }
- },
-
- closeLoginModal: () => {
- get().stopPolling();
- set({ isLoginModalOpen: false });
- },
-
- resetLoginState: () => {
- set({
- loginMode: "select",
- offlineUsername: "",
- deviceCodeData: null,
- msLoginLoading: false,
- msLoginStatus: "Waiting for authorization...",
- });
- },
-
- performOfflineLogin: async () => {
- const { offlineUsername } = get();
- if (!offlineUsername.trim()) return;
-
- try {
- const account = await invoke<Account>("login_offline", {
- username: offlineUsername,
- });
- set({
- currentAccount: account,
- isLoginModalOpen: false,
- offlineUsername: "",
- });
- } catch (error) {
- // Keep UI-friendly behavior consistent with prior code
- alert("Login failed: " + String(error));
- }
- },
-
- startMicrosoftLogin: async () => {
- // Prepare UI state
- set({
- msLoginLoading: true,
- msLoginStatus: "Waiting for authorization...",
- loginMode: "microsoft",
- deviceCodeData: null,
- });
-
- // Listen to general launcher logs so we can display progress to the user.
- // The backend emits logs via "launcher-log"; using that keeps this store decoupled
- // from a dedicated auth event channel (backend may reuse launcher-log).
- try {
- const unlisten = await listen("launcher-log", (event) => {
- const payload = event.payload;
- // Normalize payload to string if possible
- const message =
- typeof payload === "string"
- ? payload
- : (payload?.toString?.() ?? JSON.stringify(payload));
- set({ msLoginStatus: message });
- });
- set({ authProgressUnlisten: unlisten });
- } catch (err) {
- console.warn("Failed to attach launcher-log listener:", err);
- }
-
- try {
- const deviceCodeData = await invoke<DeviceCodeResponse>(
- "start_microsoft_login",
- );
- set({ deviceCodeData });
-
- if (deviceCodeData) {
- // Try to copy user code to clipboard for convenience (best-effort)
- try {
- await navigator.clipboard?.writeText(deviceCodeData.userCode ?? "");
- } catch (err) {
- // ignore clipboard errors
- console.debug("Clipboard copy failed:", err);
- }
-
- // Open verification URI in default browser
- try {
- if (deviceCodeData.verificationUri) {
- await open(deviceCodeData.verificationUri);
- }
- } catch (err) {
- console.debug("Failed to open verification URI:", err);
- }
-
- // Start polling for completion
- // `interval` from the bindings is a bigint (seconds). Convert safely to number.
- const intervalSeconds =
- deviceCodeData.interval !== undefined &&
- deviceCodeData.interval !== null
- ? Number(deviceCodeData.interval)
- : 5;
- const intervalMs = intervalSeconds * 1000;
- const pollInterval = setInterval(
- () => get().checkLoginStatus(deviceCodeData.deviceCode),
- intervalMs,
- );
- set({ pollInterval });
- }
- } catch (error) {
- toast.error(`Failed to start Microsoft login: ${error}`);
- set({ loginMode: "select" });
- // cleanup listener if present
- const { authProgressUnlisten } = get();
- if (authProgressUnlisten) {
- authProgressUnlisten();
- set({ authProgressUnlisten: null });
- }
- } finally {
- set({ msLoginLoading: false });
- }
- },
-
- checkLoginStatus: async (deviceCode: string) => {
- const { isPollingRequestActive } = get();
- if (isPollingRequestActive) return;
-
- set({ isPollingRequestActive: true });
-
- try {
- const account = await invoke<Account>("complete_microsoft_login", {
- deviceCode,
- });
-
- // On success, stop polling and cleanup listener
- get().stopPolling();
- const { authProgressUnlisten } = get();
- if (authProgressUnlisten) {
- authProgressUnlisten();
- set({ authProgressUnlisten: null });
- }
-
- set({
- currentAccount: account,
- isLoginModalOpen: false,
- });
- } catch (error: unknown) {
- const errStr = String(error);
- if (errStr.includes("authorization_pending")) {
- // Still waiting — keep polling
- } else {
- set({ msLoginStatus: "Error: " + errStr });
-
- if (
- errStr.includes("expired_token") ||
- errStr.includes("access_denied")
- ) {
- // Terminal errors — stop polling and reset state
- get().stopPolling();
- const { authProgressUnlisten } = get();
- if (authProgressUnlisten) {
- authProgressUnlisten();
- set({ authProgressUnlisten: null });
- }
- alert("Login failed: " + errStr);
- set({ loginMode: "select" });
- }
- }
- } finally {
- set({ isPollingRequestActive: false });
- }
- },
-
- stopPolling: () => {
- const { pollInterval, authProgressUnlisten } = get();
- if (pollInterval) {
- try {
- clearInterval(pollInterval);
- } catch (err) {
- console.debug("Failed to clear poll interval:", err);
- }
- set({ pollInterval: null });
- }
- if (authProgressUnlisten) {
- try {
- authProgressUnlisten();
- } catch (err) {
- console.debug("Failed to unlisten auth progress:", err);
- }
- set({ authProgressUnlisten: null });
- }
- },
-
- cancelMicrosoftLogin: () => {
- get().stopPolling();
- set({
- deviceCodeData: null,
- msLoginLoading: false,
- msLoginStatus: "",
- loginMode: "select",
- });
- },
-
- setLoginMode: (mode: "select" | "offline" | "microsoft") => {
- set({ loginMode: mode });
- },
-
- setOfflineUsername: (username: string) => {
- set({ offlineUsername: username });
- },
-}));
diff --git a/packages/ui-new/src/stores/game-store.ts b/packages/ui-new/src/stores/game-store.ts
deleted file mode 100644
index fa0f9f8..0000000
--- a/packages/ui-new/src/stores/game-store.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { toast } from "sonner";
-import { create } from "zustand";
-import { getVersions } from "@/client";
-import type { Version } from "@/types/bindings/manifest";
-
-interface GameState {
- // State
- versions: Version[];
- selectedVersion: string;
-
- // Computed property
- latestRelease: Version | undefined;
-
- // Actions
- loadVersions: (instanceId?: string) => Promise<void>;
- startGame: (
- currentAccount: any,
- openLoginModal: () => void,
- activeInstanceId: string | null,
- setView: (view: any) => void,
- ) => Promise<void>;
- setSelectedVersion: (version: string) => void;
- setVersions: (versions: Version[]) => void;
-}
-
-export const useGameStore = create<GameState>((set, get) => ({
- // Initial state
- versions: [],
- selectedVersion: "",
-
- // Computed property
- get latestRelease() {
- return get().versions.find((v) => v.type === "release");
- },
-
- // Actions
- loadVersions: async (instanceId?: string) => {
- console.log("Loading versions for instance:", instanceId);
- 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 getVersions();
- set({ versions: versions ?? [] });
- } catch (e) {
- console.error("Failed to load versions:", e);
- // Keep the store consistent on error by clearing versions.
- set({ versions: [] });
- }
- },
-
- startGame: async (
- currentAccount,
- openLoginModal,
- activeInstanceId,
- setView,
- ) => {
- const { selectedVersion } = get();
-
- if (!currentAccount) {
- alert("Please login first!");
- openLoginModal();
- return;
- }
-
- if (!selectedVersion) {
- alert("Please select a version!");
- return;
- }
-
- if (!activeInstanceId) {
- alert("Please select an instance first!");
- setView("instances");
- return;
- }
-
- toast.info("Preparing to launch " + selectedVersion + "...");
-
- try {
- // Note: In production, this would call Tauri invoke
- // const msg = await invoke<string>("start_game", {
- // instanceId: activeInstanceId,
- // versionId: selectedVersion,
- // });
-
- // Simulate success
- await new Promise((resolve) => setTimeout(resolve, 1000));
- toast.success("Game started successfully!");
- } catch (e) {
- console.error(e);
- toast.error(`Error: ${e}`);
- }
- },
-
- setSelectedVersion: (version: string) => {
- set({ selectedVersion: version });
- },
-
- setVersions: (versions: Version[]) => {
- set({ versions });
- },
-}));
diff --git a/packages/ui-new/src/stores/logs-store.ts b/packages/ui-new/src/stores/logs-store.ts
deleted file mode 100644
index b19f206..0000000
--- a/packages/ui-new/src/stores/logs-store.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import { listen } from "@tauri-apps/api/event";
-import { create } from "zustand";
-
-export interface LogEntry {
- id: number;
- timestamp: string;
- level: "info" | "warn" | "error" | "debug" | "fatal";
- source: string;
- message: string;
-}
-
-// Parse Minecraft/Java log format: [HH:MM:SS] [Thread/LEVEL]: message
-// or: [HH:MM:SS] [Thread/LEVEL] [Source]: message
-const GAME_LOG_REGEX =
- /^\[[\d:]+\]\s*\[([^\]]+)\/(\w+)\](?:\s*\[([^\]]+)\])?:\s*(.*)$/;
-
-function parseGameLogLevel(levelStr: string): LogEntry["level"] {
- const upper = levelStr.toUpperCase();
- if (upper === "INFO") return "info";
- if (upper === "WARN" || upper === "WARNING") return "warn";
- if (upper === "ERROR" || upper === "SEVERE") return "error";
- if (
- upper === "DEBUG" ||
- upper === "TRACE" ||
- upper === "FINE" ||
- upper === "FINER" ||
- upper === "FINEST"
- )
- return "debug";
- if (upper === "FATAL") return "fatal";
- return "info";
-}
-
-interface LogsState {
- // State
- logs: LogEntry[];
- sources: Set<string>;
- nextId: number;
- maxLogs: number;
- initialized: boolean;
-
- // Actions
- addLog: (level: LogEntry["level"], source: string, message: string) => void;
- addGameLog: (rawLine: string, isStderr: boolean) => void;
- clear: () => void;
- exportLogs: (filteredLogs: LogEntry[]) => string;
- init: () => Promise<void>;
- setLogs: (logs: LogEntry[]) => void;
- setSources: (sources: Set<string>) => void;
-}
-
-export const useLogsStore = create<LogsState>((set, get) => ({
- // Initial state
- logs: [],
- sources: new Set(["Launcher"]),
- nextId: 0,
- maxLogs: 5000,
- initialized: false,
-
- // Actions
- addLog: (level, source, message) => {
- const { nextId, logs, maxLogs, sources } = get();
- const now = new Date();
- const timestamp =
- now.toLocaleTimeString() +
- "." +
- now.getMilliseconds().toString().padStart(3, "0");
-
- const newLog: LogEntry = {
- id: nextId,
- timestamp,
- level,
- source,
- message,
- };
-
- const newLogs = [...logs, newLog];
- const newSources = new Set(sources);
-
- // Track source
- if (!newSources.has(source)) {
- newSources.add(source);
- }
-
- // Trim logs if exceeding max
- const trimmedLogs =
- newLogs.length > maxLogs ? newLogs.slice(-maxLogs) : newLogs;
-
- set({
- logs: trimmedLogs,
- sources: newSources,
- nextId: nextId + 1,
- });
- },
-
- addGameLog: (rawLine, isStderr) => {
- const match = rawLine.match(GAME_LOG_REGEX);
-
- if (match) {
- const [, thread, levelStr, extraSource, message] = match;
- const level = parseGameLogLevel(levelStr);
- // Use extraSource if available, otherwise use thread name as source hint
- const source = extraSource || `Game/${thread.split("-")[0]}`;
- get().addLog(level, source, message);
- } else {
- // Fallback: couldn't parse, use stderr as error indicator
- const level = isStderr ? "error" : "info";
- get().addLog(level, "Game", rawLine);
- }
- },
-
- clear: () => {
- set({
- logs: [],
- sources: new Set(["Launcher"]),
- });
- get().addLog("info", "Launcher", "Logs cleared");
- },
-
- exportLogs: (filteredLogs) => {
- return filteredLogs
- .map(
- (l) =>
- `[${l.timestamp}] [${l.source}/${l.level.toUpperCase()}] ${l.message}`,
- )
- .join("\n");
- },
-
- init: async () => {
- const { initialized } = get();
- if (initialized) return;
-
- set({ initialized: true });
-
- // Initial log
- get().addLog("info", "Launcher", "Logs initialized");
-
- // General Launcher Logs
- await listen<string>("launcher-log", (e) => {
- get().addLog("info", "Launcher", e.payload);
- });
-
- // Game Stdout - parse log level
- await listen<string>("game-stdout", (e) => {
- get().addGameLog(e.payload, false);
- });
-
- // Game Stderr - parse log level, default to error
- await listen<string>("game-stderr", (e) => {
- get().addGameLog(e.payload, true);
- });
-
- // Download Events (Summarized)
- await listen("download-start", (e: any) => {
- get().addLog(
- "info",
- "Downloader",
- `Starting batch download of ${e.payload} files...`,
- );
- });
-
- await listen("download-complete", () => {
- get().addLog("info", "Downloader", "All downloads completed.");
- });
-
- // Listen to file download progress to log finished files
- await listen<any>("download-progress", (e) => {
- const p = e.payload;
- if (p.status === "Finished") {
- if (p.file.endsWith(".jar")) {
- get().addLog("info", "Downloader", `Downloaded ${p.file}`);
- }
- }
- });
-
- // Java Download
- await listen<any>("java-download-progress", (e) => {
- const p = e.payload;
- if (p.status === "Downloading" && p.percentage === 0) {
- get().addLog(
- "info",
- "JavaInstaller",
- `Downloading Java: ${p.file_name}`,
- );
- } else if (p.status === "Completed") {
- get().addLog("info", "JavaInstaller", `Java installed: ${p.file_name}`);
- } else if (p.status === "Error") {
- get().addLog("error", "JavaInstaller", `Java download error`);
- }
- });
- },
-
- setLogs: (logs) => {
- set({ logs });
- },
-
- setSources: (sources) => {
- set({ sources });
- },
-}));
diff --git a/packages/ui-new/src/stores/releases-store.ts b/packages/ui-new/src/stores/releases-store.ts
deleted file mode 100644
index 56afa08..0000000
--- a/packages/ui-new/src/stores/releases-store.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { create } from "zustand";
-import type { GithubRelease } from "@/types/bindings/core";
-
-interface ReleasesState {
- // State
- releases: GithubRelease[];
- isLoading: boolean;
- isLoaded: boolean;
- error: string | null;
-
- // Actions
- loadReleases: () => Promise<void>;
- setReleases: (releases: GithubRelease[]) => void;
- setIsLoading: (isLoading: boolean) => void;
- setIsLoaded: (isLoaded: boolean) => void;
- setError: (error: string | null) => void;
-}
-
-export const useReleasesStore = create<ReleasesState>((set, get) => ({
- // Initial state
- releases: [],
- isLoading: false,
- isLoaded: false,
- error: null,
-
- // Actions
- loadReleases: async () => {
- const { isLoaded, isLoading } = get();
-
- // If already loaded or currently loading, skip to prevent duplicate requests
- if (isLoaded || isLoading) return;
-
- set({ isLoading: true, error: null });
-
- try {
- const releases = await invoke<GithubRelease[]>("get_github_releases");
- set({ releases, isLoaded: true });
- } catch (e) {
- const error = e instanceof Error ? e.message : String(e);
- console.error("Failed to load releases:", e);
- set({ error });
- } finally {
- set({ isLoading: false });
- }
- },
-
- setReleases: (releases) => {
- set({ releases });
- },
-
- setIsLoading: (isLoading) => {
- set({ isLoading });
- },
-
- setIsLoaded: (isLoaded) => {
- set({ isLoaded });
- },
-
- setError: (error) => {
- set({ error });
- },
-}));
diff --git a/packages/ui-new/src/stores/settings-store.ts b/packages/ui-new/src/stores/settings-store.ts
deleted file mode 100644
index 0bfc1e1..0000000
--- a/packages/ui-new/src/stores/settings-store.ts
+++ /dev/null
@@ -1,568 +0,0 @@
-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 {
- JavaDownloadProgress,
- PendingJavaDownload,
-} from "../types/bindings/downloader";
-import type {
- JavaCatalog,
- JavaInstallation,
- JavaReleaseInfo,
-} from "../types/bindings/java";
-
-type JavaDownloadSource = "adoptium" | "mojang" | "azul";
-
-/**
- * State shape for settings store.
- *
- * Note: Uses camelCase naming to match ts-rs generated bindings (which now use
- * `serde(rename_all = "camelCase")`). When reading raw binding objects from
- * invoke, convert/mapping should be applied where necessary.
- */
-interface SettingsState {
- // State
- settings: LauncherConfig;
- javaInstallations: JavaInstallation[];
- isDetectingJava: boolean;
- showJavaDownloadModal: boolean;
- selectedDownloadSource: JavaDownloadSource;
- javaCatalog: JavaCatalog | null;
- isLoadingCatalog: boolean;
- catalogError: string;
- selectedMajorVersion: number | null;
- selectedImageType: "jre" | "jdk";
- showOnlyRecommended: boolean;
- searchQuery: string;
- isDownloadingJava: boolean;
- downloadProgress: JavaDownloadProgress | null;
- javaDownloadStatus: string;
- pendingDownloads: PendingJavaDownload[];
- ollamaModels: ModelInfo[];
- openaiModels: ModelInfo[];
- isLoadingOllamaModels: boolean;
- isLoadingOpenaiModels: boolean;
- ollamaModelsError: string;
- openaiModelsError: string;
- showConfigEditor: boolean;
- rawConfigContent: string;
- configFilePath: string;
- configEditorError: string;
-
- // Computed / derived
- backgroundUrl: string | undefined;
- filteredReleases: JavaReleaseInfo[];
- availableMajorVersions: number[];
- installStatus: (
- version: number,
- imageType: string,
- ) => "installed" | "downloading" | "available";
- selectedRelease: JavaReleaseInfo | null;
- currentModelOptions: Array<{
- value: string;
- label: string;
- details?: string;
- }>;
-
- // Actions
- loadSettings: () => Promise<void>;
- saveSettings: () => Promise<void>;
- // compatibility helper to mirror the older set({ key: value }) usage
- set: (patch: Partial<Record<string, unknown>>) => void;
-
- detectJava: () => Promise<void>;
- selectJava: (path: string) => void;
-
- openJavaDownloadModal: () => Promise<void>;
- closeJavaDownloadModal: () => void;
- loadJavaCatalog: (forceRefresh: boolean) => Promise<void>;
- refreshCatalog: () => Promise<void>;
- loadPendingDownloads: () => Promise<void>;
- selectMajorVersion: (version: number) => void;
- downloadJava: () => Promise<void>;
- cancelDownload: () => Promise<void>;
- resumeDownloads: () => Promise<void>;
-
- openConfigEditor: () => Promise<void>;
- closeConfigEditor: () => void;
- saveRawConfig: () => Promise<void>;
-
- loadOllamaModels: () => Promise<void>;
- loadOpenaiModels: () => Promise<void>;
-
- setSetting: <K extends keyof LauncherConfig>(
- key: K,
- value: LauncherConfig[K],
- ) => void;
- setAssistantSetting: <K extends keyof LauncherConfig["assistant"]>(
- key: K,
- value: LauncherConfig["assistant"][K],
- ) => void;
- setFeatureFlag: <K extends keyof LauncherConfig["featureFlags"]>(
- key: K,
- value: LauncherConfig["featureFlags"][K],
- ) => void;
-
- // Private
- progressUnlisten: UnlistenFn | null;
-}
-
-/**
- * Default settings (camelCase) — lightweight defaults used until `get_settings`
- * returns real values.
- */
-const defaultSettings: LauncherConfig = {
- minMemory: 1024,
- maxMemory: 2048,
- javaPath: "java",
- width: 854,
- height: 480,
- downloadThreads: 32,
- enableGpuAcceleration: false,
- enableVisualEffects: true,
- activeEffect: "constellation",
- theme: "dark",
- customBackgroundPath: null,
- logUploadService: "paste.rs",
- pastebinApiKey: null,
- assistant: {
- enabled: true,
- llmProvider: "ollama",
- ollamaEndpoint: "http://localhost:11434",
- ollamaModel: "llama3",
- openaiApiKey: null,
- openaiEndpoint: "https://api.openai.com/v1",
- openaiModel: "gpt-3.5-turbo",
- systemPrompt:
- "You are a helpful Minecraft expert assistant. You help players with game issues, mod installation, performance optimization, and gameplay tips. Analyze any game logs provided and give concise, actionable advice.",
- responseLanguage: "auto",
- ttsEnabled: false,
- ttsProvider: "disabled",
- },
- useSharedCaches: false,
- keepLegacyPerInstanceStorage: true,
- featureFlags: {
- demoUser: false,
- quickPlayEnabled: false,
- quickPlayPath: null,
- quickPlaySingleplayer: true,
- quickPlayMultiplayerServer: null,
- },
-};
-
-export const useSettingsStore = create<SettingsState>((set, get) => ({
- // initial state
- settings: defaultSettings,
- javaInstallations: [],
- isDetectingJava: false,
- showJavaDownloadModal: false,
- selectedDownloadSource: "adoptium",
- javaCatalog: null,
- isLoadingCatalog: false,
- catalogError: "",
- selectedMajorVersion: null,
- selectedImageType: "jre",
- showOnlyRecommended: true,
- searchQuery: "",
- isDownloadingJava: false,
- downloadProgress: null,
- javaDownloadStatus: "",
- pendingDownloads: [],
- ollamaModels: [],
- openaiModels: [],
- isLoadingOllamaModels: false,
- isLoadingOpenaiModels: false,
- ollamaModelsError: "",
- openaiModelsError: "",
- showConfigEditor: false,
- rawConfigContent: "",
- configFilePath: "",
- configEditorError: "",
- progressUnlisten: null,
-
- // derived getters
- get backgroundUrl() {
- const { settings } = get();
- if (settings.customBackgroundPath) {
- return convertFileSrc(settings.customBackgroundPath);
- }
- return undefined;
- },
-
- get filteredReleases() {
- const {
- javaCatalog,
- selectedMajorVersion,
- selectedImageType,
- showOnlyRecommended,
- searchQuery,
- } = get();
-
- if (!javaCatalog) return [];
-
- let releases = javaCatalog.releases;
-
- if (selectedMajorVersion !== null) {
- releases = releases.filter(
- (r) => r.majorVersion === selectedMajorVersion,
- );
- }
-
- releases = releases.filter((r) => r.imageType === selectedImageType);
-
- if (showOnlyRecommended) {
- releases = releases.filter((r) => r.isLts);
- }
-
- if (searchQuery.trim() !== "") {
- const q = searchQuery.toLowerCase();
- releases = releases.filter(
- (r) =>
- r.version.toLowerCase().includes(q) ||
- (r.releaseName ?? "").toLowerCase().includes(q),
- );
- }
-
- // sort newest-first by parsed version number
- return releases.sort((a, b) => {
- const aVer = parseFloat(a.version.split("-")[0]);
- const bVer = parseFloat(b.version.split("-")[0]);
- return bVer - aVer;
- });
- },
-
- get availableMajorVersions() {
- return get().javaCatalog?.availableMajorVersions || [];
- },
-
- installStatus: (version: number, imageType: string) => {
- const {
- javaInstallations,
- pendingDownloads,
- isDownloadingJava,
- downloadProgress,
- } = get();
-
- const installed = javaInstallations.some(
- (inst) => parseInt(inst.version.split(".")[0], 10) === version,
- );
- if (installed) return "installed";
-
- if (
- isDownloadingJava &&
- downloadProgress?.fileName?.includes(`${version}`)
- ) {
- return "downloading";
- }
-
- const pending = pendingDownloads.some(
- (d) => d.majorVersion === version && d.imageType === imageType,
- );
- if (pending) return "downloading";
-
- return "available";
- },
-
- get selectedRelease() {
- const { javaCatalog, selectedMajorVersion, selectedImageType } = get();
- if (!javaCatalog || selectedMajorVersion === null) return null;
- return (
- javaCatalog.releases.find(
- (r) =>
- r.majorVersion === selectedMajorVersion &&
- r.imageType === selectedImageType,
- ) || null
- );
- },
-
- get currentModelOptions() {
- const { settings, ollamaModels, openaiModels } = get();
- const provider = settings.assistant.llmProvider;
- if (provider === "ollama") {
- return ollamaModels.map((m) => ({
- value: m.id,
- label: m.name,
- details: m.details || m.size || "",
- }));
- } else {
- return openaiModels.map((m) => ({
- value: m.id,
- label: m.name,
- details: m.details || "",
- }));
- }
- },
-
- // actions
- loadSettings: async () => {
- try {
- const result = await invoke<LauncherConfig>("get_settings");
- // result already uses camelCase fields from bindings
- set({ settings: result });
-
- // enforce dark theme at app-level if necessary
- if (result.theme !== "dark") {
- const updated = { ...result, theme: "dark" } as LauncherConfig;
- set({ settings: updated });
- await invoke("save_settings", { config: updated });
- }
-
- // ensure customBackgroundPath is undefined rather than null for reactiveness
- if (!result.customBackgroundPath) {
- set((s) => ({
- settings: { ...s.settings, customBackgroundPath: null },
- }));
- }
- } catch (e) {
- console.error("Failed to load settings:", e);
- }
- },
-
- saveSettings: async () => {
- try {
- const { settings } = get();
-
- // Clean up empty strings to null where appropriate
- if ((settings.customBackgroundPath ?? "") === "") {
- set((state) => ({
- settings: { ...state.settings, customBackgroundPath: null },
- }));
- }
-
- await invoke("save_settings", { config: settings });
- toast.success("Settings saved!");
- } catch (e) {
- console.error("Failed to save settings:", e);
- toast.error(`Error saving settings: ${String(e)}`);
- }
- },
-
- set: (patch: Partial<Record<string, unknown>>) => {
- set(patch);
- },
-
- detectJava: async () => {
- set({ isDetectingJava: true });
- try {
- const installs = await invoke<JavaInstallation[]>("detect_java");
- set({ javaInstallations: installs });
- if (installs.length === 0) toast.info("No Java installations found");
- else toast.success(`Found ${installs.length} Java installation(s)`);
- } catch (e) {
- console.error("Failed to detect Java:", e);
- toast.error(`Error detecting Java: ${String(e)}`);
- } finally {
- set({ isDetectingJava: false });
- }
- },
-
- selectJava: (path: string) => {
- set((s) => ({ settings: { ...s.settings, javaPath: path } }));
- },
-
- openJavaDownloadModal: async () => {
- set({
- showJavaDownloadModal: true,
- javaDownloadStatus: "",
- catalogError: "",
- downloadProgress: null,
- });
-
- // attach event listener for download progress
- const state = get();
- if (state.progressUnlisten) {
- state.progressUnlisten();
- }
-
- const unlisten = await listen<JavaDownloadProgress>(
- "java-download-progress",
- (event) => {
- set({ downloadProgress: event.payload });
- },
- );
-
- set({ progressUnlisten: unlisten });
-
- // load catalog and pending downloads
- await get().loadJavaCatalog(false);
- await get().loadPendingDownloads();
- },
-
- closeJavaDownloadModal: () => {
- const { isDownloadingJava, progressUnlisten } = get();
-
- if (!isDownloadingJava) {
- set({ showJavaDownloadModal: false });
- if (progressUnlisten) {
- try {
- progressUnlisten();
- } catch {
- // ignore
- }
- set({ progressUnlisten: null });
- }
- }
- },
-
- loadJavaCatalog: async (forceRefresh: boolean) => {
- set({ isLoadingCatalog: true, catalogError: "" });
- try {
- const cmd = forceRefresh ? "refresh_java_catalog" : "get_java_catalog";
- const result = await invoke<JavaCatalog>(cmd);
- set({ javaCatalog: result, isLoadingCatalog: false });
- } catch (e) {
- console.error("Failed to load Java catalog:", e);
- set({ catalogError: String(e), isLoadingCatalog: false });
- }
- },
-
- refreshCatalog: async () => {
- await get().loadJavaCatalog(true);
- },
-
- loadPendingDownloads: async () => {
- try {
- const pending = await invoke<PendingJavaDownload[]>(
- "get_pending_java_downloads",
- );
- set({ pendingDownloads: pending });
- } catch (e) {
- console.error("Failed to load pending downloads:", e);
- }
- },
-
- selectMajorVersion: (version: number) => {
- set({ selectedMajorVersion: version });
- },
-
- downloadJava: async () => {
- const { selectedMajorVersion, selectedImageType, selectedDownloadSource } =
- get();
- if (!selectedMajorVersion) return;
- set({ isDownloadingJava: true, javaDownloadStatus: "Starting..." });
- try {
- const result = await downloadAdoptiumJava(
- selectedMajorVersion,
- selectedImageType,
- selectedDownloadSource,
- );
- set({
- javaDownloadStatus: `Java ${selectedMajorVersion} download started: ${result.path}`,
- });
- toast.success("Download started");
- } catch (e) {
- console.error("Failed to download Java:", e);
- toast.error(`Failed to start Java download: ${String(e)}`);
- } finally {
- set({ isDownloadingJava: false });
- }
- },
-
- cancelDownload: async () => {
- try {
- await invoke("cancel_java_download");
- toast.success("Cancelled Java download");
- set({ isDownloadingJava: false, javaDownloadStatus: "" });
- } catch (e) {
- console.error("Failed to cancel download:", e);
- toast.error(`Failed to cancel download: ${String(e)}`);
- }
- },
-
- resumeDownloads: async () => {
- try {
- const installed = await invoke<boolean>("resume_java_downloads");
- if (installed) toast.success("Resumed Java downloads");
- else toast.info("No downloads to resume");
- } catch (e) {
- console.error("Failed to resume downloads:", e);
- toast.error(`Failed to resume downloads: ${String(e)}`);
- }
- },
-
- openConfigEditor: async () => {
- try {
- const path = await invoke<string>("get_config_path");
- const content = await invoke<string>("read_config_raw");
- set({
- configFilePath: path,
- rawConfigContent: content,
- showConfigEditor: true,
- });
- } catch (e) {
- console.error("Failed to open config editor:", e);
- set({ configEditorError: String(e) });
- }
- },
-
- closeConfigEditor: () => {
- set({
- showConfigEditor: false,
- rawConfigContent: "",
- configFilePath: "",
- configEditorError: "",
- });
- },
-
- saveRawConfig: async () => {
- try {
- await invoke("write_config_raw", { content: get().rawConfigContent });
- toast.success("Config saved");
- set({ showConfigEditor: false });
- } catch (e) {
- console.error("Failed to save config:", e);
- set({ configEditorError: String(e) });
- toast.error(`Failed to save config: ${String(e)}`);
- }
- },
-
- loadOllamaModels: async () => {
- set({ isLoadingOllamaModels: true, ollamaModelsError: "" });
- try {
- const models = await invoke<ModelInfo[]>("get_ollama_models");
- set({ ollamaModels: models, isLoadingOllamaModels: false });
- } catch (e) {
- console.error("Failed to load Ollama models:", e);
- set({ isLoadingOllamaModels: false, ollamaModelsError: String(e) });
- }
- },
-
- loadOpenaiModels: async () => {
- set({ isLoadingOpenaiModels: true, openaiModelsError: "" });
- try {
- const models = await invoke<ModelInfo[]>("get_openai_models");
- set({ openaiModels: models, isLoadingOpenaiModels: false });
- } catch (e) {
- console.error("Failed to load OpenAI models:", e);
- set({ isLoadingOpenaiModels: false, openaiModelsError: String(e) });
- }
- },
-
- setSetting: (key, value) => {
- set((s) => ({
- settings: { ...s.settings, [key]: value } as unknown as LauncherConfig,
- }));
- },
-
- setAssistantSetting: (key, value) => {
- set((s) => ({
- settings: {
- ...s.settings,
- assistant: { ...s.settings.assistant, [key]: value },
- } as LauncherConfig,
- }));
- },
-
- setFeatureFlag: (key, value) => {
- set((s) => ({
- settings: {
- ...s.settings,
- featureFlags: { ...s.settings.featureFlags, [key]: value },
- } as LauncherConfig,
- }));
- },
-}));
diff --git a/packages/ui-new/src/stores/ui-store.ts b/packages/ui-new/src/stores/ui-store.ts
deleted file mode 100644
index 89b9191..0000000
--- a/packages/ui-new/src/stores/ui-store.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { create } from "zustand";
-
-export type ViewType = "home" | "versions" | "settings" | "guide" | "instances";
-
-interface UIState {
- // State
- currentView: ViewType;
- showConsole: boolean;
- appVersion: string;
-
- // Actions
- toggleConsole: () => void;
- setView: (view: ViewType) => void;
- setAppVersion: (version: string) => void;
-}
-
-export const useUIStore = create<UIState>((set) => ({
- // Initial state
- currentView: "home",
- showConsole: false,
- appVersion: "...",
-
- // Actions
- toggleConsole: () => {
- set((state) => ({ showConsole: !state.showConsole }));
- },
-
- setView: (view: ViewType) => {
- set({ currentView: view });
- },
-
- setAppVersion: (version: string) => {
- set({ appVersion: version });
- },
-}));
-
-// Provide lowercase alias for compatibility with existing imports.
-// Use a function wrapper to ensure the named export exists as a callable value
-// at runtime (some bundlers/tree-shakers may remove simple aliases).
-export function useUiStore() {
- return useUIStore();
-}
diff --git a/packages/ui-new/src/types/bindings/account.ts b/packages/ui-new/src/types/bindings/account.ts
deleted file mode 100644
index 168d138..0000000
--- a/packages/ui-new/src/types/bindings/account.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-import type { OfflineAccount } from "./auth";
-
-export type AccountStorage = { file_path: string };
-
-/**
- * Stored account data for persistence
- */
-export type AccountStore = {
- accounts: Array<StoredAccount>;
- active_account_id: string | null;
-};
-
-export type StoredAccount =
- | ({ type: "Offline" } & OfflineAccount)
- | ({ type: "Microsoft" } & StoredMicrosoftAccount);
-
-/**
- * Microsoft account with refresh token for persistence
- */
-export type StoredMicrosoftAccount = {
- username: string;
- uuid: string;
- access_token: string;
- refresh_token: string | null;
- ms_refresh_token: string | null;
- expires_at: bigint;
-};
diff --git a/packages/ui-new/src/types/bindings/assistant.ts b/packages/ui-new/src/types/bindings/assistant.ts
deleted file mode 100644
index 827f008..0000000
--- a/packages/ui-new/src/types/bindings/assistant.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type GenerationStats = {
- totalDuration: bigint;
- loadDuration: bigint;
- promptEvalCount: bigint;
- promptEvalDuration: bigint;
- evalCount: bigint;
- evalDuration: bigint;
-};
-
-export type Message = { role: string; content: string };
-
-export type ModelInfo = {
- id: string;
- name: string;
- size: string | null;
- details: string | null;
-};
-
-export type StreamChunk = {
- content: string;
- done: boolean;
- stats: GenerationStats | null;
-};
diff --git a/packages/ui-new/src/types/bindings/auth.ts b/packages/ui-new/src/types/bindings/auth.ts
deleted file mode 100644
index 563a924..0000000
--- a/packages/ui-new/src/types/bindings/auth.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type Account =
- | ({ type: "offline" } & OfflineAccount)
- | ({ type: "microsoft" } & MicrosoftAccount);
-
-export type DeviceCodeResponse = {
- userCode: string;
- deviceCode: string;
- verificationUri: string;
- expiresIn: bigint;
- interval: bigint;
- message: string | null;
-};
-
-export type MicrosoftAccount = {
- username: string;
- uuid: string;
- accessToken: string;
- refreshToken: string | null;
- expiresAt: bigint;
-};
-
-export type MinecraftProfile = { id: string; name: string };
-
-export type OfflineAccount = { username: string; uuid: string };
-
-export type TokenResponse = {
- access_token: string;
- refresh_token: string | null;
- expires_in: bigint;
-};
diff --git a/packages/ui-new/src/types/bindings/config.ts b/packages/ui-new/src/types/bindings/config.ts
deleted file mode 100644
index e9de4f5..0000000
--- a/packages/ui-new/src/types/bindings/config.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type AssistantConfig = {
- enabled: boolean;
- llmProvider: string;
- ollamaEndpoint: string;
- ollamaModel: string;
- openaiApiKey: string | null;
- openaiEndpoint: string;
- openaiModel: string;
- systemPrompt: string;
- responseLanguage: string;
- ttsEnabled: boolean;
- ttsProvider: string;
-};
-
-/**
- * Feature-gated arguments configuration
- */
-export type FeatureFlags = {
- /**
- * Demo user: enables demo-related arguments when rules require it
- */
- demoUser: boolean;
- /**
- * Quick Play: enable quick play arguments
- */
- quickPlayEnabled: boolean;
- /**
- * Quick Play singleplayer world path (if provided)
- */
- quickPlayPath: string | null;
- /**
- * Quick Play singleplayer flag
- */
- quickPlaySingleplayer: boolean;
- /**
- * Quick Play multiplayer server address (optional)
- */
- quickPlayMultiplayerServer: string | null;
-};
-
-export type LauncherConfig = {
- minMemory: number;
- maxMemory: number;
- javaPath: string;
- width: number;
- height: number;
- downloadThreads: number;
- customBackgroundPath: string | null;
- enableGpuAcceleration: boolean;
- enableVisualEffects: boolean;
- activeEffect: string;
- theme: string;
- logUploadService: string;
- pastebinApiKey: string | null;
- assistant: AssistantConfig;
- useSharedCaches: boolean;
- keepLegacyPerInstanceStorage: boolean;
- featureFlags: FeatureFlags;
-};
diff --git a/packages/ui-new/src/types/bindings/core.ts b/packages/ui-new/src/types/bindings/core.ts
deleted file mode 100644
index 94e3bde..0000000
--- a/packages/ui-new/src/types/bindings/core.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-/**
- * File information for instance file browser
- */
-export type FileInfo = {
- name: string;
- path: string;
- isDirectory: boolean;
- size: bigint;
- modified: bigint;
-};
-
-export type GithubRelease = {
- tagName: string;
- name: string;
- publishedAt: string;
- body: string;
- htmlUrl: string;
-};
-
-/**
- * Installed version info
- */
-export type InstalledVersion = { id: string; type: string };
-
-/**
- * Migrate instance caches to shared global caches
- */
-export type MigrationResult = {
- movedFiles: number;
- hardlinks: number;
- copies: number;
- savedBytes: bigint;
- savedMb: number;
-};
-
-export type PastebinResponse = { url: string };
-
-/**
- * Version metadata for display in the UI
- */
-export type VersionMetadata = {
- id: string;
- javaVersion: bigint | null;
- isInstalled: boolean;
-};
diff --git a/packages/ui-new/src/types/bindings/downloader.ts b/packages/ui-new/src/types/bindings/downloader.ts
deleted file mode 100644
index f2be278..0000000
--- a/packages/ui-new/src/types/bindings/downloader.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-/**
- * Metadata for resumable downloads stored in .part.meta file
- */
-export type DownloadMetadata = {
- url: string;
- fileName: string;
- totalSize: bigint;
- downloadedBytes: bigint;
- checksum: string | null;
- timestamp: bigint;
- segments: Array<DownloadSegment>;
-};
-
-/**
- * Download queue for persistence
- */
-export type DownloadQueue = { pendingDownloads: Array<PendingJavaDownload> };
-
-/**
- * A download segment for multi-segment parallel downloading
- */
-export type DownloadSegment = {
- start: bigint;
- end: bigint;
- downloaded: bigint;
- completed: boolean;
-};
-
-export type DownloadTask = {
- url: string;
- path: string;
- sha1: string | null;
- sha256: string | null;
-};
-
-/**
- * Progress event for Java download
- */
-export type JavaDownloadProgress = {
- fileName: string;
- downloadedBytes: bigint;
- totalBytes: bigint;
- speedBytesPerSec: bigint;
- etaSeconds: bigint;
- status: string;
- percentage: number;
-};
-
-/**
- * Pending download task for queue persistence
- */
-export type PendingJavaDownload = {
- majorVersion: number;
- imageType: string;
- downloadUrl: string;
- fileName: string;
- fileSize: bigint;
- checksum: string | null;
- installPath: string;
- createdAt: bigint;
-};
-
-export type ProgressEvent = {
- file: string;
- downloaded: bigint;
- total: bigint;
- status: string;
- completedFiles: number;
- totalFiles: number;
- totalDownloadedBytes: bigint;
-};
diff --git a/packages/ui-new/src/types/bindings/fabric.ts b/packages/ui-new/src/types/bindings/fabric.ts
deleted file mode 100644
index 181f8be..0000000
--- a/packages/ui-new/src/types/bindings/fabric.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-/**
- * Represents a Minecraft version supported by Fabric.
- */
-export type FabricGameVersion = { version: string; stable: boolean };
-
-/**
- * Represents a Fabric intermediary mapping version.
- */
-export type FabricIntermediaryVersion = {
- maven: string;
- version: string;
- stable: boolean;
-};
-
-/**
- * Launcher metadata from Fabric Meta API.
- */
-export type FabricLauncherMeta = {
- version: number;
- libraries: FabricLibraries;
- mainClass: FabricMainClass;
-};
-
-/**
- * Libraries required by Fabric loader.
- */
-export type FabricLibraries = {
- client: Array<FabricLibrary>;
- common: Array<FabricLibrary>;
- server: Array<FabricLibrary>;
-};
-
-/**
- * A single Fabric library dependency.
- */
-export type FabricLibrary = { name: string; url: string | null };
-
-/**
- * Represents a combined loader + intermediary version entry.
- */
-export type FabricLoaderEntry = {
- loader: FabricLoaderVersion;
- intermediary: FabricIntermediaryVersion;
- launcherMeta: FabricLauncherMeta;
-};
-
-/**
- * Represents a Fabric loader version from the Meta API.
- */
-export type FabricLoaderVersion = {
- separator: string;
- build: number;
- maven: string;
- version: string;
- stable: boolean;
-};
-
-/**
- * Main class configuration for Fabric.
- * Can be either a struct with client/server fields or a simple string.
- */
-export type FabricMainClass = { client: string; server: string } | string;
-
-/**
- * Information about an installed Fabric version.
- */
-export type InstalledFabricVersion = {
- id: string;
- minecraftVersion: string;
- loaderVersion: string;
- path: string;
-};
diff --git a/packages/ui-new/src/types/bindings/forge.ts b/packages/ui-new/src/types/bindings/forge.ts
deleted file mode 100644
index a9790e7..0000000
--- a/packages/ui-new/src/types/bindings/forge.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-/**
- * Represents a Forge version entry.
- */
-export type ForgeVersion = {
- version: string;
- minecraftVersion: string;
- recommended: boolean;
- latest: boolean;
-};
-
-/**
- * Information about an installed Forge version.
- */
-export type InstalledForgeVersion = {
- id: string;
- minecraftVersion: string;
- forgeVersion: string;
- path: string;
-};
diff --git a/packages/ui-new/src/types/bindings/game-version.ts b/packages/ui-new/src/types/bindings/game-version.ts
deleted file mode 100644
index 1b1c395..0000000
--- a/packages/ui-new/src/types/bindings/game-version.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type Arguments = {
- game: Record<string, unknown>;
- jvm: Record<string, unknown>;
-};
-
-export type AssetIndex = {
- id: string;
- sha1: string;
- size: bigint;
- url: string;
- totalSize: bigint | null;
-};
-
-export type DownloadArtifact = {
- sha1: string | null;
- size: bigint | null;
- url: string;
- path: string | null;
-};
-
-export type Downloads = {
- client: DownloadArtifact;
- server: DownloadArtifact | null;
-};
-
-/**
- * Represents a Minecraft version JSON, supporting both vanilla and modded (Fabric/Forge) formats.
- * Modded versions use `inheritsFrom` to reference a parent vanilla version.
- */
-export type GameVersion = {
- id: string;
- /**
- * Optional for mod loaders that inherit from vanilla
- */
- downloads: Downloads | null;
- /**
- * Optional for mod loaders that inherit from vanilla
- */
- assetIndex: AssetIndex | null;
- libraries: Array<Library>;
- mainClass: string;
- minecraftArguments: string | null;
- arguments: Arguments | null;
- javaVersion: JavaVersion | null;
- /**
- * For mod loaders: the vanilla version this inherits from
- */
- inheritsFrom: string | null;
- /**
- * Fabric/Forge may specify a custom assets version
- */
- assets: string | null;
- /**
- * Release type (release, snapshot, old_beta, etc.)
- */
- type: string | null;
-};
-
-export type JavaVersion = { component: string; majorVersion: bigint };
-
-export type Library = {
- downloads: LibraryDownloads | null;
- name: string;
- rules: Array<Rule> | null;
- natives: Record<string, unknown>;
- /**
- * Maven repository URL for mod loader libraries
- */
- url: string | null;
-};
-
-export type LibraryDownloads = {
- artifact: DownloadArtifact | null;
- classifiers: Record<string, unknown>;
-};
-
-export type OsRule = {
- name: string | null;
- version: string | null;
- arch: string | null;
-};
-
-export type Rule = {
- action: string;
- os: OsRule | null;
- features: Record<string, unknown>;
-};
diff --git a/packages/ui-new/src/types/bindings/index.ts b/packages/ui-new/src/types/bindings/index.ts
deleted file mode 100644
index 9bde037..0000000
--- a/packages/ui-new/src/types/bindings/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export * from "./account";
-export * from "./assistant";
-export * from "./auth";
-export * from "./config";
-export * from "./core";
-export * from "./downloader";
-export * from "./fabric";
-export * from "./forge";
-export * from "./game-version";
-export * from "./instance";
-export * from "./java";
-export * from "./manifest";
diff --git a/packages/ui-new/src/types/bindings/instance.ts b/packages/ui-new/src/types/bindings/instance.ts
deleted file mode 100644
index 2c4f8ae..0000000
--- a/packages/ui-new/src/types/bindings/instance.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-/**
- * Represents a game instance/profile
- */
-export type Instance = {
- id: string;
- name: string;
- gameDir: string;
- versionId: string | null;
- createdAt: bigint;
- lastPlayed: bigint | null;
- iconPath: string | null;
- notes: string | null;
- modLoader: string | null;
- modLoaderVersion: string | null;
- jvmArgsOverride: string | null;
- memoryOverride: MemoryOverride | null;
- javaPathOverride: string | null;
-};
-
-/**
- * Configuration for all instances
- */
-export type InstanceConfig = {
- instances: Array<Instance>;
- activeInstanceId: string | null;
-};
-
-/**
- * Memory settings override for an instance
- */
-export type MemoryOverride = { min: number; max: number };
diff --git a/packages/ui-new/src/types/bindings/java/core.ts b/packages/ui-new/src/types/bindings/java/core.ts
deleted file mode 100644
index 099dea9..0000000
--- a/packages/ui-new/src/types/bindings/java/core.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type JavaCatalog = {
- releases: Array<JavaReleaseInfo>;
- availableMajorVersions: Array<number>;
- ltsVersions: Array<number>;
- cachedAt: bigint;
-};
-
-export type JavaDownloadInfo = {
- version: string;
- release_name: string;
- download_url: string;
- file_name: string;
- file_size: bigint;
- checksum: string | null;
- image_type: string;
-};
-
-export type JavaInstallation = {
- path: string;
- version: string;
- arch: string;
- vendor: string;
- source: string;
- is64bit: boolean;
-};
-
-export type JavaReleaseInfo = {
- majorVersion: number;
- imageType: string;
- version: string;
- releaseName: string;
- releaseDate: string | null;
- fileSize: bigint;
- checksum: string | null;
- downloadUrl: string;
- isLts: boolean;
- isAvailable: boolean;
- architecture: string;
-};
diff --git a/packages/ui-new/src/types/bindings/java/index.ts b/packages/ui-new/src/types/bindings/java/index.ts
deleted file mode 100644
index 2f2754c..0000000
--- a/packages/ui-new/src/types/bindings/java/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from "./core";
-export * from "./persistence";
-export * from "./providers";
diff --git a/packages/ui-new/src/types/bindings/java/persistence.ts b/packages/ui-new/src/types/bindings/java/persistence.ts
deleted file mode 100644
index 7a2b576..0000000
--- a/packages/ui-new/src/types/bindings/java/persistence.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type JavaConfig = {
- user_defined_paths: Array<string>;
- preferred_java_path: string | null;
- last_detection_time: bigint;
-};
diff --git a/packages/ui-new/src/types/bindings/java/providers/adoptium.ts b/packages/ui-new/src/types/bindings/java/providers/adoptium.ts
deleted file mode 100644
index 65fc42b..0000000
--- a/packages/ui-new/src/types/bindings/java/providers/adoptium.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type AdoptiumAsset = {
- binary: AdoptiumBinary;
- release_name: string;
- version: AdoptiumVersionData;
-};
-
-export type AdoptiumBinary = {
- os: string;
- architecture: string;
- image_type: string;
- package: AdoptiumPackage;
- updated_at: string | null;
-};
-
-export type AdoptiumPackage = {
- name: string;
- link: string;
- size: bigint;
- checksum: string | null;
-};
-
-export type AdoptiumVersionData = {
- major: number;
- minor: number;
- security: number;
- semver: string;
- openjdk_version: string;
-};
-
-export type AvailableReleases = {
- available_releases: Array<number>;
- available_lts_releases: Array<number>;
- most_recent_lts: number | null;
- most_recent_feature_release: number | null;
-};
diff --git a/packages/ui-new/src/types/bindings/java/providers/index.ts b/packages/ui-new/src/types/bindings/java/providers/index.ts
deleted file mode 100644
index 3e28711..0000000
--- a/packages/ui-new/src/types/bindings/java/providers/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./adoptium";
diff --git a/packages/ui-new/src/types/bindings/manifest.ts b/packages/ui-new/src/types/bindings/manifest.ts
deleted file mode 100644
index 2180962..0000000
--- a/packages/ui-new/src/types/bindings/manifest.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
-
-export type Latest = { release: string; snapshot: string };
-
-export type Version = {
- id: string;
- type: string;
- url: string;
- time: string;
- releaseTime: string;
- /**
- * Java version requirement (major version number)
- * This is populated from the version JSON file if the version is installed locally
- */
- javaVersion: bigint | null;
- /**
- * Whether this version is installed locally
- */
- isInstalled: boolean | null;
-};
-
-export type VersionManifest = { latest: Latest; versions: Array<Version> };
diff --git a/packages/ui-new/src/types/index.ts b/packages/ui-new/src/types/index.ts
deleted file mode 100644
index 9e592d7..0000000
--- a/packages/ui-new/src/types/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./bindings";