diff --git a/components/ConfigurationModal.tsx b/components/ConfigurationModal.tsx index d3cf836..0a97568 100644 --- a/components/ConfigurationModal.tsx +++ b/components/ConfigurationModal.tsx @@ -7,6 +7,26 @@ import Dropdown from './Dropdown'; import { baseWallpapers } from './utils/baseWallpapers'; import { addWallpaperToChromeStorageLocal, removeWallpaperFromChromeStorageLocal, checkChromeStorageLocalAvailable } from './utils/StorageLocalManager'; +const REQUIRED_LOCAL_STORAGE_KEYS = ['config', 'categories', 'userWallpapers', 'wallpaperState'] as const; + +type RequiredLocalStorageKey = typeof REQUIRED_LOCAL_STORAGE_KEYS[number]; + +const safeParse = (value: string | null): unknown => { + if (value === null) { + return null; + } + + try { + return JSON.parse(value); + } catch { + return value; + } +}; + +const toStorageString = (value: unknown): string => { + return typeof value === 'string' ? value : JSON.stringify(value); +}; + interface ConfigurationModalProps { onClose: () => void; onSave: (config: any) => void; @@ -51,6 +71,7 @@ const ConfigurationModal: React.FC = ({ onClose, onSave const [chromeStorageAvailable, setChromeStorageAvailable] = useState(false); const menuRef = useRef(null); const fileInputRef = useRef(null); + const importInputRef = useRef(null); const isSaving = useRef(false); const [isVisible, setIsVisible] = useState(false); @@ -236,6 +257,86 @@ const ConfigurationModal: React.FC = ({ onClose, onSave } }; + const handleExportConfig = () => { + const exportPayload = { + version: 1, + exportedAt: new Date().toISOString(), + requiredLocalStorageKeys: [...REQUIRED_LOCAL_STORAGE_KEYS], + localStorage: REQUIRED_LOCAL_STORAGE_KEYS.reduce((acc, key) => { + acc[key] = safeParse(localStorage.getItem(key)); + return acc; + }, {} as Record), + }; + + const blob = new Blob([JSON.stringify(exportPayload, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `vision-start-config-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }; + + const handleImportClick = () => { + importInputRef.current?.click(); + }; + + const handleImportConfig = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) { + return; + } + + try { + const fileContent = await file.text(); + const parsed = JSON.parse(fileContent); + const localStorageData = parsed?.localStorage && typeof parsed.localStorage === 'object' + ? parsed.localStorage + : parsed; + + if (!localStorageData || typeof localStorageData !== 'object') { + throw new Error('Invalid import file format.'); + } + + let importedAny = false; + + REQUIRED_LOCAL_STORAGE_KEYS.forEach((key) => { + if (Object.prototype.hasOwnProperty.call(localStorageData, key)) { + const rawValue = (localStorageData as Record)[key]; + localStorage.setItem(key, toStorageString(rawValue)); + importedAny = true; + } + }); + + if (!importedAny) { + throw new Error(`No required keys found. Expected: ${REQUIRED_LOCAL_STORAGE_KEYS.join(', ')}`); + } + + const importedConfig = (localStorageData as Record).config; + const importedUserWallpapers = (localStorageData as Record).userWallpapers; + + if (importedConfig && typeof importedConfig === 'object') { + setConfig(importedConfig as typeof config); + onWallpaperChange({ currentWallpapers: (importedConfig as { currentWallpapers?: string[] }).currentWallpapers || [] }); + onSave(importedConfig); + } + + if (Array.isArray(importedUserWallpapers)) { + setUserWallpapers(importedUserWallpapers as Wallpaper[]); + } + + alert('Configuration imported successfully. The page will reload to apply all data.'); + window.location.reload(); + } catch (error) { + alert('Could not import configuration. Please use a valid export JSON file.'); + console.error(error); + } finally { + event.target.value = ''; + } + }; + const allWallpapers = [...baseWallpapers, ...userWallpapers]; return ( @@ -641,13 +742,30 @@ const ConfigurationModal: React.FC = ({ onClose, onSave )}
-
+
+
+ + + +
+
+
@@ -655,4 +773,4 @@ const ConfigurationModal: React.FC = ({ onClose, onSave ); }; -export default ConfigurationModal; \ No newline at end of file +export default ConfigurationModal;