import React, { useState, useEffect, useCallback } from 'react'; import { motion, AnimatePresence, useReducedMotion } from 'motion/react'; import { AppState, PaperSize } from './types'; import { FileUpload } from './components/FileUpload'; import { StyleSelector } from './components/StyleSelector'; import { Preview } from './components/Preview'; import { ZoomControl } from './components/ZoomControl'; import { useSettings } from './hooks/useSettings'; import { useTemplates } from './hooks/useTemplates'; import { useDialog } from './hooks/useDialog'; // @ts-ignore import { parse } from 'marked'; import { Sparkles, Loader2, FileType, Keyboard, X, RefreshCw } from 'lucide-react'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; // Keyboard shortcuts help component const KeyboardShortcutsHelp: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => { const { dialogRef, handleBackdropClick, close } = useDialog(isOpen, { onClose }); const shortcuts = [ { key: '↑ / ↓', description: 'Navigate styles' }, { key: '← / →', description: 'Navigate categories (when focused)' }, { key: 'Enter', description: 'Select style or category' }, { key: 'Home / End', description: 'First/last item' }, { key: 'PgUp / PgDn', description: 'Jump 5 items' }, { key: 'Tab', description: 'Switch between sections' }, { key: 'Ctrl + Enter', description: 'Generate document' }, { key: 'Escape', description: 'Go back / Close' }, ]; if (!isOpen) return null; return ( e.stopPropagation()} >

Keyboard Shortcuts

{shortcuts.map((shortcut) => (
{shortcut.description}
{shortcut.key}
))}

Press Escape or click outside to close

); }; const App: React.FC = () => { const prefersReducedMotion = useReducedMotion(); const [appState, setAppState] = useState(AppState.UPLOAD); const [content, setContent] = useState(''); const [inputFileName, setInputFileName] = useState(''); const [selectedStyle, setSelectedStyle] = useState(null); const [paperSize, setPaperSize] = useState('Letter'); const [generatedHtml, setGeneratedHtml] = useState(''); const [error, setError] = useState(null); const [showShortcuts, setShowShortcuts] = useState(false); const [statusMessage, setStatusMessage] = useState(''); const { uiZoom, setUiZoom, isLoaded } = useSettings(); const { templates, categories, isLoading: templatesLoading, error: templatesError, refresh, openFolder } = useTemplates(); // Global keyboard shortcut: Toggle help with ? or / useKeyboardNavigation({ onEnter: undefined, }, []); // Note: Native file drop is handled by the FileUpload component // The Tauri file drop is disabled to allow the webview to handle drag events // This preserves the drag hover animations in the FileUpload component // Announce state changes to screen readers useEffect(() => { const messages: Record = { [AppState.UPLOAD]: 'Upload screen', [AppState.CONFIG]: 'Style configuration', [AppState.GENERATING]: 'Generating document', [AppState.PREVIEW]: 'Document preview', }; setStatusMessage(messages[appState] || ''); }, [appState]); // Global keydown listener for shortcuts help useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { const tag = (e.target as HTMLElement)?.tagName; const isEditable = tag === 'INPUT' || tag === 'TEXTAREA' || (e.target as HTMLElement)?.isContentEditable; if (isEditable) return; if (e.key === '?' || e.key === '/') { e.preventDefault(); setShowShortcuts(prev => !prev); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, []); const handleFileLoaded = (text: string, fileName: string = '') => { setContent(text); setInputFileName(fileName); setAppState(AppState.CONFIG); }; const handleGenerate = useCallback(async () => { if (!selectedStyle || !content) return; setAppState(AppState.GENERATING); setError(null); try { await new Promise(resolve => setTimeout(resolve, 800)); const html = await parse(content); if (!html) throw new Error("No content generated"); console.log('--- STAGE 1: MARKDOWN GENERATION ---'); console.log('First 500 chars of HTML:', html.substring(0, 500)); console.log('Contains h1?', html.includes(' { setAppState(AppState.UPLOAD); setContent(''); setGeneratedHtml(''); setSelectedStyle(null); setInputFileName(''); }; const handleBackToConfig = () => { setAppState(AppState.CONFIG); }; if (!isLoaded) { return (
Loading TypoGenie
); } if (appState === AppState.PREVIEW) { return (
{statusMessage}
); } return (
{statusMessage}
{/* Keyboard Shortcuts Modal */} setShowShortcuts(false)} /> {/* Header - Fixed height */}

TypoGenie

{/* UI Zoom Control */} {/* Refresh templates button */} {appState === AppState.CONFIG && ( )} {/* Keyboard shortcuts hint */} setShowShortcuts(true)} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} className="hidden sm:flex items-center gap-2 px-3 py-1.5 text-xs text-zinc-400 hover:text-zinc-300 bg-zinc-900 hover:bg-zinc-800 rounded-lg transition-colors border border-zinc-800" title="Show keyboard shortcuts" > setShowShortcuts(true)} role="button" aria-label="Show keyboard shortcuts">? {appState !== AppState.UPLOAD && (
  1. Configure
  2. Generate
  3. Preview
)}
{/* Main Content - Takes remaining space */}
{/* Animated background blobs */}
{appState === AppState.UPLOAD && ( Turn Markdown into
Professional Word Docs.
Upload your raw text. Select a style. Get a formatted DOCX file ready for Microsoft Word.
)} {appState === AppState.CONFIG && ( {error && ( {error} )} )} {appState === AppState.GENERATING && ( Formatting Document Applying typographic rules and preparing print-ready layout... )}
{/* Footer - Fixed height, always visible */}

TypoGenie - Professional Typesetting Engine

); }; export default App;