diff --git a/packages/extension/src/components/option/ImportExport.tsx b/packages/extension/src/components/option/ImportExport.tsx index 3b9b218a..1eb478a9 100644 --- a/packages/extension/src/components/option/ImportExport.tsx +++ b/packages/extension/src/components/option/ImportExport.tsx @@ -1,360 +1,65 @@ -import { useState, useRef, useEffect } from "react" +import { useState } from "react" import { Dialog } from "./Dialog" -import type { UserSettings, Caches, ShortcutSettings } from "@/types" - -import { Storage, STORAGE_KEY, LOCAL_STORAGE_KEY } from "@/services/storage" -import { - DailyBackupManager, - WeeklyBackupManager, - LegacyBackupManager, -} from "@/services/storage/backupManager" -import { Settings, migrate } from "@/services/settings/settings" -import { enhancedSettings } from "@/services/settings/enhancedSettings" -import { CACHE_SECTIONS } from "@/services/settings/settingsCache" -import { isBase64, isUrl } from "@/lib/utils" -import { APP_ID } from "@/const" import { t } from "@/services/i18n" import { Download, Upload, Undo2, RotateCcw } from "lucide-react" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" -import type { BackupData } from "@/services/storage/backupManager" import { TEST_IDS } from "@/testIds" +import { useImportExport } from "@/hooks/option/useImportExport" +import { + BACKUP_TYPES, + isCheckingBackups, + hasAvailableBackup, + getAvailableBackups, +} from "@/services/settings/importExport" +import type { BackupType } from "@/services/settings/importExport" import css from "./Option.module.css" -// Backup type constants -const BACKUP_TYPES = { - LEGACY: "legacy", - DAILY: "daily", - WEEKLY: "weekly", -} as const - -// Backup status constants -const BACKUP_STATUS = { - CHECKING: "checking", - AVAILABLE: "available", - NONE: "none", -} as const - -type BackupType = (typeof BACKUP_TYPES)[keyof typeof BACKUP_TYPES] -type BackupStatus = (typeof BACKUP_STATUS)[keyof typeof BACKUP_STATUS] - -function getTimestamp() { - const date = new Date() - const year = date.getFullYear() - const month = (date.getMonth() + 1).toString().padStart(2, "0") - const day = date.getDate().toString().padStart(2, "0") - const hours = date.getHours().toString().padStart(2, "0") - const minutes = date.getMinutes().toString().padStart(2, "0") - return `${year}${month}${day}_${hours}${minutes}` +const getBackupTypeLabel = (type: BackupType) => { + switch (type) { + case BACKUP_TYPES.LEGACY: + return t("Option_RestoreFromBackup_legacy") + case BACKUP_TYPES.DAILY: + return t("Option_RestoreFromBackup_daily") + case BACKUP_TYPES.WEEKLY: + return t("Option_RestoreFromBackup_weekly") + } } export function ImportExport() { const [resetDialog, setResetDialog] = useState(false) const [importDialog, setImportDialog] = useState(false) const [restoreDialog, setRestoreDialog] = useState(false) - const [importJson, setImportJson] = useState() - const [backupData, setBackupData] = useState<{ - [BACKUP_TYPES.LEGACY]: { - status: BackupStatus - info: { - timestamp: number - commandCount: number - folderCount?: number - } | null - } - [BACKUP_TYPES.DAILY]: { - status: BackupStatus - info: { - timestamp: number - commandCount: number - folderCount?: number - } | null - } - [BACKUP_TYPES.WEEKLY]: { - status: BackupStatus - info: { - timestamp: number - commandCount: number - folderCount?: number - } | null - } - }>({ - [BACKUP_TYPES.LEGACY]: { status: BACKUP_STATUS.CHECKING, info: null }, - [BACKUP_TYPES.DAILY]: { status: BACKUP_STATUS.CHECKING, info: null }, - [BACKUP_TYPES.WEEKLY]: { status: BACKUP_STATUS.CHECKING, info: null }, - }) - const [selectedBackupType, setSelectedBackupType] = useState( - BACKUP_TYPES.LEGACY, - ) - const inputFile = useRef(null) - - // Check backup status on initialization - useEffect(() => { - checkBackupStatus() - }, []) - - const checkBackupStatus = async () => { - try { - const newBackupData = { ...backupData } - - // 1. Check legacy backup - const legacyBackup = await Storage.get( - LOCAL_STORAGE_KEY.COMMANDS_BACKUP, - ) - - if ( - legacyBackup && - legacyBackup.commands && - Array.isArray(legacyBackup.commands) - ) { - newBackupData[BACKUP_TYPES.LEGACY] = { - status: BACKUP_STATUS.AVAILABLE, - info: { - timestamp: legacyBackup.timestamp, - commandCount: legacyBackup.commands.length, - folderCount: Array.isArray(legacyBackup.folders) - ? legacyBackup.folders.length - : 0, - }, - } - } else { - newBackupData[BACKUP_TYPES.LEGACY] = { - status: BACKUP_STATUS.NONE, - info: null, - } - } - - // 2. Check daily backup - const dailyBackupManager = new DailyBackupManager() - const dailyBackup = await dailyBackupManager.getLastBackupData() - - if ( - dailyBackup && - dailyBackup.commands && - Array.isArray(dailyBackup.commands) - ) { - newBackupData[BACKUP_TYPES.DAILY] = { - status: BACKUP_STATUS.AVAILABLE, - info: { - timestamp: dailyBackup.timestamp, - commandCount: dailyBackup.commands.length, - folderCount: Array.isArray(dailyBackup.folders) - ? dailyBackup.folders.length - : 0, - }, - } - } else { - newBackupData[BACKUP_TYPES.DAILY] = { - status: BACKUP_STATUS.NONE, - info: null, - } - } - - // 3. Check weekly backup - const weeklyBackupManager = new WeeklyBackupManager() - const weeklyBackup = await weeklyBackupManager.getLastBackupData() - - if ( - weeklyBackup && - weeklyBackup.commands && - Array.isArray(weeklyBackup.commands) - ) { - newBackupData[BACKUP_TYPES.WEEKLY] = { - status: BACKUP_STATUS.AVAILABLE, - info: { - timestamp: weeklyBackup.timestamp, - commandCount: weeklyBackup.commands.length, - folderCount: Array.isArray(weeklyBackup.folders) - ? weeklyBackup.folders.length - : 0, - }, - } - } else { - newBackupData[BACKUP_TYPES.WEEKLY] = { - status: BACKUP_STATUS.NONE, - info: null, - } - } - - setBackupData(newBackupData) - - // Set default selection to first available backup - if ( - newBackupData[BACKUP_TYPES.LEGACY].status === BACKUP_STATUS.AVAILABLE - ) { - setSelectedBackupType(BACKUP_TYPES.LEGACY) - } else if ( - newBackupData[BACKUP_TYPES.DAILY].status === BACKUP_STATUS.AVAILABLE - ) { - setSelectedBackupType(BACKUP_TYPES.DAILY) - } else if ( - newBackupData[BACKUP_TYPES.WEEKLY].status === BACKUP_STATUS.AVAILABLE - ) { - setSelectedBackupType(BACKUP_TYPES.WEEKLY) - } - } catch (error) { - console.error("Failed to check backup status:", error) - setBackupData({ - [BACKUP_TYPES.LEGACY]: { status: BACKUP_STATUS.NONE, info: null }, - [BACKUP_TYPES.DAILY]: { status: BACKUP_STATUS.NONE, info: null }, - [BACKUP_TYPES.WEEKLY]: { status: BACKUP_STATUS.NONE, info: null }, - }) - } - } - - const handleReset = () => { - setResetDialog(true) - } + const { + importJson, + backupStatus, + selectedBackupType, + setSelectedBackupType, + reset, + exportSettings, + selectImportFile, + runImport, + restore, + } = useImportExport() + const availableBackups = getAvailableBackups(backupStatus) const handleResetClose = (ret: boolean) => { - if (ret) { - Settings.reset().then(() => location.reload()) - } + if (ret) reset() setResetDialog(false) } - const handleExport = async () => { - const data = await Storage.get(STORAGE_KEY.USER) - data.commands = await Storage.getCommands() - data.shortcuts = await Storage.get(STORAGE_KEY.SHORTCUTS) - - // for back compatibility - // cache key to image data url - const caches = (await enhancedSettings.getSection( - CACHE_SECTIONS.CACHES, - true, - )) as Caches - for (const c of data.commands) { - if (!c.iconUrl) continue - if (isBase64(c.iconUrl) || isUrl(c.iconUrl)) continue - if (caches?.images?.[c.iconUrl]) { - c.iconUrl = caches.images[c.iconUrl] - } - } - - const text = JSON.stringify(data, null, 2) - const blob = new Blob([text], { type: "text/plain" }) - const url = URL.createObjectURL(blob) - const a = document.createElement("a") - document.body.appendChild(a) - a.download = `${APP_ID}_${getTimestamp()}.json` - a.href = url - a.click() - a.remove() - URL.revokeObjectURL(url) - } - - const handleImport = () => { - if (inputFile == null || inputFile.current == null) return - const files = inputFile.current.files - if (files == null) return - for (let i = 0; i < files.length; i++) { - const file = files[i] - const reader = new FileReader() - reader.onload = (e) => { - if (e.target != null) { - const text = e.target.result as string - const json = JSON.parse(text) - setImportJson(json) - } - } - reader.readAsText(file) - } + const handleImportFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (file) selectImportFile(file) } const handleImportClose = (ret: boolean) => { - if (ret && importJson != null) { - ;(async () => { - const { - commandExecutionCount = 0, - hasShownReviewRequest = false, - hasDismissedPromptHistoryBanner = false, - hasShownHubShareToast = false, - hasShownOnboarding = false, - } = await enhancedSettings.get() - const data = await migrate({ - ...importJson, - commandExecutionCount, - hasShownReviewRequest, - hasDismissedPromptHistoryBanner, - hasShownHubShareToast, - hasShownOnboarding, - stars: [], - }) - await Settings.set(data) - location.reload() - })() - } + if (ret) runImport() setImportDialog(false) } - const handleRestore = async () => { - setRestoreDialog(true) - } - const handleRestoreClose = (ret: boolean) => { - if (ret) { - ;(async () => { - try { - let backupCommands: any[] = [] - - if (selectedBackupType === BACKUP_TYPES.LEGACY) { - // Restore from legacy backup - const manager = new LegacyBackupManager() - const data = await manager.restoreFromBackup() - backupCommands = data.commands - - if (data.folders && data.folders.length > 0) { - // Restore folders to settings - const currentSettings = await enhancedSettings.get() - await Settings.set({ - ...currentSettings, - folders: data.folders, - }) - } - } else if (selectedBackupType === BACKUP_TYPES.DAILY) { - // Restore from daily backup - const dailyBackupManager = new DailyBackupManager() - const dailyData = await dailyBackupManager.restoreFromBackup() - backupCommands = dailyData.commands - - if (dailyData.folders && dailyData.folders.length > 0) { - // Restore folders to settings - const currentSettings = await enhancedSettings.get() - await Settings.set({ - ...currentSettings, - folders: dailyData.folders, - }) - } - } else if (selectedBackupType === BACKUP_TYPES.WEEKLY) { - // Restore from weekly backup - const weeklyBackupManager = new WeeklyBackupManager() - const weeklyData = await weeklyBackupManager.restoreFromBackup() - backupCommands = weeklyData.commands - - if (weeklyData.folders && weeklyData.folders.length > 0) { - // Restore folders to settings - const currentSettings = await enhancedSettings.get() - await Settings.set({ - ...currentSettings, - folders: weeklyData.folders, - }) - } - } - - if (backupCommands.length > 0) { - // Save restored commands - await Storage.setCommands(backupCommands) - location.reload() - } else { - alert(t("Option_RestoreFromBackup_failed")) - } - } catch (error) { - console.error("Failed to restore from backup:", error) - alert("Failed to restore from backup.") - } - })() - } + if (ret) restore() setRestoreDialog(false) } @@ -376,7 +81,7 @@ export function ImportExport() { {t("Option_Import")}