Initial commit: TypoGenie - Markdown to Word document converter
This commit is contained in:
165
App.tsx
Normal file
165
App.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AppState, PaperSize } from './types';
|
||||
import { FileUpload } from './components/FileUpload';
|
||||
import { StyleSelector } from './components/StyleSelector';
|
||||
import { Preview } from './components/Preview';
|
||||
// @ts-ignore
|
||||
import { parse } from 'marked';
|
||||
import { Sparkles, Loader2, FileType } from 'lucide-react';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [appState, setAppState] = useState<AppState>(AppState.UPLOAD);
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [selectedStyle, setSelectedStyle] = useState<string | null>(null);
|
||||
const [paperSize, setPaperSize] = useState<PaperSize>('Letter');
|
||||
const [generatedHtml, setGeneratedHtml] = useState<string>('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleFileLoaded = (text: string) => {
|
||||
setContent(text);
|
||||
setAppState(AppState.CONFIG);
|
||||
};
|
||||
|
||||
const handleGenerate = async () => {
|
||||
if (!selectedStyle || !content) return;
|
||||
|
||||
setAppState(AppState.GENERATING);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Small artificial delay to show the "Processing" state for better UX,
|
||||
// otherwise it flickers too fast since local parsing is instant.
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
// Parse markdown to HTML using the local 'marked' library
|
||||
const html = await parse(content);
|
||||
|
||||
if (!html) throw new Error("No content generated");
|
||||
|
||||
setGeneratedHtml(html);
|
||||
setAppState(AppState.PREVIEW);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Failed to process the document. Please check your file format and try again.");
|
||||
setAppState(AppState.CONFIG);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setAppState(AppState.UPLOAD);
|
||||
setContent('');
|
||||
setGeneratedHtml('');
|
||||
setSelectedStyle(null);
|
||||
};
|
||||
|
||||
const handleBackToConfig = () => {
|
||||
setAppState(AppState.CONFIG);
|
||||
};
|
||||
|
||||
// Render Logic
|
||||
if (appState === AppState.PREVIEW) {
|
||||
// Pass selectedStyleId to Preview so it can lookup font config
|
||||
// We add the prop via spread or explicit
|
||||
return (
|
||||
// @ts-ignore - Adding prop dynamically if interface not fully updated in previous file change block (it was)
|
||||
<Preview
|
||||
htmlContent={generatedHtml}
|
||||
onBack={handleBackToConfig}
|
||||
paperSize={paperSize}
|
||||
// @ts-ignore
|
||||
selectedStyleId={selectedStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-950 text-zinc-100 flex flex-col font-sans selection:bg-indigo-500/30">
|
||||
|
||||
{/* Header */}
|
||||
<header className="border-b border-zinc-800 bg-zinc-950/50 backdrop-blur-sm sticky top-0 z-40">
|
||||
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 cursor-pointer" onClick={handleReset}>
|
||||
<div className="bg-gradient-to-br from-indigo-500 to-violet-600 p-2 rounded-lg">
|
||||
<FileType className="text-white" size={20} />
|
||||
</div>
|
||||
<h1 className="text-xl font-bold tracking-tight text-white">TypoGenie</h1>
|
||||
</div>
|
||||
{appState !== AppState.UPLOAD && (
|
||||
<div className="flex items-center gap-4 text-sm text-zinc-500">
|
||||
<span className={appState === AppState.CONFIG ? "text-indigo-400 font-medium" : ""}>Configure</span>
|
||||
<span>/</span>
|
||||
<span className={appState === AppState.GENERATING ? "text-indigo-400 font-medium" : ""}>Generate</span>
|
||||
<span>/</span>
|
||||
<span>Preview</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-grow flex flex-col relative">
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-[20%] -left-[10%] w-[50%] h-[50%] bg-indigo-900/10 rounded-full blur-3xl"></div>
|
||||
<div className="absolute top-[20%] -right-[10%] w-[40%] h-[40%] bg-violet-900/10 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 w-full max-w-7xl mx-auto px-6 py-12 flex-grow flex flex-col">
|
||||
|
||||
{appState === AppState.UPLOAD && (
|
||||
<div className="flex flex-col items-center justify-center flex-grow space-y-8 animate-in fade-in zoom-in-95 duration-500">
|
||||
<div className="text-center space-y-4 max-w-2xl">
|
||||
<h2 className="text-4xl md:text-5xl font-extrabold tracking-tight text-white">
|
||||
Turn Markdown into <br/>
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-violet-400">
|
||||
Professional Word Docs.
|
||||
</span>
|
||||
</h2>
|
||||
<p className="text-lg text-zinc-400 leading-relaxed">
|
||||
Upload your raw text. Select a style.
|
||||
Get a formatted DOCX file ready for Microsoft Word.
|
||||
</p>
|
||||
</div>
|
||||
<FileUpload onFileLoaded={handleFileLoaded} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{appState === AppState.CONFIG && (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<StyleSelector
|
||||
selectedStyle={selectedStyle}
|
||||
onSelectStyle={setSelectedStyle}
|
||||
selectedPaperSize={paperSize}
|
||||
onSelectPaperSize={setPaperSize}
|
||||
onGenerate={handleGenerate}
|
||||
/>
|
||||
{error && (
|
||||
<div className="mt-6 p-4 bg-red-900/20 border border-red-800 rounded-xl text-center text-red-300">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{appState === AppState.GENERATING && (
|
||||
<div className="flex flex-col items-center justify-center flex-grow text-center animate-in fade-in duration-700">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-indigo-500 blur-xl opacity-20 animate-pulse"></div>
|
||||
<Loader2 size={64} className="text-indigo-400 animate-spin relative z-10" />
|
||||
</div>
|
||||
<h3 className="mt-8 text-2xl font-bold text-white">Formatting Document</h3>
|
||||
<p className="mt-2 text-zinc-400 max-w-md">
|
||||
Applying typographic rules and preparing print-ready layout...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="py-6 text-center text-zinc-600 text-sm border-t border-zinc-900 print:hidden">
|
||||
<p>TypoGenie - Professional Typesetting Engine</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user