/
F1lya
/
Modal_Window
Обзор
Документация
Войти
/
F1lya
/
Modal_Window
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
ImageEditor.tsx
258 строк
9 KB
F1lya
upload files
20 окт 2025, 14:17
20 окт 2025, 14:17
f9aac0b
Код
Авторство
О чём код?
import React, { useRef, useEffect, useCallback, useState } from 'react'; import { useImage } from '../hooks/useImage'; import { calculateContainerSize } from '../utils/imageUtils'; import { cropImage, downloadBlob, generateThumbnail } from '../utils/canvasUtils'; import { Frame, ContainerSize } from '../types'; import './ImageEditor.css'; const ImageEditor: React.FC = () => { const containerRef = useRef<HTMLDivElement>(null); const [containerSize, setContainerSize] = useState<ContainerSize>({ width: 600, height: 400 }); const [frame, setFrame] = useState<Frame>({ x: 100, y: 100, width: 200, height: 150 }); const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); const [frameStart, setFrameStart] = useState<Frame>({ x: 0, y: 0, width: 0, height: 0 }); const [thumbnailUrl, setThumbnailUrl] = useState<string>(''); const { imageState, loadImage, getImageFormat, validateImageFile, cleanup } = useImage(); const handleFileSelect = async (file: File) => { const validation = validateImageFile(file); if (!validation.isValid) { alert(validation.error); return; } try { await loadImage(file); } catch (error) { console.error('Error loading image:', error); } }; // Calculate container size when image loads useEffect(() => { if (imageState.image && containerRef.current) { const newSize = calculateContainerSize(imageState.image); setContainerSize(newSize); // Initialize frame const frameSize = Math.min(newSize.width, newSize.height) * 0.5; const x = (newSize.width - frameSize) / 2; const y = (newSize.height - frameSize) / 2; setFrame({ x: Math.max(0, x), y: Math.max(0, y), width: frameSize, height: frameSize }); } }, [imageState.image]); // Update thumbnail when frame changes useEffect(() => { if (imageState.image && imageState.imageUrl) { const newThumbnailUrl = generateThumbnail(imageState.image, frame, containerSize); setThumbnailUrl(newThumbnailUrl); } }, [frame, imageState.image, containerSize, imageState.imageUrl]); const handleFrameMouseDown = (e: React.MouseEvent) => { e.preventDefault(); setIsDragging(true); setDragStart({ x: e.clientX, y: e.clientY }); setFrameStart({ ...frame }); }; const handleMouseMove = useCallback((e: MouseEvent) => { if (!isDragging || !imageState.image) return; const deltaX = e.clientX - dragStart.x; const deltaY = e.clientY - dragStart.y; const newX = Math.max(0, Math.min(frameStart.x + deltaX, containerSize.width - frame.width)); const newY = Math.max(0, Math.min(frameStart.y + deltaY, containerSize.height - frame.height)); setFrame(prev => ({ ...prev, x: newX, y: newY })); }, [isDragging, dragStart, frameStart, containerSize, frame, imageState.image]); const handleMouseUp = useCallback(() => { setIsDragging(false); }, []); useEffect(() => { if (isDragging) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; } }, [isDragging, handleMouseMove, handleMouseUp]); useEffect(() => { return cleanup; }, [cleanup]); const handleSave = async () => { if (!imageState.image) return; try { const format = getImageFormat(imageState.imageUrl); const blob = await cropImage(imageState.image, frame, containerSize, format); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `cropped-image-${timestamp}.${format === 'jpeg' ? 'jpg' : format}`; downloadBlob(blob, filename); } catch (error) { console.error('Error saving image:', error); alert('Failed to save image. Please try again.'); } }; const handleResetFrame = () => { if (imageState.image) { const frameSize = Math.min(containerSize.width, containerSize.height) * 0.5; const x = (containerSize.width - frameSize) / 2; const y = (containerSize.height - frameSize) / 2; setFrame({ x: Math.max(0, x), y: Math.max(0, y), width: frameSize, height: frameSize }); } }; return ( <div className="image-editor"> <div className="image-editor__upload-section"> <input type="file" accept=".png,.jpg,.jpeg,.gif,.webp" onChange={(e) => { const file = e.target.files?.[0]; if (file) handleFileSelect(file); }} style={{ display: 'none' }} id="file-upload" /> <label htmlFor="file-upload" className="image-editor__upload-button"> Choose Image </label> {imageState.isLoading && ( <div className="image-editor__loading">Loading image...</div> )} {imageState.error && ( <div className="image-editor__error">{imageState.error}</div> )} </div> {imageState.imageUrl && !imageState.isLoading && ( <div className="image-editor__workspace"> <div className="image-editor__main-content"> <div className="image-editor__instructions"> <h3>How to use:</h3> <ul> <li>Drag the frame to move the crop area</li> <li>Preview updates in real-time on the right</li> <li>Save your cropped image</li> </ul> </div> <div className="image-editor__editor-area"> <div ref={containerRef} className="image-editor__container" style={{ width: containerSize.width, height: containerSize.height }} > <img src={imageState.imageUrl} alt="Edit" className="image-editor__image" /> <div className={`image-editor__frame ${isDragging ? 'image-editor__frame--dragging' : ''}`} style={{ left: frame.x, top: frame.y, width: frame.width, height: frame.height }} onMouseDown={handleFrameMouseDown} /> </div> <div className="image-editor__controls"> <div className="image-editor__frame-info"> <div>Position: {Math.round(frame.x)} × {Math.round(frame.y)}</div> <div>Size: {Math.round(frame.width)} × {Math.round(frame.height)}</div> </div> <div className="image-editor__actions"> <button className="image-editor__button image-editor__button--secondary" onClick={handleResetFrame} disabled={!imageState.image} > Reset Frame </button> <button className="image-editor__button image-editor__button--primary" onClick={handleSave} disabled={!imageState.image} > Save Image </button> </div> </div> </div> </div> <div className="image-editor__thumbnail"> <div className="thumbnail__header"> <h3>Live Preview</h3> <div className="thumbnail__dimensions"> {Math.round(frame.width)} × {Math.round(frame.height)} </div> </div> <div className="thumbnail__preview"> {thumbnailUrl ? ( <img src={thumbnailUrl} alt="Cropped preview" className="thumbnail__image" /> ) : ( <div className="thumbnail__placeholder"> Move the frame to see preview </div> )} </div> <div className="thumbnail__footer"> Real-time preview updates as you adjust the crop area </div> </div> </div> )} </div> ); }; export default ImageEditor;