diff --git a/.minecraft/instances/123/instance.json b/.minecraft/instances/123/instance.json new file mode 100644 index 0000000..ba8a8a7 --- /dev/null +++ b/.minecraft/instances/123/instance.json @@ -0,0 +1,12 @@ +{ + "name": "123", + "author": "", + "description": "123", + "runtime": { + "minecraft": "1.21.4" + }, + "creationDate": 1785342079436, + "lastAccessDate": 1785342079436, + "lastPlayedDate": 0, + "playtime": 0 +} \ No newline at end of file diff --git a/Koring.yml b/Koring.yml index 055ce30..e69de29 100644 --- a/Koring.yml +++ b/Koring.yml @@ -1 +0,0 @@ -oobe: false diff --git a/electron/config.ts b/electron/config.ts index add8ae5..6658e26 100644 --- a/electron/config.ts +++ b/electron/config.ts @@ -70,6 +70,18 @@ export interface SecurityIdConfig { authUrl: string; } +export interface InstanceMeta { + name: string; + displayName: string; + icon: string; + gameVersion: string; + loader: string; + loaderVersion: string; + createdAt: number; + lastPlayed: number; + playtime: number; +} + export interface NetworkConfig { securityId: SecurityIdConfig; } @@ -85,6 +97,7 @@ export interface AppConfig { advanced: AdvancedConfig; download: DownloadConfig; network: NetworkConfig; + instances: InstanceMeta[]; } const DEFAULTS: AppConfig = { @@ -98,6 +111,7 @@ const DEFAULTS: AppConfig = { advanced: { afterLaunch: 'close', winMode: 'default', customWidth: 854, customHeight: 480, gameArgs: '', preLaunchCmd: '', debugMode: false }, download: { fileSource: 'mirror', versionSource: 'mirror', threads: 16, speedLimit: 0 }, network: { securityId: { enabled: false, authUrl: '' } }, + instances: [], }; function migrate(config: AppConfig): AppConfig { diff --git a/src/api/config.ts b/src/api/config.ts index 2dc8056..fe517a0 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -60,6 +60,18 @@ export interface NetworkConfig { securityId: SecurityIdConfig; } +export interface InstanceMeta { + name: string; + displayName: string; + icon: string; + gameVersion: string; + loader: string; + loaderVersion: string; + createdAt: number; + lastPlayed: number; + playtime: number; +} + export interface AppConfig { version: number; oobe: boolean; @@ -71,6 +83,7 @@ export interface AppConfig { advanced: AdvancedConfig; download: DownloadConfig; network: NetworkConfig; + instances: InstanceMeta[]; } interface CommandResult { diff --git a/src/components/VersionCard.tsx b/src/components/VersionCard.tsx index 48fd47f..d530803 100644 --- a/src/components/VersionCard.tsx +++ b/src/components/VersionCard.tsx @@ -3,8 +3,9 @@ import { VERSION } from "@/lib/version"; import { useUpdateStore } from "@/stores/updateStore"; import { relaunchApp } from "@/api/update"; import Silk from "@/components/silk/Silk"; -import { Button } from "@/components/ui/button"; +import { Button } from "@heroui/react"; import clsx from "clsx"; +import { Suspense } from "react"; const modeColors: Record = { dev: "#F59E0B", @@ -12,6 +13,12 @@ const modeColors: Record = { run: "#3B82F6", }; +const modeGradients: Record = { + dev: "linear-gradient(135deg, #F59E0B, #D97706)", + beta: "linear-gradient(135deg, #10B981, #059669)", + run: "linear-gradient(135deg, #3B82F6, #2563EB)", +}; + const modeLabels: Record = { dev: "开发版", beta: "测试版", @@ -22,124 +29,88 @@ type UpdateState = "latest" | "hasUpdate" | "installed"; interface VersionCardProps { className?: string; - /** 开发者模式:覆盖颜色 */ overrideMode?: string | null; - /** 开发者模式:覆盖更新状态 */ overrideState?: UpdateState | null; - /** 开发者模式:遮罩透明度 (0-100) */ - overlayOpacity?: number; - /** 开发者模式:模糊强度 (px) */ - blurAmount?: number; + simple?: boolean; } export function VersionCard({ className, overrideMode, overrideState, - overlayOpacity = 30, - blurAmount = 12, + simple = false, }: VersionCardProps) { const color = modeColors[overrideMode ?? BUILD_MODE] ?? modeColors.run; + const gradient = modeGradients[overrideMode ?? BUILD_MODE] ?? modeGradients.run; const label = modeLabels[overrideMode ?? BUILD_MODE] ?? modeLabels.run; const { checking, downloading, installed, update, check, install } = useUpdateStore(); const effectiveState: UpdateState = overrideState ?? (installed ? "installed" : update ? "hasUpdate" : "latest"); - return ( -
- {/* Silk 背景 */} -
- -
+ const Btn = (props: React.ComponentProps) => ( + - + + 查看亮点 )} {effectiveState === "hasUpdate" && ( <> - - 有新的版本可用 - - - + )} {effectiveState === "installed" && ( <> - - 更新已下载 - - + 更新已下载 + 立即更新 )}
diff --git a/src/components/setting/ErrorBoundary.tsx b/src/components/setting/ErrorBoundary.tsx new file mode 100644 index 0000000..bcb563d --- /dev/null +++ b/src/components/setting/ErrorBoundary.tsx @@ -0,0 +1,38 @@ +import { Component, type ReactNode } from "react"; +import { Button } from "@heroui/react"; +import { AlertTriangle } from "lucide-react"; + +interface Props { children: ReactNode; } +interface State { hasError: boolean; errorMsg: string; } + +export class ErrorBoundary extends Component { + state: State = { hasError: false, errorMsg: "" }; + + static getDerivedStateFromError(error: Error) { + return { hasError: true, errorMsg: error.message }; + } + + render() { + if (this.state.hasError) { + return ( +
+ +

+ 页面渲染出错,请尝试刷新 +

+

+ {this.state.errorMsg} +

+ +
+ ); + } + return this.props.children; + } +} diff --git a/src/components/setting/Surface.tsx b/src/components/setting/Surface.tsx new file mode 100644 index 0000000..78e7af1 --- /dev/null +++ b/src/components/setting/Surface.tsx @@ -0,0 +1,75 @@ +import { cn } from "@/lib/utils" + +interface SurfaceProps extends React.HTMLAttributes { + variant?: "raised" | "flat" | "bordered" + frost?: "none" | "sm" | "md" | "lg" + padding?: "none" | "sm" | "md" | "lg" +} + +const frostMap = { + none: "", + sm: "backdrop-blur-[6px]", + md: "backdrop-blur-[12px]", + lg: "backdrop-blur-[20px]", +} + +const paddingMap = { + none: "", + sm: "px-3 py-2.5", + md: "px-5 py-4", + lg: "px-6 py-5", +} + +function Surface({ + variant = "raised", + frost = "none", + padding = "md", + className, + children, + ...props +}: SurfaceProps) { + return ( +
+ {children} +
+ ) +} + +function SurfaceHeader({ className, children, ...props }: React.HTMLAttributes) { + return ( +
+ {children} +
+ ) +} + +function SurfaceContent({ className, children, ...props }: React.HTMLAttributes) { + return ( +
+ {children} +
+ ) +} + +export { Surface, SurfaceHeader, SurfaceContent } diff --git a/src/components/setting/index.ts b/src/components/setting/index.ts index 1cb71a0..10f37ee 100644 --- a/src/components/setting/index.ts +++ b/src/components/setting/index.ts @@ -1,3 +1,5 @@ export { SettingCard } from "./SettingCard"; export { SettingRow } from "./SettingRow"; export { PageHeader, SectionTitle } from "./SectionTitle"; +export { Surface, SurfaceHeader, SurfaceContent } from "./Surface"; +export { Skeleton } from "@/components/ui/skeleton"; \ No newline at end of file diff --git a/src/components/silk/Silk.tsx b/src/components/silk/Silk.tsx index 9fa9e36..6ff1a75 100644 --- a/src/components/silk/Silk.tsx +++ b/src/components/silk/Silk.tsx @@ -126,8 +126,8 @@ const Silk = ({ speed = 5, scale = 1, color = "#7B7481", noiseIntensity = 1.5, r diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..9191632 --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,17 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ className, ...props }: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/pages/gallery/CreateDialog.tsx b/src/pages/gallery/CreateDialog.tsx new file mode 100644 index 0000000..f81d158 --- /dev/null +++ b/src/pages/gallery/CreateDialog.tsx @@ -0,0 +1,189 @@ +import { useState, useEffect } from "react"; +import { Button, Input, RadioGroup, Radio, Slider } from "@heroui/react"; +import { X, Loader2, Plus } from "lucide-react"; +import { getMinecraftVersionList, getFabricVersionList, getForgeVersionList, getQuiltVersionList, type InstanceRuntime } from "@/api/instance"; + +interface CreateDialogProps { + isOpen: boolean; + onClose: () => void; + onCreate: (name: string, runtime: InstanceRuntime, displayName?: string) => Promise; +} + +const loaderOptions = [ + { value: "vanilla", label: "原版 (Vanilla)", desc: "纯净 Minecraft,无模组加载器" }, + { value: "forge", label: "Forge", desc: "最经典的模组加载器,模组生态最丰富" }, + { value: "fabric", label: "Fabric", desc: "轻量快速,适合性能优化与辅助模组" }, + { value: "quilt", label: "Quilt", desc: "Fabric 的分支,实验性功能更多" }, + { value: "neoforged", label: "NeoForge", desc: "Forge 的现代化分支,1.20+ 推荐" }, +]; + +export function CreateDialog({ isOpen, onClose, onCreate }: CreateDialogProps) { + const [step, setStep] = useState(0); + const [name, setName] = useState(""); + const [displayName, setDisplayName] = useState(""); + const [loader, setLoader] = useState("vanilla"); + const [mcVersion, setMcVersion] = useState(""); + const [memory, setMemory] = useState(4); + const [creating, setCreating] = useState(false); + const [mcVersions, setMcVersions] = useState([]); + const [loadingVersions, setLoadingVersions] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + if (isOpen) { loadMcVersions(); } else { resetForm(); } + }, [isOpen]); + + const resetForm = () => { + setStep(0); setName(""); setDisplayName(""); + setLoader("vanilla"); setMcVersion(""); setMemory(4); + setError(""); setCreating(false); + }; + + const loadMcVersions = async () => { + setLoadingVersions(true); + try { + const list = await getMinecraftVersionList(); + const releases = list.filter((v: string) => /^1\.\d+/.test(v)).slice(0, 24); + setMcVersions(releases.length ? releases : ["1.21.4","1.21","1.20.6","1.20.4","1.20.1","1.19.4","1.19.2","1.18.2","1.17.1","1.16.5"]); + if (!mcVersion && releases.length) setMcVersion(releases[0]); + } catch { setMcVersions(["1.21.4","1.21","1.20.1","1.19.2","1.18.2","1.16.5"]); if (!mcVersion) setMcVersion("1.21.4"); } + setLoadingVersions(false); + }; + + const handleCreate = async () => { + if (!name.trim()) { setError("请输入实例名称"); return; } + setCreating(true); + try { + const runtime: any = { minecraft: mcVersion }; + if (loader === "forge") runtime.forge = "latest"; + else if (loader === "fabric") runtime.fabricLoader = "latest"; + else if (loader === "quilt") runtime.quiltLoader = "latest"; + else if (loader === "neoforged") runtime.neoForged = "latest"; + await onCreate(name.trim(), runtime, displayName.trim() || undefined); + onClose(); + } catch (e: any) { setError(e.message || "创建失败"); } + setCreating(false); + }; + + if (!isOpen) return null; + + return ( +
+
+
+ {/* Header */} +
+
+ +

新建实例

+
+ +
+ + {/* Body */} +
+ {/* 步骤指示器 */} +
+ {["基本信息", "版本选择", "内存设置"].map((label, i) => ( +
+
{i < step ? "✓" : i + 1}
+ {label} + {i < 2 &&
} +
+ ))} +
+ + {step === 0 && ( +
+ setName(e.target.value.replace(/\s/g, "-").toLowerCase())} autoFocus /> + setDisplayName(e.target.value)} /> +
+ )} + + {step === 1 && ( +
+
+

Minecraft 版本

+ {loadingVersions ? ( +
加载中...
+ ) : ( +
+ {mcVersions.map((v) => ( + + ))} +
+ )} +
+ +
+

模组加载器

+ + {loaderOptions.map((opt) => ( + + +

{opt.label}

{opt.desc}

+
+
+ ))} +
+
+
+ )} + + {step === 2 && ( +
+
+
+

内存分配

+ {memory} GB +
+ setMemory(typeof v === "number" ? v : v[0])} minValue={1} maxValue={16} step={1}> + + +
+
+

创建摘要

+

名称: {displayName || name}

+

版本: Minecraft {mcVersion} ({loaderOptions.find(o=>o.value===loader)?.label})

+

内存: {memory} GB

+
+
+ )} + + {error &&

{error}

} +
+ + {/* Footer */} +
+
+ {step > 0 && } +
+
+ + {step < 2 ? ( + + ) : ( + + )} +
+
+
+
+ ); +} diff --git a/src/pages/gallery/InstanceCard.tsx b/src/pages/gallery/InstanceCard.tsx new file mode 100644 index 0000000..188d0cb --- /dev/null +++ b/src/pages/gallery/InstanceCard.tsx @@ -0,0 +1,154 @@ +import { Button } from "@heroui/react"; +import { + Play, + Settings, + Clock, + Gamepad2, +} from "lucide-react"; +import clsx from "clsx"; +import type { InstanceInfo } from "@/api/instance"; + +function getLoader(runtime: InstanceInfo["config"]["runtime"]): string { + if (runtime.forge) return "forge"; + if (runtime.fabricLoader) return "fabric"; + if (runtime.quiltLoader) return "quilt"; + if (runtime.neoForged) return "neoforged"; + return "vanilla"; +} + +function getLoaderVersion(runtime: InstanceInfo["config"]["runtime"]): string { + return runtime.forge || runtime.fabricLoader || runtime.quiltLoader || runtime.neoForged || ""; +} + +const loaderBadgeColors: Record = { + vanilla: "bg-green-500/10 text-green-600 dark:bg-green-500/15 dark:text-green-400 border-green-500/20", + forge: "bg-orange-500/10 text-orange-600 dark:bg-orange-500/15 dark:text-orange-400 border-orange-500/20", + fabric: "bg-sky-500/10 text-sky-600 dark:bg-sky-500/15 dark:text-sky-400 border-sky-500/20", + quilt: "bg-indigo-500/10 text-indigo-600 dark:bg-indigo-500/15 dark:text-indigo-400 border-indigo-500/20", + neoforged: "bg-red-500/10 text-red-600 dark:bg-red-500/15 dark:text-red-400 border-red-500/20", +}; + +const loaderLabels: Record = { + vanilla: "原版", + forge: "Forge", + fabric: "Fabric", + quilt: "Quilt", + neoforged: "NeoForge", +}; + +const loaderColors: Record = { + vanilla: "bg-green-400", + forge: "bg-orange-400", + fabric: "bg-sky-400", + quilt: "bg-indigo-400", + neoforged: "bg-red-400", +}; + +function formatPlaytime(ms: number): string { + if (!ms || ms < 60000) return ""; + const hours = Math.floor(ms / 3600000); + const mins = Math.floor((ms % 3600000) / 60000); + if (hours > 0) return `${hours}h ${mins}m`; + return `${mins}m`; +} + +function formatLastPlayed(ts: number): string { + if (!ts) return ""; + const diff = Date.now() - ts; + if (diff < 3600000) return "刚刚"; + if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`; + if (diff < 604800000) return `${Math.floor(diff / 86400000)}天前`; + return new Date(ts).toLocaleDateString("zh-CN"); +} + +interface InstanceCardProps { + instance: InstanceInfo; + displayName?: string; + onPlay: (name: string) => void; + onSettings: (name: string) => void; + isLaunching: boolean; +} + +export function InstanceCard({ instance, displayName, onPlay, onSettings, isLaunching }: InstanceCardProps) { + const { name, config, modCount, healthy } = instance; + const loader = getLoader(config.runtime); + const version = config.runtime.minecraft || "未知"; + const loaderLabel = loaderLabels[loader] ?? loader; + const badgeCls = loaderBadgeColors[loader] ?? loaderBadgeColors.vanilla; + const topColor = loaderColors[loader] ?? loaderColors.vanilla; + const title = displayName || config.name || name; + + return ( +
+
+ +
+
+
+

{title}

+

{name}

+
+ {healthy === false && ( + + 异常 + + )} + +
+ +
+ + {loaderLabel} + + + {version} + + {modCount > 0 && ( + + {modCount} 模组 + + )} +
+ +
+ {config.lastPlayedDate ? ( + + {formatLastPlayed(config.lastPlayedDate)} + + ) : null} + {config.playtime > 0 && ( + + {formatPlaytime(config.playtime)} + + )} +
+ + +
+
+ ); +} diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index a3bad69..349c3c0 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -1,8 +1,167 @@ +import { useEffect, useState, useCallback } from "react"; +import { Button } from "@heroui/react"; +import { Plus, Gamepad2, RefreshCw } from "lucide-react"; +import { useInstanceStore } from "@/stores/instanceStore"; +import { useConfigStore } from "@/stores/configStore"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Surface } from "@/components/setting/Surface"; +import { InstanceCard } from "./InstanceCard"; +import { CreateDialog } from "./CreateDialog"; +import type { InstanceRuntime } from "@/api/instance"; + export function Gallery() { + const { instances, loading, fetchInstances, create, launch } = useInstanceStore(); + const gameConfig = useConfigStore((s) => s.config.game); + const configInstances = useConfigStore((s) => s.config.instances); + const setInstances = useConfigStore((s) => s.setInstances); + + const [createOpen, setCreateOpen] = useState(false); + const [launching, setLaunching] = useState(null); + + const loadInstances = useCallback(async () => { + await fetchInstances(gameConfig.gameDir); + }, [fetchInstances, gameConfig.gameDir]); + + useEffect(() => { + loadInstances(); + }, [loadInstances]); + + const handleCreate = async (name: string, runtime: InstanceRuntime, displayName?: string) => { + await create(name, gameConfig.gameDir, runtime, { + description: displayName, + }); + + // Sync to Koring.yml + const loader = runtime.forge ? "forge" + : runtime.fabricLoader ? "fabric" + : runtime.quiltLoader ? "quilt" + : runtime.neoForged ? "neoforged" + : "vanilla"; + + const loaderVersion = runtime.forge || runtime.fabricLoader || runtime.quiltLoader || runtime.neoForged || ""; + + const updated = configInstances.filter((m) => m.name !== name); + updated.push({ + name, + displayName: displayName || name, + icon: "", + gameVersion: runtime.minecraft, + loader, + loaderVersion, + createdAt: Date.now(), + lastPlayed: 0, + playtime: 0, + }); + setInstances(updated); + setCreateOpen(false); + }; + + const handlePlay = async (name: string) => { + setLaunching(name); + try { + await launch(name, gameConfig.gameDir); + } catch { + // Launch events handled via IPC listeners + } + // Update lastPlayed in config + const idx = configInstances.findIndex((m) => m.name === name); + if (idx >= 0) { + const updated = [...configInstances]; + updated[idx] = { ...updated[idx], lastPlayed: Date.now() }; + setInstances(updated); + } + setLaunching(null); + }; + + const handleSettings = (name: string) => { + // TODO: Navigate to instance settings + console.log("Settings for:", name); + }; + + const getDisplayName = (name: string) => { + return configInstances.find((m) => m.name === name)?.displayName; + }; + return ( -
-

实例管理

-

管理与选择我的世界实例

+
+ {/* 头部 */} +
+
+

实例管理

+

+ 创建、管理和启动 Minecraft 实例 +

+
+
+ + +
+
+ + {/* 加载骨架 */} + {loading && instances.length === 0 && ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + +
+ + +
+ + +
+ +
+
+ ))} +
+ )} + + {/* 空状态 */} + {!loading && instances.length === 0 && ( + +
+
+ +
+
+

还没有任何实例

+

+ 点击"新建实例"创建你的第一个 Minecraft 游戏实例 +

+
+ +
+
+ )} + + {/* 实例网格 */} + {instances.length > 0 && ( +
+ {instances.map((inst) => ( + + ))} +
+ )} + + setCreateOpen(false)} + onCreate={handleCreate} + />
); } diff --git a/src/pages/setting/general/home.tsx b/src/pages/setting/general/home.tsx index 7a3be0e..f324e2e 100644 --- a/src/pages/setting/general/home.tsx +++ b/src/pages/setting/general/home.tsx @@ -1,6 +1,3 @@ -import { useKoringAuthStore } from "@/stores/koringAuthStore"; -import { BUILD_MODE } from "@/lib/mode"; -import { useUpdateStore } from "@/stores/updateStore"; import { UserCircle, Gamepad2, @@ -10,10 +7,9 @@ import { Cpu, Info, ChevronRight, - Search, } from "lucide-react"; -import { Avatar } from "@heroui/react"; -import { SettingCard, PageHeader, SectionTitle } from "@/components/setting"; +import { VersionCard } from "@/components/VersionCard"; +import { Surface, PageHeader, SectionTitle } from "@/components/setting"; interface ShortcutItem { icon: React.ReactNode; @@ -26,34 +22,28 @@ function ShortcutTile({ icon, label, desc, navKey, onClick }: ShortcutItem & { o return ( ); } -const modeLabels: Record = { - dev: "开发版", - beta: "测试版", - run: "正式版", -}; - const shortcuts: ShortcutItem[] = [ - { icon: , label: "Koring 账户", desc: "同步数据、皮肤与个人配置", navKey: "account" }, - { icon: , label: "游戏账户与档案", desc: "管理游戏内账户和档案配置", navKey: "game-account" }, - { icon: , label: "主题与背景", desc: "深色模式、背景图片与视差", navKey: "theme-bg" }, - { icon: , label: "下载设置", desc: "下载线程数与存储路径", navKey: "download" }, - { icon: , label: "联机功能", desc: "以太联机与陶瓦联机", navKey: "ether-online" }, - { icon: , label: "Java 虚拟机与内存", desc: "Java 路径与内存分配", navKey: "java-mem" }, - { icon: , label: "关于 Koring Launcher", desc: "版本信息与更新", navKey: "about" }, + { icon: , label: "Koring 账户", desc: "同步数据、皮肤与个人配置", navKey: "account" }, + { icon: , label: "游戏账户与档案", desc: "管理游戏内账户和档案配置", navKey: "game-account" }, + { icon: , label: "主题与背景", desc: "深色模式、背景图片与视差", navKey: "theme-bg" }, + { icon: , label: "下载设置", desc: "下载线程数与存储路径", navKey: "download" }, + { icon: , label: "联机功能", desc: "以太联机与陶瓦联机", navKey: "ether-online" }, + { icon: , label: "Java 虚拟机与内存", desc: "Java 路径与内存分配", navKey: "java-mem" }, + { icon: , label: "关于 Koring Launcher", desc: "版本信息与更新", navKey: "about" }, ]; interface HomeSettingProps { @@ -61,55 +51,22 @@ interface HomeSettingProps { } export function HomeSetting({ onNavigate }: HomeSettingProps) { - const user = useKoringAuthStore((s) => s.user); - const { update } = useUpdateStore(); - return (
- +
- - - 查找设置 - - -
- 快速概览 - -
- - {user?.picture ? ( - - ) : ( - - - - )} - -
-

- {user ? (user.name || user.username) : "未登录"} -

-

- {user ? "Koring 账户" : "登录以同步数据和皮肤"} -

-
- - {modeLabels[BUILD_MODE] ?? BUILD_MODE} - {update ? " · 有更新" : ""} - -
-
-
+
常用设置 -
- {shortcuts.map((s) => ( - - ))} -
+ +
+ {shortcuts.map((s) => ( + + ))} +
+
diff --git a/src/stores/configStore.ts b/src/stores/configStore.ts index 84ae845..18d4f8c 100644 --- a/src/stores/configStore.ts +++ b/src/stores/configStore.ts @@ -11,6 +11,7 @@ import { type AdvancedConfig, type DownloadConfig, type NetworkConfig, + type InstanceMeta, } from "@/api/config"; import { DEFAULT_BG } from "@/lib/mode"; @@ -39,6 +40,7 @@ interface ConfigState { setAdvanced: (patch: Partial) => void; setDownload: (patch: Partial) => void; setNetwork: (patch: Partial) => void; + setInstances: (instances: InstanceMeta[]) => void; setOobe: (value: boolean) => void; } @@ -53,6 +55,7 @@ const DEFAULT_CONFIG: AppConfig = { advanced: { afterLaunch: "close", winMode: "default", customWidth: 854, customHeight: 480, gameArgs: "", preLaunchCmd: "", debugMode: false }, download: { fileSource: "mirror", versionSource: "mirror", threads: 16, speedLimit: 0 }, network: { securityId: { enabled: false, authUrl: "" } }, + instances: [], }; export const useConfigStore = create((set, get) => ({ @@ -132,6 +135,13 @@ export const useConfigStore = create((set, get) => ({ debouncedSave(next); }, + setInstances: (instances) => { + const { config } = get(); + const next = { ...config, instances }; + set({ config: next }); + debouncedSave(next); + }, + setOobe: (value) => { const { config } = get(); const next = { ...config, oobe: value }; diff --git a/vite.config.ts b/vite.config.ts index 90dd18b..5b93f70 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -38,5 +38,8 @@ export default defineConfig(async () => ({ server: { port: 1420, strictPort: true, + watch: { + ignored: ["**/Koring.yml", "**/koring-auth.json"], + }, }, }));