From 8715eb1f415630a36411b99ce42753ff5f5fe3e5 Mon Sep 17 00:00:00 2001 From: fullex <0xfullex@gmail.com> Date: Sun, 10 Aug 2025 00:19:09 +0800 Subject: [PATCH] feat(migration): add new IPC channels and enhance migration flow This commit introduces new IPC channels for starting the migration flow, restarting the application, and closing the migration window. It also updates the migration logic to improve user interaction and error handling during the migration process. Additionally, the migration interface has been enhanced with a step indicator and localized messages for better user experience. --- packages/shared/IpcChannel.ts | 3 + .../DataRefactorMigrateService.ts | 113 ++++---- .../dataRefactorMigrate/MigrateApp.tsx | 273 +++++++++--------- 3 files changed, 203 insertions(+), 186 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 1a76c16e5c..7494b62fc3 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -192,6 +192,9 @@ export enum IpcChannel { DataMigrate_RequireBackup = 'data-migrate:require-backup', DataMigrate_BackupCompleted = 'data-migrate:backup-completed', DataMigrate_ShowBackupDialog = 'data-migrate:show-backup-dialog', + DataMigrate_StartFlow = 'data-migrate:start-flow', + DataMigrate_RestartApp = 'data-migrate:restart-app', + DataMigrate_CloseWindow = 'data-migrate:close-window', // zip Zip_Compress = 'zip:compress', diff --git a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts index 9b92433d72..0f489b7742 100644 --- a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts +++ b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts @@ -40,7 +40,6 @@ class DataRefactorMigrateService { private migrateWindow: BrowserWindow | null = null private backupManager: BackupManager private backupCompletionResolver: ((value: boolean) => void) | null = null - private backupTimeout: NodeJS.Timeout | null = null private db = dbService.getDb() private currentProgress: MigrationProgress = { stage: 'idle', @@ -127,6 +126,35 @@ class DataRefactorMigrateService { } }) + ipcMain.handle(IpcChannel.DataMigrate_StartFlow, async () => { + try { + return await this.startMigrationFlow() + } catch (error) { + logger.error('IPC handler error: startMigrationFlow', error as Error) + throw error + } + }) + + ipcMain.handle(IpcChannel.DataMigrate_RestartApp, () => { + try { + this.restartApplication() + return true + } catch (error) { + logger.error('IPC handler error: restartApplication', error as Error) + throw error + } + }) + + ipcMain.handle(IpcChannel.DataMigrate_CloseWindow, () => { + try { + this.closeMigrateWindow() + return true + } catch (error) { + logger.error('IPC handler error: closeMigrateWindow', error as Error) + throw error + } + }) + logger.info('Migration IPC handlers registered successfully') } @@ -144,6 +172,9 @@ class DataRefactorMigrateService { ipcMain.removeAllListeners(IpcChannel.DataMigrate_Cancel) ipcMain.removeAllListeners(IpcChannel.DataMigrate_BackupCompleted) ipcMain.removeAllListeners(IpcChannel.DataMigrate_ShowBackupDialog) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_StartFlow) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_RestartApp) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_CloseWindow) logger.info('Migration IPC handlers unregistered successfully') } catch (error) { @@ -294,32 +325,37 @@ class DataRefactorMigrateService { async runMigration(): Promise { if (this.isMigrating) { logger.warn('Migration already in progress') + this.migrateWindow?.show() return } + this.isMigrating = true + logger.info('Showing migration window') + + // Create migration window + const window = this.createMigrateWindow() + + // Wait for window to be ready + await new Promise((resolve) => { + if (window.webContents.isLoading()) { + window.webContents.once('did-finish-load', () => resolve()) + } else { + resolve() + } + }) + } + + async startMigrationFlow(): Promise { + if (!this.isMigrating) { + logger.warn('Migration not started, cannot execute flow.') + return + } + logger.info('Starting migration flow from user action') try { - this.isMigrating = true - logger.info('Starting migration process') - - // Create migration window - const window = this.createMigrateWindow() - - // Wait for window to be ready - await new Promise((resolve) => { - if (window.webContents.isLoading()) { - window.webContents.once('did-finish-load', () => resolve()) - } else { - resolve() - } - }) - - // Start the migration flow await this.executeMigrationFlow() } catch (error) { logger.error('Migration process failed', error as Error) - throw error - } finally { - this.isMigrating = false + // error is already handled in executeMigrationFlow } } @@ -355,33 +391,15 @@ class DataRefactorMigrateService { // Step 3: Mark as completed await this.markMigrationCompleted() - await this.updateProgress('completed', 100, 'Migration completed! App will restart in 3 seconds...') - - // Wait a moment to show success message, then restart the app - await new Promise((resolve) => { - setTimeout(() => { - logger.info('Migration completed successfully, restarting application') - this.restartApplication() - resolve() - }, 3000) - }) + await this.updateProgress('completed', 100, 'Migration completed! Please restart the app.') } catch (error) { logger.error('Migration flow failed', error as Error) await this.updateProgress( 'error', 0, - `Migration failed: ${error instanceof Error ? error.message : String(error)}. Please restart the app to try again.` + `Migration failed: ${error instanceof Error ? error.message : String(error)}. Please close this window and restart the app to try again.` ) - // Wait a moment to show error message, then close migration window - // Do NOT restart on error - let user handle the situation - await new Promise((resolve) => { - setTimeout(() => { - this.closeMigrateWindow() - resolve() - }, 8000) // Show error for longer (8 seconds) to give user time to read - }) - throw error } } @@ -425,14 +443,6 @@ class DataRefactorMigrateService { // Store resolver for later use this.backupCompletionResolver = resolve - // Set up timeout (5 minutes) - this.backupTimeout = setTimeout(() => { - logger.warn('Backup completion timeout') - this.backupCompletionResolver = null - this.backupTimeout = null - resolve(false) - }, 300000) // 5 minutes - // The actual completion will be triggered by notifyBackupCompleted() method }) } @@ -444,12 +454,6 @@ class DataRefactorMigrateService { if (this.backupCompletionResolver) { logger.info('Backup completed by user') - // Clear timeout if it exists - if (this.backupTimeout) { - clearTimeout(this.backupTimeout) - this.backupTimeout = null - } - this.backupCompletionResolver(true) this.backupCompletionResolver = null } @@ -535,6 +539,7 @@ class DataRefactorMigrateService { this.migrateWindow = null } + this.isMigrating = false // Clean up migration-specific IPC handlers this.unregisterMigrationIpcHandlers() } diff --git a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx index c0c81a2e70..27798f7761 100644 --- a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx +++ b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx @@ -1,80 +1,11 @@ -import { CheckCircleOutlined, ExclamationCircleOutlined, LoadingOutlined } from '@ant-design/icons' +import { AppLogo } from '@renderer/config/env' import { IpcChannel } from '@shared/IpcChannel' -import { Alert, Button, Card, Progress, Space, Typography } from 'antd' -import React, { useEffect, useState } from 'react' +import { Alert, Button, Card, Progress, Space, Steps, Typography } from 'antd' +import React, { useEffect, useMemo, useState } from 'react' import styled from 'styled-components' const { Title, Text } = Typography -const Container = styled.div` - width: 100%; - height: 100vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 20px; -` - -const MigrationCard = styled(Card)` - width: 100%; - max-width: 500px; - border-radius: 12px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); - - .ant-card-head { - background: #f8f9fa; - border-bottom: 1px solid #e9ecef; - } - - .ant-card-body { - padding: 32px 24px; - } -` - -const LogoContainer = styled.div` - display: flex; - justify-content: center; - margin-bottom: 24px; - - img { - width: 64px; - height: 64px; - } -` - -const StageIndicator = styled.div<{ stage: string }>` - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 16px; - - .stage-icon { - font-size: 20px; - color: ${(props) => { - switch (props.stage) { - case 'completed': - return '#52c41a' - case 'error': - return '#ff4d4f' - default: - return '#1890ff' - } - }}; - } -` - -const ProgressContainer = styled.div` - margin: 24px 0; -` - -const MessageContainer = styled.div` - text-align: center; - margin: 16px 0; - min-height: 24px; -` - interface MigrationProgress { stage: string progress: number @@ -87,7 +18,7 @@ const MigrateApp: React.FC = () => { stage: 'idle', progress: 0, total: 100, - message: 'Initializing migration...' + message: '准备开始迁移...' }) const [showBackupRequired, setShowBackupRequired] = useState(false) @@ -120,6 +51,43 @@ const MigrateApp: React.FC = () => { } }, []) + const currentStep = useMemo(() => { + switch (progress.stage) { + case 'idle': + return 0 + case 'backup': + return 1 + case 'migration': + return 2 + case 'completed': + return 4 + case 'error': + case 'cancelled': + return 3 + default: + return 0 + } + }, [progress.stage]) + + const stepStatus = useMemo(() => { + if (progress.stage === 'error' || progress.stage === 'cancelled') { + return 'error' + } + return 'process' + }, [progress.stage]) + + const handleStartMigration = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_StartFlow) + } + + const handleRestartApp = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_RestartApp) + } + + const handleCloseWindow = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_CloseWindow) + } + const handleCancel = () => { window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_Cancel) } @@ -135,35 +103,6 @@ const MigrateApp: React.FC = () => { window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_BackupCompleted) } - const getStageTitle = () => { - switch (progress.stage) { - case 'backup': - return 'Creating Backup' - case 'migration': - return 'Migrating Data' - case 'completed': - return 'Migration Completed' - case 'error': - return 'Migration Failed' - case 'cancelled': - return 'Migration Cancelled' - default: - return 'Preparing Migration' - } - } - - const getStageIcon = () => { - switch (progress.stage) { - case 'completed': - return - case 'error': - case 'cancelled': - return - default: - return - } - } - const getProgressColor = () => { switch (progress.stage) { case 'completed': @@ -176,8 +115,37 @@ const MigrateApp: React.FC = () => { } } - const showCancelButton = () => { - return progress.stage !== 'completed' && progress.stage !== 'error' && progress.stage !== 'cancelled' + const renderActionButtons = () => { + switch (progress.stage) { + case 'idle': + return ( + + ) + case 'completed': + return ( + + ) + case 'error': + case 'cancelled': + return ( + + + + ) + case 'backup': + case 'migration': + return ( + + ) + default: + return null + } } return ( @@ -192,28 +160,28 @@ const MigrateApp: React.FC = () => { } bordered={false}> - Cherry Studio + Cherry Studio - - {getStageIcon()} - - {getStageTitle()} - - - - - + - + + + {progress.stage !== 'idle' && ( + + + + )} {progress.message} @@ -259,18 +227,59 @@ const MigrateApp: React.FC = () => { /> )} - {showCancelButton() && ( -
- - - -
- )} +
{renderActionButtons()}
) } +const Container = styled.div` + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 20px; +` + +const MigrationCard = styled(Card)` + width: 100%; + max-width: 500px; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + + .ant-card-head { + background: #f8f9fa; + border-bottom: 1px solid #e9ecef; + } + + .ant-card-body { + padding: 32px 24px; + } +` + +const LogoContainer = styled.div` + display: flex; + justify-content: center; + margin-bottom: 24px; + + img { + width: 64px; + height: 64px; + border-radius: 8px; + } +` + +const ProgressContainer = styled.div` + margin: 24px 0; +` + +const MessageContainer = styled.div` + text-align: center; + margin: 16px 0; + min-height: 24px; +` + export default MigrateApp