Phase 5: Photo Pre-Editor (Filerobot)

- Added react-filerobot-image-editor dependency
- PhotoPreEditor component with full editing capabilities
- Crop, filters, adjustments, annotations, watermark tabs
- Opens after image upload, before adding to canvas
- Exports edited image as PNG for canvas use

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Khalid A
2026-04-21 01:20:11 -05:00
parent 4a735e2f2e
commit 7bf9ce3a9c
5 changed files with 112 additions and 5 deletions

View File

@@ -15,7 +15,8 @@
"react-konva": "^18.2.10",
"konva": "^9.3.18",
"use-image": "^1.1.1",
"@xenova/transformers": "^2.17.2"
"@xenova/transformers": "^2.17.2",
"react-filerobot-image-editor": "^4.8.1"
},
"devDependencies": {
"@eslint/js": "^9.39.4",

View File

@@ -0,0 +1,58 @@
import { useState } from 'react';
import FilerobotImageEditor from 'react-filerobot-image-editor';
export function PhotoPreEditor({ imageSrc, onComplete, onClose }) {
const [saving, setSaving] = useState(false);
const handleComplete = (editedImageObject, designState) => {
setSaving(true);
// Export the edited image
editedImageObject.exportAsync({
quality: 1,
mimeType: 'image/png',
}).then((blob) => {
const url = URL.createObjectURL(blob);
setSaving(false);
onComplete(url);
}).catch((error) => {
console.error('Export failed:', error);
setSaving(false);
onClose();
});
};
return (
<div className="filerobot-overlay">
<div className="filerobot-container">
<FilerobotImageEditor
source={imageSrc}
onSave={handleComplete}
onClose={onClose}
annotationsCommon={{
fill: '#ff0000',
stroke: '#000000',
strokeWidth: 0,
}}
annotations={[
'Text',
'Rectangle',
'Ellipse',
'Line',
'Pen',
'Eraser',
]}
tabs={['adjust', 'filters', 'finetune', 'annotate', 'watermark']}
defaultTabId="adjust"
theme={{
accentColor: '#38bdf8',
palettePrimary: '#38bdf8',
}}
saveButtonProps={{
label: saving ? 'Exporting...' : 'Use Edited Image',
}}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1 @@
export { PhotoPreEditor } from './PhotoPreEditor';

View File

@@ -1,9 +1,11 @@
import { useRef, useState } from 'react';
import { useState } from 'react';
import { PhotoPreEditor } from '../editor/PhotoPreEditor';
export function UploadTab({ onUpload }) {
const fileInputRef = useRef(null);
const fileInputRef = useState(null)[0];
const [isDragging, setIsDragging] = useState(false);
const [uploading, setUploading] = useState(false);
const [editingImage, setEditingImage] = useState(null);
const handleFile = async (file) => {
if (!file || !file.type.startsWith('image/')) return;
@@ -20,7 +22,8 @@ export function UploadTab({ onUpload }) {
if (response.ok) {
const data = await response.json();
onUpload(data);
// Open photo editor with uploaded image
setEditingImage(data.original.url);
}
} catch (error) {
console.error('Upload failed:', error);
@@ -45,6 +48,25 @@ export function UploadTab({ onUpload }) {
setIsDragging(false);
};
const handleEditorComplete = (editedImageUrl) => {
onUpload({ preview: { url: editedImageUrl } });
setEditingImage(null);
};
const handleEditorClose = () => {
setEditingImage(null);
};
if (editingImage) {
return (
<PhotoPreEditor
imageSrc={editingImage}
onComplete={handleEditorComplete}
onClose={handleEditorClose}
/>
);
}
return (
<div className="upload-tab">
<h3>Upload Image</h3>
@@ -53,7 +75,7 @@ export function UploadTab({ onUpload }) {
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onClick={() => fileInputRef.current?.click()}
onClick={() => fileInputRef?.click()}
>
{uploading ? (
<div className="uploading-state">
@@ -65,6 +87,9 @@ export function UploadTab({ onUpload }) {
<div className="upload-icon">📁</div>
<p>Drop image here or click to upload</p>
<p className="upload-hint">PNG, JPG, WEBP up to 20MB</p>
<p className="upload-hint" style={{ marginTop: '0.5rem' }}>
Edit with crop, filters, and effects before adding to design
</p>
</>
)}
<input

View File

@@ -538,6 +538,28 @@ input, textarea, select {
animation: spin 1s linear infinite;
}
/* Filerobot Editor Overlay */
.filerobot-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.filerobot-container {
width: 90%;
height: 90%;
background: #1e1e1e;
border-radius: var(--radius-lg);
overflow: hidden;
}
/* Responsive */
@media (max-width: 900px) {
.app-layout {