mirror of
https://github.com/dream-pep/koring-launcher.git
synced 2026-09-12 05:45:18 +08:00
Integrate @xmcl/task, import instances & UI updates
Add a new xmcl task system and instance-import workflow. Introduces electron/core/task.ts with @xmcl/task executors (install, sims), task lifecycle IPC (start/pause/resume/cancel) and stage mapping. Detect mod loaders in instance scanner and add importExistingInstance with IPC. Update front-end task store, types and UI (TaskButton, TaskCard, TaskQueue, TaskDebug) to support paused state, xmcl path/state and pause/resume actions. Improve game-dir UI with loader badges and bulk import. Small UX/dialog tweaks (alert-dialog, About) and dependency/pnpm workspace updates. Adds Koring.yml theme default.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
oobe: false
|
oobe: false
|
||||||
|
theme:
|
||||||
|
darkMode: light
|
||||||
game:
|
game:
|
||||||
gameDirs: []
|
gameDirs: []
|
||||||
|
|||||||
+142
-6
@@ -18,14 +18,14 @@ import { rewriteToMirror } from './installer';
|
|||||||
|
|
||||||
// ==================== BMCLAPI 镜像源配置 ====================
|
// ==================== BMCLAPI 镜像源配置 ====================
|
||||||
// 版本清单(@xmcl/installer 的 getVersionList 通过 remote 参数覆盖)
|
// 版本清单(@xmcl/installer 的 getVersionList 通过 remote 参数覆盖)
|
||||||
const BMCLAPI_VERSION_MANIFEST = 'https://bmclapi2.bangbang93.com/mc/game/version_manifest.json';
|
export const BMCLAPI_VERSION_MANIFEST = 'https://bmclapi2.bangbang93.com/mc/game/version_manifest.json';
|
||||||
// Maven 仓库镜像(Libraries / Forge / NeoForge / Fabric 构件)
|
// Maven 仓库镜像(Libraries / Forge / NeoForge / Fabric 构件)
|
||||||
const BMCLAPI_MAVEN = 'https://bmclapi2.bangbang93.com/maven';
|
export const BMCLAPI_MAVEN = 'https://bmclapi2.bangbang93.com/maven';
|
||||||
// 资源文件镜像(assets)
|
// 资源文件镜像(assets)
|
||||||
const BMCLAPI_ASSETS = 'https://bmclapi2.bangbang93.com/assets';
|
export const BMCLAPI_ASSETS = 'https://bmclapi2.bangbang93.com/assets';
|
||||||
|
|
||||||
// 供 @xmcl/installer FetchOptions 使用的镜像 fetch(meta.fabricmc.net 等元数据接口走镜像)
|
// 供 @xmcl/installer FetchOptions 使用的镜像 fetch(meta.fabricmc.net 等元数据接口走镜像)
|
||||||
const mirrorFetch: typeof fetch = (url, init) => fetch(rewriteToMirror(String(url)), init);
|
export const mirrorFetch: typeof fetch = (url, init) => fetch(rewriteToMirror(String(url)), init);
|
||||||
|
|
||||||
export interface InstanceRuntime {
|
export interface InstanceRuntime {
|
||||||
minecraft: string;
|
minecraft: string;
|
||||||
@@ -427,6 +427,33 @@ export interface ScannedVersion {
|
|||||||
hasJar: boolean;
|
hasJar: boolean;
|
||||||
/** JSON 文件是否存在 */
|
/** JSON 文件是否存在 */
|
||||||
hasJson: boolean;
|
hasJson: boolean;
|
||||||
|
/** 检测到的 mod 加载器 */
|
||||||
|
loaders: string[];
|
||||||
|
/** 是否健康(JSON + JAR 都存在) */
|
||||||
|
healthy: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从版本 JSON 中检测 mod 加载器
|
||||||
|
function detectLoaders(jsonPath: string): string[] {
|
||||||
|
const loaders: string[] = [];
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(jsonPath, 'utf-8');
|
||||||
|
const json = JSON.parse(raw);
|
||||||
|
const libs: { name?: string }[] = json.libraries || [];
|
||||||
|
for (const lib of libs) {
|
||||||
|
const name = lib.name || "";
|
||||||
|
if (name.startsWith("net.minecraftforge:forge:") || name.startsWith("net.neoforged:")) {
|
||||||
|
if (!loaders.includes("forge")) loaders.push("forge");
|
||||||
|
} else if (name.startsWith("net.fabricmc:yarn:") || name.startsWith("net.fabricmc:fabric-loader:")) {
|
||||||
|
if (!loaders.includes("fabric")) loaders.push("fabric");
|
||||||
|
} else if (name.startsWith("org.quiltmc:quilt-loader:")) {
|
||||||
|
if (!loaders.includes("quilt")) loaders.push("quilt");
|
||||||
|
} else if (name.startsWith("optifine:OptiFine:")) {
|
||||||
|
if (!loaders.includes("optifine")) loaders.push("optifine");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return loaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扫描指定目录,返回 versions 子目录中所有已安装版本的详细信息
|
// 扫描指定目录,返回 versions 子目录中所有已安装版本的详细信息
|
||||||
@@ -447,7 +474,6 @@ export function scanGameDirectories(gamePath: string): ScannedVersion[] {
|
|||||||
let type = "unknown";
|
let type = "unknown";
|
||||||
let releaseTime: string | undefined;
|
let releaseTime: string | undefined;
|
||||||
|
|
||||||
// 尝试读取 version JSON 获取类型和时间
|
|
||||||
if (hasJson) {
|
if (hasJson) {
|
||||||
try {
|
try {
|
||||||
const raw = fs.readFileSync(jsonPath, 'utf-8');
|
const raw = fs.readFileSync(jsonPath, 'utf-8');
|
||||||
@@ -457,9 +483,119 @@ export function scanGameDirectories(gamePath: string): ScannedVersion[] {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { id, type, releaseTime, hasJar, hasJson };
|
const loaders = hasJson ? detectLoaders(jsonPath) : [];
|
||||||
|
const healthy = hasJson && hasJar;
|
||||||
|
|
||||||
|
return { id, type, releaseTime, hasJar, hasJson, loaders, healthy };
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从已安装版本导入实例(不下载,直接复制版本文件)
|
||||||
|
export async function importExistingInstance(
|
||||||
|
name: string,
|
||||||
|
gamePath: string,
|
||||||
|
versionId: string,
|
||||||
|
options?: {
|
||||||
|
description?: string;
|
||||||
|
java?: string;
|
||||||
|
minMemory?: number;
|
||||||
|
maxMemory?: number;
|
||||||
|
}
|
||||||
|
): Promise<InstanceInfo> {
|
||||||
|
const instancePath = path.join(gamePath, 'instances', name);
|
||||||
|
if (fs.existsSync(instancePath)) {
|
||||||
|
throw new Error(`Instance already exists: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查源版本目录是否存在
|
||||||
|
const srcVersionDir = path.join(gamePath, 'versions', versionId);
|
||||||
|
if (!fs.existsSync(srcVersionDir)) {
|
||||||
|
throw new Error(`Version directory not found: ${versionId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取源版本 JSON 获取类型信息
|
||||||
|
const srcJsonPath = path.join(srcVersionDir, `${versionId}.json`);
|
||||||
|
if (!fs.existsSync(srcJsonPath)) {
|
||||||
|
throw new Error(`Version JSON not found: ${versionId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = "release";
|
||||||
|
let releaseTime: string | undefined;
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(srcJsonPath, 'utf-8');
|
||||||
|
const json = JSON.parse(raw);
|
||||||
|
type = json.type || "release";
|
||||||
|
releaseTime = json.releaseTime;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// 构建 InstanceRuntime
|
||||||
|
const runtime: InstanceRuntime = {
|
||||||
|
minecraft: versionId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从 JSON 中检测加载器并填充 runtime
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(srcJsonPath, 'utf-8');
|
||||||
|
const json = JSON.parse(raw);
|
||||||
|
const libs: { name?: string }[] = json.libraries || [];
|
||||||
|
for (const lib of libs) {
|
||||||
|
const name = lib.name || "";
|
||||||
|
// Forge: net.minecraftforge:forge:1.20.1-47.2.0
|
||||||
|
const forgeMatch = name.match(/^net\.minecraftforge:forge:([\d.]+(?:-\d+(?:\.\d+)*)?)/);
|
||||||
|
if (forgeMatch) { runtime.forge = forgeMatch[1]; continue; }
|
||||||
|
// NeoForge: net.neoforged:neoforge:21.0.0
|
||||||
|
const neoMatch = name.match(/^net\.neoforged:neoforge:([\d.]+)/);
|
||||||
|
if (neoMatch) { runtime.neoForged = neoMatch[1]; continue; }
|
||||||
|
// Fabric: net.fabricmc:fabric-loader:0.15.11
|
||||||
|
const fabricMatch = name.match(/^net\.fabricmc:fabric-loader:([\d.]+)/);
|
||||||
|
if (fabricMatch) { runtime.fabricLoader = fabricMatch[1]; continue; }
|
||||||
|
// Quilt: org.quiltmc:quilt-loader:0.26.0
|
||||||
|
const quiltMatch = name.match(/^org\.quiltmc:quilt-loader:([\d.]+)/);
|
||||||
|
if (quiltMatch) { runtime.quiltLoader = quiltMatch[1]; continue; }
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// 创建实例目录结构
|
||||||
|
fs.mkdirSync(instancePath, { recursive: true });
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const config: InstanceConfig = {
|
||||||
|
name,
|
||||||
|
author: '',
|
||||||
|
description: options?.description || `Imported from version ${versionId}`,
|
||||||
|
runtime,
|
||||||
|
java: options?.java,
|
||||||
|
minMemory: options?.minMemory,
|
||||||
|
maxMemory: options?.maxMemory,
|
||||||
|
creationDate: now,
|
||||||
|
lastAccessDate: now,
|
||||||
|
lastPlayedDate: 0,
|
||||||
|
playtime: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(getInstanceConfigPath(instancePath), JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
|
||||||
|
// 创建标准子目录
|
||||||
|
for (const dir of ['mods', 'config', 'resourcepacks', 'shaderpacks', 'saves', 'screenshots', 'logs']) {
|
||||||
|
fs.mkdirSync(path.join(instancePath, dir), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制版本文件到实例目录
|
||||||
|
const destVersionDir = path.join(instancePath, 'versions', versionId);
|
||||||
|
fs.mkdirSync(destVersionDir, { recursive: true });
|
||||||
|
|
||||||
|
// 复制所有版本文件
|
||||||
|
const files = fs.readdirSync(srcVersionDir);
|
||||||
|
for (const file of files) {
|
||||||
|
const srcFile = path.join(srcVersionDir, file);
|
||||||
|
const destFile = path.join(destVersionDir, file);
|
||||||
|
if (fs.statSync(srcFile).isFile()) {
|
||||||
|
fs.copyFileSync(srcFile, destFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInstanceInfo(name, gamePath);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
// __ __ __ __ ______ __ __ ______ __ __ ______ ______
|
||||||
|
// /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
|
||||||
|
// \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
|
||||||
|
// \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
|
||||||
|
// \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
|
||||||
|
//
|
||||||
|
// 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
|
||||||
|
// 未经允许的情况下删除此版权头可能会受到民事指控
|
||||||
|
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
|
import { AbortableTask, CancelledError, task, type Task } from '@xmcl/task';
|
||||||
|
import {
|
||||||
|
installTask,
|
||||||
|
installDependenciesTask,
|
||||||
|
installForgeTask,
|
||||||
|
installNeoForgedTask,
|
||||||
|
installFabric,
|
||||||
|
installQuiltVersion,
|
||||||
|
getVersionList,
|
||||||
|
} from '@xmcl/installer';
|
||||||
|
import { Version } from '@xmcl/core';
|
||||||
|
import { rewriteToMirror } from './installer';
|
||||||
|
import {
|
||||||
|
createInstance,
|
||||||
|
updateInstance,
|
||||||
|
BMCLAPI_VERSION_MANIFEST,
|
||||||
|
BMCLAPI_MAVEN,
|
||||||
|
BMCLAPI_ASSETS,
|
||||||
|
mirrorFetch,
|
||||||
|
type InstanceRuntime,
|
||||||
|
} from './instance';
|
||||||
|
|
||||||
|
// 任务执行钩子:主进程用它向渲染进程广播日志
|
||||||
|
export interface TaskHooks {
|
||||||
|
log: (level: 'info' | 'warn' | 'error', message: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例安装任务参数(与前端 addSidecarTask 的 params 对齐)
|
||||||
|
export interface InstallTaskParams {
|
||||||
|
name: string;
|
||||||
|
gamePath: string;
|
||||||
|
runtime: InstanceRuntime;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟任务参数(调试页使用)
|
||||||
|
export interface SimTaskParams {
|
||||||
|
duration?: number;
|
||||||
|
failAt?: number;
|
||||||
|
failMessage?: string;
|
||||||
|
total?: number;
|
||||||
|
threads?: number;
|
||||||
|
steps?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行器工厂:根据参数与钩子创建 @xmcl/task 任务
|
||||||
|
type ExecutorFactory = (params: Record<string, unknown>, hooks: TaskHooks) => Task<unknown>;
|
||||||
|
|
||||||
|
// 执行器注册表:executorName → 工厂
|
||||||
|
const executorRegistry = new Map<string, ExecutorFactory>();
|
||||||
|
|
||||||
|
// ==================== 模拟执行器(调试用)====================
|
||||||
|
|
||||||
|
// 模拟任务基类:AbortableTask 子类,支持取消 / 暂停 / 进度
|
||||||
|
abstract class SimTask extends AbortableTask<number> {
|
||||||
|
protected aborted = false;
|
||||||
|
protected stepIndex = 0;
|
||||||
|
protected stepTotal = 20;
|
||||||
|
|
||||||
|
// 进度展示:已推进的步数 / 总步数
|
||||||
|
override get progress(): number {
|
||||||
|
return this.stepIndex;
|
||||||
|
}
|
||||||
|
override get total(): number {
|
||||||
|
return this.stepTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每个步骤推进一次进度并抛出日志
|
||||||
|
protected step(hooks: TaskHooks, message?: string): void {
|
||||||
|
this.stepIndex += 1;
|
||||||
|
if (message) hooks.log('info', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待恢复:暂停期间每 50ms 轮询一次,直到恢复或取消
|
||||||
|
protected async waitResume(): Promise<void> {
|
||||||
|
while (this.isPaused && !this.aborted) {
|
||||||
|
await new Promise((r) => setTimeout(r, 50));
|
||||||
|
}
|
||||||
|
if (this.aborted) throw new CancelledError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消回调:标记中止标志
|
||||||
|
protected abort(): void {
|
||||||
|
this.aborted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断错误是否为取消导致
|
||||||
|
protected isAbortedError(e: unknown): boolean {
|
||||||
|
return e instanceof CancelledError || this.aborted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep:按毫秒数模拟耗时任务
|
||||||
|
class SleepTask extends SimTask {
|
||||||
|
constructor(
|
||||||
|
private readonly durationMs: number,
|
||||||
|
private readonly failAt: number,
|
||||||
|
private readonly failMessage: string | undefined,
|
||||||
|
private readonly hooks: TaskHooks,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
override async process(): Promise<number> {
|
||||||
|
const interval = this.durationMs / this.stepTotal;
|
||||||
|
for (let i = 0; i < this.stepTotal; i++) {
|
||||||
|
await this.waitResume();
|
||||||
|
this.step(this.hooks, `进度 ${Math.round(((i + 1) / this.stepTotal) * 100)}%`);
|
||||||
|
if (i + 1 === this.failAt) {
|
||||||
|
this.hooks.log('error', this.failMessage ?? '模拟失败:网络连接超时');
|
||||||
|
throw new Error(this.failMessage ?? '网络连接超时');
|
||||||
|
}
|
||||||
|
await new Promise((r) => setTimeout(r, interval));
|
||||||
|
}
|
||||||
|
this.hooks.log('info', '任务完成');
|
||||||
|
return this.stepIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// download:模拟分段下载(total 单位,threads 并发)
|
||||||
|
class DownloadSimTask extends SimTask {
|
||||||
|
private done = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly totalUnits: number,
|
||||||
|
private readonly threads: number,
|
||||||
|
private readonly hooks: TaskHooks,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.stepTotal = totalUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
override get progress(): number {
|
||||||
|
return this.done;
|
||||||
|
}
|
||||||
|
override get total(): number {
|
||||||
|
return this.totalUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async process(): Promise<number> {
|
||||||
|
const chunk = Math.max(1, Math.round(this.totalUnits / this.threads));
|
||||||
|
while (this.done < this.totalUnits) {
|
||||||
|
await this.waitResume();
|
||||||
|
this.done = Math.min(this.totalUnits, this.done + chunk);
|
||||||
|
this.hooks.log('info', `已下载 ${this.done}/${this.totalUnits}`);
|
||||||
|
await new Promise((r) => setTimeout(r, 120));
|
||||||
|
}
|
||||||
|
this.hooks.log('info', '下载完成');
|
||||||
|
return this.done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// install-sim:模拟多步骤安装(steps 步)
|
||||||
|
class InstallSimTask extends SimTask {
|
||||||
|
constructor(
|
||||||
|
private readonly installSteps: number,
|
||||||
|
private readonly hooks: TaskHooks,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.stepTotal = installSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async process(): Promise<number> {
|
||||||
|
for (let i = 1; i <= this.installSteps; i++) {
|
||||||
|
await this.waitResume();
|
||||||
|
this.step(this.hooks, `安装步骤 ${i}/${this.installSteps}`);
|
||||||
|
await new Promise((r) => setTimeout(r, 150));
|
||||||
|
}
|
||||||
|
this.hooks.log('info', '安装完成');
|
||||||
|
return this.stepIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册模拟执行器
|
||||||
|
executorRegistry.set('sleep', (raw, hooks) => {
|
||||||
|
const p = raw as unknown as SimTaskParams;
|
||||||
|
return new SleepTask(p.duration ?? 3000, p.failAt ?? -1, p.failMessage, hooks);
|
||||||
|
});
|
||||||
|
|
||||||
|
executorRegistry.set('download', (raw, hooks) => {
|
||||||
|
const p = raw as unknown as SimTaskParams;
|
||||||
|
return new DownloadSimTask(p.total ?? 100, p.threads ?? 4, hooks);
|
||||||
|
});
|
||||||
|
|
||||||
|
executorRegistry.set('install-sim', (raw, hooks) => {
|
||||||
|
const p = raw as unknown as SimTaskParams;
|
||||||
|
return new InstallSimTask(p.steps ?? 10, hooks);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 真实执行器(实例安装)====================
|
||||||
|
|
||||||
|
// install:使用 @xmcl/installer 的 Task 版本函数安装 Minecraft 实例
|
||||||
|
// 任务树:install.create → install.minecraft → (forge|neoforge|fabric|quilt) → install.dependencies
|
||||||
|
executorRegistry.set('install', (rawParams, hooks) => {
|
||||||
|
const p = rawParams as unknown as InstallTaskParams;
|
||||||
|
const { name, gamePath, runtime } = p;
|
||||||
|
const instancePath = path.join(gamePath, 'instances', name);
|
||||||
|
|
||||||
|
return task('install', async function () {
|
||||||
|
// 子任务 1:创建实例目录
|
||||||
|
hooks.log('info', `创建实例: ${name}`);
|
||||||
|
await this.yield(
|
||||||
|
task('create', async () => {
|
||||||
|
await createInstance(name, gamePath, runtime, { description: p.description });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 预步骤:获取版本清单,定位 Minecraft 版本元数据(走 BMCLAPI 镜像)
|
||||||
|
const versionList = await getVersionList({ remote: BMCLAPI_VERSION_MANIFEST, fetch: mirrorFetch });
|
||||||
|
const versionInfo = versionList.versions.find((v) => v.id === runtime.minecraft);
|
||||||
|
if (!versionInfo) {
|
||||||
|
throw new Error(`Minecraft 版本 ${runtime.minecraft} 不存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子任务 2:安装 Minecraft 本体(版本 JSON / 客户端 JAR)
|
||||||
|
hooks.log('info', `下载 Minecraft ${runtime.minecraft}...`);
|
||||||
|
await this.yield(
|
||||||
|
installTask(
|
||||||
|
{ id: versionInfo.id, url: versionInfo.url },
|
||||||
|
instancePath,
|
||||||
|
{
|
||||||
|
// 版本 JSON / 客户端 JAR 走 BMCLAPI 镜像
|
||||||
|
json: (v) => rewriteToMirror(v.url),
|
||||||
|
client: (v) => (v.downloads?.client ? rewriteToMirror(v.downloads.client.url) : []),
|
||||||
|
mavenHost: BMCLAPI_MAVEN,
|
||||||
|
assetsHost: BMCLAPI_ASSETS,
|
||||||
|
},
|
||||||
|
).setName('minecraft'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 子任务 3:模组加载器(Forge / NeoForge 有 Task 版本;Fabric / Quilt 为 Promise)
|
||||||
|
if (runtime.forge) {
|
||||||
|
hooks.log('info', `安装 Forge ${runtime.forge}...`);
|
||||||
|
await this.yield(
|
||||||
|
installForgeTask(
|
||||||
|
{ version: runtime.forge, mcversion: runtime.minecraft },
|
||||||
|
instancePath,
|
||||||
|
{ mavenHost: BMCLAPI_MAVEN },
|
||||||
|
).setName('forge'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtime.fabricLoader) {
|
||||||
|
hooks.log('info', `安装 Fabric ${runtime.fabricLoader}...`);
|
||||||
|
await installFabric({
|
||||||
|
minecraftVersion: runtime.minecraft,
|
||||||
|
version: runtime.fabricLoader,
|
||||||
|
minecraft: instancePath,
|
||||||
|
fetch: mirrorFetch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtime.quiltLoader) {
|
||||||
|
hooks.log('info', `安装 Quilt ${runtime.quiltLoader}...`);
|
||||||
|
await installQuiltVersion({
|
||||||
|
minecraftVersion: runtime.minecraft,
|
||||||
|
version: runtime.quiltLoader,
|
||||||
|
minecraft: instancePath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runtime.neoForged) {
|
||||||
|
hooks.log('info', `安装 NeoForge ${runtime.neoForged}...`);
|
||||||
|
await this.yield(
|
||||||
|
installNeoForgedTask('neoforge', runtime.neoForged, instancePath, { mavenHost: BMCLAPI_MAVEN }).setName('neoforge'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子任务 4:安装依赖(libraries + assets)
|
||||||
|
hooks.log('info', '安装依赖...');
|
||||||
|
const resolved = await Version.parse(instancePath, runtime.minecraft);
|
||||||
|
await this.yield(
|
||||||
|
installDependenciesTask(resolved, { mavenHost: BMCLAPI_MAVEN, assetsHost: BMCLAPI_ASSETS }).setName('dependencies'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 收尾:更新实例最近访问时间
|
||||||
|
await updateInstance(name, gamePath, { lastAccessDate: Date.now() });
|
||||||
|
hooks.log('info', `实例「${name}」创建完成`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 对外接口 ====================
|
||||||
|
|
||||||
|
// 根据执行器名称创建 @xmcl/task 任务;未知执行器返回 undefined
|
||||||
|
export function createXmclTask(
|
||||||
|
executorName: string,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
hooks: TaskHooks,
|
||||||
|
): Task<unknown> | undefined {
|
||||||
|
const factory = executorRegistry.get(executorName);
|
||||||
|
return factory ? factory(params, hooks) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 @xmcl/task 的 path 映射为前端展示的阶段文案
|
||||||
|
const STAGE_MAP: [RegExp, string][] = [
|
||||||
|
[/^install\.create/, '创建实例'],
|
||||||
|
[/^install\.minecraft/, '下载 Minecraft'],
|
||||||
|
[/^install\.forge/, '安装 Forge'],
|
||||||
|
[/^install\.neoforge/, '安装 NeoForge'],
|
||||||
|
[/^install\.fabric/, '安装 Fabric'],
|
||||||
|
[/^install\.quilt/, '安装 Quilt'],
|
||||||
|
[/^install\.dependencies/, '安装依赖'],
|
||||||
|
[/^sleep/, '模拟计时'],
|
||||||
|
[/^download/, '模拟下载'],
|
||||||
|
[/^install-sim/, '模拟安装'],
|
||||||
|
];
|
||||||
|
|
||||||
|
export function stageFromPath(taskPath: string): string {
|
||||||
|
for (const [pattern, label] of STAGE_MAP) {
|
||||||
|
if (pattern.test(taskPath)) return label;
|
||||||
|
}
|
||||||
|
return taskPath || '执行中';
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
getFabricVersionList,
|
getFabricVersionList,
|
||||||
getQuiltVersionList,
|
getQuiltVersionList,
|
||||||
scanGameDirectories,
|
scanGameDirectories,
|
||||||
|
importExistingInstance,
|
||||||
type InstanceRuntime,
|
type InstanceRuntime,
|
||||||
type InstanceConfig,
|
type InstanceConfig,
|
||||||
} from '../core/instance';
|
} from '../core/instance';
|
||||||
@@ -205,4 +206,32 @@ export function registerInstanceHandlers(win: WinRef) {
|
|||||||
return { success: false, data: null, error: String(e) };
|
return { success: false, data: null, error: String(e) };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 从已安装版本导入实例
|
||||||
|
ipcMain.handle('instance:import', async (_event, payload: {
|
||||||
|
name: string;
|
||||||
|
gamePath: string;
|
||||||
|
versionId: string;
|
||||||
|
description?: string;
|
||||||
|
java?: string;
|
||||||
|
minMemory?: number;
|
||||||
|
maxMemory?: number;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const data = await importExistingInstance(
|
||||||
|
payload.name,
|
||||||
|
payload.gamePath,
|
||||||
|
payload.versionId,
|
||||||
|
{
|
||||||
|
description: payload.description,
|
||||||
|
java: payload.java,
|
||||||
|
minMemory: payload.minMemory,
|
||||||
|
maxMemory: payload.maxMemory,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { success: true, data, error: null };
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return { success: false, data: null, error: String(e) };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-71
@@ -1,23 +1,33 @@
|
|||||||
|
// __ __ __ __ ______ __ __ ______ __ __ ______ ______
|
||||||
|
// /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
|
||||||
|
// \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
|
||||||
|
// \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
|
||||||
|
// \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
|
||||||
|
//
|
||||||
|
// 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
|
||||||
|
// 未经允许的情况下删除此版权头可能会受到民事指控
|
||||||
|
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
||||||
|
|
||||||
import electron from 'electron';
|
import electron from 'electron';
|
||||||
import { createInstance, installInstanceGame, type InstanceRuntime } from '../core/instance';
|
import { CancelledError, type Task } from '@xmcl/task';
|
||||||
|
import { createXmclTask, stageFromPath, type TaskHooks } from '../core/task';
|
||||||
|
|
||||||
const { ipcMain } = electron;
|
const { ipcMain } = electron;
|
||||||
|
|
||||||
const runningTasks = new Map<string, AbortController>();
|
// 运行中的任务映射:taskId → @xmcl/task 实例
|
||||||
|
const runningTasks = new Map<string, Task<unknown>>();
|
||||||
|
|
||||||
interface WinRef {
|
interface WinRef {
|
||||||
mainWindow: electron.BrowserWindow | null;
|
mainWindow: electron.BrowserWindow | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在任务中发送进度事件到渲染进程
|
// 向渲染进程广播任务事件(统一走 task:progress 通道)
|
||||||
function emitProgress(win: WinRef, taskId: string, current: number, total: number, stage: string) {
|
function emit(win: WinRef, taskId: string, payload: Record<string, unknown>) {
|
||||||
win.mainWindow?.webContents.send('task:progress', { taskId, current, total, stage, event: 'task:progress' });
|
win.mainWindow?.webContents.send('task:progress', { taskId, ...payload });
|
||||||
}
|
|
||||||
function emitLog(win: WinRef, taskId: string, level: string, message: string) {
|
|
||||||
win.mainWindow?.webContents.send('task:progress', { taskId, event: 'task:log', level, message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerTaskHandlers(win: WinRef) {
|
export function registerTaskHandlers(win: WinRef) {
|
||||||
|
// 启动任务:创建 @xmcl/task 任务并通过 startAndWait 回调广播事件
|
||||||
ipcMain.handle('task:start', async (_event, payload: {
|
ipcMain.handle('task:start', async (_event, payload: {
|
||||||
taskId: string;
|
taskId: string;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -26,80 +36,80 @@ export function registerTaskHandlers(win: WinRef) {
|
|||||||
executorName: string;
|
executorName: string;
|
||||||
params?: Record<string, unknown>;
|
params?: Record<string, unknown>;
|
||||||
}) => {
|
}) => {
|
||||||
|
// 任务钩子:日志广播到渲染进程
|
||||||
|
const hooks: TaskHooks = {
|
||||||
|
log: (level, message) => emit(win, payload.taskId, { event: 'task:log', level, message }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从执行器注册表创建 @xmcl/task 任务
|
||||||
|
const xmclTask = createXmclTask(payload.executorName, payload.params ?? {}, hooks);
|
||||||
|
if (!xmclTask) {
|
||||||
|
return { success: false, data: null, error: `未知执行器: ${payload.executorName}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
runningTasks.set(payload.taskId, xmclTask);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
// 使用 @xmcl/task 的任务上下文回调驱动事件广播
|
||||||
runningTasks.set(payload.taskId, controller);
|
await xmclTask.startAndWait({
|
||||||
|
onStart: (t) => emit(win, payload.taskId, { event: 'task:started', xmclPath: t.path }),
|
||||||
win.mainWindow?.webContents.send('task:progress', {
|
onUpdate: (t) => emit(win, payload.taskId, {
|
||||||
taskId: payload.taskId,
|
event: 'task:progress',
|
||||||
event: 'task:started',
|
current: t.progress,
|
||||||
xmclPath: payload.executorName,
|
total: t.total,
|
||||||
|
stage: stageFromPath(t.path),
|
||||||
|
xmclPath: t.path,
|
||||||
|
}),
|
||||||
|
onPaused: () => emit(win, payload.taskId, { event: 'task:paused' }),
|
||||||
|
onResumed: () => emit(win, payload.taskId, { event: 'task:resumed' }),
|
||||||
|
onCancelled: () => emit(win, payload.taskId, { event: 'task:cancelled' }),
|
||||||
|
onSucceed: () => emit(win, payload.taskId, { event: 'task:completed' }),
|
||||||
|
onFailed: (t, error) => emit(win, payload.taskId, { event: 'task:failed', error: String(error) }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 根据 executorName 选择处理逻辑
|
|
||||||
if (payload.executorName === 'install') {
|
|
||||||
// 使用 @xmcl/core + @xmcl/installer 安装 Minecraft 实例
|
|
||||||
const params = payload.params ?? {};
|
|
||||||
const name = String(params.name ?? `mc-${Date.now()}`);
|
|
||||||
const gamePath = String(params.gamePath ?? '.minecraft');
|
|
||||||
const runtime = (params.runtime ?? { minecraft: '1.21.4' }) as InstanceRuntime;
|
|
||||||
const description = String(params.description ?? '');
|
|
||||||
|
|
||||||
emitProgress(win, payload.taskId, 0, 100, '创建实例目录');
|
|
||||||
emitLog(win, payload.taskId, 'info', `正在创建实例: ${name}`);
|
|
||||||
|
|
||||||
// Step 1: 创建实例(写入 instance.json)
|
|
||||||
await createInstance(name, gamePath, runtime, { description });
|
|
||||||
emitProgress(win, payload.taskId, 10, 100, '实例目录已创建');
|
|
||||||
|
|
||||||
// Step 2: 安装游戏文件(@xmcl/installer)
|
|
||||||
emitLog(win, payload.taskId, 'info', `正在下载 Minecraft ${runtime.minecraft}...`);
|
|
||||||
await installInstanceGame(name, gamePath, {
|
|
||||||
onProgress: (progress) => {
|
|
||||||
if (controller.signal.aborted) return;
|
|
||||||
const mapped = Math.round(10 + (progress.current / Math.max(progress.total, 1)) * 80);
|
|
||||||
emitProgress(win, payload.taskId, mapped, 100, progress.stage || '安装中');
|
|
||||||
if (progress.message) emitLog(win, payload.taskId, 'info', progress.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
emitProgress(win, payload.taskId, 100, 100, '安装完成');
|
|
||||||
emitLog(win, payload.taskId, 'info', `实例「${name}」创建完成`);
|
|
||||||
} else {
|
|
||||||
// 默认模拟执行
|
|
||||||
const steps = 20;
|
|
||||||
for (let i = 0; i <= steps; i++) {
|
|
||||||
if (controller.signal.aborted) throw new Error('已取消');
|
|
||||||
emitProgress(win, payload.taskId, i, steps, `步骤 ${i}/${steps}`);
|
|
||||||
await new Promise((r) => setTimeout(r, 150));
|
|
||||||
}
|
|
||||||
emitLog(win, payload.taskId, 'info', '任务完成');
|
|
||||||
}
|
|
||||||
|
|
||||||
runningTasks.delete(payload.taskId);
|
runningTasks.delete(payload.taskId);
|
||||||
win.mainWindow?.webContents.send('task:progress', {
|
|
||||||
taskId: payload.taskId,
|
|
||||||
event: 'task:completed',
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, data: null, error: null };
|
return { success: true, data: null, error: null };
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
runningTasks.delete(payload.taskId);
|
runningTasks.delete(payload.taskId);
|
||||||
const errMsg = e instanceof Error ? e.message : String(e);
|
// 取消不是错误:状态已通过 task:cancelled 事件广播
|
||||||
win.mainWindow?.webContents.send('task:progress', {
|
if (e instanceof CancelledError) {
|
||||||
taskId: payload.taskId,
|
return { success: true, data: null, error: null };
|
||||||
event: 'task:failed',
|
}
|
||||||
error: errMsg,
|
return { success: false, data: null, error: e instanceof Error ? e.message : String(e) };
|
||||||
});
|
|
||||||
return { success: false, data: null, error: errMsg };
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 取消任务:调用 @xmcl/task 的 cancel(),取消信号沿任务树向下传播
|
||||||
ipcMain.handle('task:cancel', async (_event, payload: { taskId: string }) => {
|
ipcMain.handle('task:cancel', async (_event, payload: { taskId: string }) => {
|
||||||
try {
|
try {
|
||||||
const controller = runningTasks.get(payload.taskId);
|
const xmclTask = runningTasks.get(payload.taskId);
|
||||||
if (controller) {
|
if (xmclTask) {
|
||||||
controller.abort();
|
await xmclTask.cancel();
|
||||||
|
}
|
||||||
|
return { success: true, data: null, error: null };
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return { success: false, data: null, error: String(e) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暂停任务:@xmcl/task 原生 Paused 状态
|
||||||
|
ipcMain.handle('task:pause', async (_event, payload: { taskId: string }) => {
|
||||||
|
try {
|
||||||
|
const xmclTask = runningTasks.get(payload.taskId);
|
||||||
|
if (xmclTask) {
|
||||||
|
await xmclTask.pause();
|
||||||
|
}
|
||||||
|
return { success: true, data: null, error: null };
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return { success: false, data: null, error: String(e) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 恢复任务:从 Paused 状态恢复
|
||||||
|
ipcMain.handle('task:resume', async (_event, payload: { taskId: string }) => {
|
||||||
|
try {
|
||||||
|
const xmclTask = runningTasks.get(payload.taskId);
|
||||||
|
if (xmclTask) {
|
||||||
|
await xmclTask.resume();
|
||||||
}
|
}
|
||||||
return { success: true, data: null, error: null };
|
return { success: true, data: null, error: null };
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|||||||
+8
-2
@@ -33,14 +33,20 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.6.0",
|
"@base-ui/react": "^1.6.0",
|
||||||
|
"@floating-ui/react-dom": "^2.1.8",
|
||||||
|
"@floating-ui/utils": "^0.2.11",
|
||||||
"@fontsource-variable/geist": "^5.2.9",
|
"@fontsource-variable/geist": "^5.2.9",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@heroui/react": "^3.2.2",
|
"@heroui/react": "^3.2.2",
|
||||||
"@heroui/styles": "^3.2.2",
|
"@heroui/styles": "^3.2.2",
|
||||||
|
"@internationalized/date": "^3.12.3",
|
||||||
|
"@internationalized/number": "^3.6.7",
|
||||||
|
"@internationalized/string": "^3.2.10",
|
||||||
"@react-three/fiber": "^9.6.1",
|
"@react-three/fiber": "^9.6.1",
|
||||||
"@types/three": "^0.184.1",
|
"@types/three": "^0.184.1",
|
||||||
"@xmcl/core": "^2.15.1",
|
"@xmcl/core": "~2.15.1",
|
||||||
"@xmcl/installer": "^6.1.2",
|
"@xmcl/installer": "~6.1.2",
|
||||||
|
"@xmcl/task": "4.1.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
|||||||
Generated
+213
-181
@@ -11,6 +11,12 @@ importers:
|
|||||||
'@base-ui/react':
|
'@base-ui/react':
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
version: 1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
|
'@floating-ui/react-dom':
|
||||||
|
specifier: ^2.1.8
|
||||||
|
version: 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
|
'@floating-ui/utils':
|
||||||
|
specifier: ^0.2.11
|
||||||
|
version: 0.2.11
|
||||||
'@fontsource-variable/geist':
|
'@fontsource-variable/geist':
|
||||||
specifier: ^5.2.9
|
specifier: ^5.2.9
|
||||||
version: 5.2.9
|
version: 5.2.9
|
||||||
@@ -23,6 +29,15 @@ importers:
|
|||||||
'@heroui/styles':
|
'@heroui/styles':
|
||||||
specifier: ^3.2.2
|
specifier: ^3.2.2
|
||||||
version: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.1)
|
version: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.1)
|
||||||
|
'@internationalized/date':
|
||||||
|
specifier: ^3.12.3
|
||||||
|
version: 3.12.3
|
||||||
|
'@internationalized/number':
|
||||||
|
specifier: ^3.6.7
|
||||||
|
version: 3.6.7
|
||||||
|
'@internationalized/string':
|
||||||
|
specifier: ^3.2.10
|
||||||
|
version: 3.2.10
|
||||||
'@react-three/fiber':
|
'@react-three/fiber':
|
||||||
specifier: ^9.6.1
|
specifier: ^9.6.1
|
||||||
version: 9.6.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.184.0)
|
version: 9.6.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.184.0)
|
||||||
@@ -30,11 +45,14 @@ importers:
|
|||||||
specifier: ^0.184.1
|
specifier: ^0.184.1
|
||||||
version: 0.184.1
|
version: 0.184.1
|
||||||
'@xmcl/core':
|
'@xmcl/core':
|
||||||
specifier: ^2.15.1
|
specifier: ~2.15.1
|
||||||
version: 2.15.1(yauzl@2.10.0)
|
version: 2.15.1(yauzl@2.10.0)
|
||||||
'@xmcl/installer':
|
'@xmcl/installer':
|
||||||
specifier: ^6.1.2
|
specifier: ~6.1.2
|
||||||
version: 6.1.2
|
version: 6.1.2
|
||||||
|
'@xmcl/task':
|
||||||
|
specifier: 4.1.1
|
||||||
|
version: 4.1.1
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@@ -92,19 +110,19 @@ importers:
|
|||||||
version: 19.2.3(@types/react@19.2.17)
|
version: 19.2.3(@types/react@19.2.17)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^4.6.0
|
specifier: ^4.6.0
|
||||||
version: 4.7.0(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4))
|
version: 4.7.0(supports-color@8.1.1)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4))
|
||||||
concurrently:
|
concurrently:
|
||||||
specifier: ^9.1.0
|
specifier: ^9.1.0
|
||||||
version: 9.2.3
|
version: 9.2.3
|
||||||
electron:
|
electron:
|
||||||
specifier: ^33.0.0
|
specifier: ^33.0.0
|
||||||
version: 33.4.11
|
version: 33.4.11(supports-color@8.1.1)
|
||||||
electron-builder:
|
electron-builder:
|
||||||
specifier: ^25.1.8
|
specifier: ^25.1.8
|
||||||
version: 25.1.8(electron-builder-squirrel-windows@25.1.8)
|
version: 25.1.8(bluebird@3.7.2)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1)
|
||||||
shadcn:
|
shadcn:
|
||||||
specifier: ^4.11.0
|
specifier: ^4.11.0
|
||||||
version: 4.11.0(typescript@5.8.3)
|
version: 4.11.0(supports-color@8.1.1)(typescript@5.8.3)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^4.3.1
|
specifier: ^4.3.1
|
||||||
version: 4.3.1
|
version: 4.3.1
|
||||||
@@ -729,14 +747,17 @@ packages:
|
|||||||
'@internationalized/date@3.12.2':
|
'@internationalized/date@3.12.2':
|
||||||
resolution: {integrity: sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==}
|
resolution: {integrity: sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==}
|
||||||
|
|
||||||
|
'@internationalized/date@3.12.3':
|
||||||
|
resolution: {integrity: sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==}
|
||||||
|
|
||||||
'@internationalized/message@3.1.10':
|
'@internationalized/message@3.1.10':
|
||||||
resolution: {integrity: sha512-nc0Or6EdWHqZRcsXb6P9hBIpLsfSl/ILh0rk5h/OVBpzmhdExXtPy2cQtWsq8XKRBpRHwDNnAHt4OpolcB7dog==}
|
resolution: {integrity: sha512-nc0Or6EdWHqZRcsXb6P9hBIpLsfSl/ILh0rk5h/OVBpzmhdExXtPy2cQtWsq8XKRBpRHwDNnAHt4OpolcB7dog==}
|
||||||
|
|
||||||
'@internationalized/number@3.6.7':
|
'@internationalized/number@3.6.7':
|
||||||
resolution: {integrity: sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==}
|
resolution: {integrity: sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==}
|
||||||
|
|
||||||
'@internationalized/string@3.2.9':
|
'@internationalized/string@3.2.10':
|
||||||
resolution: {integrity: sha512-kzP/M/mbQxODlmOt4bIQZ2SBVUWUSqMLXooXixnX7noche8WHaQcA+nwFN1K2KCF/cp+LDUhcJsCicwkvhD1pg==}
|
resolution: {integrity: sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==}
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
'@isaacs/cliui@8.0.2':
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
@@ -1373,6 +1394,7 @@ packages:
|
|||||||
'@xmldom/xmldom@0.9.10':
|
'@xmldom/xmldom@0.9.10':
|
||||||
resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==}
|
resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==}
|
||||||
engines: {node: '>=14.6'}
|
engines: {node: '>=14.6'}
|
||||||
|
deprecated: this version has critical issues, please update to the latest version
|
||||||
|
|
||||||
abbrev@1.1.1:
|
abbrev@1.1.1:
|
||||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||||
@@ -2279,16 +2301,17 @@ packages:
|
|||||||
|
|
||||||
glob@10.4.5:
|
glob@10.4.5:
|
||||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||||
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
glob@7.2.3:
|
glob@7.2.3:
|
||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Glob versions prior to v9 are no longer supported
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
glob@8.1.0:
|
glob@8.1.0:
|
||||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
deprecated: Glob versions prior to v9 are no longer supported
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
global-agent@3.0.0:
|
global-agent@3.0.0:
|
||||||
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
|
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
|
||||||
@@ -3613,6 +3636,7 @@ packages:
|
|||||||
tar@6.2.1:
|
tar@6.2.1:
|
||||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
temp-file@3.4.0:
|
temp-file@3.4.0:
|
||||||
resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==}
|
resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==}
|
||||||
@@ -3938,20 +3962,20 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/compat-data@7.29.7': {}
|
'@babel/compat-data@7.29.7': {}
|
||||||
|
|
||||||
'@babel/core@7.29.7':
|
'@babel/core@7.29.7(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.29.7
|
'@babel/code-frame': 7.29.7
|
||||||
'@babel/generator': 7.29.7
|
'@babel/generator': 7.29.7
|
||||||
'@babel/helper-compilation-targets': 7.29.7
|
'@babel/helper-compilation-targets': 7.29.7
|
||||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
|
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
'@babel/helpers': 7.29.7
|
'@babel/helpers': 7.29.7
|
||||||
'@babel/parser': 7.29.7
|
'@babel/parser': 7.29.7
|
||||||
'@babel/template': 7.29.7
|
'@babel/template': 7.29.7
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/types': 7.29.7
|
'@babel/types': 7.29.7
|
||||||
'@jridgewell/remapping': 2.3.5
|
'@jridgewell/remapping': 2.3.5
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
@@ -3978,41 +4002,41 @@ snapshots:
|
|||||||
lru-cache: 5.1.1
|
lru-cache: 5.1.1
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
|
|
||||||
'@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)':
|
'@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-annotate-as-pure': 7.29.7
|
'@babel/helper-annotate-as-pure': 7.29.7
|
||||||
'@babel/helper-member-expression-to-functions': 7.29.7
|
'@babel/helper-member-expression-to-functions': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-optimise-call-expression': 7.29.7
|
'@babel/helper-optimise-call-expression': 7.29.7
|
||||||
'@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7)
|
'@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7
|
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.7(supports-color@8.1.1)
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/helper-globals@7.29.7': {}
|
'@babel/helper-globals@7.29.7': {}
|
||||||
|
|
||||||
'@babel/helper-member-expression-to-functions@7.29.7':
|
'@babel/helper-member-expression-to-functions@7.29.7(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/types': 7.29.7
|
'@babel/types': 7.29.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/helper-module-imports@7.29.7':
|
'@babel/helper-module-imports@7.29.7(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/types': 7.29.7
|
'@babel/types': 7.29.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)':
|
'@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-module-imports': 7.29.7
|
'@babel/helper-module-imports': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.7(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -4022,18 +4046,18 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/helper-plugin-utils@7.29.7': {}
|
'@babel/helper-plugin-utils@7.29.7': {}
|
||||||
|
|
||||||
'@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)':
|
'@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-member-expression-to-functions': 7.29.7
|
'@babel/helper-member-expression-to-functions': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-optimise-call-expression': 7.29.7
|
'@babel/helper-optimise-call-expression': 7.29.7
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.7(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/helper-skip-transparent-expression-wrappers@7.29.7':
|
'@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/traverse': 7.29.7
|
'@babel/traverse': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/types': 7.29.7
|
'@babel/types': 7.29.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -4053,53 +4077,53 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.29.7
|
'@babel/types': 7.29.7
|
||||||
|
|
||||||
'@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)':
|
'@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-plugin-utils': 7.29.7
|
'@babel/helper-plugin-utils': 7.29.7
|
||||||
|
|
||||||
'@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)':
|
'@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-plugin-utils': 7.29.7
|
'@babel/helper-plugin-utils': 7.29.7
|
||||||
|
|
||||||
'@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)':
|
'@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
|
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
'@babel/helper-plugin-utils': 7.29.7
|
'@babel/helper-plugin-utils': 7.29.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)':
|
'@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-plugin-utils': 7.29.7
|
'@babel/helper-plugin-utils': 7.29.7
|
||||||
|
|
||||||
'@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)':
|
'@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-plugin-utils': 7.29.7
|
'@babel/helper-plugin-utils': 7.29.7
|
||||||
|
|
||||||
'@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)':
|
'@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-annotate-as-pure': 7.29.7
|
'@babel/helper-annotate-as-pure': 7.29.7
|
||||||
'@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7)
|
'@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
'@babel/helper-plugin-utils': 7.29.7
|
'@babel/helper-plugin-utils': 7.29.7
|
||||||
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7
|
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@babel/preset-typescript@7.29.7(@babel/core@7.29.7)':
|
'@babel/preset-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/helper-plugin-utils': 7.29.7
|
'@babel/helper-plugin-utils': 7.29.7
|
||||||
'@babel/helper-validator-option': 7.29.7
|
'@babel/helper-validator-option': 7.29.7
|
||||||
'@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
|
||||||
'@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
'@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -4111,7 +4135,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.29.7
|
'@babel/parser': 7.29.7
|
||||||
'@babel/types': 7.29.7
|
'@babel/types': 7.29.7
|
||||||
|
|
||||||
'@babel/traverse@7.29.7':
|
'@babel/traverse@7.29.7(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.29.7
|
'@babel/code-frame': 7.29.7
|
||||||
'@babel/generator': 7.29.7
|
'@babel/generator': 7.29.7
|
||||||
@@ -4119,7 +4143,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.29.7
|
'@babel/parser': 7.29.7
|
||||||
'@babel/template': 7.29.7
|
'@babel/template': 7.29.7
|
||||||
'@babel/types': 7.29.7
|
'@babel/types': 7.29.7
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -4187,32 +4211,32 @@ snapshots:
|
|||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
minimatch: 3.1.5
|
minimatch: 3.1.5
|
||||||
|
|
||||||
'@electron/get@2.0.3':
|
'@electron/get@2.0.3(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
env-paths: 2.2.1
|
env-paths: 2.2.1
|
||||||
fs-extra: 8.1.0
|
fs-extra: 8.1.0
|
||||||
got: 11.8.6
|
got: 11.8.6
|
||||||
progress: 2.0.3
|
progress: 2.0.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
sumchecker: 3.0.1
|
sumchecker: 3.0.1(supports-color@8.1.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
global-agent: 3.0.0
|
global-agent: 3.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@electron/notarize@2.5.0':
|
'@electron/notarize@2.5.0(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
fs-extra: 9.1.0
|
fs-extra: 9.1.0
|
||||||
promise-retry: 2.0.1
|
promise-retry: 2.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@electron/osx-sign@1.3.1':
|
'@electron/osx-sign@1.3.1(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
compare-version: 0.1.2
|
compare-version: 0.1.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
isbinaryfile: 4.0.10
|
isbinaryfile: 4.0.10
|
||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
@@ -4220,19 +4244,19 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@electron/rebuild@3.6.1':
|
'@electron/rebuild@3.6.1(bluebird@3.7.2)(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@malept/cross-spawn-promise': 2.0.0
|
'@malept/cross-spawn-promise': 2.0.0
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
detect-libc: 2.1.2
|
detect-libc: 2.1.2
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
got: 11.8.6
|
got: 11.8.6
|
||||||
node-abi: 3.92.0
|
node-abi: 3.92.0
|
||||||
node-api-version: 0.2.1
|
node-api-version: 0.2.1
|
||||||
node-gyp: 9.4.1
|
node-gyp: 9.4.1(bluebird@3.7.2)(supports-color@8.1.1)
|
||||||
ora: 5.4.1
|
ora: 5.4.1
|
||||||
read-binary-file-arch: 1.0.6
|
read-binary-file-arch: 1.0.6(supports-color@8.1.1)
|
||||||
semver: 7.8.4
|
semver: 7.8.4
|
||||||
tar: 6.2.1
|
tar: 6.2.1
|
||||||
yargs: 17.7.3
|
yargs: 17.7.3
|
||||||
@@ -4240,11 +4264,11 @@ snapshots:
|
|||||||
- bluebird
|
- bluebird
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@electron/universal@2.0.1':
|
'@electron/universal@2.0.1(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@electron/asar': 3.4.1
|
'@electron/asar': 3.4.1
|
||||||
'@malept/cross-spawn-promise': 2.0.0
|
'@malept/cross-spawn-promise': 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
dir-compare: 4.2.0
|
dir-compare: 4.2.0
|
||||||
fs-extra: 11.3.5
|
fs-extra: 11.3.5
|
||||||
minimatch: 9.0.9
|
minimatch: 9.0.9
|
||||||
@@ -4504,6 +4528,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
|
|
||||||
|
'@internationalized/date@3.12.3':
|
||||||
|
dependencies:
|
||||||
|
'@swc/helpers': 0.5.23
|
||||||
|
|
||||||
'@internationalized/message@3.1.10':
|
'@internationalized/message@3.1.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
@@ -4513,7 +4541,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
|
|
||||||
'@internationalized/string@3.2.9':
|
'@internationalized/string@3.2.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
|
|
||||||
@@ -4549,16 +4577,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
|
|
||||||
'@malept/flatpak-bundler@0.4.0':
|
'@malept/flatpak-bundler@0.4.0(supports-color@8.1.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
fs-extra: 9.1.0
|
fs-extra: 9.1.0
|
||||||
lodash: 4.18.1
|
lodash: 4.18.1
|
||||||
tmp-promise: 3.0.3
|
tmp-promise: 3.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
|
'@modelcontextprotocol/sdk@1.29.0(supports-color@8.1.1)(zod@3.25.76)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@hono/node-server': 1.19.14(hono@4.12.26)
|
'@hono/node-server': 1.19.14(hono@4.12.26)
|
||||||
ajv: 8.20.0
|
ajv: 8.20.0
|
||||||
@@ -4568,8 +4596,8 @@ snapshots:
|
|||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
eventsource: 3.0.7
|
eventsource: 3.0.7
|
||||||
eventsource-parser: 3.1.0
|
eventsource-parser: 3.1.0
|
||||||
express: 5.2.1
|
express: 5.2.1(supports-color@8.1.1)
|
||||||
express-rate-limit: 8.5.2(express@5.2.1)
|
express-rate-limit: 8.5.2(express@5.2.1(supports-color@8.1.1))
|
||||||
hono: 4.12.26
|
hono: 4.12.26
|
||||||
jose: 6.2.3
|
jose: 6.2.3
|
||||||
json-schema-typed: 8.0.2
|
json-schema-typed: 8.0.2
|
||||||
@@ -4682,9 +4710,9 @@ snapshots:
|
|||||||
|
|
||||||
'@react-aria/i18n@3.13.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
'@react-aria/i18n@3.13.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.12.2
|
'@internationalized/date': 3.12.3
|
||||||
'@internationalized/message': 3.1.10
|
'@internationalized/message': 3.1.10
|
||||||
'@internationalized/string': 3.2.9
|
'@internationalized/string': 3.2.10
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
react: 19.2.7
|
react: 19.2.7
|
||||||
react-aria: 3.50.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
react-aria: 3.50.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
@@ -5053,11 +5081,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 26.0.1
|
'@types/node': 26.0.1
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.7.0(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4))':
|
'@vitejs/plugin-react@4.7.0(supports-color@8.1.1)(vite@7.3.5(@types/node@26.0.1)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
|
||||||
'@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
|
||||||
'@rolldown/pluginutils': 1.0.0-beta.27
|
'@rolldown/pluginutils': 1.0.0-beta.27
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
react-refresh: 0.17.0
|
react-refresh: 0.17.0
|
||||||
@@ -5111,9 +5139,9 @@ snapshots:
|
|||||||
mime-types: 3.0.2
|
mime-types: 3.0.2
|
||||||
negotiator: 1.0.0
|
negotiator: 1.0.0
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -5168,28 +5196,28 @@ snapshots:
|
|||||||
|
|
||||||
app-builder-bin@5.0.0-alpha.10: {}
|
app-builder-bin@5.0.0-alpha.10: {}
|
||||||
|
|
||||||
app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8):
|
app-builder-lib@25.1.8(bluebird@3.7.2)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@develar/schema-utils': 2.6.5
|
'@develar/schema-utils': 2.6.5
|
||||||
'@electron/notarize': 2.5.0
|
'@electron/notarize': 2.5.0(supports-color@8.1.1)
|
||||||
'@electron/osx-sign': 1.3.1
|
'@electron/osx-sign': 1.3.1(supports-color@8.1.1)
|
||||||
'@electron/rebuild': 3.6.1
|
'@electron/rebuild': 3.6.1(bluebird@3.7.2)(supports-color@8.1.1)
|
||||||
'@electron/universal': 2.0.1
|
'@electron/universal': 2.0.1(supports-color@8.1.1)
|
||||||
'@malept/flatpak-bundler': 0.4.0
|
'@malept/flatpak-bundler': 0.4.0(supports-color@8.1.1)
|
||||||
'@types/fs-extra': 9.0.13
|
'@types/fs-extra': 9.0.13
|
||||||
async-exit-hook: 2.0.1
|
async-exit-hook: 2.0.1
|
||||||
bluebird-lst: 1.0.9
|
bluebird-lst: 1.0.9
|
||||||
builder-util: 25.1.7
|
builder-util: 25.1.7(supports-color@8.1.1)
|
||||||
builder-util-runtime: 9.2.10
|
builder-util-runtime: 9.2.10(supports-color@8.1.1)
|
||||||
chromium-pickle-js: 0.2.0
|
chromium-pickle-js: 0.2.0
|
||||||
config-file-ts: 0.2.8-rc1
|
config-file-ts: 0.2.8-rc1
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8)
|
dmg-builder: 25.1.8(bluebird@3.7.2)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1)
|
||||||
dotenv: 16.6.1
|
dotenv: 16.6.1
|
||||||
dotenv-expand: 11.0.7
|
dotenv-expand: 11.0.7
|
||||||
ejs: 3.1.10
|
ejs: 3.1.10
|
||||||
electron-builder-squirrel-windows: 25.1.8(dmg-builder@25.1.8)
|
electron-builder-squirrel-windows: 25.1.8(bluebird@3.7.2)(dmg-builder@25.1.8)(supports-color@8.1.1)
|
||||||
electron-publish: 25.1.7
|
electron-publish: 25.1.7(supports-color@8.1.1)
|
||||||
form-data: 4.0.6
|
form-data: 4.0.6
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
hosted-git-info: 4.1.0
|
hosted-git-info: 4.1.0
|
||||||
@@ -5297,11 +5325,11 @@ snapshots:
|
|||||||
|
|
||||||
bluebird@3.7.2: {}
|
bluebird@3.7.2: {}
|
||||||
|
|
||||||
body-parser@2.3.0:
|
body-parser@2.3.0(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
bytes: 3.1.2
|
bytes: 3.1.2
|
||||||
content-type: 2.0.0
|
content-type: 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
http-errors: 2.0.1
|
http-errors: 2.0.1
|
||||||
iconv-lite: 0.7.2
|
iconv-lite: 0.7.2
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
@@ -5355,26 +5383,26 @@ snapshots:
|
|||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
|
|
||||||
builder-util-runtime@9.2.10:
|
builder-util-runtime@9.2.10(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
sax: 1.6.0
|
sax: 1.6.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
builder-util@25.1.7:
|
builder-util@25.1.7(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
7zip-bin: 5.2.0
|
7zip-bin: 5.2.0
|
||||||
'@types/debug': 4.1.13
|
'@types/debug': 4.1.13
|
||||||
app-builder-bin: 5.0.0-alpha.10
|
app-builder-bin: 5.0.0-alpha.10
|
||||||
bluebird-lst: 1.0.9
|
bluebird-lst: 1.0.9
|
||||||
builder-util-runtime: 9.2.10
|
builder-util-runtime: 9.2.10(supports-color@8.1.1)
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2(supports-color@8.1.1)
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6(supports-color@8.1.1)
|
||||||
is-ci: 3.0.1
|
is-ci: 3.0.1
|
||||||
js-yaml: 4.2.0
|
js-yaml: 4.2.0
|
||||||
source-map-support: 0.5.21
|
source-map-support: 0.5.21
|
||||||
@@ -5389,7 +5417,7 @@ snapshots:
|
|||||||
|
|
||||||
bytes@3.1.2: {}
|
bytes@3.1.2: {}
|
||||||
|
|
||||||
cacache@16.1.3:
|
cacache@16.1.3(bluebird@3.7.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@npmcli/fs': 2.1.2
|
'@npmcli/fs': 2.1.2
|
||||||
'@npmcli/move-file': 2.0.1
|
'@npmcli/move-file': 2.0.1
|
||||||
@@ -5404,7 +5432,7 @@ snapshots:
|
|||||||
minipass-pipeline: 1.2.4
|
minipass-pipeline: 1.2.4
|
||||||
mkdirp: 1.0.4
|
mkdirp: 1.0.4
|
||||||
p-map: 4.0.0
|
p-map: 4.0.0
|
||||||
promise-inflight: 1.0.1
|
promise-inflight: 1.0.1(bluebird@3.7.2)
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
ssri: 9.0.1
|
ssri: 9.0.1
|
||||||
tar: 6.2.1
|
tar: 6.2.1
|
||||||
@@ -5618,9 +5646,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn: 3.1.0
|
mimic-fn: 3.1.0
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
optionalDependencies:
|
||||||
|
supports-color: 8.1.1
|
||||||
|
|
||||||
decimal.js@10.6.0: {}
|
decimal.js@10.6.0: {}
|
||||||
|
|
||||||
@@ -5681,11 +5711,11 @@ snapshots:
|
|||||||
minimatch: 3.1.5
|
minimatch: 3.1.5
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8):
|
dmg-builder@25.1.8(bluebird@3.7.2)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)
|
app-builder-lib: 25.1.8(bluebird@3.7.2)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1)
|
||||||
builder-util: 25.1.7
|
builder-util: 25.1.7(supports-color@8.1.1)
|
||||||
builder-util-runtime: 9.2.10
|
builder-util-runtime: 9.2.10(supports-color@8.1.1)
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
iconv-lite: 0.6.3
|
iconv-lite: 0.6.3
|
||||||
js-yaml: 4.2.0
|
js-yaml: 4.2.0
|
||||||
@@ -5764,24 +5794,24 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jake: 10.9.4
|
jake: 10.9.4
|
||||||
|
|
||||||
electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8):
|
electron-builder-squirrel-windows@25.1.8(bluebird@3.7.2)(dmg-builder@25.1.8)(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)
|
app-builder-lib: 25.1.8(bluebird@3.7.2)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1)
|
||||||
archiver: 5.3.2
|
archiver: 5.3.2
|
||||||
builder-util: 25.1.7
|
builder-util: 25.1.7(supports-color@8.1.1)
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bluebird
|
- bluebird
|
||||||
- dmg-builder
|
- dmg-builder
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8):
|
electron-builder@25.1.8(bluebird@3.7.2)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)
|
app-builder-lib: 25.1.8(bluebird@3.7.2)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1)
|
||||||
builder-util: 25.1.7
|
builder-util: 25.1.7(supports-color@8.1.1)
|
||||||
builder-util-runtime: 9.2.10
|
builder-util-runtime: 9.2.10(supports-color@8.1.1)
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8)
|
dmg-builder: 25.1.8(bluebird@3.7.2)(electron-builder-squirrel-windows@25.1.8)(supports-color@8.1.1)
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
is-ci: 3.0.1
|
is-ci: 3.0.1
|
||||||
lazy-val: 1.0.5
|
lazy-val: 1.0.5
|
||||||
@@ -5792,11 +5822,11 @@ snapshots:
|
|||||||
- electron-builder-squirrel-windows
|
- electron-builder-squirrel-windows
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
electron-publish@25.1.7:
|
electron-publish@25.1.7(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/fs-extra': 9.0.13
|
'@types/fs-extra': 9.0.13
|
||||||
builder-util: 25.1.7
|
builder-util: 25.1.7(supports-color@8.1.1)
|
||||||
builder-util-runtime: 9.2.10
|
builder-util-runtime: 9.2.10(supports-color@8.1.1)
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
lazy-val: 1.0.5
|
lazy-val: 1.0.5
|
||||||
@@ -5806,11 +5836,11 @@ snapshots:
|
|||||||
|
|
||||||
electron-to-chromium@1.5.376: {}
|
electron-to-chromium@1.5.376: {}
|
||||||
|
|
||||||
electron@33.4.11:
|
electron@33.4.11(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@electron/get': 2.0.3
|
'@electron/get': 2.0.3(supports-color@8.1.1)
|
||||||
'@types/node': 20.19.43
|
'@types/node': 20.19.43
|
||||||
extract-zip: 2.0.1
|
extract-zip: 2.0.1(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -5973,25 +6003,25 @@ snapshots:
|
|||||||
|
|
||||||
exponential-backoff@3.1.3: {}
|
exponential-backoff@3.1.3: {}
|
||||||
|
|
||||||
express-rate-limit@8.5.2(express@5.2.1):
|
express-rate-limit@8.5.2(express@5.2.1(supports-color@8.1.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
express: 5.2.1
|
express: 5.2.1(supports-color@8.1.1)
|
||||||
ip-address: 10.2.0
|
ip-address: 10.2.0
|
||||||
|
|
||||||
express@5.2.1:
|
express@5.2.1(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
accepts: 2.0.0
|
accepts: 2.0.0
|
||||||
body-parser: 2.3.0
|
body-parser: 2.3.0(supports-color@8.1.1)
|
||||||
content-disposition: 1.1.0
|
content-disposition: 1.1.0
|
||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
cookie-signature: 1.2.2
|
cookie-signature: 1.2.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
etag: 1.8.1
|
etag: 1.8.1
|
||||||
finalhandler: 2.1.1
|
finalhandler: 2.1.1(supports-color@8.1.1)
|
||||||
fresh: 2.0.0
|
fresh: 2.0.0
|
||||||
http-errors: 2.0.1
|
http-errors: 2.0.1
|
||||||
merge-descriptors: 2.0.0
|
merge-descriptors: 2.0.0
|
||||||
@@ -6002,18 +6032,18 @@ snapshots:
|
|||||||
proxy-addr: 2.0.7
|
proxy-addr: 2.0.7
|
||||||
qs: 6.15.2
|
qs: 6.15.2
|
||||||
range-parser: 1.2.1
|
range-parser: 1.2.1
|
||||||
router: 2.2.0
|
router: 2.2.0(supports-color@8.1.1)
|
||||||
send: 1.2.1
|
send: 1.2.1(supports-color@8.1.1)
|
||||||
serve-static: 2.2.1
|
serve-static: 2.2.1(supports-color@8.1.1)
|
||||||
statuses: 2.0.2
|
statuses: 2.0.2
|
||||||
type-is: 2.1.0
|
type-is: 2.1.0
|
||||||
vary: 1.1.2
|
vary: 1.1.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
extract-zip@2.0.1:
|
extract-zip@2.0.1(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
get-stream: 5.2.0
|
get-stream: 5.2.0
|
||||||
yauzl: 2.10.0
|
yauzl: 2.10.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -6069,9 +6099,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
finalhandler@2.1.1:
|
finalhandler@2.1.1(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
@@ -6303,18 +6333,18 @@ snapshots:
|
|||||||
statuses: 2.0.2
|
statuses: 2.0.2
|
||||||
toidentifier: 1.0.1
|
toidentifier: 1.0.1
|
||||||
|
|
||||||
http-proxy-agent@5.0.0:
|
http-proxy-agent@5.0.0(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tootallnate/once': 2.0.1
|
'@tootallnate/once': 2.0.1
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2(supports-color@8.1.1)
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -6323,17 +6353,17 @@ snapshots:
|
|||||||
quick-lru: 5.1.1
|
quick-lru: 5.1.1
|
||||||
resolve-alpn: 1.2.1
|
resolve-alpn: 1.2.1
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2(supports-color@8.1.1)
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
https-proxy-agent@7.0.6:
|
https-proxy-agent@7.0.6(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -6643,13 +6673,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
make-fetch-happen@10.2.1:
|
make-fetch-happen@10.2.1(bluebird@3.7.2)(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
agentkeepalive: 4.6.0
|
agentkeepalive: 4.6.0
|
||||||
cacache: 16.1.3
|
cacache: 16.1.3(bluebird@3.7.2)
|
||||||
http-cache-semantics: 4.2.0
|
http-cache-semantics: 4.2.0
|
||||||
http-proxy-agent: 5.0.0
|
http-proxy-agent: 5.0.0(supports-color@8.1.1)
|
||||||
https-proxy-agent: 5.0.1
|
https-proxy-agent: 5.0.1(supports-color@8.1.1)
|
||||||
is-lambda: 1.0.1
|
is-lambda: 1.0.1
|
||||||
lru-cache: 7.18.3
|
lru-cache: 7.18.3
|
||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
@@ -6659,7 +6689,7 @@ snapshots:
|
|||||||
minipass-pipeline: 1.2.4
|
minipass-pipeline: 1.2.4
|
||||||
negotiator: 0.6.4
|
negotiator: 0.6.4
|
||||||
promise-retry: 2.0.1
|
promise-retry: 2.0.1
|
||||||
socks-proxy-agent: 7.0.0
|
socks-proxy-agent: 7.0.0(supports-color@8.1.1)
|
||||||
ssri: 9.0.1
|
ssri: 9.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bluebird
|
- bluebird
|
||||||
@@ -6814,13 +6844,13 @@ snapshots:
|
|||||||
fetch-blob: 3.2.0
|
fetch-blob: 3.2.0
|
||||||
formdata-polyfill: 4.0.10
|
formdata-polyfill: 4.0.10
|
||||||
|
|
||||||
node-gyp@9.4.1:
|
node-gyp@9.4.1(bluebird@3.7.2)(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
env-paths: 2.2.1
|
env-paths: 2.2.1
|
||||||
exponential-backoff: 3.1.3
|
exponential-backoff: 3.1.3
|
||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
make-fetch-happen: 10.2.1
|
make-fetch-happen: 10.2.1(bluebird@3.7.2)(supports-color@8.1.1)
|
||||||
nopt: 6.0.0
|
nopt: 6.0.0
|
||||||
npmlog: 6.0.2
|
npmlog: 6.0.2
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
@@ -7027,7 +7057,9 @@ snapshots:
|
|||||||
|
|
||||||
progress@2.0.3: {}
|
progress@2.0.3: {}
|
||||||
|
|
||||||
promise-inflight@1.0.1: {}
|
promise-inflight@1.0.1(bluebird@3.7.2):
|
||||||
|
optionalDependencies:
|
||||||
|
bluebird: 3.7.2
|
||||||
|
|
||||||
promise-retry@2.0.1:
|
promise-retry@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7080,7 +7112,7 @@ snapshots:
|
|||||||
|
|
||||||
react-aria-components@1.19.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
react-aria-components@1.19.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.12.2
|
'@internationalized/date': 3.12.3
|
||||||
'@react-types/shared': 3.36.0(react@19.2.7)
|
'@react-types/shared': 3.36.0(react@19.2.7)
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
client-only: 0.0.1
|
client-only: 0.0.1
|
||||||
@@ -7091,9 +7123,9 @@ snapshots:
|
|||||||
|
|
||||||
react-aria@3.50.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
react-aria@3.50.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.12.2
|
'@internationalized/date': 3.12.3
|
||||||
'@internationalized/number': 3.6.7
|
'@internationalized/number': 3.6.7
|
||||||
'@internationalized/string': 3.2.9
|
'@internationalized/string': 3.2.10
|
||||||
'@react-types/shared': 3.36.0(react@19.2.7)
|
'@react-types/shared': 3.36.0(react@19.2.7)
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
aria-hidden: 1.2.6
|
aria-hidden: 1.2.6
|
||||||
@@ -7114,9 +7146,9 @@ snapshots:
|
|||||||
|
|
||||||
react-stately@3.48.0(react@19.2.7):
|
react-stately@3.48.0(react@19.2.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.12.2
|
'@internationalized/date': 3.12.3
|
||||||
'@internationalized/number': 3.6.7
|
'@internationalized/number': 3.6.7
|
||||||
'@internationalized/string': 3.2.9
|
'@internationalized/string': 3.2.10
|
||||||
'@react-types/shared': 3.36.0(react@19.2.7)
|
'@react-types/shared': 3.36.0(react@19.2.7)
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
react: 19.2.7
|
react: 19.2.7
|
||||||
@@ -7139,9 +7171,9 @@ snapshots:
|
|||||||
|
|
||||||
react@19.2.7: {}
|
react@19.2.7: {}
|
||||||
|
|
||||||
read-binary-file-arch@1.0.6:
|
read-binary-file-arch@1.0.6(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -7250,9 +7282,9 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc': 4.62.0
|
'@rollup/rollup-win32-x64-msvc': 4.62.0
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
router@2.2.0:
|
router@2.2.0(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
is-promise: 4.0.0
|
is-promise: 4.0.0
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
@@ -7291,9 +7323,9 @@ snapshots:
|
|||||||
|
|
||||||
semver@7.8.4: {}
|
semver@7.8.4: {}
|
||||||
|
|
||||||
send@1.2.1:
|
send@1.2.1(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
etag: 1.8.1
|
etag: 1.8.1
|
||||||
@@ -7312,12 +7344,12 @@ snapshots:
|
|||||||
type-fest: 0.13.1
|
type-fest: 0.13.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
serve-static@2.2.1:
|
serve-static@2.2.1(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
send: 1.2.1
|
send: 1.2.1(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -7325,14 +7357,14 @@ snapshots:
|
|||||||
|
|
||||||
setprototypeof@1.2.0: {}
|
setprototypeof@1.2.0: {}
|
||||||
|
|
||||||
shadcn@4.11.0(typescript@5.8.3):
|
shadcn@4.11.0(supports-color@8.1.1)(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7(supports-color@8.1.1)
|
||||||
'@babel/parser': 7.29.7
|
'@babel/parser': 7.29.7
|
||||||
'@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
'@babel/preset-typescript': 7.29.7(@babel/core@7.29.7)
|
'@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
|
||||||
'@dotenvx/dotenvx': 1.74.2
|
'@dotenvx/dotenvx': 1.74.2
|
||||||
'@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76)
|
'@modelcontextprotocol/sdk': 1.29.0(supports-color@8.1.1)(zod@3.25.76)
|
||||||
'@types/validate-npm-package-name': 4.0.2
|
'@types/validate-npm-package-name': 4.0.2
|
||||||
browserslist: 4.28.2
|
browserslist: 4.28.2
|
||||||
commander: 14.0.3
|
commander: 14.0.3
|
||||||
@@ -7344,7 +7376,7 @@ snapshots:
|
|||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
fs-extra: 11.3.5
|
fs-extra: 11.3.5
|
||||||
fuzzysort: 3.1.0
|
fuzzysort: 3.1.0
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6(supports-color@8.1.1)
|
||||||
kleur: 4.1.5
|
kleur: 4.1.5
|
||||||
node-fetch: 3.3.2
|
node-fetch: 3.3.2
|
||||||
open: 11.0.0
|
open: 11.0.0
|
||||||
@@ -7421,10 +7453,10 @@ snapshots:
|
|||||||
|
|
||||||
smart-buffer@4.2.0: {}
|
smart-buffer@4.2.0: {}
|
||||||
|
|
||||||
socks-proxy-agent@7.0.0:
|
socks-proxy-agent@7.0.0(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2(supports-color@8.1.1)
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
socks: 2.8.9
|
socks: 2.8.9
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -7507,9 +7539,9 @@ snapshots:
|
|||||||
|
|
||||||
strip-final-newline@4.0.0: {}
|
strip-final-newline@4.0.0: {}
|
||||||
|
|
||||||
sumchecker@3.0.1:
|
sumchecker@3.0.1(supports-color@8.1.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -1,4 +1,20 @@
|
|||||||
# 允许执行构建脚本的依赖(pnpm 10+ 默认禁止依赖运行 postinstall)
|
# // __ __ __ __ ______ __ __ ______ __ __ ______ ______
|
||||||
|
# // /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
|
||||||
|
# // \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
|
||||||
|
# // \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
|
||||||
|
# // \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
|
||||||
|
# //
|
||||||
|
# // 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
|
||||||
|
# // 未经允许的情况下删除此版权头可能会受到民事指控
|
||||||
|
# // 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
||||||
|
|
||||||
|
# pnpm 10+ 工作区配置
|
||||||
|
# onlyBuiltDependencies:pnpm 10 早期版本的构建脚本允许列表
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- electron
|
||||||
|
- esbuild
|
||||||
|
|
||||||
|
# allowBuilds:pnpm 10.29+ / 11 的构建脚本最终授权(包名 → 是否允许执行 postinstall)
|
||||||
allowBuilds:
|
allowBuilds:
|
||||||
electron: true
|
electron: true
|
||||||
esbuild: true
|
esbuild: true
|
||||||
|
|||||||
@@ -142,12 +142,36 @@ export interface ScannedVersion {
|
|||||||
releaseTime?: string;
|
releaseTime?: string;
|
||||||
hasJar: boolean;
|
hasJar: boolean;
|
||||||
hasJson: boolean;
|
hasJson: boolean;
|
||||||
|
/** 检测到的 mod 加载器 */
|
||||||
|
loaders: string[];
|
||||||
|
/** 是否健康(JSON + JAR 都存在) */
|
||||||
|
healthy: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scanGameDir(gamePath: string): Promise<{ versions: ScannedVersion[] }> {
|
export async function scanGameDir(gamePath: string): Promise<{ versions: ScannedVersion[] }> {
|
||||||
return ipcInvoke<{ versions: ScannedVersion[] }>('instance:scan-dir', { gamePath });
|
return ipcInvoke<{ versions: ScannedVersion[] }>('instance:scan-dir', { gamePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从已安装版本导入实例
|
||||||
|
export async function importExistingInstance(
|
||||||
|
name: string,
|
||||||
|
gamePath: string,
|
||||||
|
versionId: string,
|
||||||
|
options?: {
|
||||||
|
description?: string;
|
||||||
|
java?: string;
|
||||||
|
minMemory?: number;
|
||||||
|
maxMemory?: number;
|
||||||
|
}
|
||||||
|
): Promise<InstanceInfo> {
|
||||||
|
return ipcInvoke<InstanceInfo>('instance:import', {
|
||||||
|
name,
|
||||||
|
gamePath,
|
||||||
|
versionId,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 通过系统对话框选择文件夹
|
// 通过系统对话框选择文件夹
|
||||||
export async function selectFolder(): Promise<{ folderPath: string } | null> {
|
export async function selectFolder(): Promise<{ folderPath: string } | null> {
|
||||||
return ipcInvoke<{ folderPath: string } | null>('dialog:openFolder');
|
return ipcInvoke<{ folderPath: string } | null>('dialog:openFolder');
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
|
// __ __ __ __ ______ __ __ ______ __ __ ______ ______
|
||||||
|
// /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
|
||||||
|
// \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
|
||||||
|
// \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
|
||||||
|
// \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
|
||||||
|
//
|
||||||
|
// 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
|
||||||
|
// 未经允许的情况下删除此版权头可能会受到民事指控
|
||||||
|
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
||||||
|
|
||||||
import { useTaskStore } from "@/stores/taskStore";
|
import { useTaskStore } from "@/stores/taskStore";
|
||||||
import { useRouteStore } from "@/stores/routeStore";
|
import { useRouteStore } from "@/stores/routeStore";
|
||||||
import { ListTodo } from "lucide-react";
|
import { ListTodo, Pause } from "lucide-react";
|
||||||
|
|
||||||
export function TaskButton() {
|
export function TaskButton() {
|
||||||
const navigate = useRouteStore((s) => s.navigate);
|
const navigate = useRouteStore((s) => s.navigate);
|
||||||
const isRunning = useTaskStore((s) => s.isRunning);
|
const tasks = useTaskStore((s) => s.tasks);
|
||||||
|
|
||||||
const running = isRunning();
|
const running = tasks.some((t) => t.status === "running" || t.status === "pending");
|
||||||
|
const paused = tasks.some((t) => t.status === "paused");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -31,6 +42,8 @@ export function TaskButton() {
|
|||||||
style={{ animationDuration: "1.2s" }}
|
style={{ animationDuration: "1.2s" }}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
) : paused ? (
|
||||||
|
<Pause className="w-3.5 h-3.5 text-amber-500" />
|
||||||
) : (
|
) : (
|
||||||
<ListTodo className="w-3.5 h-3.5" />
|
<ListTodo className="w-3.5 h-3.5" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
// __ __ __ __ ______ __ __ ______ __ __ ______ ______
|
||||||
|
// /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
|
||||||
|
// \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
|
||||||
|
// \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
|
||||||
|
// \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
|
||||||
|
//
|
||||||
|
// 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
|
||||||
|
// 未经允许的情况下删除此版权头可能会受到民事指控
|
||||||
|
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { Task } from "@/types/task";
|
import type { Task } from "@/types/task";
|
||||||
import { useTaskStore } from "@/stores/taskStore";
|
import { useTaskStore } from "@/stores/taskStore";
|
||||||
@@ -10,19 +20,33 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
Ban,
|
Ban,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
|
// 状态展示配置:覆盖 @xmcl/task 的 Idle/Running/Paused/Succeed/Failed/Cancelled
|
||||||
const statusConfig: Record<
|
const statusConfig: Record<
|
||||||
Task["status"],
|
Task["status"],
|
||||||
{ label: string; color: string; icon: typeof Check; barColor: string }
|
{ label: string; color: string; icon: typeof Check; barColor: string }
|
||||||
> = {
|
> = {
|
||||||
pending: { label: "等待中", color: "text-muted-foreground bg-muted/50", icon: RefreshCw, barColor: "bg-muted-foreground/30" },
|
pending: { label: "等待中", color: "text-muted-foreground bg-muted/50", icon: RefreshCw, barColor: "bg-muted-foreground/30" },
|
||||||
running: { label: "运行中", color: "text-foreground bg-foreground/10", icon: RefreshCw, barColor: "bg-primary" },
|
running: { label: "运行中", color: "text-foreground bg-foreground/10", icon: RefreshCw, barColor: "bg-primary" },
|
||||||
|
paused: { label: "已暂停", color: "text-amber-600 dark:text-amber-400 bg-amber-500/10", icon: Pause, barColor: "bg-amber-500" },
|
||||||
completed: { label: "已完成", color: "text-green-600 dark:text-green-400 bg-green-500/10", icon: Check, barColor: "bg-green-500" },
|
completed: { label: "已完成", color: "text-green-600 dark:text-green-400 bg-green-500/10", icon: Check, barColor: "bg-green-500" },
|
||||||
failed: { label: "失败", color: "text-red-600 dark:text-red-400 bg-red-500/10", icon: AlertCircle, barColor: "bg-red-500" },
|
failed: { label: "失败", color: "text-red-600 dark:text-red-400 bg-red-500/10", icon: AlertCircle, barColor: "bg-red-500" },
|
||||||
cancelled: { label: "已取消", color: "text-muted-foreground bg-muted/50", icon: Ban, barColor: "bg-muted-foreground/30" },
|
cancelled: { label: "已取消", color: "text-muted-foreground bg-muted/50", icon: Ban, barColor: "bg-muted-foreground/30" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @xmcl/task 状态徽标文案
|
||||||
|
const xmclStateLabels: Record<string, string> = {
|
||||||
|
idle: "Idle",
|
||||||
|
running: "Running",
|
||||||
|
paused: "Paused",
|
||||||
|
succeed: "Succeed",
|
||||||
|
failed: "Failed",
|
||||||
|
cancelled: "Cancelled",
|
||||||
|
};
|
||||||
|
|
||||||
function formatTime(ts: number): string {
|
function formatTime(ts: number): string {
|
||||||
const d = new Date(ts);
|
const d = new Date(ts);
|
||||||
return d.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
return d.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
||||||
@@ -43,14 +67,17 @@ interface TaskCardProps {
|
|||||||
export function TaskCard({ task }: TaskCardProps) {
|
export function TaskCard({ task }: TaskCardProps) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const cancelTask = useTaskStore((s) => s.cancelTask);
|
const cancelTask = useTaskStore((s) => s.cancelTask);
|
||||||
|
const pauseTask = useTaskStore((s) => s.pauseTask);
|
||||||
|
const resumeTask = useTaskStore((s) => s.resumeTask);
|
||||||
const removeTask = useTaskStore((s) => s.removeTask);
|
const removeTask = useTaskStore((s) => s.removeTask);
|
||||||
const retryTask = useTaskStore((s) => s.retryTask);
|
const retryTask = useTaskStore((s) => s.retryTask);
|
||||||
|
|
||||||
const sc = statusConfig[task.status];
|
const sc = statusConfig[task.status];
|
||||||
const isRunning = task.status === "running";
|
const isRunning = task.status === "running";
|
||||||
const isPending = task.status === "pending";
|
const isPending = task.status === "pending";
|
||||||
|
const isPaused = task.status === "paused";
|
||||||
const isFinished = task.status === "completed" || task.status === "failed" || task.status === "cancelled";
|
const isFinished = task.status === "completed" || task.status === "failed" || task.status === "cancelled";
|
||||||
const canCancel = isRunning || isPending;
|
const canCancel = isRunning || isPending || isPaused;
|
||||||
const canRetry = task.status === "failed";
|
const canRetry = task.status === "failed";
|
||||||
const hasLogs = task.logs.length > 0;
|
const hasLogs = task.logs.length > 0;
|
||||||
const progressPct =
|
const progressPct =
|
||||||
@@ -69,14 +96,25 @@ export function TaskCard({ task }: TaskCardProps) {
|
|||||||
<span className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 ${sc.color}`}>
|
<span className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 ${sc.color}`}>
|
||||||
{sc.label}
|
{sc.label}
|
||||||
</span>
|
</span>
|
||||||
|
{/* @xmcl/task 原始状态徽标 */}
|
||||||
|
{task.xmclState && (
|
||||||
|
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-mono font-medium shrink-0 bg-primary/10 text-primary/80">
|
||||||
|
{xmclStateLabels[task.xmclState] ?? task.xmclState}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{task.description && (
|
{task.description && (
|
||||||
<p className="text-[12px] text-muted-foreground truncate mb-2">{task.description}</p>
|
<p className="text-[12px] text-muted-foreground truncate mb-2">{task.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* @xmcl/task 任务路径(如 install.minecraft) */}
|
||||||
|
{task.xmclPath && (
|
||||||
|
<p className="text-[10px] font-mono text-primary/50 mb-1.5 truncate">{task.xmclPath}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Progress */}
|
{/* Progress */}
|
||||||
{(isRunning || isPending) && (
|
{(isRunning || isPending || isPaused) && (
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<Progress
|
<Progress
|
||||||
value={progressPct ?? (isPending ? 0 : 0)}
|
value={progressPct ?? (isPending ? 0 : 0)}
|
||||||
@@ -99,12 +137,35 @@ export function TaskCard({ task }: TaskCardProps) {
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
|
{isRunning && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
onClick={() => pauseTask(task.id)}
|
||||||
|
className="h-6 w-6 text-muted-foreground hover:text-amber-500"
|
||||||
|
title="暂停"
|
||||||
|
>
|
||||||
|
<Pause className="w-3.5 h-3.5" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isPaused && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
onClick={() => resumeTask(task.id)}
|
||||||
|
className="h-6 w-6 text-muted-foreground hover:text-foreground"
|
||||||
|
title="继续"
|
||||||
|
>
|
||||||
|
<Play className="w-3.5 h-3.5" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{canCancel && (
|
{canCancel && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
onClick={() => cancelTask(task.id)}
|
onClick={() => cancelTask(task.id)}
|
||||||
className="h-6 w-6 text-muted-foreground hover:text-destructive"
|
className="h-6 w-6 text-muted-foreground hover:text-destructive"
|
||||||
|
title="取消"
|
||||||
>
|
>
|
||||||
<X className="w-3.5 h-3.5" />
|
<X className="w-3.5 h-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -115,6 +176,7 @@ export function TaskCard({ task }: TaskCardProps) {
|
|||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
onClick={() => retryTask(task.id)}
|
onClick={() => retryTask(task.id)}
|
||||||
className="h-6 w-6 text-muted-foreground hover:text-foreground"
|
className="h-6 w-6 text-muted-foreground hover:text-foreground"
|
||||||
|
title="重试"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-3.5 h-3.5" />
|
<RefreshCw className="w-3.5 h-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -125,6 +187,7 @@ export function TaskCard({ task }: TaskCardProps) {
|
|||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
onClick={() => removeTask(task.id)}
|
onClick={() => removeTask(task.id)}
|
||||||
className="h-6 w-6 text-muted-foreground hover:text-foreground"
|
className="h-6 w-6 text-muted-foreground hover:text-foreground"
|
||||||
|
title="移除"
|
||||||
>
|
>
|
||||||
<X className="w-3.5 h-3.5" />
|
<X className="w-3.5 h-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -141,12 +141,15 @@ function AlertDialogDescription({
|
|||||||
|
|
||||||
function AlertDialogAction({
|
function AlertDialogAction({
|
||||||
className,
|
className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: AlertDialogPrimitive.Close.Props &
|
||||||
|
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<AlertDialogPrimitive.Close
|
||||||
data-slot="alert-dialog-action"
|
data-slot="alert-dialog-action"
|
||||||
className={cn(className)}
|
render={<Button variant={variant} size={size} className={cn(className)} />}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
+116
-70
@@ -1,28 +1,36 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { AlertTriangle, TestTube, RotateCcw, Copy, Trash2 } from "lucide-react";
|
import { AlertTriangle, TestTube, Copy, Trash2 } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { GlassCard, SettingRow, PageHeader } from "./components";
|
||||||
|
import { useConfirmDialogStore } from "@/stores/confirmDialogStore";
|
||||||
|
|
||||||
export function CrashDebug() {
|
export function CrashDebug() {
|
||||||
const [log, setLog] = useState("");
|
const [log, setLog] = useState("");
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [status, setStatus] = useState("");
|
const [status, setStatus] = useState("");
|
||||||
|
const openDialog = useConfirmDialogStore((s) => s.openDialog);
|
||||||
|
|
||||||
|
// 模拟渲染进程崩溃
|
||||||
const handleSimulateCrash = async () => {
|
const handleSimulateCrash = async () => {
|
||||||
setStatus("正在模拟崩溃...");
|
setStatus("正在模拟崩溃...");
|
||||||
await window.electronAPI?.simulateCrash();
|
await window.electronAPI?.simulateCrash();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 直接弹出崩溃弹窗 UI(不真正崩溃)
|
||||||
const handleTestDialog = async () => {
|
const handleTestDialog = async () => {
|
||||||
setStatus("正在打开崩溃弹窗...");
|
setStatus("正在打开崩溃弹窗...");
|
||||||
await window.electronAPI?.testCrashDialog();
|
await window.electronAPI?.testCrashDialog();
|
||||||
setStatus("崩溃弹窗已打开");
|
setStatus("崩溃弹窗已打开");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 读取崩溃日志文件
|
||||||
const handleReadLog = async () => {
|
const handleReadLog = async () => {
|
||||||
const result = await window.electronAPI?.invoke("crash:readLog") as string;
|
const result = await window.electronAPI?.invoke("crash:readLog") as string;
|
||||||
setLog(result || "(空)");
|
setLog(result || "(空)");
|
||||||
setStatus("日志已加载");
|
setStatus("日志已加载");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 复制崩溃日志到剪贴板
|
||||||
const handleCopyLog = async () => {
|
const handleCopyLog = async () => {
|
||||||
if (!log) return;
|
if (!log) return;
|
||||||
await navigator.clipboard.writeText(log);
|
await navigator.clipboard.writeText(log);
|
||||||
@@ -30,95 +38,133 @@ export function CrashDebug() {
|
|||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 执行强还原配置
|
||||||
const handleFactoryReset = async () => {
|
const handleFactoryReset = async () => {
|
||||||
if (!confirm("确定要执行强还原配置吗?这将删除 Koring.yml、koring-auth.json 和背景缓存,但不会影响实例。")) return;
|
|
||||||
setStatus("正在还原...");
|
setStatus("正在还原...");
|
||||||
await window.electronAPI?.invoke("crash:factoryReset");
|
await window.electronAPI?.invoke("crash:factoryReset");
|
||||||
setStatus("还原完成");
|
setStatus("还原完成");
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnBase = "flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all duration-150 cursor-pointer active:scale-[0.97]";
|
// 强还原前弹出确认对话框
|
||||||
|
const confirmFactoryReset = () => {
|
||||||
|
openDialog({
|
||||||
|
title: "强还原配置",
|
||||||
|
description:
|
||||||
|
"确定要执行强还原配置吗?这将删除 Koring.yml、koring-auth.json 和背景缓存,但不会影响实例。",
|
||||||
|
confirmLabel: "强还原",
|
||||||
|
onConfirm: () => {
|
||||||
|
void handleFactoryReset();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="max-w-2xl mx-auto p-8">
|
||||||
<h2 className="text-xl font-bold text-foreground mb-1">崩溃测试</h2>
|
<PageHeader title="崩溃测试" desc="测试崩溃检测、崩溃弹窗与恢复功能" />
|
||||||
<p className="text-sm text-muted-foreground mb-6">测试崩溃检测、崩溃弹窗与恢复功能</p>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* 模拟崩溃 */}
|
{/* 模拟崩溃 */}
|
||||||
<section className="glass-card p-5 space-y-3">
|
<div>
|
||||||
<div className="flex items-center gap-2.5">
|
<h3 className="text-sm font-semibold text-foreground/40 uppercase tracking-wider mb-3">
|
||||||
<AlertTriangle className="w-4 h-4 text-red-500" />
|
|
||||||
<h3 className="text-sm font-semibold text-foreground">模拟渲染进程崩溃</h3>
|
|
||||||
</div>
|
|
||||||
<p className="text-[13px] text-muted-foreground">
|
|
||||||
调用 <code className="px-1.5 py-0.5 rounded bg-foreground/[0.06] text-foreground/80 text-xs">forcefullyCrashRenderer()</code> 强制销毁渲染进程,触发 <code className="px-1.5 py-0.5 rounded bg-foreground/[0.06] text-foreground/80 text-xs">render-process-gone</code> 事件,崩溃弹窗应自动弹出。
|
|
||||||
</p>
|
|
||||||
<button onClick={handleSimulateCrash} className={`${btnBase} bg-red-500/10 text-red-600 dark:text-red-400 hover:bg-red-500/20`}>
|
|
||||||
<AlertTriangle className="w-4 h-4" />
|
|
||||||
模拟崩溃
|
模拟崩溃
|
||||||
</button>
|
</h3>
|
||||||
</section>
|
<GlassCard>
|
||||||
|
<SettingRow
|
||||||
|
label="模拟渲染进程崩溃"
|
||||||
|
desc="调用 forcefullyCrashRenderer() 强制销毁渲染进程,触发 render-process-gone 事件,崩溃弹窗应自动弹出。"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleSimulateCrash}
|
||||||
|
>
|
||||||
|
<AlertTriangle className="w-3.5 h-3.5 mr-1.5" />
|
||||||
|
模拟崩溃
|
||||||
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
|
</GlassCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 测试崩溃弹窗 */}
|
{/* 测试崩溃弹窗 */}
|
||||||
<section className="glass-card p-5 space-y-3">
|
<div>
|
||||||
<div className="flex items-center gap-2.5">
|
<h3 className="text-sm font-semibold text-foreground/40 uppercase tracking-wider mb-3">
|
||||||
<TestTube className="w-4 h-4 text-amber-500" />
|
崩溃弹窗
|
||||||
<h3 className="text-sm font-semibold text-foreground">测试崩溃弹窗</h3>
|
</h3>
|
||||||
</div>
|
<GlassCard>
|
||||||
<p className="text-[13px] text-muted-foreground">
|
<SettingRow
|
||||||
不会真正崩溃,直接弹出崩溃弹窗 UI,用于验证窗口样式、按钮功能是否正常。
|
label="测试崩溃弹窗"
|
||||||
</p>
|
desc="不会真正崩溃,直接弹出崩溃弹窗 UI,用于验证窗口样式、按钮功能是否正常。"
|
||||||
<button onClick={handleTestDialog} className={`${btnBase} bg-amber-500/10 text-amber-600 dark:text-amber-400 hover:bg-amber-500/20`}>
|
>
|
||||||
<TestTube className="w-4 h-4" />
|
<Button variant="outline" size="sm" onClick={handleTestDialog}>
|
||||||
测试崩溃弹窗
|
<TestTube className="w-3.5 h-3.5 mr-1.5" />
|
||||||
</button>
|
测试崩溃弹窗
|
||||||
</section>
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
|
</GlassCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 崩溃日志 */}
|
{/* 崩溃日志 */}
|
||||||
<section className="glass-card p-5 space-y-3">
|
<div>
|
||||||
<div className="flex items-center gap-2.5">
|
<h3 className="text-sm font-semibold text-foreground/40 uppercase tracking-wider mb-3">
|
||||||
<Copy className="w-4 h-4 text-blue-500" />
|
崩溃日志
|
||||||
<h3 className="text-sm font-semibold text-foreground">崩溃日志</h3>
|
</h3>
|
||||||
</div>
|
<GlassCard>
|
||||||
<p className="text-[13px] text-muted-foreground">
|
<div className="flex items-center justify-between gap-4">
|
||||||
读取 <code className="px-1.5 py-0.5 rounded bg-foreground/[0.06] text-foreground/80 text-xs">koring-crash.log</code> 文件内容,可复制发送给开发人员。
|
<div className="flex-1 min-w-0">
|
||||||
</p>
|
<p className="text-sm font-medium text-foreground">
|
||||||
<div className="flex gap-2">
|
读取崩溃日志
|
||||||
<button onClick={handleReadLog} className={`${btnBase} bg-blue-500/10 text-blue-600 dark:text-blue-400 hover:bg-blue-500/20`}>
|
</p>
|
||||||
<Copy className="w-4 h-4" />
|
<p className="text-[13px] text-muted-foreground mt-0.5">
|
||||||
读取日志
|
读取 koring-crash.log 文件内容,可复制发送给开发人员。
|
||||||
</button>
|
</p>
|
||||||
<button onClick={handleCopyLog} disabled={!log} className={`${btnBase} bg-blue-500/10 text-blue-600 dark:text-blue-400 hover:bg-blue-500/20 disabled:opacity-40`}>
|
</div>
|
||||||
<Copy className="w-4 h-4" />
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
{copied ? "已复制" : "复制到剪贴板"}
|
<Button variant="outline" size="sm" onClick={handleReadLog}>
|
||||||
</button>
|
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||||
</div>
|
读取
|
||||||
{log && (
|
</Button>
|
||||||
<pre className="mt-2 p-3 rounded-lg bg-foreground/[0.03] border border-border/50 text-xs text-foreground/70 overflow-auto max-h-40 font-mono">
|
<Button
|
||||||
{log}
|
variant="outline"
|
||||||
</pre>
|
size="sm"
|
||||||
)}
|
onClick={handleCopyLog}
|
||||||
</section>
|
disabled={!log}
|
||||||
|
>
|
||||||
|
<Copy className="w-3.5 h-3.5 mr-1.5" />
|
||||||
|
{copied ? "已复制" : "复制"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{log && (
|
||||||
|
<pre className="mt-3 p-3 rounded-lg bg-foreground/[0.03] border border-border/50 text-xs text-foreground/70 overflow-auto max-h-40 font-mono">
|
||||||
|
{log}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</GlassCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 强还原配置 */}
|
{/* 强还原配置 */}
|
||||||
<section className="glass-card p-5 space-y-3">
|
<div>
|
||||||
<div className="flex items-center gap-2.5">
|
<h3 className="text-sm font-semibold text-foreground/40 uppercase tracking-wider mb-3">
|
||||||
<Trash2 className="w-4 h-4 text-orange-500" />
|
|
||||||
<h3 className="text-sm font-semibold text-foreground">强还原配置</h3>
|
|
||||||
</div>
|
|
||||||
<p className="text-[13px] text-muted-foreground">
|
|
||||||
删除 <code className="px-1.5 py-0.5 rounded bg-foreground/[0.06] text-foreground/80 text-xs">Koring.yml</code>、
|
|
||||||
<code className="px-1.5 py-0.5 rounded bg-foreground/[0.06] text-foreground/80 text-xs">koring-auth.json</code> 和背景缓存。
|
|
||||||
<strong className="text-foreground/80"> 不会影响实例数据。</strong>
|
|
||||||
</p>
|
|
||||||
<button onClick={handleFactoryReset} className={`${btnBase} bg-orange-500/10 text-orange-600 dark:text-orange-400 hover:bg-orange-500/20`}>
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
强还原配置
|
强还原配置
|
||||||
</button>
|
</h3>
|
||||||
</section>
|
<GlassCard>
|
||||||
|
<SettingRow
|
||||||
|
label="还原配置与缓存"
|
||||||
|
desc="删除 Koring.yml、koring-auth.json 和背景缓存,不会影响实例数据。"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={confirmFactoryReset}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5 mr-1.5" />
|
||||||
|
强还原
|
||||||
|
</Button>
|
||||||
|
</SettingRow>
|
||||||
|
</GlassCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 状态 */}
|
{/* 操作状态提示 */}
|
||||||
{status && (
|
{status && (
|
||||||
<p className="text-xs text-muted-foreground/60">{status}</p>
|
<p className="text-xs text-muted-foreground/60">{status}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
Ban,
|
Ban,
|
||||||
Server,
|
Server,
|
||||||
|
Pause,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
const taskTypes: { type: TaskType; label: string; icon: typeof Download; color: string; bg: string }[] = [
|
const taskTypes: { type: TaskType; label: string; icon: typeof Download; color: string; bg: string }[] = [
|
||||||
@@ -68,6 +69,7 @@ function simulateSidecarTask(
|
|||||||
const statusIcons = {
|
const statusIcons = {
|
||||||
pending: RefreshCw,
|
pending: RefreshCw,
|
||||||
running: RefreshCw,
|
running: RefreshCw,
|
||||||
|
paused: Pause,
|
||||||
completed: Check,
|
completed: Check,
|
||||||
failed: AlertCircle,
|
failed: AlertCircle,
|
||||||
cancelled: Ban,
|
cancelled: Ban,
|
||||||
@@ -139,6 +141,37 @@ export function TaskDebug() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</GlassCard>
|
</GlassCard>
|
||||||
|
<GlassCard>
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-foreground">暂停 / 恢复测试</p>
|
||||||
|
<p className="text-[13px] text-muted-foreground mt-0.5">运行 5s 计时任务,3 秒后自动暂停,验证 @xmcl/task 的 Paused 状态</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const id = simulateSidecarTask("custom", "暂停/恢复测试 (5s)", "sleep", { duration: 5000 });
|
||||||
|
// 3 秒后自动暂停,验证 Paused 状态
|
||||||
|
setTimeout(() => useTaskStore.getState().pauseTask(id), 3000);
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg bg-foreground/[0.06] hover:bg-foreground/[0.1] text-[12px] font-medium transition-colors"
|
||||||
|
>
|
||||||
|
<Pause className="w-3.5 h-3.5" />
|
||||||
|
运行并暂停
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const paused = useTaskStore.getState().tasks.find((t) => t.status === "paused");
|
||||||
|
if (paused) useTaskStore.getState().resumeTask(paused.id);
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg bg-foreground/[0.06] hover:bg-foreground/[0.1] text-[12px] font-medium transition-colors"
|
||||||
|
>
|
||||||
|
<Play className="w-3.5 h-3.5" />
|
||||||
|
恢复
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GlassCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -203,7 +236,7 @@ export function TaskDebug() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => simulateSidecarTask("install", "Sidecar Install", "install", { steps: 10 })}
|
onClick={() => simulateSidecarTask("install", "Sidecar Install", "install-sim", { steps: 10 })}
|
||||||
className="inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg bg-foreground/[0.06] hover:bg-foreground/[0.1] text-[12px] font-medium transition-colors"
|
className="inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg bg-foreground/[0.06] hover:bg-foreground/[0.1] text-[12px] font-medium transition-colors"
|
||||||
>
|
>
|
||||||
运行
|
运行
|
||||||
@@ -311,6 +344,8 @@ export function TaskDebug() {
|
|||||||
className={`w-4 h-4 shrink-0 ${
|
className={`w-4 h-4 shrink-0 ${
|
||||||
t.status === "running"
|
t.status === "running"
|
||||||
? "animate-spin text-blue-500"
|
? "animate-spin text-blue-500"
|
||||||
|
: t.status === "paused"
|
||||||
|
? "text-amber-500"
|
||||||
: t.status === "completed"
|
: t.status === "completed"
|
||||||
? "text-green-500"
|
? "text-green-500"
|
||||||
: t.status === "failed"
|
: t.status === "failed"
|
||||||
@@ -322,7 +357,8 @@ export function TaskDebug() {
|
|||||||
<p className="text-sm font-medium text-foreground truncate">{t.title}</p>
|
<p className="text-sm font-medium text-foreground truncate">{t.title}</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{t.type} · {t.status} · {t.logs.length} 条日志
|
{t.type} · {t.status} · {t.logs.length} 条日志
|
||||||
{t.xmclPath && <span className="ml-1 text-primary/60">@xmcl</span>}
|
{t.xmclState && <span className="ml-1 text-primary/60">@{t.xmclState}</span>}
|
||||||
|
{t.xmclPath && <span className="ml-1 text-primary/60">xmcl:{t.xmclPath}</span>}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -8,12 +8,13 @@
|
|||||||
// 未经允许的情况下删除此版权头可能会受到民事指控
|
// 未经允许的情况下删除此版权头可能会受到民事指控
|
||||||
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { Button, Skeleton } from "@heroui/react";
|
import { Button, Skeleton } from "@heroui/react";
|
||||||
import { RefreshCw, FolderOpen, Trash2, Search, Plus, CircleCheck, CircleAlert, Home, Check } from "lucide-react";
|
import { RefreshCw, FolderOpen, Trash2, Search, Plus, CircleCheck, CircleAlert, Home, Check, Download, Loader2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useConfigStore } from "@/stores/configStore";
|
import { useConfigStore } from "@/stores/configStore";
|
||||||
import { scanGameDir, selectFolder, type ScannedVersion } from "@/api/instance";
|
import { useInstanceStore } from "@/stores/instanceStore";
|
||||||
|
import { scanGameDir, selectFolder, importExistingInstance, type ScannedVersion } from "@/api/instance";
|
||||||
import { SettingCard, PageHeader, SectionTitle } from "@/components/setting";
|
import { SettingCard, PageHeader, SectionTitle } from "@/components/setting";
|
||||||
|
|
||||||
// 版本类型标签样式
|
// 版本类型标签样式
|
||||||
@@ -25,18 +26,24 @@ const TYPE_BADGE: Record<string, { label: string; cls: string }> = {
|
|||||||
unknown: { label: "未知", cls: "bg-foreground/[0.06] dark:bg-white/[0.06] text-muted-foreground" },
|
unknown: { label: "未知", cls: "bg-foreground/[0.06] dark:bg-white/[0.06] text-muted-foreground" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载器标签样式
|
||||||
|
const LOADER_BADGE: Record<string, { label: string; cls: string }> = {
|
||||||
|
forge: { label: "Forge", cls: "bg-orange-500/10 text-orange-600 dark:text-orange-400" },
|
||||||
|
fabric: { label: "Fabric", cls: "bg-sky-500/10 text-sky-600 dark:text-sky-400" },
|
||||||
|
quilt: { label: "Quilt", cls: "bg-pink-500/10 text-pink-600 dark:text-pink-400" },
|
||||||
|
optifine: { label: "OptiFine", cls: "bg-violet-500/10 text-violet-600 dark:text-violet-400" },
|
||||||
|
};
|
||||||
|
|
||||||
function getBadge(type: string) {
|
function getBadge(type: string) {
|
||||||
return TYPE_BADGE[type] ?? TYPE_BADGE.unknown;
|
return TYPE_BADGE[type] ?? TYPE_BADGE.unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化发布时间
|
|
||||||
function formatTime(iso?: string): string {
|
function formatTime(iso?: string): string {
|
||||||
if (!iso) return "-";
|
if (!iso) return "-";
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
return d.toLocaleDateString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit" });
|
return d.toLocaleDateString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扫描到的文件状态图标
|
|
||||||
function FileStatus({ ok, label }: { ok: boolean; label: string }) {
|
function FileStatus({ ok, label }: { ok: boolean; label: string }) {
|
||||||
return (
|
return (
|
||||||
<span className={`inline-flex items-center gap-1 text-[11px] ${ok ? "text-green-600 dark:text-green-400" : "text-red-500/70"}`}>
|
<span className={`inline-flex items-center gap-1 text-[11px] ${ok ? "text-green-600 dark:text-green-400" : "text-red-500/70"}`}>
|
||||||
@@ -49,16 +56,20 @@ function FileStatus({ ok, label }: { ok: boolean; label: string }) {
|
|||||||
export function GameDirSetting() {
|
export function GameDirSetting() {
|
||||||
const game = useConfigStore((s) => s.config.game);
|
const game = useConfigStore((s) => s.config.game);
|
||||||
const setGame = useConfigStore((s) => s.setGame);
|
const setGame = useConfigStore((s) => s.setGame);
|
||||||
|
const fetchInstances = useInstanceStore((s) => s.fetchInstances);
|
||||||
|
|
||||||
const [scanning, setScanning] = useState(false);
|
const [scanning, setScanning] = useState(false);
|
||||||
const [scanTarget, setScanTarget] = useState<string>(""); // 正在扫描的目标路径
|
const [scanTarget, setScanTarget] = useState<string>("");
|
||||||
const [scanResults, setScanResults] = useState<ScannedVersion[] | null>(null);
|
const [scanResults, setScanResults] = useState<ScannedVersion[] | null>(null);
|
||||||
|
const [importing, setImporting] = useState<string | null>(null); // 正在导入的版本 ID
|
||||||
|
const [importingBatch, setImportingBatch] = useState(false);
|
||||||
|
|
||||||
const dirs = game.gameDirs ?? [];
|
const dirs = game.gameDirs ?? [];
|
||||||
|
const gameDir = game.gameDir || ".minecraft";
|
||||||
|
|
||||||
// 扫描指定目录中已安装的游戏版本
|
// 扫描指定目录
|
||||||
const handleScan = async (targetPath?: string) => {
|
const handleScan = useCallback(async (targetPath?: string) => {
|
||||||
const scanPath = targetPath || game.gameDir || ".minecraft";
|
const scanPath = targetPath || gameDir;
|
||||||
setScanning(true);
|
setScanning(true);
|
||||||
setScanTarget(scanPath);
|
setScanTarget(scanPath);
|
||||||
setScanResults(null);
|
setScanResults(null);
|
||||||
@@ -74,6 +85,63 @@ export function GameDirSetting() {
|
|||||||
toast.error(`扫描失败: ${e.message}`);
|
toast.error(`扫描失败: ${e.message}`);
|
||||||
}
|
}
|
||||||
setScanning(false);
|
setScanning(false);
|
||||||
|
}, [gameDir]);
|
||||||
|
|
||||||
|
// 组件挂载时自动扫描主目录
|
||||||
|
useEffect(() => {
|
||||||
|
handleScan(gameDir);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 主目录变化时自动扫描
|
||||||
|
useEffect(() => {
|
||||||
|
if (scanTarget && scanTarget !== gameDir) {
|
||||||
|
// 目录已变化但还没扫描过新目录
|
||||||
|
}
|
||||||
|
}, [gameDir]);
|
||||||
|
|
||||||
|
// 导入单个版本
|
||||||
|
const handleImport = async (versionId: string) => {
|
||||||
|
setImporting(versionId);
|
||||||
|
try {
|
||||||
|
await importExistingInstance(versionId, gameDir, versionId, {
|
||||||
|
description: `Imported from ${gameDir}`,
|
||||||
|
});
|
||||||
|
await fetchInstances(gameDir);
|
||||||
|
toast.success(`已导入版本 ${versionId}`);
|
||||||
|
} catch (e: any) {
|
||||||
|
toast.error(`导入失败: ${e.message}`);
|
||||||
|
}
|
||||||
|
setImporting(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量导入所有健康版本
|
||||||
|
const handleImportAll = async () => {
|
||||||
|
if (!scanResults || scanResults.length === 0) return;
|
||||||
|
const healthy = scanResults.filter((v) => v.healthy);
|
||||||
|
if (healthy.length === 0) {
|
||||||
|
toast.info("没有可导入的健康版本");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setImportingBatch(true);
|
||||||
|
let success = 0;
|
||||||
|
let failed = 0;
|
||||||
|
for (const v of healthy) {
|
||||||
|
try {
|
||||||
|
await importExistingInstance(v.id, gameDir, v.id, {
|
||||||
|
description: `Imported from ${gameDir}`,
|
||||||
|
});
|
||||||
|
success++;
|
||||||
|
} catch {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fetchInstances(gameDir);
|
||||||
|
setImportingBatch(false);
|
||||||
|
if (failed === 0) {
|
||||||
|
toast.success(`成功导入 ${success} 个版本`);
|
||||||
|
} else {
|
||||||
|
toast.warning(`导入完成:${success} 成功,${failed} 失败`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 浏览选择主目录
|
// 浏览选择主目录
|
||||||
@@ -94,14 +162,14 @@ export function GameDirSetting() {
|
|||||||
try {
|
try {
|
||||||
const result = await selectFolder();
|
const result = await selectFolder();
|
||||||
if (result?.folderPath) {
|
if (result?.folderPath) {
|
||||||
const path = result.folderPath;
|
const p = result.folderPath;
|
||||||
const current = game.gameDirs ?? [];
|
const current = game.gameDirs ?? [];
|
||||||
if (current.includes(path)) {
|
if (current.includes(p)) {
|
||||||
toast.info("该目录已在列表中");
|
toast.info("该目录已在列表中");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setGame({ gameDirs: [...current, path] });
|
setGame({ gameDirs: [...current, p] });
|
||||||
toast.success(`已添加目录: ${path}`);
|
toast.success(`已添加目录: ${p}`);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(`选择目录失败: ${e.message}`);
|
toast.error(`选择目录失败: ${e.message}`);
|
||||||
@@ -120,6 +188,9 @@ export function GameDirSetting() {
|
|||||||
toast.success(`已将 ${dir} 设为主游戏目录`);
|
toast.success(`已将 ${dir} 设为主游戏目录`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 可导入的健康版本数
|
||||||
|
const importableCount = scanResults?.filter((v) => v.healthy).length ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader title="游戏目录" desc="管理 Minecraft 游戏的存放位置与发现已安装版本" />
|
<PageHeader title="游戏目录" desc="管理 Minecraft 游戏的存放位置与发现已安装版本" />
|
||||||
@@ -131,7 +202,7 @@ export function GameDirSetting() {
|
|||||||
<SettingCard>
|
<SettingCard>
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium font-mono text-foreground truncate">{game.gameDir || ".minecraft"}</p>
|
<p className="text-sm font-medium font-mono text-foreground truncate">{gameDir}</p>
|
||||||
<p className="text-[12px] text-muted-foreground/70 mt-0.5">新实例默认安装到此目录</p>
|
<p className="text-[12px] text-muted-foreground/70 mt-0.5">新实例默认安装到此目录</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
@@ -139,15 +210,15 @@ export function GameDirSetting() {
|
|||||||
<FolderOpen className="w-3.5 h-3.5" />
|
<FolderOpen className="w-3.5 h-3.5" />
|
||||||
浏览
|
浏览
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="outline" onPress={() => handleScan(game.gameDir)} isDisabled={scanning}>
|
<Button size="sm" variant="outline" onPress={() => handleScan(gameDir)} isDisabled={scanning}>
|
||||||
<Search className="w-3.5 h-3.5" />
|
{scanning ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <RefreshCw className="w-3.5 h-3.5" />}
|
||||||
扫描
|
刷新
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 扫描中骨架屏 */}
|
{/* 扫描中骨架屏 */}
|
||||||
{scanning && scanTarget === (game.gameDir || ".minecraft") && (
|
{scanning && scanTarget === gameDir && (
|
||||||
<div className="mt-4 space-y-2">
|
<div className="mt-4 space-y-2">
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
<Skeleton key={i} className="h-10 w-full rounded-lg" />
|
<Skeleton key={i} className="h-10 w-full rounded-lg" />
|
||||||
@@ -156,42 +227,95 @@ export function GameDirSetting() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 扫描结果 */}
|
{/* 扫描结果 */}
|
||||||
{scanResults !== null && scanTarget === (game.gameDir || ".minecraft") && (
|
{scanResults !== null && scanTarget === gameDir && (
|
||||||
<div className="mt-4 border-t border-border/30 dark:border-white/[0.05] pt-4">
|
<div className="mt-4 border-t border-border/30 dark:border-white/[0.05] pt-4">
|
||||||
{scanResults.length === 0 ? (
|
{scanResults.length === 0 ? (
|
||||||
<p className="text-[13px] text-muted-foreground/70 text-center py-4">
|
<p className="text-[13px] text-muted-foreground/70 text-center py-4">
|
||||||
未在 versions 目录下发现版本,请确认 Minecraft 已下载到该目录
|
未在 versions 目录下发现版本,请确认 Minecraft 已下载到该目录
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-1.5 max-h-72 overflow-y-auto scroll-area pr-1">
|
<>
|
||||||
{scanResults.map((v) => {
|
{/* 批量操作栏 */}
|
||||||
const badge = getBadge(v.type);
|
<div className="flex items-center justify-between mb-3">
|
||||||
return (
|
<p className="text-[12px] text-muted-foreground/70">
|
||||||
<div
|
发现 {scanResults.length} 个版本,{importableCount} 个可导入
|
||||||
key={v.id}
|
</p>
|
||||||
className="flex items-center gap-3 px-3.5 py-2.5 rounded-xl bg-foreground/[0.03] dark:bg-white/[0.03] border border-border/20 dark:border-white/[0.04]"
|
<Button
|
||||||
>
|
size="sm"
|
||||||
{/* 版本图标 */}
|
variant="outline"
|
||||||
<div className={`w-7 h-7 rounded-lg flex items-center justify-center shrink-0 ${badge.cls}`}>
|
onPress={handleImportAll}
|
||||||
<Home className="w-3.5 h-3.5" />
|
isDisabled={scanning || importingBatch || importableCount === 0}
|
||||||
</div>
|
>
|
||||||
<div className="flex-1 min-w-0">
|
{importingBatch ? (
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
<p className="text-[13px] font-mono font-semibold text-foreground">{v.id}</p>
|
) : (
|
||||||
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${badge.cls}`}>
|
<Download className="w-3.5 h-3.5" />
|
||||||
{badge.label}
|
)}
|
||||||
</span>
|
全部导入
|
||||||
<span className="text-[11px] text-muted-foreground/60">{formatTime(v.releaseTime)}</span>
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 版本列表 */}
|
||||||
|
<div className="flex flex-col gap-1.5 max-h-80 overflow-y-auto scroll-area pr-1">
|
||||||
|
{scanResults.map((v) => {
|
||||||
|
const badge = getBadge(v.type);
|
||||||
|
const isImporting = importing === v.id;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={v.id}
|
||||||
|
className="flex items-center gap-3 px-3.5 py-2.5 rounded-xl bg-foreground/[0.03] dark:bg-white/[0.03] border border-border/20 dark:border-white/[0.04]"
|
||||||
|
>
|
||||||
|
{/* 版本图标 */}
|
||||||
|
<div className={`w-7 h-7 rounded-lg flex items-center justify-center shrink-0 ${badge.cls}`}>
|
||||||
|
<Home className="w-3.5 h-3.5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 mt-1">
|
<div className="flex-1 min-w-0">
|
||||||
<FileStatus ok={v.hasJson} label="JSON" />
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<FileStatus ok={v.hasJar} label="JAR" />
|
<p className="text-[13px] font-mono font-semibold text-foreground">{v.id}</p>
|
||||||
|
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${badge.cls}`}>
|
||||||
|
{badge.label}
|
||||||
|
</span>
|
||||||
|
{/* 加载器标签 */}
|
||||||
|
{v.loaders.map((loader) => (
|
||||||
|
<span
|
||||||
|
key={loader}
|
||||||
|
className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${LOADER_BADGE[loader]?.cls ?? ""}`}
|
||||||
|
>
|
||||||
|
{LOADER_BADGE[loader]?.label ?? loader}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<span className="text-[11px] text-muted-foreground/60">{formatTime(v.releaseTime)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 mt-1">
|
||||||
|
<FileStatus ok={v.hasJson} label="JSON" />
|
||||||
|
<FileStatus ok={v.hasJar} label="JAR" />
|
||||||
|
{v.loaders.length > 0 && (
|
||||||
|
<span className="text-[11px] text-muted-foreground/50">
|
||||||
|
{v.loaders.length} 个加载器
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* 导入按钮 */}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant={v.healthy ? "outline" : "ghost"}
|
||||||
|
className="min-w-0 shrink-0"
|
||||||
|
onPress={() => handleImport(v.id)}
|
||||||
|
isDisabled={isImporting || !v.healthy}
|
||||||
|
>
|
||||||
|
{isImporting ? (
|
||||||
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Download className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
|
导入
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -225,7 +349,7 @@ export function GameDirSetting() {
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-sm font-mono font-medium text-foreground truncate">{dir}</p>
|
<p className="text-sm font-mono font-medium text-foreground truncate">{dir}</p>
|
||||||
{dir === (game.gameDir || ".minecraft") && (
|
{dir === gameDir && (
|
||||||
<span className="flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded bg-primary/10 text-primary font-medium shrink-0">
|
<span className="flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded bg-primary/10 text-primary font-medium shrink-0">
|
||||||
<Check className="w-2.5 h-2.5" />
|
<Check className="w-2.5 h-2.5" />
|
||||||
当前
|
当前
|
||||||
@@ -235,7 +359,7 @@ export function GameDirSetting() {
|
|||||||
<p className="text-[12px] text-muted-foreground/70 mt-0.5">已连接的游戏目录</p>
|
<p className="text-[12px] text-muted-foreground/70 mt-0.5">已连接的游戏目录</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 shrink-0">
|
<div className="flex items-center gap-1.5 shrink-0">
|
||||||
{dir !== (game.gameDir || ".minecraft") && (
|
{dir !== gameDir && (
|
||||||
<Button size="sm" variant="ghost" className="min-w-0 h-8 text-[11px] text-muted-foreground" onPress={() => handleSetAsMain(dir)}>
|
<Button size="sm" variant="ghost" className="min-w-0 h-8 text-[11px] text-muted-foreground" onPress={() => handleSetAsMain(dir)}>
|
||||||
<Home className="w-3.5 h-3.5" />
|
<Home className="w-3.5 h-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -257,7 +381,7 @@ export function GameDirSetting() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{scanResults !== null && scanTarget === dir && scanTarget !== (game.gameDir || ".minecraft") && (
|
{scanResults !== null && scanTarget === dir && scanTarget !== gameDir && (
|
||||||
<div className="mt-3 border-t border-border/30 dark:border-white/[0.05] pt-3">
|
<div className="mt-3 border-t border-border/30 dark:border-white/[0.05] pt-3">
|
||||||
{scanResults.length === 0 ? (
|
{scanResults.length === 0 ? (
|
||||||
<p className="text-[12px] text-muted-foreground/60 py-2">未发现版本文件</p>
|
<p className="text-[12px] text-muted-foreground/60 py-2">未发现版本文件</p>
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
import { VersionCard } from "@/components/VersionCard";
|
import { VersionCard } from "@/components/VersionCard";
|
||||||
import { BUILD_MODE } from "@/lib/mode";
|
import { BUILD_MODE } from "@/lib/mode";
|
||||||
import { ExternalLink, GitFork, RotateCcw } from "lucide-react";
|
import { ExternalLink, GitFork, RotateCcw } from "lucide-react";
|
||||||
import { useConfirmDialogStore } from "@/stores/confirmDialogStore";
|
import { Link } from "@heroui/react";
|
||||||
import { Button, Link } from "@heroui/react";
|
|
||||||
import { SettingCard, SettingRow, PageHeader, SectionTitle } from "@/components/setting";
|
import { SettingCard, SettingRow, PageHeader, SectionTitle } from "@/components/setting";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogAction,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
|
|
||||||
const modeLabels: Record<string, string> = {
|
const modeLabels: Record<string, string> = {
|
||||||
dev: "开发版",
|
dev: "开发版",
|
||||||
@@ -15,20 +27,27 @@ const GITHUB_URL = "https://github.com/lingke-net/koring-launcher";
|
|||||||
const OFFICIAL_URL = "https://koring.space";
|
const OFFICIAL_URL = "https://koring.space";
|
||||||
|
|
||||||
export function AboutSetting() {
|
export function AboutSetting() {
|
||||||
const openDialog = useConfirmDialogStore((s) => s.openDialog);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [countdown, setCountdown] = useState(5);
|
||||||
|
const canConfirm = countdown <= 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
setCountdown(5);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open || countdown <= 0) return;
|
||||||
|
const timer = setTimeout(() => setCountdown((c) => c - 1), 1000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [open, countdown]);
|
||||||
|
|
||||||
const openLink = (url: string) => {
|
const openLink = (url: string) => {
|
||||||
window.electronAPI?.openExternal(url);
|
window.electronAPI?.openExternal(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetClick = () => {
|
const handleConfirm = () => {
|
||||||
openDialog({
|
window.electronAPI?.resetConfig();
|
||||||
title: "您确定要还原所有配置吗?",
|
|
||||||
description: "您还原后,您的实例将会保留,但是所有个性化配置将全部丢失,并且需要重新进行激活",
|
|
||||||
confirmLabel: "确认还原",
|
|
||||||
countdown: 5,
|
|
||||||
onConfirm: () => window.electronAPI?.resetConfig(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -86,10 +105,34 @@ export function AboutSetting() {
|
|||||||
<SectionTitle>危险操作</SectionTitle>
|
<SectionTitle>危险操作</SectionTitle>
|
||||||
<SettingCard>
|
<SettingCard>
|
||||||
<SettingRow label="还原所有设置" desc="删除所有配置文件并重启应用">
|
<SettingRow label="还原所有设置" desc="删除所有配置文件并重启应用">
|
||||||
<Button variant="danger" size="sm" onPress={handleResetClick}>
|
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||||
<RotateCcw className="w-4 h-4" />
|
<AlertDialogTrigger
|
||||||
还原
|
render={
|
||||||
</Button>
|
<Button variant="destructive" size="sm">
|
||||||
|
<RotateCcw className="w-4 h-4" />
|
||||||
|
还原
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AlertDialogContent size="sm">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>您确定要还原所有配置吗?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
您还原后,您的实例将会保留,但是所有个性化配置将全部丢失,并且需要重新进行激活
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
variant="destructive"
|
||||||
|
disabled={!canConfirm}
|
||||||
|
onClick={handleConfirm}
|
||||||
|
>
|
||||||
|
{countdown > 0 ? `确认还原 (${countdown}s)` : "确认还原"}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export function TaskQueue() {
|
|||||||
const activeTasks = tasks.filter(
|
const activeTasks = tasks.filter(
|
||||||
(t) => t.status === "pending" || t.status === "running",
|
(t) => t.status === "pending" || t.status === "running",
|
||||||
);
|
);
|
||||||
|
const pausedTasks = tasks.filter((t) => t.status === "paused");
|
||||||
const completedTasks = tasks.filter(
|
const completedTasks = tasks.filter(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.status === "completed" ||
|
t.status === "completed" ||
|
||||||
@@ -17,6 +18,7 @@ export function TaskQueue() {
|
|||||||
t.status === "cancelled",
|
t.status === "cancelled",
|
||||||
);
|
);
|
||||||
const hasCompleted = completedTasks.length > 0;
|
const hasCompleted = completedTasks.length > 0;
|
||||||
|
const activeCount = activeTasks.length + pausedTasks.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full overflow-y-auto px-6 py-5">
|
<div className="h-full overflow-y-auto px-6 py-5">
|
||||||
@@ -27,9 +29,9 @@ export function TaskQueue() {
|
|||||||
<h1 className="text-xl font-semibold text-foreground">
|
<h1 className="text-xl font-semibold text-foreground">
|
||||||
任务队列
|
任务队列
|
||||||
</h1>
|
</h1>
|
||||||
{activeTasks.length > 0 && (
|
{activeCount > 0 && (
|
||||||
<span className="px-2 py-0.5 rounded-full bg-primary/10 text-primary text-xs font-medium tabular-nums">
|
<span className="px-2 py-0.5 rounded-full bg-primary/10 text-primary text-xs font-medium tabular-nums">
|
||||||
{activeTasks.length} 进行中
|
{activeCount} 进行中
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -68,6 +70,19 @@ export function TaskQueue() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{pausedTasks.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-[11px] font-semibold uppercase tracking-wider text-amber-500/70 mb-2.5">
|
||||||
|
已暂停
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-2.5">
|
||||||
|
{pausedTasks.map((t) => (
|
||||||
|
<TaskCard key={t.id} task={t} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{completedTasks.length > 0 && (
|
{completedTasks.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-[11px] font-semibold uppercase tracking-wider text-foreground/30 mb-2.5">
|
<h3 className="text-[11px] font-semibold uppercase tracking-wider text-foreground/30 mb-2.5">
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ function listenToSidecarEvents(taskId: string) {
|
|||||||
status: "running",
|
status: "running",
|
||||||
startedAt: Date.now(),
|
startedAt: Date.now(),
|
||||||
xmclPath: data.xmclPath as string,
|
xmclPath: data.xmclPath as string,
|
||||||
|
xmclState: "running",
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ function listenToSidecarEvents(taskId: string) {
|
|||||||
total: data.total as number,
|
total: data.total as number,
|
||||||
stage: data.stage as string,
|
stage: data.stage as string,
|
||||||
},
|
},
|
||||||
|
xmclPath: (data.xmclPath as string) || undefined,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -84,9 +86,38 @@ function listenToSidecarEvents(taskId: string) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "task:paused":
|
||||||
|
updateTask(taskId, {
|
||||||
|
status: "paused",
|
||||||
|
xmclState: "paused",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "task:resumed":
|
||||||
|
updateTask(taskId, {
|
||||||
|
status: "running",
|
||||||
|
xmclState: "running",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "task:cancelled":
|
||||||
|
updateTask(taskId, {
|
||||||
|
status: "cancelled",
|
||||||
|
xmclState: "cancelled",
|
||||||
|
finishedAt: Date.now(),
|
||||||
|
});
|
||||||
|
eventListeners.get(taskId)?.();
|
||||||
|
eventListeners.delete(taskId);
|
||||||
|
setTimeout(() => {
|
||||||
|
const state = useTaskStore.getState();
|
||||||
|
saveHistory(state.tasks);
|
||||||
|
}, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
case "task:completed":
|
case "task:completed":
|
||||||
updateTask(taskId, {
|
updateTask(taskId, {
|
||||||
status: "completed",
|
status: "completed",
|
||||||
|
xmclState: "succeed",
|
||||||
finishedAt: Date.now(),
|
finishedAt: Date.now(),
|
||||||
});
|
});
|
||||||
eventListeners.get(taskId)?.();
|
eventListeners.get(taskId)?.();
|
||||||
@@ -102,6 +133,7 @@ function listenToSidecarEvents(taskId: string) {
|
|||||||
const cur = useTaskStore.getState().tasks.find((t) => t.id === taskId);
|
const cur = useTaskStore.getState().tasks.find((t) => t.id === taskId);
|
||||||
updateTask(taskId, {
|
updateTask(taskId, {
|
||||||
status: "failed",
|
status: "failed",
|
||||||
|
xmclState: "failed",
|
||||||
finishedAt: Date.now(),
|
finishedAt: Date.now(),
|
||||||
logs: cur ? [...cur.logs, errLog] : [errLog],
|
logs: cur ? [...cur.logs, errLog] : [errLog],
|
||||||
});
|
});
|
||||||
@@ -140,6 +172,9 @@ interface TaskState {
|
|||||||
|
|
||||||
// Common actions
|
// Common actions
|
||||||
cancelTask: (id: string) => void;
|
cancelTask: (id: string) => void;
|
||||||
|
// @xmcl/task 原生暂停 / 恢复
|
||||||
|
pauseTask: (id: string) => void;
|
||||||
|
resumeTask: (id: string) => void;
|
||||||
removeTask: (id: string) => void;
|
removeTask: (id: string) => void;
|
||||||
clearHistory: () => void;
|
clearHistory: () => void;
|
||||||
retryTask: (id: string) => void;
|
retryTask: (id: string) => void;
|
||||||
@@ -280,6 +315,25 @@ export const useTaskStore = create<TaskState>((set, get) => ({
|
|||||||
eventListeners.delete(id);
|
eventListeners.delete(id);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 暂停任务:主进程调用 @xmcl/task 的 pause()
|
||||||
|
pauseTask: (id) => {
|
||||||
|
const { tasks } = get();
|
||||||
|
const task = tasks.find((t) => t.id === id);
|
||||||
|
// 仅允许暂停运行中的 IPC 任务(本地任务不支持暂停)
|
||||||
|
if (task && task.status === "running" && !get().abortControllers.has(id)) {
|
||||||
|
ipcInvoke("task:pause", { taskId: id }).catch(() => {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 恢复任务:主进程调用 @xmcl/task 的 resume()
|
||||||
|
resumeTask: (id) => {
|
||||||
|
const { tasks } = get();
|
||||||
|
const task = tasks.find((t) => t.id === id);
|
||||||
|
if (task && task.status === "paused") {
|
||||||
|
ipcInvoke("task:resume", { taskId: id }).catch(() => {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
removeTask: (id) => {
|
removeTask: (id) => {
|
||||||
eventListeners.get(id)?.();
|
eventListeners.get(id)?.();
|
||||||
eventListeners.delete(id);
|
eventListeners.delete(id);
|
||||||
|
|||||||
+38
-2
@@ -1,22 +1,37 @@
|
|||||||
|
// __ __ __ __ ______ __ __ ______ __ __ ______ ______
|
||||||
|
// /\ \ /\ \ /\ "-.\ \ /\ ___\ /\ \/ / /\ ___\ /\ "-.\ \ /\ ___\ /\__ _\
|
||||||
|
// \ \ \____ \ \ \ \ \ \-. \ \ \ \__ \ \ \ _"-. \ \ __\ \ \ \-. \ \ \ __\ \/_/\ \/
|
||||||
|
// \ \_____\ \ \_\ \ \_\\"\_\ \ \_____\ \ \_\ \_\ \ \_____\ \ \_\\"\_\ \ \_____\ \ \_\
|
||||||
|
// \/_____/ \/_/ \/_/ \/_/ \/_____/ \/_/\/_/ \/_____/ \/_/ \/_/ \/_____/ \/_/
|
||||||
|
//
|
||||||
|
// 所有权利归Lingke Network (china)所有 | dream_pep拥有其艺术改变权力
|
||||||
|
// 未经允许的情况下删除此版权头可能会受到民事指控
|
||||||
|
// 请勿在未经Lingke Network (china)允许的范围内修改代码并分发
|
||||||
|
|
||||||
|
// 任务类型:install 安装 / download 下载 / update 更新 / launch 启动 / auth 认证 / sync 同步 / custom 自定义
|
||||||
export type TaskType = "install" | "download" | "update" | "launch" | "auth" | "sync" | "custom";
|
export type TaskType = "install" | "download" | "update" | "launch" | "auth" | "sync" | "custom";
|
||||||
|
|
||||||
export type TaskStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
|
// 前端任务状态:包含 @xmcl/task 的 Paused 状态
|
||||||
|
export type TaskStatus = "pending" | "running" | "paused" | "completed" | "failed" | "cancelled";
|
||||||
|
|
||||||
/** Mirrors @xmcl/task TaskState enum */
|
// @xmcl/task 的 TaskState 枚举映射(Idle/Running/Cancelled/Paused/Succeed/Failed)
|
||||||
export type XmclTaskState = "idle" | "running" | "cancelled" | "paused" | "succeed" | "failed";
|
export type XmclTaskState = "idle" | "running" | "cancelled" | "paused" | "succeed" | "failed";
|
||||||
|
|
||||||
|
// 任务日志条目
|
||||||
export interface TaskLog {
|
export interface TaskLog {
|
||||||
time: number;
|
time: number;
|
||||||
level: "info" | "warn" | "error";
|
level: "info" | "warn" | "error";
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 任务进度
|
||||||
export interface TaskProgress {
|
export interface TaskProgress {
|
||||||
current: number;
|
current: number;
|
||||||
total: number;
|
total: number;
|
||||||
stage?: string;
|
stage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 任务实体
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: string;
|
id: string;
|
||||||
type: TaskType;
|
type: TaskType;
|
||||||
@@ -30,6 +45,8 @@ export interface Task {
|
|||||||
finishedAt?: number;
|
finishedAt?: number;
|
||||||
/** Sidecar @xmcl/task routine name (dot-separated path) */
|
/** Sidecar @xmcl/task routine name (dot-separated path) */
|
||||||
xmclPath?: string;
|
xmclPath?: string;
|
||||||
|
/** @xmcl/task 原始状态(Idle/Running/Paused/Succeed/Failed/Cancelled) */
|
||||||
|
xmclState?: XmclTaskState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,6 +91,7 @@ export interface XmclTaskProgressEvent {
|
|||||||
current: number;
|
current: number;
|
||||||
total: number;
|
total: number;
|
||||||
stage?: string;
|
stage?: string;
|
||||||
|
xmclPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface XmclTaskLogEvent {
|
export interface XmclTaskLogEvent {
|
||||||
@@ -83,6 +101,21 @@ export interface XmclTaskLogEvent {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface XmclTaskPausedEvent {
|
||||||
|
event: "task:paused";
|
||||||
|
taskId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XmclTaskResumedEvent {
|
||||||
|
event: "task:resumed";
|
||||||
|
taskId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XmclTaskCancelledEvent {
|
||||||
|
event: "task:cancelled";
|
||||||
|
taskId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface XmclTaskCompletedEvent {
|
export interface XmclTaskCompletedEvent {
|
||||||
event: "task:completed";
|
event: "task:completed";
|
||||||
taskId: string;
|
taskId: string;
|
||||||
@@ -98,5 +131,8 @@ export type XmclTaskEvent =
|
|||||||
| XmclTaskStartedEvent
|
| XmclTaskStartedEvent
|
||||||
| XmclTaskProgressEvent
|
| XmclTaskProgressEvent
|
||||||
| XmclTaskLogEvent
|
| XmclTaskLogEvent
|
||||||
|
| XmclTaskPausedEvent
|
||||||
|
| XmclTaskResumedEvent
|
||||||
|
| XmclTaskCancelledEvent
|
||||||
| XmclTaskCompletedEvent
|
| XmclTaskCompletedEvent
|
||||||
| XmclTaskFailedEvent;
|
| XmclTaskFailedEvent;
|
||||||
|
|||||||
Reference in New Issue
Block a user