feat: Implement comprehensive data restore functionality with progress tracking

- Added RestorePopup component for visualizing restore process
- Enhanced BackupManager with detailed restore progress tracking
- Implemented file copy progress and stage tracking during restore
- Updated localization files with restore progress translations
- Integrated restore progress reporting to renderer process
This commit is contained in:
kangfenmao
2025-03-03 22:32:55 +08:00
parent e69d0c89a6
commit 37cf7427f9
8 changed files with 258 additions and 71 deletions
@@ -0,0 +1,100 @@
import { restore } from '@renderer/services/BackupService'
import { Modal, Progress } from 'antd'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { TopView } from '../TopView'
interface Props {
resolve: (data: any) => void
}
interface ProgressData {
stage: string
progress: number
total: number
}
const PopupContainer: React.FC<Props> = ({ resolve }) => {
const [open, setOpen] = useState(true)
const [progressData, setProgressData] = useState<ProgressData>()
const { t } = useTranslation()
useEffect(() => {
const removeListener = window.electron.ipcRenderer.on('restore-progress', (_, data: ProgressData) => {
setProgressData(data)
})
return () => {
removeListener()
}
}, [])
const onOk = async () => {
await restore()
setOpen(false)
}
const onCancel = () => {
setOpen(false)
}
const onClose = () => {
resolve({})
}
const getProgressText = () => {
if (!progressData) return ''
if (progressData.stage === 'copying_files') {
return t(`restore.progress.${progressData.stage}`, {
progress: Math.floor(progressData.progress)
})
}
return t(`restore.progress.${progressData.stage}`)
}
RestorePopup.hide = onCancel
return (
<Modal
title={t('restore.title')}
open={open}
onOk={onOk}
onCancel={onCancel}
afterClose={onClose}
transitionName="ant-move-down"
okText={t('restore.confirm.button')}
centered>
{!progressData && <div>{t('restore.content')}</div>}
{progressData && (
<div style={{ textAlign: 'center', padding: '20px 0' }}>
<Progress percent={Math.floor(progressData.progress)} strokeColor="var(--color-primary)" />
<div style={{ marginTop: 16 }}>{getProgressText()}</div>
</div>
)}
</Modal>
)
}
const TopViewKey = 'RestorePopup'
export default class RestorePopup {
static topviewId = 0
static hide() {
TopView.hide(TopViewKey)
}
static show() {
return new Promise<any>((resolve) => {
TopView.show(
<PopupContainer
resolve={(v) => {
resolve(v)
TopView.hide(TopViewKey)
}}
/>,
TopViewKey
)
})
}
}
+14
View File
@@ -897,6 +897,20 @@
"compressing": "Compressing files...",
"completed": "Backup completed"
}
},
"restore": {
"title": "Data Restore",
"confirm": "Are you sure you want to restore data?",
"confirm.button": "Select Backup File",
"content": "Restore operation will overwrite all current application data with the backup data. Please note that the restore process may take some time, thank you for your patience.",
"progress": {
"title": "Restore Progress",
"preparing": "Preparing restore...",
"extracting": "Extracting backup...",
"reading_data": "Reading data...",
"copying_files": "Copying files... {{progress}}%",
"completed": "Restore completed"
}
}
}
}
+14
View File
@@ -897,6 +897,20 @@
"compressing": "圧縮中...",
"completed": "バックアップ完了"
}
},
"restore": {
"title": "データ復元",
"confirm": "データを復元しますか?",
"confirm.button": "バックアップファイルを選択",
"content": "復元操作は現在のアプリデータをバックアップデータで上書きします。復元処理には時間がかかる場合があります。",
"progress": {
"title": "復元進捗",
"preparing": "復元準備中...",
"extracting": "バックアップ解凍中...",
"reading_data": "データ読み込み中...",
"copying_files": "ファイルコピー中... {{progress}}%",
"completed": "復元完了"
}
}
}
}
+14
View File
@@ -897,6 +897,20 @@
"compressing": "Сжатие файлов...",
"completed": "Резервная копия создана"
}
},
"restore": {
"title": "Восстановление данных",
"confirm": "Вы уверены, что хотите восстановить данные?",
"confirm.button": "Выбрать файл резервной копии",
"content": "Операция восстановления перезапишет все текущие данные приложения данными из резервной копии. Это может занять некоторое время.",
"progress": {
"title": "Прогресс восстановления",
"preparing": "Подготовка к восстановлению...",
"extracting": "Распаковка резервной копии...",
"reading_data": "Чтение данных...",
"copying_files": "Копирование файлов... {{progress}}%",
"completed": "Восстановление завершено"
}
}
}
}
+14
View File
@@ -897,6 +897,20 @@
"compressing": "压缩文件...",
"completed": "备份完成"
}
},
"restore": {
"title": "数据恢复",
"confirm": "确定要恢复数据吗?",
"confirm.button": "选择备份文件",
"content": "恢复操作将使用备份数据覆盖当前所有应用数据。请注意,恢复过程可能需要一些时间,感谢您的耐心等待。",
"progress": {
"title": "恢复进度",
"preparing": "准备恢复...",
"extracting": "解压备份...",
"reading_data": "读取数据...",
"copying_files": "复制文件... {{progress}}%",
"completed": "恢复完成"
}
}
}
}
+14
View File
@@ -897,6 +897,20 @@
"compressing": "壓縮文件...",
"completed": "備份完成"
}
},
"restore": {
"title": "資料復原",
"confirm": "確定要復原資料嗎?",
"confirm.button": "選擇備份檔案",
"content": "復原操作將使用備份資料覆蓋當前所有應用資料。請注意,復原過程可能需要一些時間,感謝您的耐心等待。",
"progress": {
"title": "復原進度",
"preparing": "準備復原...",
"extracting": "解壓備份...",
"reading_data": "讀取資料...",
"copying_files": "複製文件... {{progress}}%",
"completed": "復原完成"
}
}
}
}
@@ -3,8 +3,9 @@ import { Client } from '@notionhq/client'
import { HStack } from '@renderer/components/Layout'
import MinApp from '@renderer/components/MinApp'
import BackupPopup from '@renderer/components/Popups/BackupPopup'
import RestorePopup from '@renderer/components/Popups/RestorePopup'
import { useTheme } from '@renderer/context/ThemeProvider'
import { reset, restore } from '@renderer/services/BackupService'
import { reset } from '@renderer/services/BackupService'
import { RootState, useAppDispatch } from '@renderer/store'
import {
setNotionApiKey,
@@ -333,8 +334,6 @@ const DataSettings: FC = () => {
})
}
const handleBackup = () => BackupPopup.show()
return (
<SettingContainer theme={theme}>
<SettingGroup theme={theme}>
@@ -343,10 +342,10 @@ const DataSettings: FC = () => {
<SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
<Button onClick={handleBackup} icon={<SaveOutlined />}>
<Button onClick={BackupPopup.show} icon={<SaveOutlined />}>
{t('settings.general.backup.button')}
</Button>
<Button onClick={restore} icon={<FolderOpenOutlined />}>
<Button onClick={RestorePopup.show} icon={<FolderOpenOutlined />}>
{t('settings.general.restore.button')}
</Button>
</HStack>