feat: Transform to Tauri desktop app

- Initialize Tauri v2 project with Rust backend
- Restructure project: move source files to src/ directory
- Add Tauri configuration for Windows, macOS, and Linux builds
- Update Vite config for Tauri development workflow
- Add file system and dialog permissions for native features
- Update package.json with desktop build scripts
- Update tsconfig.json paths for new src structure
- Add Tauri and desktop badges to README
- Document desktop build process and architecture
This commit is contained in:
TypoGenie
2026-01-29 18:28:35 +02:00
parent ae5ce9d243
commit 2c22b0fce6
51 changed files with 7556 additions and 58 deletions

165
src/App.tsx Normal file
View 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;