mirror of
https://github.com/dream-pep/koring-launcher.git
synced 2026-09-12 05:45:18 +08:00
20260711存档01
This commit is contained in:
@@ -12,6 +12,7 @@ import { Store } from "./pages/store";
|
||||
import { Today } from "./pages/today";
|
||||
import { PlayLink } from "./pages/play-link";
|
||||
import { Setting } from "./pages/setting";
|
||||
import { Gallery } from "./pages/gallery";
|
||||
import { TaskQueue } from "./pages/task-queue";
|
||||
import { Debug } from "./pages/debug";
|
||||
import { SplashDebug } from "./pages/debug/splash-debug";
|
||||
@@ -27,6 +28,7 @@ const pageMap = {
|
||||
today: Today,
|
||||
"play-link": PlayLink,
|
||||
setting: Setting,
|
||||
gallery: Gallery,
|
||||
"task-queue": TaskQueue,
|
||||
oobe: Oobe,
|
||||
"oobe/about-info": OobeAboutInfo,
|
||||
|
||||
+132
-31
@@ -1,59 +1,160 @@
|
||||
import { ipcInvoke } from './ipc';
|
||||
import { ipcInvoke, onIpcEvent } from './ipc';
|
||||
|
||||
export interface InstanceRuntime {
|
||||
minecraft: string;
|
||||
forge?: string;
|
||||
neoForged?: string;
|
||||
fabricLoader?: string;
|
||||
quiltLoader?: string;
|
||||
optifine?: string;
|
||||
}
|
||||
|
||||
export interface InstanceConfig {
|
||||
name: string;
|
||||
mcVersion: string;
|
||||
loaderType?: string;
|
||||
loaderVersion?: string;
|
||||
javaPath?: string;
|
||||
memory?: { min?: string; max?: string };
|
||||
createdAt: string;
|
||||
author?: string;
|
||||
description?: string;
|
||||
runtime: InstanceRuntime;
|
||||
java?: string;
|
||||
minMemory?: number;
|
||||
maxMemory?: number;
|
||||
vmOptions?: string[];
|
||||
mcOptions?: string[];
|
||||
server?: { host: string; port?: number; name?: string };
|
||||
showLog?: boolean;
|
||||
hideLauncher?: boolean;
|
||||
icon?: string;
|
||||
creationDate: number;
|
||||
lastAccessDate: number;
|
||||
lastPlayedDate: number;
|
||||
playtime: number;
|
||||
}
|
||||
|
||||
export interface InstanceInfo {
|
||||
name: string;
|
||||
path: string;
|
||||
config: InstanceConfig;
|
||||
modCount?: number;
|
||||
modCount: number;
|
||||
resourcePackCount: number;
|
||||
screenshotCount: number;
|
||||
saveCount: number;
|
||||
healthy: boolean;
|
||||
issues: string[];
|
||||
}
|
||||
|
||||
export interface InstallProgress {
|
||||
stage: string;
|
||||
current: number;
|
||||
total: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export async function createInstance(
|
||||
name: string,
|
||||
gamePath: string,
|
||||
mcVersion: string,
|
||||
loaderType?: string,
|
||||
loaderVersion?: string,
|
||||
javaPath?: string,
|
||||
memory?: { min?: string; max?: string }
|
||||
runtime: InstanceRuntime,
|
||||
options?: {
|
||||
author?: string;
|
||||
description?: string;
|
||||
java?: string;
|
||||
minMemory?: number;
|
||||
maxMemory?: number;
|
||||
vmOptions?: string[];
|
||||
mcOptions?: string[];
|
||||
}
|
||||
): Promise<InstanceInfo> {
|
||||
return ipcInvoke<InstanceInfo>('instance:create', {
|
||||
name,
|
||||
gamePath,
|
||||
mcVersion,
|
||||
loaderType,
|
||||
loaderVersion,
|
||||
javaPath,
|
||||
memory,
|
||||
runtime,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function listInstances(instancesPath: string): Promise<InstanceInfo[]> {
|
||||
return ipcInvoke<InstanceInfo[]>('instance:list', { instancesPath });
|
||||
export async function listInstances(gamePath: string): Promise<InstanceInfo[]> {
|
||||
return ipcInvoke<InstanceInfo[]>('instance:list', { gamePath });
|
||||
}
|
||||
|
||||
export async function deleteInstance(
|
||||
name: string,
|
||||
instancesPath: string
|
||||
): Promise<{ deleted: string }> {
|
||||
return ipcInvoke<{ deleted: string }>('instance:delete', {
|
||||
name,
|
||||
instancesPath,
|
||||
});
|
||||
export async function getInstanceInfo(name: string, gamePath: string): Promise<InstanceInfo> {
|
||||
return ipcInvoke<InstanceInfo>('instance:info', { name, gamePath });
|
||||
}
|
||||
|
||||
export async function getInstanceInfo(
|
||||
export async function deleteInstance(name: string, gamePath: string): Promise<{ deleted: string }> {
|
||||
return ipcInvoke<{ deleted: string }>('instance:delete', { name, gamePath });
|
||||
}
|
||||
|
||||
export async function updateInstance(
|
||||
name: string,
|
||||
instancesPath: string
|
||||
gamePath: string,
|
||||
patch: Partial<Omit<InstanceConfig, 'name' | 'creationDate'>>
|
||||
): Promise<InstanceInfo> {
|
||||
return ipcInvoke<InstanceInfo>('instance:info', { name, instancesPath });
|
||||
return ipcInvoke<InstanceInfo>('instance:update', { name, gamePath, patch });
|
||||
}
|
||||
|
||||
export async function installInstance(
|
||||
name: string,
|
||||
gamePath: string
|
||||
): Promise<{ requestId: string }> {
|
||||
return ipcInvoke<{ requestId: string }>('instance:install', { name, gamePath });
|
||||
}
|
||||
|
||||
export async function launchInstance(
|
||||
name: string,
|
||||
gamePath: string,
|
||||
options: {
|
||||
username: string;
|
||||
uuid: string;
|
||||
accessToken?: string;
|
||||
javaPath?: string;
|
||||
server?: { host: string; port?: number };
|
||||
}
|
||||
): Promise<{ requestId: string }> {
|
||||
return ipcInvoke<{ requestId: string }>('instance:launch', { name, gamePath, ...options });
|
||||
}
|
||||
|
||||
export async function diagnoseInstance(
|
||||
name: string,
|
||||
gamePath: string
|
||||
): Promise<{ healthy: boolean; issues: string[] }> {
|
||||
return ipcInvoke<{ healthy: boolean; issues: string[] }>('instance:diagnose', { name, gamePath });
|
||||
}
|
||||
|
||||
export async function getMinecraftVersionList(type?: string) {
|
||||
return ipcInvoke<{ versions: { id: string; type: string; url: string }[] }>('instance:version-list', { type });
|
||||
}
|
||||
|
||||
export async function getForgeVersionList(mcVersion?: string) {
|
||||
return ipcInvoke<{ versions: string[] | Record<string, string[]> }>('instance:forge-version-list', { mcVersion });
|
||||
}
|
||||
|
||||
export async function getFabricVersionList(mcVersion?: string) {
|
||||
return ipcInvoke<{ versions: string[] }>('instance:fabric-version-list', { mcVersion });
|
||||
}
|
||||
|
||||
export async function getQuiltVersionList(mcVersion?: string) {
|
||||
return ipcInvoke<{ versions: string[] }>('instance:quilt-version-list', { mcVersion });
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
export function onInstallProgress(callback: (data: { requestId: string } & InstallProgress) => void) {
|
||||
return onIpcEvent('instance:progress', callback);
|
||||
}
|
||||
|
||||
export function onInstallComplete(callback: (data: { requestId: string; data: InstanceInfo }) => void) {
|
||||
return onIpcEvent('instance:install-complete', callback);
|
||||
}
|
||||
|
||||
export function onInstallError(callback: (data: { requestId: string; error: string }) => void) {
|
||||
return onIpcEvent('instance:install-error', callback);
|
||||
}
|
||||
|
||||
export function onLaunchEvent(callback: (data: { requestId: string; event: string; [key: string]: unknown }) => void) {
|
||||
return onIpcEvent('instance:launch-event', callback);
|
||||
}
|
||||
|
||||
export function onLaunchComplete(callback: (data: { requestId: string; data: { pid: number; version: string; username: string } }) => void) {
|
||||
return onIpcEvent('instance:launch-complete', callback);
|
||||
}
|
||||
|
||||
export function onLaunchError(callback: (data: { requestId: string; error: string }) => void) {
|
||||
return onIpcEvent('instance:launch-error', callback);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { BUILD_MODE } from "@/lib/mode";
|
||||
import { VERSION } from "@/lib/version";
|
||||
|
||||
export function BetaWarning() {
|
||||
useEffect(() => {
|
||||
if (BUILD_MODE === "beta" || BUILD_MODE === "dev") {
|
||||
toast.warning(`当前为 v${VERSION} BETA 测试版,不代表最终品质。`, {
|
||||
duration: Infinity,
|
||||
dismissible: true,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BUILD_MODE, LOGO_SVG } from "@/lib/mode";
|
||||
import { VERSION } from "@/lib/version";
|
||||
import { useUpdateStore } from "@/stores/updateStore";
|
||||
import { relaunchApp } from "@/api/update";
|
||||
import Silk from "@/components/silk/Silk";
|
||||
@@ -17,8 +18,6 @@ const modeLabels: Record<string, string> = {
|
||||
run: "正式版",
|
||||
};
|
||||
|
||||
const VERSION = "0.1.0";
|
||||
|
||||
type UpdateState = "latest" | "hasUpdate" | "installed";
|
||||
|
||||
interface VersionCardProps {
|
||||
@@ -50,12 +49,12 @@ export function VersionCard({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"relative overflow-hidden rounded-xl border border-white/10",
|
||||
"relative overflow-hidden rounded-xl border border-white/10 min-h-[200px]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{/* Silk 背景 */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
<div className="absolute inset-0 z-0" style={{ background: color }}>
|
||||
<Silk speed={3} scale={1.2} color={color} noiseIntensity={1.2} rotation={0.3} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
import { useEffect, useRef, useCallback, useState } from "react";
|
||||
import { useBackgroundStore } from "@/stores/backgroundStore";
|
||||
import { useThemeStore } from "@/stores/themeStore";
|
||||
import { useRouteStore } from "@/stores/routeStore";
|
||||
@@ -11,9 +11,20 @@ export function BackgroundLayer() {
|
||||
const route = useRouteStore((s) => s.current);
|
||||
const forceDisableContentBlur = useDevStore((s) => s.forceDisableContentBlur);
|
||||
const showContentBlur = route !== "home";
|
||||
const [bgImage, setBgImage] = useState(image);
|
||||
|
||||
const bgRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (image && image !== DEFAULT_BG && !image.startsWith("data:")) {
|
||||
(window as any).electronAPI?.getBackgroundDataUrl?.().then((dataUrl: string | null) => {
|
||||
if (dataUrl) setBgImage(dataUrl);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setBgImage(image);
|
||||
}, [image]);
|
||||
|
||||
const handleMouseMove = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!parallax || !bgRef.current) return;
|
||||
@@ -33,7 +44,7 @@ export function BackgroundLayer() {
|
||||
return () => window.removeEventListener("mousemove", handleMouseMove);
|
||||
}, [parallax, handleMouseMove]);
|
||||
|
||||
const bgUrl = image || DEFAULT_BG;
|
||||
const bgUrl = bgImage || DEFAULT_BG;
|
||||
const contentBlur = showContentBlur && !forceDisableContentBlur;
|
||||
|
||||
const getBackgroundStyle = (): React.CSSProperties => {
|
||||
|
||||
@@ -74,17 +74,19 @@ interface SilkPlaneProps {
|
||||
}
|
||||
|
||||
const SilkPlane = forwardRef(function SilkPlane({ uniforms }: SilkPlaneProps, ref: React.Ref<Mesh>) {
|
||||
const { viewport } = useThree();
|
||||
const { viewport, invalidate } = useThree();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref && typeof ref === "object" && ref.current) {
|
||||
ref.current.scale.set(viewport.width, viewport.height, 1);
|
||||
}
|
||||
}, [ref, viewport]);
|
||||
invalidate();
|
||||
}, [ref, viewport, invalidate]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
if (ref && typeof ref === "object" && ref.current) {
|
||||
(ref.current.material as ShaderMaterial).uniforms.uTime.value += 0.1 * delta;
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -121,7 +123,11 @@ const Silk = ({ speed = 5, scale = 1, color = "#7B7481", noiseIntensity = 1.5, r
|
||||
);
|
||||
|
||||
return (
|
||||
<Canvas dpr={[1, 2]} frameloop="always">
|
||||
<Canvas
|
||||
dpr={[1, 2]}
|
||||
style={{ position: "absolute", inset: 0, width: "100%", height: "100%", background: "black" }}
|
||||
gl={{ antialias: true, alpha: false }}
|
||||
>
|
||||
<SilkPlane ref={meshRef} uniforms={uniforms} />
|
||||
</Canvas>
|
||||
);
|
||||
|
||||
@@ -146,8 +146,8 @@ export function TitleBar({
|
||||
style={{
|
||||
WebkitAppRegion: "drag",
|
||||
background: "var(--titlebar-bg)",
|
||||
backdropFilter: "blur(16px)",
|
||||
WebkitBackdropFilter: "blur(16px)",
|
||||
backdropFilter: "blur(3px)",
|
||||
WebkitBackdropFilter: "blur(3px)",
|
||||
borderBottom: "1px solid var(--titlebar-border)",
|
||||
userSelect: "none",
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
|
||||
|
||||
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 }
|
||||
@@ -1,6 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "sonner/dist/styles.css";
|
||||
@import "@fontsource-variable/geist";
|
||||
@import "@fontsource-variable/inter";
|
||||
|
||||
@@ -198,9 +199,11 @@
|
||||
/* 内容区转场:前进方向 */
|
||||
::view-transition-old(content) {
|
||||
animation: 0.3s cubic-bezier(0.4, 0, 0.2, 1) both page-out-forward;
|
||||
pointer-events: none;
|
||||
}
|
||||
::view-transition-new(content) {
|
||||
animation: 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0.05s both page-in-forward;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* 设置子页面转场(已弃用 View Transitions,改用 keyframe) */
|
||||
@@ -208,10 +211,12 @@
|
||||
/* 内容区转场:后退方向 */
|
||||
:root[data-transition-dir="backward"] ::view-transition-old(content) {
|
||||
animation-name: page-out-backward;
|
||||
pointer-events: none;
|
||||
}
|
||||
:root[data-transition-dir="backward"] ::view-transition-new(content) {
|
||||
animation-name: page-in-backward;
|
||||
animation-delay: 0.05s;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@keyframes page-out-forward {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { type ReactNode } from "react";
|
||||
import { BackgroundLayer } from "@/components/background/BackgroundLayer";
|
||||
import { SystemLayer } from "@/components/system/SystemLayer";
|
||||
import { StartupPopup } from "@/components/StartupPopup";
|
||||
import { BetaWarning } from "@/components/BetaWarning";
|
||||
import { Toaster } from "sonner";
|
||||
import { useA11yStore } from "@/stores/a11yStore";
|
||||
import clsx from "clsx";
|
||||
|
||||
@@ -52,6 +54,17 @@ export function RootLayout({
|
||||
|
||||
{/* Startup popup — only when VITE_START_POP=true */}
|
||||
<StartupPopup />
|
||||
|
||||
{/* Beta warning toast */}
|
||||
<BetaWarning />
|
||||
|
||||
{/* Sonner toaster */}
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
richColors
|
||||
closeButton
|
||||
duration={Infinity}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
declare const __APP_VERSION__: string;
|
||||
|
||||
export const VERSION: string = typeof __APP_VERSION__ !== "undefined" ? __APP_VERSION__ : "0.0.0";
|
||||
@@ -0,0 +1,8 @@
|
||||
export function Gallery() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<h1 className="text-xl font-bold text-foreground mb-2">实例管理</h1>
|
||||
<p className="text-sm text-muted-foreground">管理与选择我的世界实例</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useInstanceStore } from "@/stores/instanceStore";
|
||||
import { useRouteStore } from "@/stores/routeStore";
|
||||
import clsx from "clsx";
|
||||
|
||||
export function InstanceTitle() {
|
||||
const currentInstance = useInstanceStore((s) => s.currentInstance);
|
||||
const navigate = useRouteStore((s) => s.navigate);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => navigate("gallery")}
|
||||
className={clsx(
|
||||
"self-start pl-1 pr-1 py-1.5 rounded-lg cursor-pointer",
|
||||
"text-5xl font-bold tracking-tight text-white",
|
||||
"hover:bg-black/20",
|
||||
"transition-colors duration-200",
|
||||
"select-none",
|
||||
)}
|
||||
>
|
||||
{currentInstance?.name ?? "选择一个实例"}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -37,6 +37,7 @@ export function StartCard({ onSettingsClick }: StartCardProps) {
|
||||
|
||||
{/* Instance button */}
|
||||
<button
|
||||
onClick={() => navigate("gallery")}
|
||||
className="flex items-center justify-center w-9 h-9 mr-0.5 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-all duration-150 cursor-pointer shrink-0 rounded-full"
|
||||
aria-label="选择实例"
|
||||
>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { InstanceTitle } from "./InstanceTitle";
|
||||
import { StartCard } from "./StartCard";
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<div className="relative h-full flex flex-col justify-end items-start p-6">
|
||||
<div className="relative h-full flex flex-col justify-end items-start p-6 gap-3">
|
||||
<InstanceTitle />
|
||||
<StartCard />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -29,7 +29,7 @@ const OFFICIAL_URL = "https://koring.app";
|
||||
|
||||
export function AboutSetting() {
|
||||
const openLink = (url: string) => {
|
||||
window.open(url, "_blank");
|
||||
window.electronAPI?.openExternal(url);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -17,7 +17,7 @@ function SettingRow({ label, desc, children }: { label: string; desc?: string; c
|
||||
}
|
||||
|
||||
const openLink = (url: string) => {
|
||||
window.open(url, "_blank");
|
||||
window.electronAPI?.openExternal(url);
|
||||
};
|
||||
|
||||
const licenses = [
|
||||
@@ -50,9 +50,9 @@ export function CopyrightSetting() {
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-foreground mb-3">项目协议</h3>
|
||||
<GlassCard>
|
||||
<SettingRow label="MIT License" desc="Copyright © 2024 Koring Launcher Contributors">
|
||||
<SettingRow label="LL-1.0 (LingkeLice 1.0)" desc="Copyright © Shenzhen Lingke Network Technology Co., Ltd.">
|
||||
<button
|
||||
onClick={() => openLink("https://opensource.org/licenses/MIT")}
|
||||
onClick={() => openLink("https://support.lingke.ink/LL-1.0")}
|
||||
className="inline-flex items-center gap-1.5 text-[13px] text-primary hover:underline"
|
||||
>
|
||||
查看
|
||||
|
||||
@@ -110,14 +110,9 @@ export function ThemeBgSetting() {
|
||||
const { image, opacity, setOpacity, blur, setBlur, setImage, reset } = useBackgroundStore();
|
||||
|
||||
const handlePickImage = async () => {
|
||||
// In Electron, use the native file dialog via IPC
|
||||
const result = await window.electronAPI?.invoke('dialog:openFile', {
|
||||
filters: [
|
||||
{ name: "图片", extensions: ["png", "jpg", "jpeg", "webp", "gif", "bmp"] },
|
||||
],
|
||||
}) as string | null;
|
||||
if (result) {
|
||||
setImage(result);
|
||||
const dataUrl = await (window as any).electronAPI?.pickBackgroundImage();
|
||||
if (dataUrl) {
|
||||
setImage(dataUrl);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
try {
|
||||
const { state, authUrl } = await microsoftLoginStart(clientId);
|
||||
set({ msAuthUrl: authUrl, msAuthState: state, loading: false });
|
||||
window.open(authUrl, "_blank");
|
||||
window.electronAPI?.openExternal(authUrl);
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
|
||||
+59
-33
@@ -4,8 +4,11 @@ import {
|
||||
listInstances,
|
||||
deleteInstance,
|
||||
getInstanceInfo,
|
||||
installInstance,
|
||||
launchInstance,
|
||||
type InstanceInfo,
|
||||
type InstanceRuntime,
|
||||
} from "../api/instance";
|
||||
import type { InstanceInfo } from "../api/instance";
|
||||
|
||||
interface InstanceState {
|
||||
instances: InstanceInfo[];
|
||||
@@ -13,18 +16,33 @@ interface InstanceState {
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
fetchInstances: (instancesPath: string) => Promise<void>;
|
||||
fetchInstances: (gamePath: string) => Promise<void>;
|
||||
create: (
|
||||
name: string,
|
||||
gamePath: string,
|
||||
mcVersion: string,
|
||||
loaderType?: string,
|
||||
loaderVersion?: string,
|
||||
javaPath?: string,
|
||||
memory?: { min?: string; max?: string }
|
||||
runtime: InstanceRuntime,
|
||||
options?: {
|
||||
author?: string;
|
||||
description?: string;
|
||||
java?: string;
|
||||
minMemory?: number;
|
||||
maxMemory?: number;
|
||||
}
|
||||
) => Promise<void>;
|
||||
remove: (name: string, instancesPath: string) => Promise<void>;
|
||||
select: (name: string, instancesPath: string) => Promise<void>;
|
||||
remove: (name: string, gamePath: string) => Promise<void>;
|
||||
select: (name: string, gamePath: string) => Promise<void>;
|
||||
install: (name: string, gamePath: string) => Promise<string>;
|
||||
launch: (
|
||||
name: string,
|
||||
gamePath: string,
|
||||
options: {
|
||||
username: string;
|
||||
uuid: string;
|
||||
accessToken?: string;
|
||||
javaPath?: string;
|
||||
server?: { host: string; port?: number };
|
||||
}
|
||||
) => Promise<string>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
@@ -34,36 +52,20 @@ export const useInstanceStore = create<InstanceState>((set) => ({
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
fetchInstances: async (instancesPath: string) => {
|
||||
fetchInstances: async (gamePath: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const instances = await listInstances(instancesPath);
|
||||
const instances = await listInstances(gamePath);
|
||||
set({ instances, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
create: async (
|
||||
name,
|
||||
gamePath,
|
||||
mcVersion,
|
||||
loaderType?,
|
||||
loaderVersion?,
|
||||
javaPath?,
|
||||
memory?
|
||||
) => {
|
||||
create: async (name, gamePath, runtime, options?) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const instance = await createInstance(
|
||||
name,
|
||||
gamePath,
|
||||
mcVersion,
|
||||
loaderType,
|
||||
loaderVersion,
|
||||
javaPath,
|
||||
memory
|
||||
);
|
||||
const instance = await createInstance(name, gamePath, runtime, options);
|
||||
set((state) => ({
|
||||
instances: [...state.instances, instance],
|
||||
loading: false,
|
||||
@@ -73,10 +75,10 @@ export const useInstanceStore = create<InstanceState>((set) => ({
|
||||
}
|
||||
},
|
||||
|
||||
remove: async (name: string, instancesPath: string) => {
|
||||
remove: async (name: string, gamePath: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
await deleteInstance(name, instancesPath);
|
||||
await deleteInstance(name, gamePath);
|
||||
set((state) => ({
|
||||
instances: state.instances.filter((i) => i.name !== name),
|
||||
currentInstance:
|
||||
@@ -88,15 +90,39 @@ export const useInstanceStore = create<InstanceState>((set) => ({
|
||||
}
|
||||
},
|
||||
|
||||
select: async (name: string, instancesPath: string) => {
|
||||
select: async (name: string, gamePath: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const instance = await getInstanceInfo(name, instancesPath);
|
||||
const instance = await getInstanceInfo(name, gamePath);
|
||||
set({ currentInstance: instance, loading: false });
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
install: async (name: string, gamePath: string) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const { requestId } = await installInstance(name, gamePath);
|
||||
set({ loading: false });
|
||||
return requestId;
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
launch: async (name, gamePath, options) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const { requestId } = await launchInstance(name, gamePath, options);
|
||||
set({ loading: false });
|
||||
return requestId;
|
||||
} catch (e: any) {
|
||||
set({ error: e.message, loading: false });
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
}));
|
||||
|
||||
@@ -6,6 +6,7 @@ export type RouteKey =
|
||||
| "today"
|
||||
| "play-link"
|
||||
| "setting"
|
||||
| "gallery"
|
||||
| "task-queue"
|
||||
| "oobe"
|
||||
| "oobe/about-info"
|
||||
@@ -29,6 +30,7 @@ interface RouteItem {
|
||||
|
||||
export const routes: RouteItem[] = [
|
||||
{ key: "home", label: "首页", path: "/home" },
|
||||
{ key: "gallery", label: "实例", path: "/gallery" },
|
||||
{ key: "store", label: "资源", path: "/store" },
|
||||
{ key: "today", label: "资讯", path: "/today" },
|
||||
{ key: "play-link", label: "联机", path: "/play-link" },
|
||||
|
||||
Vendored
+2
@@ -10,6 +10,8 @@ interface ElectronAPI {
|
||||
onResized: (callback: () => void) => () => void;
|
||||
|
||||
getTheme: () => Promise<'light' | 'dark' | 'system' | null>;
|
||||
|
||||
openExternal: (url: string) => Promise<void>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
Reference in New Issue
Block a user