From 0a2b8bd018167faae456430093286b81034c91b3 Mon Sep 17 00:00:00 2001 From: dream_pep Date: Sun, 12 Jul 2026 01:24:31 +0800 Subject: [PATCH] Add crash monitor, OOBE flow & factory-reset Introduce crash monitoring and an OOBE onboarding flow. Adds a crash logger (koring-crash.log), crash-monitor handler/window, preload-crash bridge, crash UI (crash.html, src/crash.tsx, pages/crash) and a debug page. Main process now sets up crash listeners, startup checks (first-launch, .minecraft), and a config reset/factory-reset that removes Koring.yml, koring-auth.json and background cache and can relaunch the app. Exposes config preloading to renderer, adds OOBE pages/layout, confirm dialog store/UI, trims Silk renderer settings, bumps version to 1.0.1 and adds motion dependency, and registers crash.html in Vite. Various related type and config updates included. --- .env.beta | 5 - .env.development | 5 - .env.production | 5 - CRASH_MONITOR_PLAN.md | 144 --------------- Koring.yml | 1 + crash.html | 12 ++ electron/config.ts | 12 +- electron/core/crash-logger.js | 93 ++++++++++ electron/core/crash-logger.ts | 60 +++++++ electron/handlers/config.js | 50 +++++- electron/handlers/config.ts | 20 ++- electron/handlers/crash-monitor.js | 206 +++++++++++++++++++++ electron/handlers/crash-monitor.ts | 220 +++++++++++++++++++++++ electron/main.ts | 47 ++++- electron/preload-crash.ts | 29 +++ electron/preload.ts | 14 ++ package.json | 3 +- pnpm-lock.yaml | 60 +++++++ public/background-3.png | Bin 0 -> 221537 bytes public/changelog-1.0.1.txt | 20 +++ public/protocol-beta.txt | 30 ++++ public/protocol-user.txt | 35 ++++ src/App.tsx | 24 ++- src/api/config.ts | 1 + src/components/BetaWarning.tsx | 17 -- src/components/StartupPopup.tsx | 47 ----- src/components/silk/Silk.tsx | 5 +- src/components/ui/apple-hello-effect.tsx | 70 ++++++++ src/components/ui/confirm-dialog.tsx | 47 +++++ src/crash.tsx | 10 ++ src/layouts/RootLayout.tsx | 10 +- src/pages/crash/index.tsx | 144 +++++++++++++++ src/pages/debug/crash-debug.tsx | 128 +++++++++++++ src/pages/debug/index.tsx | 10 +- src/pages/oobe/about-info.tsx | 3 +- src/pages/oobe/index.tsx | 71 ++++++-- src/pages/oobe/layout.tsx | 13 ++ src/pages/oobe/next-button.tsx | 18 ++ src/pages/oobe/step-agreement.tsx | 60 +++++++ src/pages/oobe/step-beta-test.tsx | 77 ++++++++ src/pages/oobe/step-finish.tsx | 31 ++++ src/pages/oobe/step-language.tsx | 51 ++++++ src/pages/oobe/step-version.tsx | 67 +++++++ src/pages/setting/general/about.tsx | 31 +++- src/stores/configStore.ts | 85 ++++++--- src/stores/confirmDialogStore.ts | 40 +++++ src/stores/routeStore.ts | 15 +- src/types/electron.d.ts | 9 + src/vite-env.d.ts | 7 +- vite.config.ts | 1 + 50 files changed, 1867 insertions(+), 296 deletions(-) delete mode 100644 CRASH_MONITOR_PLAN.md create mode 100644 Koring.yml create mode 100644 crash.html create mode 100644 electron/core/crash-logger.js create mode 100644 electron/core/crash-logger.ts create mode 100644 electron/handlers/crash-monitor.js create mode 100644 electron/handlers/crash-monitor.ts create mode 100644 electron/preload-crash.ts create mode 100644 public/background-3.png create mode 100644 public/changelog-1.0.1.txt create mode 100644 public/protocol-beta.txt create mode 100644 public/protocol-user.txt delete mode 100644 src/components/BetaWarning.tsx delete mode 100644 src/components/StartupPopup.tsx create mode 100644 src/components/ui/apple-hello-effect.tsx create mode 100644 src/components/ui/confirm-dialog.tsx create mode 100644 src/crash.tsx create mode 100644 src/pages/crash/index.tsx create mode 100644 src/pages/debug/crash-debug.tsx create mode 100644 src/pages/oobe/layout.tsx create mode 100644 src/pages/oobe/next-button.tsx create mode 100644 src/pages/oobe/step-agreement.tsx create mode 100644 src/pages/oobe/step-beta-test.tsx create mode 100644 src/pages/oobe/step-finish.tsx create mode 100644 src/pages/oobe/step-language.tsx create mode 100644 src/pages/oobe/step-version.tsx create mode 100644 src/stores/confirmDialogStore.ts diff --git a/.env.beta b/.env.beta index 8ec26a1..eb23526 100644 --- a/.env.beta +++ b/.env.beta @@ -1,7 +1,2 @@ VITE_BUILD_MODE=beta VITE_APP_ICON=/beta.png - -VITE_START_POP=true -VITE_START_POP_TITLE="你正在使用 UI 预览版本" -VITE_START_POP_INFO="此版本是专门用于进行检测是否有 UI 问题的版本,因此功能将会缺失" -VITE_START_POP_BOUTTON="我已了解" \ No newline at end of file diff --git a/.env.development b/.env.development index aed8c10..468d727 100644 --- a/.env.development +++ b/.env.development @@ -1,7 +1,2 @@ VITE_BUILD_MODE=dev VITE_APP_ICON=/dev.png - -VITE_START_POP=true -VITE_START_POP_TITLE="你正在使用 UI 预览版本" -VITE_START_POP_INFO="此版本是专门用于进行检测是否有UI问题的版本,因此功能将会缺失" -VITE_START_POP_BOUTTON="我已了解" \ No newline at end of file diff --git a/.env.production b/.env.production index 1be13d2..ce78263 100644 --- a/.env.production +++ b/.env.production @@ -1,7 +1,2 @@ VITE_BUILD_MODE=run VITE_APP_ICON=/run.png - -VITE_START_POP=false -VITE_START_POP_TITLE="你正在使用 UI 预览版本" -VITE_START_POP_INFO="此版本是专门用于进行检测是否有UI问题的版本,因此功能将会缺失" -VITE_START_POP_BOUTTON="我已了解" \ No newline at end of file diff --git a/CRASH_MONITOR_PLAN.md b/CRASH_MONITOR_PLAN.md deleted file mode 100644 index 1805e90..0000000 --- a/CRASH_MONITOR_PLAN.md +++ /dev/null @@ -1,144 +0,0 @@ -# Crash Monitor Implementation Plan - -## 1. Architecture Overview - -``` -┌─────────────────────────────────────────────────────────┐ -│ Main Process │ -│ ┌──────────────────────────────────────────────────┐ │ -│ │ Crash Logger (file-based, survives crashes) │ │ -│ │ - ipcMain.on('renderer-error') → write to file │ │ -│ │ - process.on('uncaughtException') → write to file │ │ -│ │ - app.on('render-process-gone') → write to file │ │ -│ │ - app.on('child-process-gone') → write to file │ │ -│ └──────────────────────────────────────────────────┘ │ -│ ┌──────────────────────────────┐ │ -│ │ UtilityProcess (monitor) │ ← isolated process │ -│ │ - Watches crash log file │ survives window │ -│ │ - Sends crash events via │ crashes │ -│ │ MessagePort to main │ │ -│ └──────────────────────────────┘ │ -│ ┌──────────────────────────────┐ │ -│ │ Crash Monitor Window │ ← separate window │ -│ │ - Shows crash dialogs │ custom UI │ -│ │ - Log viewer window │ │ -│ └──────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ↕ IPC (contextBridge) -┌─────────────────────────────────────────────────────────┐ -│ Renderer (Main Window) │ -│ - window.onerror → ipcRenderer.send('renderer-error') │ -│ - unhandledrejection → ipcRenderer.send(...) │ -│ - webContents 'render-process-gone' → recover │ -└─────────────────────────────────────────────────────────┘ -``` - -## 2. Implementation Steps - -### Step 1: Create Crash Logger Module -**File:** `electron/core/crash-logger.ts` -- Initialize crash log path (`koring-crash.log` next to executable) -- Write crash events synchronously to survive crashes -- Buffer recent events for breadcrumb trail -- Log rotation (keep last 1000 lines) - -### Step 2: Create Crash Monitor Window -**File:** `electron/handlers/crash-monitor.ts` -- Separate BrowserWindow (hidden by default) -- Listens for crash events from main process -- Shows custom crash dialog UI -- Can be opened from developer tools - -### Step 3: Create Log Viewer Window -**File:** `electron/handlers/log-viewer.ts` -- Separate BrowserWindow for viewing logs -- Real-time log streaming via IPC -- Filter by log level (error, warn, info) -- Export logs functionality - -### Step 4: Create Crash Dialog UI -**File:** `src/components/crash/CrashDialog.tsx` -- Custom styled crash dialog -- Shows error details, stack trace -- Options: Restart, View Logs, Close -- Uses existing UI components (shadcn/ui) - -### Step 5: Create Log Viewer UI -**File:** `src/components/crash/LogViewer.tsx` -- Log list with syntax highlighting -- Search/filter functionality -- Real-time updates - -### Step 6: Update Main Process -**File:** `electron/main.ts` -- Initialize crash logger early -- Set up crash event handlers -- Create crash monitor window - -### Step 7: Update Preload Script -**File:** `electron/preload.ts` -- Add renderer error capture -- Expose crash-related IPC methods - -## 3. Key Features - -1. **Crash Detection:** - - Renderer crashes (`render-process-gone`) - - Main process errors (`uncaughtException`, `unhandledRejection`) - - GPU/utility crashes (`child-process-gone`) - - Unresponsive detection (`unresponsive` event) - -2. **Crash Dialog:** - - Custom styled UI matching launcher theme - - Error message and stack trace display - - One-click restart option - - View logs option - -3. **Log Viewer:** - - Can be opened from developer tools - - Real-time log streaming - - Search and filter capabilities - - Export functionality - -4. **Process Isolation:** - - Crash monitor runs in separate window - - Main window crashes don't affect monitor - - Monitor can restart main window - -## 4. Files to Create/Modify - -**New Files:** -- `electron/core/crash-logger.ts` -- `electron/handlers/crash-monitor.ts` -- `electron/handlers/log-viewer.ts` -- `src/components/crash/CrashDialog.tsx` -- `src/components/crash/LogViewer.tsx` -- `src/pages/crash/index.tsx` - -**Modified Files:** -- `electron/main.ts` - Add crash monitoring initialization -- `electron/preload.ts` - Add error capture and IPC methods -- `src/types/electron.d.ts` - Add new IPC method types - -## 5. UI Design - -The crash dialog and log viewer will use the existing design system: -- Glass effects with backdrop-filter -- Dark theme compatible -- Consistent spacing and typography -- Uses shadcn/ui components where appropriate - -## 6. Testing Plan - -1. Test crash detection by triggering intentional errors -2. Verify crash dialog appears correctly -3. Test log viewer functionality -4. Verify main window can be restarted from crash dialog -5. Test log export functionality - -## 7. Questions for User - -1. What specific crash scenarios should we prioritize? -2. Do you want the log viewer always accessible or only in dev mode? -3. Should crash reports be sent to a server or just stored locally? -4. Any specific UI preferences for the crash dialog beyond the existing design system? diff --git a/Koring.yml b/Koring.yml new file mode 100644 index 0000000..055ce30 --- /dev/null +++ b/Koring.yml @@ -0,0 +1 @@ +oobe: false diff --git a/crash.html b/crash.html new file mode 100644 index 0000000..930ac6c --- /dev/null +++ b/crash.html @@ -0,0 +1,12 @@ + + + + + + Koring Launcher - Crash + + +
+ + + diff --git a/electron/config.ts b/electron/config.ts index f8195de..add8ae5 100644 --- a/electron/config.ts +++ b/electron/config.ts @@ -7,7 +7,7 @@ const { app } = electron; const CONFIG_FILE = 'Koring.yml'; const CURRENT_VERSION = 1; -function configPath(): string { +export function configPath(): string { if (app.isPackaged) { return path.join(path.dirname(app.getPath('exe')), CONFIG_FILE); } @@ -76,6 +76,7 @@ export interface NetworkConfig { export interface AppConfig { version: number; + oobe: boolean; theme: ThemeConfig; a11y: A11yConfig; background: BackgroundConfig; @@ -88,6 +89,7 @@ export interface AppConfig { const DEFAULTS: AppConfig = { version: CURRENT_VERSION, + oobe: true, theme: { darkMode: 'auto', parallax: true }, a11y: { reduceMotion: false, reduceTransparency: false, highContrast: false, contentBlurOpacity: 50 }, background: { bgType: 'image', image: '/background.png', blur: 0, opacity: 100 }, @@ -129,6 +131,14 @@ function diffValue(full: unknown, defaultVal: unknown): unknown { return Object.keys(result).length === 0 ? undefined : result; } +export function configExists(): boolean { + try { + return fs.existsSync(configPath()); + } catch { + return false; + } +} + export function loadConfig(): AppConfig { const filePath = configPath(); if (!fs.existsSync(filePath)) { diff --git a/electron/core/crash-logger.js b/electron/core/crash-logger.js new file mode 100644 index 0000000..e0a31eb --- /dev/null +++ b/electron/core/crash-logger.js @@ -0,0 +1,93 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.writeCrashLog = writeCrashLog; +exports.readCrashLog = readCrashLog; +exports.clearCrashLog = clearCrashLog; +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +const electron_1 = __importDefault(require("electron")); +const { app } = electron_1.default; +const LOG_FILE = 'koring-crash.log'; +const MAX_LINES = 1000; +function logPath() { + if (app.isPackaged) { + return path.join(path.dirname(app.getPath('exe')), LOG_FILE); + } + return path.join(__dirname, '..', LOG_FILE); +} +function trimFile(filePath) { + try { + if (!fs.existsSync(filePath)) + return; + const lines = fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean); + if (lines.length > MAX_LINES) { + fs.writeFileSync(filePath, lines.slice(-MAX_LINES).join('\n') + '\n', 'utf-8'); + } + } + catch { } +} +function writeCrashLog(entry) { + const filePath = logPath(); + try { + const line = JSON.stringify(entry) + '\n'; + fs.appendFileSync(filePath, line, 'utf-8'); + trimFile(filePath); + } + catch { } +} +function readCrashLog() { + const filePath = logPath(); + try { + if (!fs.existsSync(filePath)) + return ''; + return fs.readFileSync(filePath, 'utf-8'); + } + catch { + return ''; + } +} +function clearCrashLog() { + const filePath = logPath(); + try { + if (fs.existsSync(filePath)) { + fs.writeFileSync(filePath, '', 'utf-8'); + } + } + catch { } +} diff --git a/electron/core/crash-logger.ts b/electron/core/crash-logger.ts new file mode 100644 index 0000000..e6d336a --- /dev/null +++ b/electron/core/crash-logger.ts @@ -0,0 +1,60 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import electron from 'electron'; +const { app } = electron; + +const LOG_FILE = 'koring-crash.log'; +const MAX_LINES = 1000; + +function logPath(): string { + if (app.isPackaged) { + return path.join(path.dirname(app.getPath('exe')), LOG_FILE); + } + return path.join(__dirname, '..', LOG_FILE); +} + +export interface CrashEntry { + timestamp: string; + type: 'renderer-gone' | 'unresponsive' | 'uncaught-exception' | 'unhandled-rejection' | 'child-process-gone' | 'test'; + message: string; + stack?: string; + details?: Record; +} + +function trimFile(filePath: string): void { + try { + if (!fs.existsSync(filePath)) return; + const lines = fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean); + if (lines.length > MAX_LINES) { + fs.writeFileSync(filePath, lines.slice(-MAX_LINES).join('\n') + '\n', 'utf-8'); + } + } catch {} +} + +export function writeCrashLog(entry: CrashEntry): void { + const filePath = logPath(); + try { + const line = JSON.stringify(entry) + '\n'; + fs.appendFileSync(filePath, line, 'utf-8'); + trimFile(filePath); + } catch {} +} + +export function readCrashLog(): string { + const filePath = logPath(); + try { + if (!fs.existsSync(filePath)) return ''; + return fs.readFileSync(filePath, 'utf-8'); + } catch { + return ''; + } +} + +export function clearCrashLog(): void { + const filePath = logPath(); + try { + if (fs.existsSync(filePath)) { + fs.writeFileSync(filePath, '', 'utf-8'); + } + } catch {} +} diff --git a/electron/handlers/config.js b/electron/handlers/config.js index 9bdb6c5..65239e8 100644 --- a/electron/handlers/config.js +++ b/electron/handlers/config.js @@ -1,11 +1,45 @@ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.registerConfigHandlers = registerConfigHandlers; const electron_1 = __importDefault(require("electron")); -const { ipcMain } = electron_1.default; +const { ipcMain, app } = electron_1.default; +const fs = __importStar(require("fs")); const config_1 = require("../config"); function registerConfigHandlers() { ipcMain.handle('config:get', () => { @@ -26,4 +60,18 @@ function registerConfigHandlers() { return { success: false, data: null, error: String(e) }; } }); + ipcMain.handle('config:reset', () => { + try { + const filePath = (0, config_1.configPath)(); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + app.relaunch(); + app.exit(0); + return { success: true, data: null, error: null }; + } + catch (e) { + return { success: false, data: null, error: String(e) }; + } + }); } diff --git a/electron/handlers/config.ts b/electron/handlers/config.ts index 403b24f..a21a99a 100644 --- a/electron/handlers/config.ts +++ b/electron/handlers/config.ts @@ -1,6 +1,8 @@ import electron from 'electron'; -const { ipcMain } = electron; -import { loadConfig, saveConfig, type AppConfig } from '../config'; +const { ipcMain, app } = electron; +import * as fs from 'fs'; +import * as path from 'path'; +import { loadConfig, saveConfig, type AppConfig, configPath } from '../config'; export function registerConfigHandlers() { ipcMain.handle('config:get', () => { @@ -20,4 +22,18 @@ export function registerConfigHandlers() { return { success: false, data: null, error: String(e) }; } }); + + ipcMain.handle('config:reset', () => { + try { + const filePath = configPath(); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + app.relaunch(); + app.exit(0); + return { success: true, data: null, error: null }; + } catch (e: unknown) { + return { success: false, data: null, error: String(e) }; + } + }); } diff --git a/electron/handlers/crash-monitor.js b/electron/handlers/crash-monitor.js new file mode 100644 index 0000000..ff498a9 --- /dev/null +++ b/electron/handlers/crash-monitor.js @@ -0,0 +1,206 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.setupCrashListeners = setupCrashListeners; +exports.registerCrashHandlers = registerCrashHandlers; +exports.testCrashDialog = testCrashDialog; +const electron_1 = __importDefault(require("electron")); +const path_1 = __importDefault(require("path")); +const fs_1 = __importDefault(require("fs")); +const crash_logger_1 = require("../core/crash-logger"); +const { app, ipcMain, BrowserWindow } = electron_1.default; +const isDev = !app.isPackaged; +let crashWin = null; +function getIconPath() { + return path_1.default.join(__dirname, '../../build/icon.ico'); +} +function createCrashWindow() { + const iconPath = getIconPath(); + const win = new BrowserWindow({ + width: 600, + height: 460, + minWidth: 500, + maxWidth: 700, + minHeight: 460, + maxHeight: 460, + frame: false, + transparent: false, + resizable: false, + show: false, + icon: iconPath, + webPreferences: { + preload: path_1.default.join(__dirname, '../preload-crash.js'), + nodeIntegration: false, + contextIsolation: true, + sandbox: false, + }, + }); + if (isDev) { + win.loadURL('http://localhost:1420/crash.html'); + } + else { + win.loadFile(path_1.default.join(__dirname, '../../dist/crash.html')); + } + return win; +} +function sendToCrashWindow(data) { + if (!crashWin || crashWin.isDestroyed()) { + crashWin = createCrashWindow(); + } + const send = () => { + crashWin?.show(); + crashWin?.focus(); + crashWin?.webContents.send('crash:show', { + type: data.type, + message: data.message, + timestamp: data.timestamp, + }); + }; + if (crashWin.webContents.isLoading()) { + crashWin.webContents.once('did-finish-load', send); + } + else { + send(); + } +} +function setupCrashListeners(mainWindow) { + mainWindow.webContents.on('render-process-gone', (_event, details) => { + const entry = { + timestamp: new Date().toISOString(), + type: 'renderer-gone', + message: `渲染进程崩溃: ${details.reason} (退出码: ${details.exitCode})`, + details: details, + }; + (0, crash_logger_1.writeCrashLog)(entry); + sendToCrashWindow(entry); + }); + mainWindow.on('unresponsive', () => { + const entry = { + timestamp: new Date().toISOString(), + type: 'unresponsive', + message: '渲染进程无响应', + }; + (0, crash_logger_1.writeCrashLog)(entry); + sendToCrashWindow(entry); + }); + // Inject devtools crash tools when devtools opens + if (isDev) { + mainWindow.webContents.on('devtools-opened', () => { + const js = ` + (function() { + if (window.__crashToolsLoaded) return; + window.__crashToolsLoaded = true; + + console.log('%c[崩溃工具] 已加载', 'color: #f59e0b; font-weight: bold; font-size: 14px;'); + console.log('%c可用命令:', 'color: #3b82f6; font-weight: bold;'); + console.log('%c crash.simulate() %c— 模拟渲染进程崩溃', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.testDialog() %c— 测试崩溃弹窗', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.readLog() %c— 读取崩溃日志', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.factoryReset()%c— 强还原配置', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.restart() %c— 重启应用', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log(''); + + window.crash = { + simulate: function() { window.electronAPI?.simulateCrash(); }, + testDialog: function() { window.electronAPI?.testCrashDialog(); }, + readLog: function() { return window.electronAPI?.invoke('crash:readLog'); }, + factoryReset: function() { return window.electronAPI?.invoke('crash:factoryReset'); }, + restart: function() { return window.electronAPI?.invoke('crash:restart'); }, + }; + })(); + `; + mainWindow.webContents.executeJavaScript(js); + }); + } +} +function registerCrashHandlers() { + // Close crash window + ipcMain.handle('crash:closeWindow', () => { + if (crashWin && !crashWin.isDestroyed()) { + crashWin.close(); + crashWin = null; + } + }); + // Read crash log + ipcMain.handle('crash:readLog', () => { + return (0, crash_logger_1.readCrashLog)(); + }); + // Simulate crash — forcefully crash the renderer (devtools only) + ipcMain.handle('crash:simulate', (_event) => { + const senderWebContents = electron_1.default.webContents.getAllWebContents().find((wc) => wc.id === _event.sender.id); + if (senderWebContents) { + senderWebContents.forcefullyCrashRenderer(); + } + }); + // Test crash dialog — show crash window without actually crashing + ipcMain.handle('crash:testDialog', () => { + testCrashDialog(); + }); + // Factory reset + ipcMain.handle('crash:factoryReset', () => { + const dataPath = app.isPackaged + ? path_1.default.dirname(app.getPath('exe')) + : path_1.default.join(__dirname, '../..'); + // Delete config + try { + const configPath = path_1.default.join(dataPath, 'Koring.yml'); + if (fs_1.default.existsSync(configPath)) + fs_1.default.unlinkSync(configPath); + } + catch { } + // Delete auth + try { + const authPath = path_1.default.join(dataPath, 'koring-auth.json'); + if (fs_1.default.existsSync(authPath)) + fs_1.default.unlinkSync(authPath); + } + catch { } + // Delete background cache in userData + try { + const bgPath = path_1.default.join(app.getPath('userData'), 'background.png'); + if (fs_1.default.existsSync(bgPath)) + fs_1.default.unlinkSync(bgPath); + } + catch { } + // Delete crash log + (0, crash_logger_1.clearCrashLog)(); + return { success: true }; + }); + // Restart app + ipcMain.handle('crash:restart', () => { + app.relaunch(); + app.exit(0); + }); + // Main process error handlers + process.on('uncaughtException', (error) => { + const entry = { + timestamp: new Date().toISOString(), + type: 'uncaught-exception', + message: error.message, + stack: error.stack, + }; + (0, crash_logger_1.writeCrashLog)(entry); + sendToCrashWindow(entry); + }); + process.on('unhandledRejection', (reason) => { + const entry = { + timestamp: new Date().toISOString(), + type: 'unhandled-rejection', + message: String(reason), + stack: reason instanceof Error ? reason.stack : undefined, + }; + (0, crash_logger_1.writeCrashLog)(entry); + sendToCrashWindow(entry); + }); +} +// Called from devtools to test crash dialog +function testCrashDialog() { + const entry = { + timestamp: new Date().toISOString(), + type: 'test', + message: '这是一个测试崩溃弹窗', + }; + sendToCrashWindow(entry); +} diff --git a/electron/handlers/crash-monitor.ts b/electron/handlers/crash-monitor.ts new file mode 100644 index 0000000..cd1c283 --- /dev/null +++ b/electron/handlers/crash-monitor.ts @@ -0,0 +1,220 @@ +import electron from 'electron'; +import path from 'path'; +import fs from 'fs'; +import { writeCrashLog, readCrashLog, clearCrashLog, type CrashEntry } from '../core/crash-logger'; + +const { app, ipcMain, BrowserWindow } = electron; + +const isDev = !app.isPackaged; + +let crashWin: electron.BrowserWindow | null = null; + +function getIconPath(): string { + return path.join(__dirname, '../../build/icon.ico'); +} + +function createCrashWindow(): electron.BrowserWindow { + const iconPath = getIconPath(); + + const win = new BrowserWindow({ + width: 600, + height: 460, + minWidth: 500, + maxWidth: 700, + minHeight: 460, + maxHeight: 460, + frame: false, + transparent: false, + resizable: false, + show: false, + icon: iconPath, + webPreferences: { + preload: path.join(__dirname, '../preload-crash.js'), + nodeIntegration: false, + contextIsolation: true, + sandbox: false, + }, + }); + + if (isDev) { + win.loadURL('http://localhost:1420/crash.html'); + } else { + win.loadFile(path.join(__dirname, '../../dist/crash.html')); + } + + return win; +} + +function sendToCrashWindow(data: CrashEntry) { + if (!crashWin || crashWin.isDestroyed()) { + crashWin = createCrashWindow(); + } + + const send = () => { + crashWin?.show(); + crashWin?.focus(); + crashWin?.webContents.send('crash:show', { + type: data.type, + message: data.message, + timestamp: data.timestamp, + }); + }; + + if (crashWin.webContents.isLoading()) { + crashWin.webContents.once('did-finish-load', send); + } else { + send(); + } +} + +export function setupCrashListeners(mainWindow: electron.BrowserWindow) { + mainWindow.webContents.on('render-process-gone', (_event: any, details: any) => { + const entry: CrashEntry = { + timestamp: new Date().toISOString(), + type: 'renderer-gone', + message: `渲染进程崩溃: ${details.reason} (退出码: ${details.exitCode})`, + details: details as unknown as Record, + }; + writeCrashLog(entry); + sendToCrashWindow(entry); + }); + + mainWindow.on('unresponsive', () => { + const entry: CrashEntry = { + timestamp: new Date().toISOString(), + type: 'unresponsive', + message: '渲染进程无响应', + }; + writeCrashLog(entry); + sendToCrashWindow(entry); + }); + + // Inject devtools crash tools when devtools opens + if (isDev) { + mainWindow.webContents.on('devtools-opened', () => { + const js = ` + (function() { + if (window.__crashToolsLoaded) return; + window.__crashToolsLoaded = true; + + console.log('%c[崩溃工具] 已加载', 'color: #f59e0b; font-weight: bold; font-size: 14px;'); + console.log('%c可用命令:', 'color: #3b82f6; font-weight: bold;'); + console.log('%c crash.simulate() %c— 模拟渲染进程崩溃', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.testDialog() %c— 测试崩溃弹窗', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.readLog() %c— 读取崩溃日志', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.factoryReset()%c— 强还原配置', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log('%c crash.restart() %c— 重启应用', 'color: #ef4444; font-weight: bold;', 'color: inherit;'); + console.log(''); + + window.crash = { + simulate: function() { window.electronAPI?.simulateCrash(); }, + testDialog: function() { window.electronAPI?.testCrashDialog(); }, + readLog: function() { return window.electronAPI?.invoke('crash:readLog'); }, + factoryReset: function() { return window.electronAPI?.invoke('crash:factoryReset'); }, + restart: function() { return window.electronAPI?.invoke('crash:restart'); }, + }; + })(); + `; + mainWindow.webContents.executeJavaScript(js); + }); + } +} + +export function registerCrashHandlers() { + // Close crash window + ipcMain.handle('crash:closeWindow', () => { + if (crashWin && !crashWin.isDestroyed()) { + crashWin.close(); + crashWin = null; + } + }); + + // Read crash log + ipcMain.handle('crash:readLog', () => { + return readCrashLog(); + }); + + // Simulate crash — forcefully crash the renderer (devtools only) + ipcMain.handle('crash:simulate', (_event) => { + const senderWebContents = electron.webContents.getAllWebContents().find( + (wc) => wc.id === _event.sender.id + ); + if (senderWebContents) { + senderWebContents.forcefullyCrashRenderer(); + } + }); + + // Test crash dialog — show crash window without actually crashing + ipcMain.handle('crash:testDialog', () => { + testCrashDialog(); + }); + + // Factory reset + ipcMain.handle('crash:factoryReset', () => { + const dataPath = app.isPackaged + ? path.dirname(app.getPath('exe')) + : path.join(__dirname, '../..'); + + // Delete config + try { + const configPath = path.join(dataPath, 'Koring.yml'); + if (fs.existsSync(configPath)) fs.unlinkSync(configPath); + } catch {} + + // Delete auth + try { + const authPath = path.join(dataPath, 'koring-auth.json'); + if (fs.existsSync(authPath)) fs.unlinkSync(authPath); + } catch {} + + // Delete background cache in userData + try { + const bgPath = path.join(app.getPath('userData'), 'background.png'); + if (fs.existsSync(bgPath)) fs.unlinkSync(bgPath); + } catch {} + + // Delete crash log + clearCrashLog(); + + return { success: true }; + }); + + // Restart app + ipcMain.handle('crash:restart', () => { + app.relaunch(); + app.exit(0); + }); + + // Main process error handlers + process.on('uncaughtException', (error) => { + const entry: CrashEntry = { + timestamp: new Date().toISOString(), + type: 'uncaught-exception', + message: error.message, + stack: error.stack, + }; + writeCrashLog(entry); + sendToCrashWindow(entry); + }); + + process.on('unhandledRejection', (reason) => { + const entry: CrashEntry = { + timestamp: new Date().toISOString(), + type: 'unhandled-rejection', + message: String(reason), + stack: reason instanceof Error ? reason.stack : undefined, + }; + writeCrashLog(entry); + sendToCrashWindow(entry); + }); +} + +// Called from devtools to test crash dialog +export function testCrashDialog() { + const entry: CrashEntry = { + timestamp: new Date().toISOString(), + type: 'test', + message: '这是一个测试崩溃弹窗', + }; + sendToCrashWindow(entry); +} diff --git a/electron/main.ts b/electron/main.ts index cfb2d96..fd82c4e 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,5 +1,6 @@ import electron from 'electron'; import path from 'path'; +import fs from 'fs'; import { registerConfigHandlers } from './handlers/config'; import { registerAuthHandlers } from './handlers/auth'; import { registerInstallHandlers } from './handlers/install'; @@ -10,17 +11,47 @@ import { registerBackgroundHandlers } from './handlers/background'; import { registerTaskHandlers } from './handlers/task'; import { registerSystemHandlers } from './handlers/system'; import { registerWindowHandlers } from './handlers/window'; +import { registerCrashHandlers, setupCrashListeners, testCrashDialog } from './handlers/crash-monitor'; +import { loadConfig, saveConfig, configExists } from './config'; const { app } = electron; const isDev = !app.isPackaged; -// Mutable ref — handlers always read from this +// GPU acceleration flags +app.commandLine.appendSwitch('enable-gpu-rasterization'); +app.commandLine.appendSwitch('enable-zero-copy'); + const win: { mainWindow: electron.BrowserWindow | null; splashWindow: electron.BrowserWindow | null } = { mainWindow: null, splashWindow: null, }; +// Startup checks: .minecraft dir + config file + first launch detection +function runStartupChecks(): { isFirstLaunch: boolean; config: ReturnType } { + const dataPath = app.isPackaged + ? path.dirname(app.getPath('exe')) + : path.join(__dirname, '..'); + + // 1. Ensure .minecraft directory exists + const minecraftDir = path.join(dataPath, '.minecraft'); + if (!fs.existsSync(minecraftDir)) { + fs.mkdirSync(minecraftDir, { recursive: true }); + } + + // 2. Check if config exists, if not create with defaults + const hasConfig = configExists(); + const isFirstLaunch = !hasConfig; + + // 3. Load (or create) config + const config = loadConfig(); + if (isFirstLaunch) { + saveConfig(config); + } + + return { isFirstLaunch, config }; +} + function createSplashWindow(): electron.BrowserWindow { const iconPath = isDev ? path.join(__dirname, '../build/icon.ico') @@ -101,18 +132,27 @@ function registerAllHandlers() { registerTaskHandlers(win); registerSystemHandlers(); registerWindowHandlers(win); + registerCrashHandlers(); } app.whenReady().then(() => { registerAllHandlers(); + // Run startup checks before creating windows + const { isFirstLaunch, config } = runStartupChecks(); + // 1. Show splash immediately win.splashWindow = createSplashWindow(); // 2. Create main window in background win.mainWindow = createMainWindow(); - // 3. When main window finishes loading, wait a minimum time then transition + // 3. Preload config into renderer before it renders + win.mainWindow.webContents.on('did-finish-load', () => { + win.mainWindow?.webContents.send('config:preload', { config, isFirstLaunch }); + }); + + // 4. When main window finishes loading, wait a minimum time then transition let mainReady = false; let splashMinTimeDone = false; @@ -134,6 +174,9 @@ app.whenReady().then(() => { tryTransition(); }); + // Setup crash listeners on main window + setupCrashListeners(win.mainWindow); + // Minimum splash display time (1.5s) setTimeout(() => { splashMinTimeDone = true; diff --git a/electron/preload-crash.ts b/electron/preload-crash.ts new file mode 100644 index 0000000..8c96c2b --- /dev/null +++ b/electron/preload-crash.ts @@ -0,0 +1,29 @@ +import electron from 'electron'; +const { contextBridge, ipcRenderer } = electron; + +contextBridge.exposeInMainWorld('electronAPI', { + invoke: (channel: string, ...args: unknown[]) => + ipcRenderer.invoke(channel, ...args), + + on: (channel: string, callback: (...args: unknown[]) => void) => { + const handler = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args); + ipcRenderer.on(channel, handler); + return () => ipcRenderer.removeListener(channel, handler); + }, + + send: (channel: string, ...args: unknown[]) => + ipcRenderer.send(channel, ...args), + + minimize: () => ipcRenderer.invoke('window:minimize'), + maximize: () => ipcRenderer.invoke('window:maximize'), + close: () => ipcRenderer.invoke('crash:closeWindow'), + isMaximized: () => ipcRenderer.invoke('window:isMaximized'), + onResized: (callback: () => void) => { + const handler = () => callback(); + ipcRenderer.on('window:resized', handler); + return () => ipcRenderer.removeListener('window:resized', handler); + }, + + getTheme: () => ipcRenderer.invoke('window:getTheme'), + openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), +}); diff --git a/electron/preload.ts b/electron/preload.ts index 2477892..e78b7eb 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -51,6 +51,13 @@ contextBridge.exposeInMainWorld('electronAPI', { // Theme getTheme: () => ipcRenderer.invoke('window:getTheme'), + // Config preloading + onConfigPreload: (callback: (data: { config: unknown; isFirstLaunch: boolean }) => void) => { + const handler = (_event: Electron.IpcRendererEvent, data: { config: unknown; isFirstLaunch: boolean }) => callback(data); + ipcRenderer.on('config:preload', handler); + return () => ipcRenderer.removeListener('config:preload', handler); + }, + // Background image — pick file, copy to userData, return base64 data URL pickBackgroundImage: async (): Promise => { const result = await ipcRenderer.invoke('dialog:openFile', { @@ -73,4 +80,11 @@ contextBridge.exposeInMainWorld('electronAPI', { // Open external URL in system browser openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), + + // Crash monitoring — devtools only + simulateCrash: () => ipcRenderer.invoke('crash:simulate'), + testCrashDialog: () => ipcRenderer.invoke('crash:testDialog'), + + // Config reset + resetConfig: () => ipcRenderer.invoke('config:reset'), }); diff --git a/package.json b/package.json index e7517bc..7ce8e00 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "koring-launcher", "private": true, - "version": "1.0.0", + "version": "1.0.1", "description": "Koring Launcher - Minecraft launcher built with Electron + React", "author": "Shenzhen Lingke Network Technology Co., Ltd.", "license": "LL-1.0", @@ -43,6 +43,7 @@ "clsx": "^2.1.1", "js-yaml": "^4.1.0", "lucide-react": "^1.21.0", + "motion": "^12.42.2", "next-themes": "^0.4.6", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f752c01..3112337 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: lucide-react: specifier: ^1.21.0 version: 1.21.0(react@19.2.7) + motion: + specifier: ^12.42.2 + version: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -1937,6 +1940,20 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + framer-motion@12.42.2: + resolution: {integrity: sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -2644,6 +2661,26 @@ packages: engines: {node: '>=10'} hasBin: true + motion-dom@12.42.2: + resolution: {integrity: sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==} + + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} + + motion@12.42.2: + resolution: {integrity: sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5492,6 +5529,15 @@ snapshots: forwarded@0.2.0: {} + framer-motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + motion-dom: 12.42.2 + motion-utils: 12.39.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + fresh@2.0.0: {} fs-constants@1.0.0: {} @@ -6132,6 +6178,20 @@ snapshots: mkdirp@1.0.4: {} + motion-dom@12.42.2: + dependencies: + motion-utils: 12.39.0 + + motion-utils@12.39.0: {} + + motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + framer-motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tslib: 2.8.1 + optionalDependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + ms@2.1.3: {} nanoid@3.3.13: {} diff --git a/public/background-3.png b/public/background-3.png new file mode 100644 index 0000000000000000000000000000000000000000..0457b7440729bcc31a930acd53bf64bfbd623706 GIT binary patch literal 221537 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 { - useConfigStore.getState().init().then(() => { + // Listen for preloaded config from main process + const unsub = window.electronAPI?.onConfigPreload((data) => { + const { config, isFirstLaunch } = data; + useConfigStore.getState().applyPreloaded(config as AppConfig, isFirstLaunch); syncThemeFromConfig(); syncA11yFromConfig(); syncBackgroundFromConfig(); useAuthStore.getState().initFromRegistry(); + // Navigate to OOBE on first launch or if oobe not completed + if (isFirstLaunch || config.oobe) { + useRouteStore.getState().navigate("oobe"); + } }); + + return () => { unsub?.(); }; }, []); return ( diff --git a/src/api/config.ts b/src/api/config.ts index fbfbd99..2dc8056 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -62,6 +62,7 @@ export interface NetworkConfig { export interface AppConfig { version: number; + oobe: boolean; theme: ThemeConfig; a11y: A11yConfig; background: BackgroundConfig; diff --git a/src/components/BetaWarning.tsx b/src/components/BetaWarning.tsx deleted file mode 100644 index 61cb4e1..0000000 --- a/src/components/BetaWarning.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect } from "react"; -import { toast } from "sonner"; -import { BUILD_MODE } from "@/lib/mode"; -import { VERSION } from "@/lib/version"; - -export function BetaWarning() { - useEffect(() => { - if (BUILD_MODE === "beta" || BUILD_MODE === "dev") { - toast.warning(`当前为 v${VERSION} BETA 测试版,不代表最终品质。`, { - duration: Infinity, - dismissible: true, - }); - } - }, []); - - return null; -} diff --git a/src/components/StartupPopup.tsx b/src/components/StartupPopup.tsx deleted file mode 100644 index 5e648f9..0000000 --- a/src/components/StartupPopup.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useState, useEffect } from "react"; -import { - AlertDialog, - AlertDialogContent, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogAction, -} from "@/components/ui/alert-dialog"; -import { AlertTriangle } from "lucide-react"; - -const enabled = import.meta.env.VITE_START_POP === "true"; -const title = import.meta.env.VITE_START_POP_TITLE ?? ""; -const info = import.meta.env.VITE_START_POP_INFO ?? ""; -const buttonText = import.meta.env.VITE_START_POP_BOUTTON ?? "确定"; - -export function StartupPopup() { - const [open, setOpen] = useState(false); - - useEffect(() => { - if (enabled) { - setOpen(true); - } - }, []); - - if (!enabled) return null; - - return ( - - - -
- -
- {title} - {info} -
- - setOpen(false)}> - {buttonText} - - -
-
- ); -} diff --git a/src/components/silk/Silk.tsx b/src/components/silk/Silk.tsx index 10ed645..9fa9e36 100644 --- a/src/components/silk/Silk.tsx +++ b/src/components/silk/Silk.tsx @@ -124,9 +124,10 @@ const Silk = ({ speed = 5, scale = 1, color = "#7B7481", noiseIntensity = 1.5, r return ( diff --git a/src/components/ui/apple-hello-effect.tsx b/src/components/ui/apple-hello-effect.tsx new file mode 100644 index 0000000..d4d1be5 --- /dev/null +++ b/src/components/ui/apple-hello-effect.tsx @@ -0,0 +1,70 @@ +import { motion } from "motion/react" + +import { cn } from "@/lib/utils" + +const initialProps = { + pathLength: 0, + opacity: 0, +} as const + +const animateProps = { + pathLength: 1, + opacity: 1, +} as const + +type Props = React.ComponentProps & { + speed?: number + onAnimationComplete?: () => void +} + +function AppleHelloEnglishEffect({ className, speed = 1, onAnimationComplete, ...props }: Props) { + const calc = (x: number) => x * speed + + return ( + + hello + + {/* h1 */} + + + {/* h2, ello */} + + + ) +} + +export { AppleHelloEnglishEffect} + diff --git a/src/components/ui/confirm-dialog.tsx b/src/components/ui/confirm-dialog.tsx new file mode 100644 index 0000000..06d6251 --- /dev/null +++ b/src/components/ui/confirm-dialog.tsx @@ -0,0 +1,47 @@ +import { useState, useEffect } from "react"; +import { useConfirmDialogStore } from "@/stores/confirmDialogStore"; + +export function ConfirmDialog() { + const { open, title, description, confirmLabel, countdown: initCountdown, showCountdown, onConfirm, closeDialog } = + useConfirmDialogStore(); + const [countdown, setCountdown] = useState(initCountdown); + + useEffect(() => { + if (!open) return; + setCountdown(initCountdown); + }, [open, initCountdown]); + + useEffect(() => { + if (!open || !showCountdown || countdown <= 0) return; + const timer = setTimeout(() => setCountdown((c) => c - 1), 1000); + return () => clearTimeout(timer); + }, [open, showCountdown, countdown]); + + if (!open) return null; + + const canConfirm = !showCountdown || countdown <= 0; + + return ( +
+
+

{title}

+

{description}

+
+ + +
+
+
+ ); +} diff --git a/src/crash.tsx b/src/crash.tsx new file mode 100644 index 0000000..cb9cab4 --- /dev/null +++ b/src/crash.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { CrashPage } from "./pages/crash"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + , +); diff --git a/src/layouts/RootLayout.tsx b/src/layouts/RootLayout.tsx index cf1a2b1..5c9c669 100644 --- a/src/layouts/RootLayout.tsx +++ b/src/layouts/RootLayout.tsx @@ -1,8 +1,7 @@ import { type ReactNode } from "react"; import { BackgroundLayer } from "@/components/background/BackgroundLayer"; import { SystemLayer } from "@/components/system/SystemLayer"; -import { StartupPopup } from "@/components/StartupPopup"; -import { BetaWarning } from "@/components/BetaWarning"; +import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { Toaster } from "sonner"; import { useA11yStore } from "@/stores/a11yStore"; import clsx from "clsx"; @@ -52,11 +51,8 @@ export function RootLayout({ showClose={showClose} /> - {/* Startup popup — only when VITE_START_POP=true */} - - - {/* Beta warning toast */} - + {/* Global confirm dialog */} + {/* Sonner toaster */} (null); + const [copying, setCopying] = useState(false); + const [copyDone, setCopyDone] = useState(false); + + useEffect(() => { + const unsub = window.electronAPI?.on("crash:show", (...args: unknown[]) => { + const info = args[0] as CrashInfo; + setCrashInfo(info); + }); + return () => { unsub?.(); }; + }, []); + + const handleClose = () => { + window.electronAPI?.close(); + }; + + const handleCopyLog = async () => { + setCopying(true); + try { + const log = await window.electronAPI?.invoke("crash:readLog") as string; + if (log) { + await navigator.clipboard.writeText(log); + setCopyDone(true); + setTimeout(() => setCopyDone(false), 2000); + } + } catch { + } finally { + setCopying(false); + } + }; + + const handleFactoryReset = async () => { + await window.electronAPI?.invoke("crash:factoryReset"); + }; + + const handleRestart = async () => { + await window.electronAPI?.invoke("crash:restart"); + }; + + return ( +
+ {/* 顶栏 — 只有关闭按钮 */} +
+
+ + Koring Launcher + +
+ +
+ +
+
+ + {/* 崩溃内容 */} +
+
+ {/* 图标 */} +
+
+ + + + + +
+
+ + {/* 标题 */} +
+

+ 程序发生崩溃 +

+

+ 我们已记录此错误,接下来您想做什么? +

+
+ + + {/* 说明文字 */} +

+ 您可能是在一些奇怪的地方触发了内容导致崩溃,我们已准备好日志,你可以直接复制将其发送给支持人员,你也可以点击下方的重启按钮再次启动,如果还是不行您可以点击强还原配置按钮进行还原。(注意,还原只会清除启动器数据,并不会影响实例,请放心使用) +

+ + {/* 按钮组 */} +
+ + + + + +
+
+
+
+ ); +} diff --git a/src/pages/debug/crash-debug.tsx b/src/pages/debug/crash-debug.tsx new file mode 100644 index 0000000..df171b4 --- /dev/null +++ b/src/pages/debug/crash-debug.tsx @@ -0,0 +1,128 @@ +import { useState } from "react"; +import { AlertTriangle, TestTube, RotateCcw, Copy, Trash2 } from "lucide-react"; + +export function CrashDebug() { + const [log, setLog] = useState(""); + const [copied, setCopied] = useState(false); + const [status, setStatus] = useState(""); + + const handleSimulateCrash = async () => { + setStatus("正在模拟崩溃..."); + await window.electronAPI?.simulateCrash(); + }; + + const handleTestDialog = async () => { + setStatus("正在打开崩溃弹窗..."); + await window.electronAPI?.testCrashDialog(); + setStatus("崩溃弹窗已打开"); + }; + + const handleReadLog = async () => { + const result = await window.electronAPI?.invoke("crash:readLog") as string; + setLog(result || "(空)"); + setStatus("日志已加载"); + }; + + const handleCopyLog = async () => { + if (!log) return; + await navigator.clipboard.writeText(log); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const handleFactoryReset = async () => { + if (!confirm("确定要执行强还原配置吗?这将删除 Koring.yml、koring-auth.json 和背景缓存,但不会影响实例。")) return; + setStatus("正在还原..."); + await window.electronAPI?.invoke("crash:factoryReset"); + 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]"; + + return ( +
+

崩溃测试

+

测试崩溃检测、崩溃弹窗与恢复功能

+ +
+ {/* 模拟崩溃 */} +
+
+ +

模拟渲染进程崩溃

+
+

+ 调用 forcefullyCrashRenderer() 强制销毁渲染进程,触发 render-process-gone 事件,崩溃弹窗应自动弹出。 +

+ +
+ + {/* 测试崩溃弹窗 */} +
+
+ +

测试崩溃弹窗

+
+

+ 不会真正崩溃,直接弹出崩溃弹窗 UI,用于验证窗口样式、按钮功能是否正常。 +

+ +
+ + {/* 崩溃日志 */} +
+
+ +

崩溃日志

+
+

+ 读取 koring-crash.log 文件内容,可复制发送给开发人员。 +

+
+ + +
+ {log && ( +
+              {log}
+            
+ )} +
+ + {/* 强还原配置 */} +
+
+ +

强还原配置

+
+

+ 删除 Koring.yml、 + koring-auth.json 和背景缓存。 + 不会影响实例数据。 +

+ +
+ + {/* 状态 */} + {status && ( +

{status}

+ )} +
+
+ ); +} diff --git a/src/pages/debug/index.tsx b/src/pages/debug/index.tsx index 05ce642..1d9e135 100644 --- a/src/pages/debug/index.tsx +++ b/src/pages/debug/index.tsx @@ -1,7 +1,15 @@ import { useRouteStore } from "@/stores/routeStore"; -import { Monitor, Paintbrush, CreditCard, ListTodo, ChevronRight, FlaskConical, Rocket } from "lucide-react"; +import { Monitor, Paintbrush, CreditCard, ListTodo, ChevronRight, FlaskConical, Rocket, AlertTriangle } from "lucide-react"; const debugPages = [ + { + key: "debug-crash" as const, + icon: AlertTriangle, + title: "崩溃测试", + desc: "模拟崩溃、测试崩溃弹窗、查看崩溃日志与强还原配置", + color: "text-red-500", + bg: "bg-red-500/10", + }, { key: "debug-splash" as const, icon: Monitor, diff --git a/src/pages/oobe/about-info.tsx b/src/pages/oobe/about-info.tsx index f3aa5ad..fa22ec1 100644 --- a/src/pages/oobe/about-info.tsx +++ b/src/pages/oobe/about-info.tsx @@ -9,6 +9,7 @@ import { Clock, Loader2, } from "lucide-react"; +import { VERSION } from "@/lib/version"; interface InfoItem { icon: typeof Package; @@ -35,7 +36,7 @@ export function OobeAboutInfo() { const items: InfoItem[] = systemInfo && localeInfo ? [ - { icon: Package, label: "App Version", value: `v${systemInfo.app_version}`, color: "text-primary", bg: "bg-primary/10" }, + { icon: Package, label: "App Version", value: `v${VERSION}`, color: "text-primary", bg: "bg-primary/10" }, { icon: Cpu, label: "BIOS ID", value: systemInfo.bios_id, color: "text-blue-500", bg: "bg-blue-500/10" }, { icon: Monitor, label: "OS Name", value: `${systemInfo.os_name} (${systemInfo.os_version})`, color: "text-purple-500", bg: "bg-purple-500/10" }, { icon: Globe, label: "Region", value: localeInfo.region, color: "text-green-500", bg: "bg-green-500/10" }, diff --git a/src/pages/oobe/index.tsx b/src/pages/oobe/index.tsx index 487d464..4b4ea6c 100644 --- a/src/pages/oobe/index.tsx +++ b/src/pages/oobe/index.tsx @@ -1,29 +1,64 @@ import { useRouteStore } from "@/stores/routeStore"; -import { Rocket, ChevronRight } from "lucide-react"; +import { useEffect, useState, useRef } from "react"; -export function Oobe() { - const goBack = useRouteStore((s) => s.goBack); +const words = ["Hello.", "你好。", "こんにちは。", "안녕하세요。", "Bonjour."]; + +function TypewriterText() { + const [wordIndex, setWordIndex] = useState(0); + const [displayed, setDisplayed] = useState(""); + const [isDeleting, setIsDeleting] = useState(false); + const timerRef = useRef>(); + + useEffect(() => { + const word = words[wordIndex]; + + if (!isDeleting) { + if (displayed.length < word.length) { + timerRef.current = setTimeout(() => { + setDisplayed(word.slice(0, displayed.length + 1)); + }, 120); + } else { + timerRef.current = setTimeout(() => setIsDeleting(true), 1800); + } + } else { + if (displayed.length > 0) { + timerRef.current = setTimeout(() => { + setDisplayed(displayed.slice(0, -1)); + }, 60); + } else { + setIsDeleting(false); + setWordIndex((i) => (i + 1) % words.length); + } + } + + return () => clearTimeout(timerRef.current); + }, [displayed, isDeleting, wordIndex]); return ( -
-
-
- -
+ + {displayed} + + + ); +} -

- 欢迎使用 Koring Launcher -

-

- 这是 OOBE(开箱体验)页面。在这里可以引导用户完成初始设置。 -

+export function Oobe() { + const navigate = useRouteStore((s) => s.navigate); + return ( +
+ {/* 中心动画文字 */} +
+ +
+ + {/* 下方按钮 */} +
diff --git a/src/pages/oobe/layout.tsx b/src/pages/oobe/layout.tsx new file mode 100644 index 0000000..7f06e9e --- /dev/null +++ b/src/pages/oobe/layout.tsx @@ -0,0 +1,13 @@ +import { type ReactNode } from "react"; + +interface OobeLayoutProps { + children: ReactNode; +} + +export function OobeLayout({ children }: OobeLayoutProps) { + return ( +
+ {children} +
+ ); +} diff --git a/src/pages/oobe/next-button.tsx b/src/pages/oobe/next-button.tsx new file mode 100644 index 0000000..9a9eabb --- /dev/null +++ b/src/pages/oobe/next-button.tsx @@ -0,0 +1,18 @@ +interface NextButtonProps { + onClick: () => void; + disabled?: boolean; +} + +export function NextButton({ onClick, disabled = false }: NextButtonProps) { + return ( +
+ +
+ ); +} diff --git a/src/pages/oobe/step-agreement.tsx b/src/pages/oobe/step-agreement.tsx new file mode 100644 index 0000000..73351ee --- /dev/null +++ b/src/pages/oobe/step-agreement.tsx @@ -0,0 +1,60 @@ +import { useState, useEffect } from "react"; +import { useRouteStore } from "@/stores/routeStore"; +import { OobeLayout } from "./layout"; +import { NextButton } from "./next-button"; + +export function OobeAgreement() { + const navigate = useRouteStore((s) => s.navigate); + const [text, setText] = useState(""); + const [checked, setChecked] = useState(false); + + useEffect(() => { + fetch(`${import.meta.env.BASE_URL}protocol-user.txt`) + .then((r) => r.text()) + .then(setText) + .catch(() => setText("无法加载协议内容")); + }, []); + + return ( + +
+ {/* 标题 */} +
+

Koring Team 产品用户协议

+

您需要同意才可以继续

+
+ + {/* 协议内容 */} +
+
+            {text}
+          
+
+ + {/* 勾选框 */} + +
+ + navigate("oobe/version")} disabled={!checked} /> +
+ ); +} diff --git a/src/pages/oobe/step-beta-test.tsx b/src/pages/oobe/step-beta-test.tsx new file mode 100644 index 0000000..8ae87ce --- /dev/null +++ b/src/pages/oobe/step-beta-test.tsx @@ -0,0 +1,77 @@ +import { useState, useEffect } from "react"; +import { useRouteStore } from "@/stores/routeStore"; +import { OobeLayout } from "./layout"; +import { NextButton } from "./next-button"; +import { Loader2 } from "lucide-react"; + +export function OobeBetaTest() { + const navigate = useRouteStore((s) => s.navigate); + const [loading, setLoading] = useState(true); + const [text, setText] = useState(""); + const [checked, setChecked] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => { + fetch(`${import.meta.env.BASE_URL}protocol-beta.txt`) + .then((r) => r.text()) + .then(setText) + .catch(() => setText("无法加载协议内容")) + .finally(() => setLoading(false)); + }, 1500); + return () => clearTimeout(timer); + }, []); + + if (loading) { + return ( + +
+ + 正在确认版本信息,并激活... +
+
+ ); + } + + return ( + +
+ {/* 标题 */} +
+

Koring APP Beta 测试协议

+

您需要同意才可以继续

+
+ + {/* 协议内容 */} +
+
+            {text}
+          
+
+ + {/* 勾选框 */} + +
+ + navigate("oobe/finish")} disabled={!checked} /> +
+ ); +} diff --git a/src/pages/oobe/step-finish.tsx b/src/pages/oobe/step-finish.tsx new file mode 100644 index 0000000..849556a --- /dev/null +++ b/src/pages/oobe/step-finish.tsx @@ -0,0 +1,31 @@ +import { useRouteStore } from "@/stores/routeStore"; +import { useConfigStore } from "@/stores/configStore"; +import { OobeLayout } from "./layout"; +import { AppleHelloEnglishEffect } from "@/components/ui/apple-hello-effect"; + +export function OobeFinish() { + const navigate = useRouteStore((s) => s.navigate); + const setOobe = useConfigStore((s) => s.setOobe); + + const handleFinish = () => { + setOobe(false); + navigate("home"); + }; + + return ( + +
+ +
+ +
+ +
+
+ ); +} diff --git a/src/pages/oobe/step-language.tsx b/src/pages/oobe/step-language.tsx new file mode 100644 index 0000000..4232ffe --- /dev/null +++ b/src/pages/oobe/step-language.tsx @@ -0,0 +1,51 @@ +import { useState } from "react"; +import { useRouteStore } from "@/stores/routeStore"; +import { OobeLayout } from "./layout"; +import { NextButton } from "./next-button"; +import { Check } from "lucide-react"; + +const languages = [ + { key: "zh-CN", label: "简体中文", available: true }, + { key: "zh-TW", label: "繁体中文", available: false }, + { key: "en", label: "English", available: false }, + { key: "ja", label: "日本語", available: false }, + { key: "ko", label: "한국어", available: false }, + { key: "lzh", label: "文言文(中国)", available: false }, +]; + +export function OobeLanguage() { + const navigate = useRouteStore((s) => s.navigate); + const [selected, setSelected] = useState("zh-CN"); + + return ( + +
+ {languages.map((lang) => ( + + ))} +
+ + navigate("oobe/agreement")} /> +
+ ); +} diff --git a/src/pages/oobe/step-version.tsx b/src/pages/oobe/step-version.tsx new file mode 100644 index 0000000..417e6f0 --- /dev/null +++ b/src/pages/oobe/step-version.tsx @@ -0,0 +1,67 @@ +import { useState, useEffect } from "react"; +import { useRouteStore } from "@/stores/routeStore"; +import { OobeLayout } from "./layout"; +import { NextButton } from "./next-button"; +import { VERSION } from "@/lib/version"; +import { BUILD_MODE } from "@/lib/mode"; + +export function OobeVersion() { + const navigate = useRouteStore((s) => s.navigate); + const [changelog, setChangelog] = useState(""); + + const isTestBuild = BUILD_MODE === "dev" || BUILD_MODE === "beta"; + const badgeLabel = BUILD_MODE === "dev" ? "DEV" : BUILD_MODE === "beta" ? "BETA" : null; + + const nextRoute = isTestBuild ? "oobe/beta-test" : "oobe/finish"; + + useEffect(() => { + fetch(`${import.meta.env.BASE_URL}changelog-${VERSION}.txt`) + .then((r) => r.text()) + .then(setChangelog) + .catch(() => setChangelog("暂无更新内容")); + }, []); + + return ( + +
+ {/* 标题 */} +

核对您的版本信息

+ + {/* 版本号 + Badge */} +
+ + v{VERSION} + + {badgeLabel && ( + + {badgeLabel} + + )} +
+ + {/* 测试版警告 */} + {isTestBuild && ( +

+ 您正在使用测试版本,它并不稳定,不建议用于正式游戏体验,具体内容请以发行版本为准。 +

+ )} + + {/* 更新内容 */} +
+
+            {changelog}
+          
+
+
+ + navigate(nextRoute)} /> +
+ ); +} diff --git a/src/pages/setting/general/about.tsx b/src/pages/setting/general/about.tsx index f3b4bf3..6fa845f 100644 --- a/src/pages/setting/general/about.tsx +++ b/src/pages/setting/general/about.tsx @@ -1,6 +1,7 @@ import { VersionCard } from "@/components/VersionCard"; import { BUILD_MODE } from "@/lib/mode"; -import { ExternalLink, GitFork } from "lucide-react"; +import { ExternalLink, GitFork, RotateCcw } from "lucide-react"; +import { useConfirmDialogStore } from "@/stores/confirmDialogStore"; function GlassCard({ children }: { children: React.ReactNode }) { return
{children}
; @@ -28,10 +29,22 @@ const GITHUB_URL = "https://github.com/koring-launcher/koring-launcher"; const OFFICIAL_URL = "https://koring.app"; export function AboutSetting() { + const openDialog = useConfirmDialogStore((s) => s.openDialog); + const openLink = (url: string) => { window.electronAPI?.openExternal(url); }; + const handleResetClick = () => { + openDialog({ + title: "您确定要还原所有配置吗?", + description: "您还原后,您的实例将会保留,但是所有个性化配置将全部丢失,并且需要重新进行激活", + confirmLabel: "确认还原", + countdown: 5, + onConfirm: () => window.electronAPI?.resetConfig(), + }); + }; + return (

关于

@@ -92,6 +105,22 @@ export function AboutSetting() {
+ + {/* 危险操作 */} +
+

危险操作

+ + + + + +
); diff --git a/src/stores/configStore.ts b/src/stores/configStore.ts index f10ddfb..84ae845 100644 --- a/src/stores/configStore.ts +++ b/src/stores/configStore.ts @@ -14,22 +14,23 @@ import { } from "@/api/config"; import { DEFAULT_BG } from "@/lib/mode"; -// TODO: re-enable when ready -// let saveTimer: ReturnType | null = null; -// function debouncedSave(config: AppConfig) { -// if (saveTimer) clearTimeout(saveTimer); -// saveTimer = setTimeout(() => { -// saveConfig(config).catch((e) => { -// console.error("[config] save failed:", e); -// }); -// }, 300); -// } +let saveTimer: ReturnType | null = null; +function debouncedSave(config: AppConfig) { + if (saveTimer) clearTimeout(saveTimer); + saveTimer = setTimeout(() => { + saveConfig(config).catch((e) => { + console.error("[config] save failed:", e); + }); + }, 300); +} interface ConfigState { config: AppConfig; loaded: boolean; + isFirstLaunch: boolean; init: () => Promise; + applyPreloaded: (config: AppConfig, isFirstLaunch: boolean) => void; setTheme: (patch: Partial) => void; setA11y: (patch: Partial) => void; setBackground: (patch: Partial) => void; @@ -38,10 +39,12 @@ interface ConfigState { setAdvanced: (patch: Partial) => void; setDownload: (patch: Partial) => void; setNetwork: (patch: Partial) => void; + setOobe: (value: boolean) => void; } const DEFAULT_CONFIG: AppConfig = { version: 1, + oobe: true, theme: { darkMode: "auto", parallax: true }, a11y: { reduceMotion: false, reduceTransparency: false, highContrast: false, contentBlurOpacity: 50 }, background: { bgType: "image", image: DEFAULT_BG, blur: 0, opacity: 100 }, @@ -55,56 +58,84 @@ const DEFAULT_CONFIG: AppConfig = { export const useConfigStore = create((set, get) => ({ config: DEFAULT_CONFIG, loaded: false, + isFirstLaunch: false, + + applyPreloaded: (config, isFirstLaunch) => { + set({ config, isFirstLaunch, loaded: true }); + }, init: async () => { - // TODO: re-enable when ready — load from Rust/Koring.yml - // try { - // const config = await getConfig(); - // set({ config, loaded: true }); - // } catch (e) { - // console.error("[config] init failed, using defaults:", e); - // set({ loaded: true }); - // } - set({ loaded: true }); + // If already preloaded, skip IPC call + if (get().loaded) return; + try { + const config = await getConfig(); + set({ config, loaded: true }); + } catch (e) { + console.error("[config] init failed, using defaults:", e); + set({ loaded: true }); + } }, setTheme: (patch) => { const { config } = get(); - set({ config: { ...config, theme: { ...config.theme, ...patch } } }); + const next = { ...config, theme: { ...config.theme, ...patch } }; + set({ config: next }); + debouncedSave(next); }, setA11y: (patch) => { const { config } = get(); - set({ config: { ...config, a11y: { ...config.a11y, ...patch } } }); + const next = { ...config, a11y: { ...config.a11y, ...patch } }; + set({ config: next }); + debouncedSave(next); }, setBackground: (patch) => { const { config } = get(); - set({ config: { ...config, background: { ...config.background, ...patch } } }); + const next = { ...config, background: { ...config.background, ...patch } }; + set({ config: next }); + debouncedSave(next); }, setGame: (patch) => { const { config } = get(); - set({ config: { ...config, game: { ...config.game, ...patch } } }); + const next = { ...config, game: { ...config.game, ...patch } }; + set({ config: next }); + debouncedSave(next); }, setJava: (patch) => { const { config } = get(); - set({ config: { ...config, java: { ...config.java, ...patch } } }); + const next = { ...config, java: { ...config.java, ...patch } }; + set({ config: next }); + debouncedSave(next); }, setAdvanced: (patch) => { const { config } = get(); - set({ config: { ...config, advanced: { ...config.advanced, ...patch } } }); + const next = { ...config, advanced: { ...config.advanced, ...patch } }; + set({ config: next }); + debouncedSave(next); }, setDownload: (patch) => { const { config } = get(); - set({ config: { ...config, download: { ...config.download, ...patch } } }); + const next = { ...config, download: { ...config.download, ...patch } }; + set({ config: next }); + debouncedSave(next); }, setNetwork: (patch) => { const { config } = get(); - set({ config: { ...config, network: { ...config.network, ...patch } } }); + const next = { ...config, network: { ...config.network, ...patch } }; + set({ config: next }); + debouncedSave(next); + }, + + setOobe: (value) => { + const { config } = get(); + const next = { ...config, oobe: value }; + set({ config: next }); + debouncedSave(next); }, })); diff --git a/src/stores/confirmDialogStore.ts b/src/stores/confirmDialogStore.ts new file mode 100644 index 0000000..65a1e7f --- /dev/null +++ b/src/stores/confirmDialogStore.ts @@ -0,0 +1,40 @@ +import { create } from "zustand"; + +interface ConfirmDialogState { + open: boolean; + title: string; + description: string; + confirmLabel: string; + countdown: number; + showCountdown: boolean; + onConfirm: () => void; + openDialog: (opts: { + title: string; + description: string; + confirmLabel?: string; + countdown?: number; + onConfirm: () => void; + }) => void; + closeDialog: () => void; +} + +export const useConfirmDialogStore = create((set) => ({ + open: false, + title: "", + description: "", + confirmLabel: "确认", + countdown: 0, + showCountdown: false, + onConfirm: () => {}, + openDialog: (opts) => + set({ + open: true, + title: opts.title, + description: opts.description, + confirmLabel: opts.confirmLabel ?? "确认", + countdown: opts.countdown ?? 0, + showCountdown: (opts.countdown ?? 0) > 0, + onConfirm: opts.onConfirm, + }), + closeDialog: () => set({ open: false }), +})); diff --git a/src/stores/routeStore.ts b/src/stores/routeStore.ts index eea9d75..ae99d8c 100644 --- a/src/stores/routeStore.ts +++ b/src/stores/routeStore.ts @@ -9,12 +9,18 @@ export type RouteKey = | "gallery" | "task-queue" | "oobe" + | "oobe/language" + | "oobe/agreement" + | "oobe/version" + | "oobe/beta-test" + | "oobe/finish" | "oobe/about-info" | "debug" | "debug-splash" | "debug-display" | "debug-version-card" - | "debug-task"; + | "debug-task" + | "debug-crash"; export type TitleBarMode = "default" | "sub" | "window" | "oobe"; @@ -41,6 +47,11 @@ export const allRoutes: RouteItem[] = [ ...routes, { key: "task-queue", label: "任务队列", path: "/task-queue", hidden: true }, { key: "oobe", label: "OOBE", path: "/oobe", hidden: true }, + { key: "oobe/language", label: "语言设置", path: "/oobe/language", hidden: true }, + { key: "oobe/agreement", label: "同意协议", path: "/oobe/agreement", hidden: true }, + { key: "oobe/version", label: "当前版本", path: "/oobe/version", hidden: true }, + { key: "oobe/beta-test", label: "测试协议", path: "/oobe/beta-test", hidden: true }, + { key: "oobe/finish", label: "完成", path: "/oobe/finish", hidden: true }, { key: "oobe/about-info", label: "关于信息", path: "/oobe/about-info", hidden: true, backable: true }, { key: "debug", label: "调试", path: "/debug", hidden: true }, { key: "debug-splash", label: "启动动画调试", path: "/debug/splash", hidden: true }, @@ -52,7 +63,7 @@ export const allRoutes: RouteItem[] = [ const topLevelKeys = new Set(routes.map((r) => r.key)); function getRouteTitleBarMode(key: RouteKey): TitleBarMode { - if (key === "oobe") return "oobe"; + if (key === "oobe" || key.startsWith("oobe/")) return "oobe"; return topLevelKeys.has(key) ? "default" : "sub"; } diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 890a10f..0b2a868 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -11,7 +11,16 @@ interface ElectronAPI { getTheme: () => Promise<'light' | 'dark' | 'system' | null>; + onConfigPreload: (callback: (data: { config: unknown; isFirstLaunch: boolean }) => void) => () => void; + openExternal: (url: string) => Promise; + + // Crash monitoring + simulateCrash: () => Promise; + testCrashDialog: () => Promise; + + // Config reset + resetConfig: () => Promise; } declare global { diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 9943448..2964b0b 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,11 +1,6 @@ /// -interface ImportMetaEnv { - readonly VITE_START_POP: string; - readonly VITE_START_POP_TITLE: string; - readonly VITE_START_POP_INFO: string; - readonly VITE_START_POP_BOUTTON: string; -} +interface ImportMetaEnv {} interface ImportMeta { readonly env: ImportMetaEnv; diff --git a/vite.config.ts b/vite.config.ts index 7734e71..848daa4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,6 +20,7 @@ export default defineConfig(async () => ({ input: { main: path.resolve(__dirname, "index.html"), splash: path.resolve(__dirname, "splash.html"), + crash: path.resolve(__dirname, "crash.html"), }, }, },