import { useState, useEffect, useRef } from 'react'; import { getSettings, saveSettings, saveBackgroundImage, SettingsType, HighlighterRule, exportFullWorkspace, importWorkspace, resetBackgroundImage } from '../../lib/db'; import ReactCrop, { Crop } from 'react-image-crop'; import 'react-image-crop/dist/ReactCrop.css'; import { Settings01Icon, Add01Icon, Delete01Icon, Download01Icon, Upload01Icon, Copy01Icon } from 'hugeicons-react'; function extractTextFromLexical(jsonString?: string) { if (!jsonString) return ''; try { const root = JSON.parse(jsonString).root; let text = ''; function traverse(node: any) { if (node.type === 'text') text += node.text; if (node.type === 'linebreak') text += '\n'; if (node.children) { node.children.forEach(traverse); if (node.type === 'paragraph') text += '\n'; // Add newline for paragraphs } } traverse(root); return text.trim(); } catch { return ''; } } export default function SettingsModal({ onClose }: { onClose: () => void }) { const [settings, setSettings] = useState(null); const [imgSrc, setImgSrc] = useState(''); const [crop, setCrop] = useState(); const [importText, setImportText] = useState(''); const [showImport, setShowImport] = useState(false); const imgRef = useRef(null); useEffect(() => { getSettings().then(setSettings); }, []); const handleChangeCryptography = async (font: any) => { if (!settings) return; const newSettings = { ...settings, typography: font }; setSettings(newSettings); await saveSettings(newSettings); window.location.reload(); }; const handleChangeOpacity = async (field: 'bgOpacity' | 'fgOpacity', val: number) => { if (!settings) return; const newSettings = { ...settings, [field]: val }; setSettings(newSettings); await saveSettings(newSettings); document.documentElement.style.setProperty(`--${field === 'bgOpacity' ? 'bg' : 'fg'}-opacity`, val.toString()); }; const handleImageUpload = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const reader = new FileReader(); reader.addEventListener('load', () => setImgSrc(reader.result?.toString() || '')); reader.readAsDataURL(e.target.files[0]); } }; const completeImageSave = async () => { if (imgSrc && imgRef.current && crop) { const canvas = document.createElement('canvas'); const scaleX = imgRef.current.naturalWidth / imgRef.current.width; const scaleY = imgRef.current.naturalHeight / imgRef.current.height; canvas.width = crop.width; canvas.height = crop.height; const ctx = canvas.getContext('2d'); if (ctx) { ctx.drawImage( imgRef.current, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY, 0, 0, crop.width, crop.height ); const base64Image = canvas.toDataURL('image/jpeg'); await saveBackgroundImage(base64Image); window.location.reload(); } } else if (imgSrc) { await saveBackgroundImage(imgSrc); window.location.reload(); } }; const handleResetBackground = async () => { await resetBackgroundImage(); window.location.reload(); }; const handleUpdateRule = async (id: string, updates: Partial) => { if (!settings) return; const newRules = settings.highlighters.map(r => r.id === id ? { ...r, ...updates } : r); const newSettings = { ...settings, highlighters: newRules }; setSettings(newSettings); await saveSettings(newSettings); }; const handleAddRule = async () => { if (!settings) return; const newRule: HighlighterRule = { id: Math.random().toString(36).substring(2, 9), openSymbol: '(', closeSymbol: ')', color: '#ffb3ba', baseOpacity: 0.1, stackMode: 'larger-lighter' }; const newSettings = { ...settings, highlighters: [...settings.highlighters, newRule] }; setSettings(newSettings); await saveSettings(newSettings); }; const handleDeleteRule = async (id: string) => { if (!settings) return; const newSettings = { ...settings, highlighters: settings.highlighters.filter(r => r.id !== id) }; setSettings(newSettings); await saveSettings(newSettings); }; const handleExport = async () => { const data = await exportFullWorkspace(); const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `workspace-backup-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); }; const handleCopyCleanWorkspace = async () => { const data = await exportFullWorkspace(); let textOut = '--- WORKSPACE EXPORT ---\n\n'; data.files.forEach(f => { if (f.type === 'file') { textOut += `[FILE: ${f.name}]\n`; textOut += extractTextFromLexical(f.content); textOut += '\n\n'; } }); await navigator.clipboard.writeText(textOut); alert('Clean textual workspace copied to clipboard!'); }; const handleImport = async () => { try { const data = JSON.parse(importText); if (data.files && data.settings) { await importWorkspace(data); alert('Import successful! Reloading...'); window.location.reload(); } else { alert('Invalid workspace JSON format.'); } } catch (e) { alert('Failed to parse JSON.'); } }; if (!settings) return null; return (
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)', backdropFilter: 'blur(12px)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px' }}>

System Settings

{/* Persistence Section */}

Data Management

{showImport && (