feat: implement Figlet, Pastel, and Unit tools with a unified layout
- Add Figlet text converter with font selection and history - Add Pastel color palette generator and manipulation suite - Add comprehensive Units converter with category-based logic - Introduce AppShell with Sidebar and Header for navigation - Modernize theme system with CSS variables and new animations - Update project configuration and dependencies
This commit is contained in:
38
lib/figlet/constants/templates.ts
Normal file
38
lib/figlet/constants/templates.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export interface TextTemplate {
|
||||
id: string;
|
||||
label: string;
|
||||
text: string;
|
||||
category: 'greeting' | 'tech' | 'fun' | 'seasonal';
|
||||
}
|
||||
|
||||
export const TEXT_TEMPLATES: TextTemplate[] = [
|
||||
// Greetings
|
||||
{ id: 'hello', label: 'Hello', text: 'Hello!', category: 'greeting' },
|
||||
{ id: 'welcome', label: 'Welcome', text: 'Welcome', category: 'greeting' },
|
||||
{ id: 'hello-world', label: 'Hello World', text: 'Hello World', category: 'greeting' },
|
||||
|
||||
// Tech
|
||||
{ id: 'code', label: 'Code', text: 'CODE', category: 'tech' },
|
||||
{ id: 'dev', label: 'Developer', text: 'DEV', category: 'tech' },
|
||||
{ id: 'hack', label: 'Hack', text: 'HACK', category: 'tech' },
|
||||
{ id: 'terminal', label: 'Terminal', text: 'Terminal', category: 'tech' },
|
||||
{ id: 'git', label: 'Git', text: 'Git', category: 'tech' },
|
||||
|
||||
// Fun
|
||||
{ id: 'awesome', label: 'Awesome', text: 'AWESOME', category: 'fun' },
|
||||
{ id: 'cool', label: 'Cool', text: 'COOL', category: 'fun' },
|
||||
{ id: 'epic', label: 'Epic', text: 'EPIC', category: 'fun' },
|
||||
{ id: 'wow', label: 'Wow', text: 'WOW!', category: 'fun' },
|
||||
|
||||
// Seasonal
|
||||
{ id: 'happy-birthday', label: 'Happy Birthday', text: 'Happy Birthday!', category: 'seasonal' },
|
||||
{ id: 'congrats', label: 'Congrats', text: 'Congrats!', category: 'seasonal' },
|
||||
{ id: 'thanks', label: 'Thanks', text: 'Thanks!', category: 'seasonal' },
|
||||
];
|
||||
|
||||
export const TEMPLATE_CATEGORIES = [
|
||||
{ id: 'greeting', label: 'Greetings', icon: '👋' },
|
||||
{ id: 'tech', label: 'Tech', icon: '💻' },
|
||||
{ id: 'fun', label: 'Fun', icon: '🎉' },
|
||||
{ id: 'seasonal', label: 'Seasonal', icon: '🎊' },
|
||||
] as const;
|
||||
80
lib/figlet/figletService.ts
Normal file
80
lib/figlet/figletService.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import figlet from 'figlet';
|
||||
import type { FigletOptions } from '@/types/figlet';
|
||||
import { loadFont } from './fontLoader';
|
||||
|
||||
/**
|
||||
* Convert text to ASCII art using figlet
|
||||
*/
|
||||
export async function textToAscii(
|
||||
text: string,
|
||||
fontName: string = 'Standard',
|
||||
options: FigletOptions = {}
|
||||
): Promise<string> {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the font
|
||||
const fontData = await loadFont(fontName);
|
||||
|
||||
if (!fontData) {
|
||||
throw new Error(`Font ${fontName} could not be loaded`);
|
||||
}
|
||||
|
||||
// Parse and load the font into figlet
|
||||
figlet.parseFont(fontName, fontData);
|
||||
|
||||
// Generate ASCII art
|
||||
return new Promise((resolve, reject) => {
|
||||
figlet.text(
|
||||
text,
|
||||
{
|
||||
font: fontName,
|
||||
horizontalLayout: options.horizontalLayout || 'default',
|
||||
verticalLayout: options.verticalLayout || 'default',
|
||||
width: options.width,
|
||||
whitespaceBreak: options.whitespaceBreak ?? true,
|
||||
},
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result || '');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error generating ASCII art:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ASCII art synchronously (requires font to be pre-loaded)
|
||||
*/
|
||||
export function textToAsciiSync(
|
||||
text: string,
|
||||
fontName: string = 'Standard',
|
||||
options: FigletOptions = {}
|
||||
): string {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return figlet.textSync(text, {
|
||||
font: fontName as any,
|
||||
horizontalLayout: options.horizontalLayout || 'default',
|
||||
verticalLayout: options.verticalLayout || 'default',
|
||||
width: options.width,
|
||||
whitespaceBreak: options.whitespaceBreak ?? true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error generating ASCII art (sync):', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
61
lib/figlet/fontLoader.ts
Normal file
61
lib/figlet/fontLoader.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { FigletFont } from '@/types/figlet';
|
||||
|
||||
// Cache for loaded fonts
|
||||
const fontCache = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* Get list of all available figlet fonts
|
||||
*/
|
||||
export async function getFontList(): Promise<FigletFont[]> {
|
||||
try {
|
||||
const response = await fetch('/api/fonts');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch font list');
|
||||
}
|
||||
const fonts: FigletFont[] = await response.json();
|
||||
return fonts;
|
||||
} catch (error) {
|
||||
console.error('Error fetching font list:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a specific font file content
|
||||
*/
|
||||
export async function loadFont(fontName: string): Promise<string | null> {
|
||||
// Check cache first
|
||||
if (fontCache.has(fontName)) {
|
||||
return fontCache.get(fontName)!;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/fonts/figlet-fonts/${fontName}.flf`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load font: ${fontName}`);
|
||||
}
|
||||
const fontData = await response.text();
|
||||
|
||||
// Cache the font
|
||||
fontCache.set(fontName, fontData);
|
||||
|
||||
return fontData;
|
||||
} catch (error) {
|
||||
console.error(`Error loading font ${fontName}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a font into cache
|
||||
*/
|
||||
export async function preloadFont(fontName: string): Promise<void> {
|
||||
await loadFont(fontName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear font cache
|
||||
*/
|
||||
export function clearFontCache(): void {
|
||||
fontCache.clear();
|
||||
}
|
||||
34
lib/figlet/hooks/useKeyboardShortcuts.ts
Normal file
34
lib/figlet/hooks/useKeyboardShortcuts.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export interface KeyboardShortcut {
|
||||
key: string;
|
||||
ctrlKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
shiftKey?: boolean;
|
||||
handler: () => void;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
for (const shortcut of shortcuts) {
|
||||
const keyMatches = event.key.toLowerCase() === shortcut.key.toLowerCase();
|
||||
const ctrlMatches = shortcut.ctrlKey ? event.ctrlKey || event.metaKey : !event.ctrlKey && !event.metaKey;
|
||||
const metaMatches = shortcut.metaKey ? event.metaKey : true;
|
||||
const shiftMatches = shortcut.shiftKey ? event.shiftKey : !event.shiftKey;
|
||||
|
||||
if (keyMatches && ctrlMatches && shiftMatches) {
|
||||
event.preventDefault();
|
||||
shortcut.handler();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [shortcuts]);
|
||||
}
|
||||
Reference in New Issue
Block a user