Files
paint-ui/components/modals/new-image-dialog.tsx
Sebastian Krüger b93ae377d0 feat(phase-6): implement comprehensive file operations system
This commit completes Phase 6 of the paint-ui implementation, adding full
file import/export capabilities with drag-drop and clipboard support.

**New Files:**
- lib/file-utils.ts: Core file operations (open, save, export, project format)
- hooks/use-file-operations.ts: React hook for file operations
- hooks/use-drag-drop.ts: Drag & drop state management
- hooks/use-clipboard.ts: Clipboard paste event handling
- components/editor/file-menu.tsx: File menu dropdown component
- components/modals/export-dialog.tsx: Export dialog with format/quality options
- components/modals/new-image-dialog.tsx: New image dialog with presets
- components/modals/index.ts: Modals barrel export

**Updated Files:**
- components/editor/editor-layout.tsx: Integrated FileMenu, drag-drop overlay, clipboard paste
- components/editor/index.ts: Added file-menu export

**Features:**
-  Create new images with dimension presets (Full HD, HD, 800x600, custom)
-  Open image files (PNG, JPG, WEBP) as new layers
-  Save/load .paint project files (JSON with base64 layer data)
-  Export as PNG/JPEG/WEBP with quality control
-  Drag & drop file upload with visual overlay
-  Clipboard paste support (Ctrl+V)
-  File type validation and error handling
-  DataTransfer API integration for unified file handling

**Project File Format (.paint):**
- JSON structure with version, dimensions, layer metadata
- Base64-encoded PNG data for each layer
- Preserves layer properties (opacity, blend mode, order, visibility)

Build verified: ✓ Compiled successfully in 1233ms

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 02:06:49 +01:00

144 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState } from 'react';
import { X, Image } from 'lucide-react';
interface NewImageDialogProps {
isOpen: boolean;
onClose: () => void;
onCreate: (width: number, height: number, backgroundColor: string) => void;
}
const PRESETS = [
{ name: 'Custom', width: 800, height: 600 },
{ name: '1920×1080 (Full HD)', width: 1920, height: 1080 },
{ name: '1280×720 (HD)', width: 1280, height: 720 },
{ name: '800×600', width: 800, height: 600 },
{ name: '640×480', width: 640, height: 480 },
];
export function NewImageDialog({ isOpen, onClose, onCreate }: NewImageDialogProps) {
const [width, setWidth] = useState(800);
const [height, setHeight] = useState(600);
const [backgroundColor, setBackgroundColor] = useState('#ffffff');
if (!isOpen) return null;
const handleCreate = () => {
onCreate(width, height, backgroundColor);
onClose();
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onClick={onClose}>
<div
className="bg-card border border-border rounded-lg shadow-lg w-full max-w-md"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border">
<h2 className="text-lg font-semibold text-card-foreground">New Image</h2>
<button
onClick={onClose}
className="p-1 hover:bg-accent rounded transition-colors"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Content */}
<div className="p-4 space-y-4">
{/* Presets */}
<div className="space-y-2">
<label className="text-sm font-medium text-card-foreground">
Presets
</label>
<select
className="w-full px-3 py-2 rounded-md border border-border bg-background text-foreground"
onChange={(e) => {
const preset = PRESETS[Number(e.target.value)];
setWidth(preset.width);
setHeight(preset.height);
}}
>
{PRESETS.map((preset, index) => (
<option key={index} value={index}>
{preset.name}
</option>
))}
</select>
</div>
{/* Dimensions */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-medium text-card-foreground">
Width
</label>
<input
type="number"
min="1"
max="4000"
value={width}
onChange={(e) => setWidth(Number(e.target.value))}
className="w-full px-3 py-2 rounded-md border border-border bg-background text-foreground"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-card-foreground">
Height
</label>
<input
type="number"
min="1"
max="4000"
value={height}
onChange={(e) => setHeight(Number(e.target.value))}
className="w-full px-3 py-2 rounded-md border border-border bg-background text-foreground"
/>
</div>
</div>
{/* Background Color */}
<div className="space-y-2">
<label className="text-sm font-medium text-card-foreground">
Background Color
</label>
<div className="flex gap-2">
<input
type="color"
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="w-20 h-10 rounded-md border border-border cursor-pointer"
/>
<input
type="text"
value={backgroundColor}
onChange={(e) => setBackgroundColor(e.target.value)}
className="flex-1 px-3 py-2 rounded-md border border-border bg-background text-foreground font-mono uppercase"
/>
</div>
</div>
</div>
{/* Footer */}
<div className="flex justify-end gap-2 p-4 border-t border-border">
<button
onClick={onClose}
className="px-4 py-2 rounded-md hover:bg-accent transition-colors"
>
Cancel
</button>
<button
onClick={handleCreate}
className="flex items-center gap-2 px-4 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
>
<Image className="h-4 w-4" />
Create
</button>
</div>
</div>
</div>
);
}