diff --git a/app/api/fonts/route.ts b/app/api/fonts/route.ts new file mode 100644 index 0000000..1eb0061 --- /dev/null +++ b/app/api/fonts/route.ts @@ -0,0 +1,29 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +export const dynamic = 'force-static'; + +export async function GET() { + try { + const fontsDir = path.join(process.cwd(), 'public/fonts/figlet-fonts'); + const files = fs.readdirSync(fontsDir); + + const fonts = files + .filter(file => file.endsWith('.flf')) + .map(file => { + const name = file.replace('.flf', ''); + return { + name, + fileName: file, + path: `/fonts/figlet-fonts/${file}`, + }; + }) + .sort((a, b) => a.name.localeCompare(b.name)); + + return NextResponse.json(fonts); + } catch (error) { + console.error('Error reading fonts directory:', error); + return NextResponse.json({ error: 'Failed to load fonts' }, { status: 500 }); + } +} diff --git a/app/page.tsx b/app/page.tsx index 958be58..5042a2a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,27 +1,41 @@ +import { FigletConverter } from '@/components/converter/FigletConverter'; + export default function Home() { return ( -
+
-

Figlet UI

-

- ASCII Art Text Generator with 700+ Fonts +

Figlet UI

+

+ ASCII Art Text Generator with 373 Fonts

-
-
-{`  _____ _       _      _     _   _ ___
- |  ___(_) __ _| | ___| |_  | | | |_ _|
- | |_  | |/ _\` | |/ _ \\ __| | | | || |
- |  _| | | (_| | |  __/ |_  | |_| || |
- |_|   |_|\\__, |_|\\___|\\__|  \\___/|___|
-          |___/                         `}
-          
-

- Coming soon: A modern interface for generating beautiful ASCII art text. + + +

+
); diff --git a/components/converter/FigletConverter.tsx b/components/converter/FigletConverter.tsx new file mode 100644 index 0000000..2f2f7a6 --- /dev/null +++ b/components/converter/FigletConverter.tsx @@ -0,0 +1,109 @@ +'use client'; + +import * as React from 'react'; +import { TextInput } from './TextInput'; +import { FontPreview } from './FontPreview'; +import { FontSelector } from './FontSelector'; +import { textToAscii } from '@/lib/figlet/figletService'; +import { getFontList } from '@/lib/figlet/fontLoader'; +import { debounce } from '@/lib/utils/debounce'; +import type { FigletFont } from '@/types/figlet'; + +export function FigletConverter() { + const [text, setText] = React.useState('Figlet UI'); + const [selectedFont, setSelectedFont] = React.useState('Standard'); + const [asciiArt, setAsciiArt] = React.useState(''); + const [fonts, setFonts] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(false); + const [isCopied, setIsCopied] = React.useState(false); + + // Load fonts on mount + React.useEffect(() => { + getFontList().then(setFonts); + }, []); + + // Generate ASCII art + const generateAsciiArt = React.useCallback( + debounce(async (inputText: string, fontName: string) => { + if (!inputText) { + setAsciiArt(''); + setIsLoading(false); + return; + } + + setIsLoading(true); + try { + const result = await textToAscii(inputText, fontName); + setAsciiArt(result); + } catch (error) { + console.error('Error generating ASCII art:', error); + setAsciiArt('Error generating ASCII art. Please try a different font.'); + } finally { + setIsLoading(false); + } + }, 300), + [] + ); + + // Trigger generation when text or font changes + React.useEffect(() => { + generateAsciiArt(text, selectedFont); + }, [text, selectedFont, generateAsciiArt]); + + // Copy to clipboard + const handleCopy = async () => { + if (!asciiArt) return; + + try { + await navigator.clipboard.writeText(asciiArt); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch (error) { + console.error('Failed to copy:', error); + } + }; + + // Download as text file + const handleDownload = () => { + if (!asciiArt) return; + + const blob = new Blob([asciiArt], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `figlet-${selectedFont}-${Date.now()}.txt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + return ( +
+ {/* Left Column - Input and Preview */} +
+ + + +
+ + {/* Right Column - Font Selector */} +
+ +
+
+ ); +} diff --git a/components/converter/FontPreview.tsx b/components/converter/FontPreview.tsx new file mode 100644 index 0000000..b5261c4 --- /dev/null +++ b/components/converter/FontPreview.tsx @@ -0,0 +1,57 @@ +'use client'; + +import * as React from 'react'; +import { Card } from '@/components/ui/Card'; +import { Button } from '@/components/ui/Button'; +import { Copy, Download } from 'lucide-react'; +import { cn } from '@/lib/utils/cn'; + +export interface FontPreviewProps { + text: string; + isLoading?: boolean; + onCopy?: () => void; + onDownload?: () => void; + className?: string; +} + +export function FontPreview({ text, isLoading, onCopy, onDownload, className }: FontPreviewProps) { + return ( + +
+
+

Preview

+
+ {onCopy && ( + + )} + {onDownload && ( + + )} +
+
+ +
+ {isLoading ? ( +
+
Generating...
+
+ ) : text ? ( +
+              {text}
+            
+ ) : ( +
+
Your ASCII art will appear here
+
+ )} +
+
+
+ ); +} diff --git a/components/converter/FontSelector.tsx b/components/converter/FontSelector.tsx new file mode 100644 index 0000000..6313d7c --- /dev/null +++ b/components/converter/FontSelector.tsx @@ -0,0 +1,73 @@ +'use client'; + +import * as React from 'react'; +import { Input } from '@/components/ui/Input'; +import { Card } from '@/components/ui/Card'; +import { Search } from 'lucide-react'; +import { cn } from '@/lib/utils/cn'; +import type { FigletFont } from '@/types/figlet'; + +export interface FontSelectorProps { + fonts: FigletFont[]; + selectedFont: string; + onSelectFont: (fontName: string) => void; + className?: string; +} + +export function FontSelector({ fonts, selectedFont, onSelectFont, className }: FontSelectorProps) { + const [searchQuery, setSearchQuery] = React.useState(''); + + const filteredFonts = React.useMemo(() => { + if (!searchQuery) return fonts; + + const query = searchQuery.toLowerCase(); + return fonts.filter(font => + font.name.toLowerCase().includes(query) + ); + }, [fonts, searchQuery]); + + return ( + +
+

Select Font

+ +
+ + setSearchQuery(e.target.value)} + className="pl-9" + /> +
+ +
+ {filteredFonts.length === 0 ? ( +
+ No fonts found +
+ ) : ( + filteredFonts.map((font) => ( + + )) + )} +
+ +
+ {filteredFonts.length} font{filteredFonts.length !== 1 ? 's' : ''} available +
+
+
+ ); +} diff --git a/components/converter/TextInput.tsx b/components/converter/TextInput.tsx new file mode 100644 index 0000000..a8b6a26 --- /dev/null +++ b/components/converter/TextInput.tsx @@ -0,0 +1,28 @@ +'use client'; + +import * as React from 'react'; +import { cn } from '@/lib/utils/cn'; + +export interface TextInputProps { + value: string; + onChange: (value: string) => void; + placeholder?: string; + className?: string; +} + +export function TextInput({ value, onChange, placeholder, className }: TextInputProps) { + return ( +
+