mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-26 09:29:30 +02:00
feat: edit image
This commit is contained in:
127
src/pages/tools/image/generic/editor/index.tsx
Normal file
127
src/pages/tools/image/generic/editor/index.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import ToolImageInput from '@components/input/ToolImageInput';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
|
||||
// Import the image editor with proper typing
|
||||
import FilerobotImageEditor, {
|
||||
FilerobotImageEditorConfig
|
||||
} from 'react-filerobot-image-editor';
|
||||
|
||||
export default function ImageEditor({ title }: ToolComponentProps) {
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||
|
||||
// Handle file input change
|
||||
const handleInputChange = useCallback((file: File | null) => {
|
||||
setInput(file);
|
||||
if (file) {
|
||||
// Create object URL for the image editor
|
||||
const url = URL.createObjectURL(file);
|
||||
setImageUrl(url);
|
||||
setIsEditorOpen(true);
|
||||
} else {
|
||||
setImageUrl(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onCloseEditor = (reason: string) => {
|
||||
if (reason === 'close-button-clicked') {
|
||||
setIsEditorOpen(false);
|
||||
setImageUrl(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle save from image editor
|
||||
const handleSave: FilerobotImageEditorConfig['onSave'] = (
|
||||
editedImageObject,
|
||||
designState
|
||||
) => {
|
||||
console.log('Image saved:', editedImageObject, designState);
|
||||
|
||||
if (editedImageObject && editedImageObject.imageBase64) {
|
||||
// Convert base64 to blob
|
||||
const base64Data = editedImageObject.imageBase64.split(',')[1];
|
||||
const byteCharacters = atob(base64Data);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: editedImageObject.mimeType });
|
||||
|
||||
const editedFile = new File(
|
||||
[blob],
|
||||
editedImageObject.fullName ?? 'edited.png',
|
||||
{
|
||||
type: editedImageObject.mimeType
|
||||
}
|
||||
);
|
||||
// Create a temporary download link
|
||||
const url = URL.createObjectURL(editedFile);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = editedFile.name; // This will be the name of the downloaded file
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
// Release the blob URL
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultImageName = () => {
|
||||
if (!input) return;
|
||||
const originalName = input?.name || 'edited-image';
|
||||
const nameWithoutExt = originalName.replace(/\.[^/.]+$/, '');
|
||||
const editedFileName = `${nameWithoutExt}-edited`;
|
||||
return editedFileName;
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
initialValues={{}}
|
||||
getGroups={null}
|
||||
input={input}
|
||||
inputComponent={
|
||||
isEditorOpen ? (
|
||||
imageUrl && (
|
||||
<Box style={{ width: '100%', height: '70vh' }}>
|
||||
<FilerobotImageEditor
|
||||
source={imageUrl}
|
||||
onSave={handleSave}
|
||||
onClose={onCloseEditor}
|
||||
annotationsCommon={{
|
||||
fill: 'blue'
|
||||
}}
|
||||
defaultSavedImageName={getDefaultImageName()}
|
||||
Rotate={{ angle: 90, componentType: 'slider' }}
|
||||
savingPixelRatio={1}
|
||||
previewPixelRatio={1}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
) : (
|
||||
<ToolImageInput
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
accept={['image/*']}
|
||||
title="Upload Image to Edit"
|
||||
/>
|
||||
)
|
||||
}
|
||||
toolInfo={{
|
||||
title: 'Image Editor',
|
||||
description:
|
||||
'A powerful image editing tool that provides professional-grade features including cropping, rotating, color adjustments, text annotations, drawing tools, and watermarking. Edit your images directly in your browser without the need for external software.'
|
||||
}}
|
||||
compute={() => {}}
|
||||
/>
|
||||
);
|
||||
}
|
28
src/pages/tools/image/generic/editor/meta.ts
Normal file
28
src/pages/tools/image/generic/editor/meta.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('image-generic', {
|
||||
name: 'Image Editor',
|
||||
path: 'editor',
|
||||
icon: 'mdi:image-edit',
|
||||
description:
|
||||
'Advanced image editor with tools for cropping, rotating, annotating, adjusting colors, and adding watermarks. Edit your images with professional-grade tools directly in your browser.',
|
||||
shortDescription: 'Edit images with advanced tools and features',
|
||||
keywords: [
|
||||
'image',
|
||||
'editor',
|
||||
'edit',
|
||||
'crop',
|
||||
'rotate',
|
||||
'annotate',
|
||||
'adjust',
|
||||
'watermark',
|
||||
'text',
|
||||
'drawing',
|
||||
'filters',
|
||||
'brightness',
|
||||
'contrast',
|
||||
'saturation'
|
||||
],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
@@ -9,7 +9,9 @@ import { tool as imageToText } from './image-to-text/meta';
|
||||
import { tool as qrCodeGenerator } from './qr-code/meta';
|
||||
import { tool as rotateImage } from './rotate/meta';
|
||||
import { tool as convertToJpg } from './convert-to-jpg/meta';
|
||||
import { tool as imageEditor } from './editor/meta';
|
||||
export const imageGenericTools = [
|
||||
imageEditor,
|
||||
resizeImage,
|
||||
compressImage,
|
||||
removeBackground,
|
||||
|
Reference in New Issue
Block a user