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:
193
app/(app)/pastel/batch/page.tsx
Normal file
193
app/(app)/pastel/batch/page.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { PaletteGrid } from '@/components/pastel/color/PaletteGrid';
|
||||
import { ExportMenu } from '@/components/pastel/tools/ExportMenu';
|
||||
import { useLighten, useDarken, useSaturate, useDesaturate, useRotate } from '@/lib/pastel/api/queries';
|
||||
import { Loader2, Upload, Download } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type Operation = 'lighten' | 'darken' | 'saturate' | 'desaturate' | 'rotate';
|
||||
|
||||
export default function BatchPage() {
|
||||
const [inputColors, setInputColors] = useState('');
|
||||
const [operation, setOperation] = useState<Operation>('lighten');
|
||||
const [amount, setAmount] = useState(0.2);
|
||||
const [outputColors, setOutputColors] = useState<string[]>([]);
|
||||
|
||||
const lightenMutation = useLighten();
|
||||
const darkenMutation = useDarken();
|
||||
const saturateMutation = useSaturate();
|
||||
const desaturateMutation = useDesaturate();
|
||||
const rotateMutation = useRotate();
|
||||
|
||||
const parseColors = (text: string): string[] => {
|
||||
// Parse colors from text (one per line, or comma-separated)
|
||||
return text
|
||||
.split(/[\n,]/)
|
||||
.map((c) => c.trim())
|
||||
.filter((c) => c.length > 0 && c.match(/^#?[0-9a-fA-F]{3,8}$/));
|
||||
};
|
||||
|
||||
const handleProcess = async () => {
|
||||
const colors = parseColors(inputColors);
|
||||
|
||||
if (colors.length === 0) {
|
||||
toast.error('No valid colors found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (colors.length > 100) {
|
||||
toast.error('Maximum 100 colors allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let result;
|
||||
|
||||
switch (operation) {
|
||||
case 'lighten':
|
||||
result = await lightenMutation.mutateAsync({ colors, amount });
|
||||
break;
|
||||
case 'darken':
|
||||
result = await darkenMutation.mutateAsync({ colors, amount });
|
||||
break;
|
||||
case 'saturate':
|
||||
result = await saturateMutation.mutateAsync({ colors, amount });
|
||||
break;
|
||||
case 'desaturate':
|
||||
result = await desaturateMutation.mutateAsync({ colors, amount });
|
||||
break;
|
||||
case 'rotate':
|
||||
result = await rotateMutation.mutateAsync({ colors, amount: amount * 360 });
|
||||
break;
|
||||
}
|
||||
|
||||
// Extract output colors from the result
|
||||
const processed = result.colors.map((c) => c.output);
|
||||
setOutputColors(processed);
|
||||
toast.success(`Processed ${processed.length} colors`);
|
||||
} catch (error) {
|
||||
toast.error('Failed to process colors');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const isPending =
|
||||
lightenMutation.isPending ||
|
||||
darkenMutation.isPending ||
|
||||
saturateMutation.isPending ||
|
||||
desaturateMutation.isPending ||
|
||||
rotateMutation.isPending;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen py-12">
|
||||
<div className="max-w-7xl mx-auto px-8 space-y-8">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold mb-2">Batch Operations</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Process multiple colors at once with manipulation operations
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* Input */}
|
||||
<div className="space-y-6">
|
||||
<div className="p-6 border rounded-lg bg-card">
|
||||
<h2 className="text-xl font-semibold mb-4">Input Colors</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Enter colors (one per line or comma-separated). Supports hex format
|
||||
</p>
|
||||
|
||||
<textarea
|
||||
value={inputColors}
|
||||
onChange={(e) => setInputColors(e.target.value)}
|
||||
placeholder="#ff0099, #00ff99, #9900ff #ff5533 #3355ff"
|
||||
className="w-full h-48 p-3 border rounded-lg bg-background font-mono text-sm"
|
||||
/>
|
||||
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
{parseColors(inputColors).length} valid colors found
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border rounded-lg bg-card">
|
||||
<h2 className="text-xl font-semibold mb-4">Operation</h2>
|
||||
<div className="space-y-4">
|
||||
<Select
|
||||
label="Operation"
|
||||
value={operation}
|
||||
onChange={(e) => setOperation(e.target.value as Operation)}
|
||||
>
|
||||
<option value="lighten">Lighten</option>
|
||||
<option value="darken">Darken</option>
|
||||
<option value="saturate">Saturate</option>
|
||||
<option value="desaturate">Desaturate</option>
|
||||
<option value="rotate">Rotate Hue</option>
|
||||
</Select>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-2 block">
|
||||
Amount: {operation === 'rotate' ? (amount * 360).toFixed(0) + '°' : (amount * 100).toFixed(0) + '%'}
|
||||
</label>
|
||||
<Input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleProcess}
|
||||
disabled={isPending || parseColors(inputColors).length === 0}
|
||||
className="w-full"
|
||||
>
|
||||
{isPending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Processing..
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
Process Colors
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Output */}
|
||||
<div className="space-y-6">
|
||||
{outputColors.length > 0 ? (
|
||||
<>
|
||||
<div className="p-6 border rounded-lg bg-card">
|
||||
<h2 className="text-xl font-semibold mb-4">
|
||||
Output Colors ({outputColors.length})
|
||||
</h2>
|
||||
<PaletteGrid colors={outputColors} />
|
||||
</div>
|
||||
|
||||
<div className="p-6 border rounded-lg bg-card">
|
||||
<ExportMenu colors={outputColors} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="p-12 border rounded-lg bg-card text-center text-muted-foreground">
|
||||
<Download className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>Enter colors and click Process to see results</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user