From 39257f64b1456ac01ee496625bf508d21855da28 Mon Sep 17 00:00:00 2001 From: fullex <0xfullex@gmail.com> Date: Sun, 10 Aug 2025 13:51:08 +0800 Subject: [PATCH] feat(migration): enhance migration UI and refactor layout This commit updates the migration window dimensions for improved usability, sets explicit entry points for preload scripts, and enhances the overall layout of the migration interface. It introduces new styles for buttons and alerts, improves the structure of the migration steps, and refines the user experience with clearer feedback and options during the migration process. --- electron.vite.config.ts | 13 +- .../DataRefactorMigrateService.ts | 17 +- src/preload/simplest.ts | 17 + src/renderer/dataRefactorMigrate.html | 48 +- .../dataRefactorMigrate/MigrateApp.tsx | 421 ++++++++++++------ .../dataRefactorMigrate/entryPoint.tsx | 7 +- 6 files changed, 377 insertions(+), 146 deletions(-) create mode 100644 src/preload/simplest.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index ff9a0c9e92..35cf21a6f0 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -54,7 +54,18 @@ export default defineConfig({ } }, build: { - sourcemap: isDev + sourcemap: isDev, + rollupOptions: { + // Unlike renderer which auto-discovers entries from HTML files, + // preload requires explicit entry point configuration for multiple scripts + input: { + index: resolve(__dirname, 'src/preload/index.ts'), + simplest: resolve(__dirname, 'src/preload/simplest.ts') // Minimal preload + }, + output: { + entryFileNames: '[name].js' + } + } } }, renderer: { diff --git a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts index dfc7aa432c..16695c89f2 100644 --- a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts +++ b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts @@ -375,16 +375,16 @@ class DataRefactorMigrateService { this.registerMigrationIpcHandlers() this.migrateWindow = new BrowserWindow({ - width: 800, - height: 650, - resizable: true, - maximizable: true, - minimizable: true, + width: 640, + height: 480, + resizable: false, + maximizable: false, + minimizable: false, show: false, + frame: false, autoHideMenuBar: true, - titleBarStyle: 'default', webPreferences: { - preload: join(__dirname, '../preload/index.js'), + preload: join(__dirname, '../preload/simplest.js'), sandbox: false, webSecurity: false, contextIsolation: true @@ -400,9 +400,6 @@ class DataRefactorMigrateService { this.migrateWindow.once('ready-to-show', () => { this.migrateWindow?.show() - if (!app.isPackaged) { - this.migrateWindow?.webContents.openDevTools() - } }) this.migrateWindow.on('closed', () => { diff --git a/src/preload/simplest.ts b/src/preload/simplest.ts new file mode 100644 index 0000000000..785bdbca8f --- /dev/null +++ b/src/preload/simplest.ts @@ -0,0 +1,17 @@ +import { electronAPI } from '@electron-toolkit/preload' +import { contextBridge } from 'electron' + +// Use `contextBridge` APIs to expose Electron APIs to +// renderer only if context isolation is enabled, otherwise +// just add to the DOM global. +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld('electron', electronAPI) + } catch (error) { + // eslint-disable-next-line no-restricted-syntax + console.error('[Preload]Failed to expose APIs:', error as Error) + } +} else { + // @ts-ignore (define in dts) + window.electron = electronAPI +} diff --git a/src/renderer/dataRefactorMigrate.html b/src/renderer/dataRefactorMigrate.html index 4a23d3d7c7..93e2bfbec1 100644 --- a/src/renderer/dataRefactorMigrate.html +++ b/src/renderer/dataRefactorMigrate.html @@ -9,8 +9,54 @@ - + + \ No newline at end of file diff --git a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx index 1b9bec4973..3e93807d30 100644 --- a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx +++ b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx @@ -1,11 +1,10 @@ -import { CheckCircleOutlined, CloudUploadOutlined, LoadingOutlined, RocketOutlined } from '@ant-design/icons' +import { AppLogo } from '@renderer/config/env' import { IpcChannel } from '@shared/IpcChannel' -import { Alert, Button, Card, Progress, Space, Steps, Typography } from 'antd' +import { Button, Progress, Space, Steps } from 'antd' +import { AlertTriangle, CheckCircle, Database, Loader2, Rocket } from 'lucide-react' import React, { useEffect, useMemo, useState } from 'react' import styled from 'styled-components' -const { Title, Text } = Typography - type MigrationStage = | 'introduction' // Introduction phase - user can cancel | 'backup_required' // Backup required - show backup requirement @@ -116,33 +115,37 @@ const MigrateApp: React.FC = () => { const getProgressColor = () => { switch (progress.stage) { case 'completed': - return '#52c41a' + return 'var(--color-primary)' case 'error': return '#ff4d4f' case 'backup_confirmed': - return '#52c41a' + return 'var(--color-primary)' default: - return '#1890ff' + return 'var(--color-primary)' } } const getCurrentStepIcon = () => { switch (progress.stage) { case 'introduction': - return + return case 'backup_required': case 'backup_progress': - return + return case 'backup_confirmed': - return + return case 'migration': - return + return ( + + + + ) case 'completed': - return + return case 'error': - return
⚠️
+ return default: - return + return } } @@ -150,48 +153,62 @@ const MigrateApp: React.FC = () => { switch (progress.stage) { case 'introduction': return ( - + <> + - + ) case 'backup_required': return ( - + <> + + - - + ) case 'backup_confirmed': return ( - + - - + + + + ) case 'migration': - return + return ( + +
+ +
+ ) case 'completed': return ( - + +
+ +
) case 'error': return ( - + - - + + + + ) default: return null @@ -200,94 +217,109 @@ const MigrateApp: React.FC = () => { return ( - - - 数据迁移向导 - - - } - bordered={false}> -
- -
+ {/* Header */} +
+ -
{getCurrentStepIcon()}
+ 数据迁移向导 +
- {progress.stage !== 'introduction' && progress.stage !== 'error' && ( - - + {/* Left Sidebar with Steps */} + + + - - )} + + - - {progress.message} - + {/* Right Content Area */} + + + {getCurrentStepIcon()} - {progress.stage === 'introduction' && ( - - )} + {progress.stage === 'introduction' && ( + + 将数据迁移到新的架构中 + + Cherry Studio对数据的存储和使用方式进行了重大重构,在新的架构下,效率和安全性将会得到极大提升。 +
+
+ 数据必须进行迁移,才能在新版本中使用。 +
+
+ 我们会指导你完成迁移,迁移过程不会损坏原来的数据,你随时可以取消迁移,并继续使用旧版本。 +
+
+ )} - {progress.stage === 'backup_required' && ( - - )} + {progress.stage === 'backup_required' && ( + + 创建数据备份 + + 迁移前必须创建数据备份以确保数据安全。请选择备份位置或确认已有最新备份。 + + + )} - {progress.stage === 'backup_confirmed' && ( - - )} + {progress.stage === 'backup_progress' && ( + + 准备数据备份 + 请选择备份位置,保存后等待备份完成。 + + )} - {progress.stage === 'error' && ( - - )} + {progress.stage === 'backup_confirmed' && ( + + 备份完成 + + 数据备份已完成,现在可以安全地开始迁移。 + + + )} - {progress.stage === 'completed' && ( - - )} + {progress.stage === 'error' && ( + + 迁移失败 + + {progress.error || '迁移过程遇到错误,您可以重新尝试或继续使用之前版本(原始数据完好保存)。'} + + + )} -
{renderActionButtons()}
-
+ {progress.stage === 'completed' && ( + + 迁移完成 + 数据已成功迁移,重启应用后即可正常使用。 + + )} + + {progress.stage !== 'introduction' && + progress.stage !== 'error' && + progress.stage !== 'backup_required' && + progress.stage !== 'backup_confirmed' && ( + + + + )} + + + + + {/* Footer */} +
{renderActionButtons()}
) } @@ -297,36 +329,163 @@ const Container = styled.div` height: 100vh; display: flex; flex-direction: column; - justify-content: center; - align-items: center; - background: #f5f5f5; - padding: 20px; + background: #fff; ` -const MigrationCard = styled(Card)` +const Header = styled.div` + height: 48px; + background: rgb(240, 240, 240); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + -webkit-app-region: drag; + user-select: none; +` + +const HeaderTitle = styled.div` + font-size: 16px; + font-weight: 600; + color: black; + margin-left: 12px; +` + +const HeaderLogo = styled.img` + width: 24px; + height: 24px; + border-radius: 6px; +` + +const MainContent = styled.div` + flex: 1; + display: flex; + overflow: hidden; +` + +const LeftSidebar = styled.div` + width: 150px; + background: #fff; + border-right: 1px solid #f0f0f0; + display: flex; + flex-direction: column; +` + +const StepsContainer = styled.div` + padding: 32px 24px; + flex: 1; + + .ant-steps-item-process .ant-steps-item-icon { + background-color: var(--color-primary); + border-color: var(--color-primary-soft); + } + + .ant-steps-item-finish .ant-steps-item-icon { + background-color: var(--color-primary-mute); + border-color: var(--color-primary-mute); + } + + .ant-steps-item-finish .ant-steps-item-icon > .ant-steps-icon { + color: var(--color-primary); + } + + .ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon { + color: #fff; + } + + .ant-steps-item-wait .ant-steps-item-icon { + border-color: #d9d9d9; + } +` + +const RightContent = styled.div` + flex: 1; + display: flex; + flex-direction: column; +` + +const ContentArea = styled.div` + flex: 1; + display: flex; + flex-direction: column; + /* justify-content: center; */ + /* align-items: center; */ + /* margin: 0 auto; */ width: 100%; - max-width: 600px; - border-radius: 12px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + padding: 24px; +` - .ant-card-head { - background: #fff; - border-bottom: 1px solid #f0f0f0; - } +const Footer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; - .ant-card-body { - padding: 24px 32px 32px 32px; - } + background: rgb(250, 250, 250); + + height: 64px; + + padding: 0 24px; + gap: 16px; +` + +const Spacer = styled.div` + flex: 1; ` const ProgressContainer = styled.div` - margin: 24px 0; + margin: 32px 0; + width: 100%; ` -const MessageContainer = styled.div` +const ButtonRow = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + min-width: 300px; +` + +const InfoIcon = styled.div` + display: flex; + justify-content: center; + align-items: center; + margin-top: 12px; +` + +const InfoCard = styled.div<{ variant?: 'info' | 'warning' | 'success' | 'error' }>` + width: 100%; +` + +const InfoTitle = styled.div` + margin-bottom: 32px; + margin-top: 32px; + font-size: 16px; + font-weight: 600; + color: var(--color-primary); + line-height: 1.4; text-align: center; - margin: 16px 0; - min-height: 24px; +` + +const InfoDescription = styled.p` + margin: 0; + color: rgba(0, 0, 0, 0.68); + line-height: 1.8; + max-width: 420px; + margin: 0 auto; +` + +const SpinningIcon = styled.div` + display: inline-block; + animation: spin 2s linear infinite; + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } ` export default MigrateApp diff --git a/src/renderer/src/windows/dataRefactorMigrate/entryPoint.tsx b/src/renderer/src/windows/dataRefactorMigrate/entryPoint.tsx index 67ce227a54..c5cd7a55ea 100644 --- a/src/renderer/src/windows/dataRefactorMigrate/entryPoint.tsx +++ b/src/renderer/src/windows/dataRefactorMigrate/entryPoint.tsx @@ -1,9 +1,10 @@ -import '../../assets/styles/index.scss' +import '@ant-design/v5-patch-for-react-19' +import '@renderer/assets/styles/index.scss' -import ReactDOM from 'react-dom/client' +import { createRoot } from 'react-dom/client' import MigrateApp from './MigrateApp' -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) +const root = createRoot(document.getElementById('root') as HTMLElement) root.render()