image section
This commit is contained in:
Generated
+48
-3
@@ -17,6 +17,7 @@
|
|||||||
"konva": "^10.0.9",
|
"konva": "^10.0.9",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"react-dropzone": "^14.3.8",
|
||||||
"react-i18next": "^16.3.0",
|
"react-i18next": "^16.3.0",
|
||||||
"react-konva": "^19.2.0",
|
"react-konva": "^19.2.0",
|
||||||
"react-router-dom": "^7.9.5",
|
"react-router-dom": "^7.9.5",
|
||||||
@@ -2238,6 +2239,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/attr-accept": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
|
||||||
|
"integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-plugin-react-compiler": {
|
"node_modules/babel-plugin-react-compiler": {
|
||||||
"version": "19.1.0-rc.3",
|
"version": "19.1.0-rc.3",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.3.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.3.tgz",
|
||||||
@@ -2827,6 +2837,18 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-selector": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -3118,9 +3140,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3815,6 +3837,23 @@
|
|||||||
"react": "^19.2.0"
|
"react": "^19.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dropzone": {
|
||||||
|
"version": "14.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
|
||||||
|
"integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"attr-accept": "^2.2.4",
|
||||||
|
"file-selector": "^2.1.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8 || 18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-i18next": {
|
"node_modules/react-i18next": {
|
||||||
"version": "16.3.0",
|
"version": "16.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.3.0.tgz",
|
||||||
@@ -4209,6 +4248,12 @@
|
|||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"konva": "^10.0.9",
|
"konva": "^10.0.9",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"react-dropzone": "^14.3.8",
|
||||||
"react-i18next": "^16.3.0",
|
"react-i18next": "^16.3.0",
|
||||||
"react-konva": "^19.2.0",
|
"react-konva": "^19.2.0",
|
||||||
"react-router-dom": "^7.9.5",
|
"react-router-dom": "^7.9.5",
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { CloseCircle, DocumentUpload, Gallery } from 'iconsax-react'
|
||||||
|
import { type FC, useCallback, useEffect, useState } from 'react'
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import { clx } from '../helpers/utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label: string;
|
||||||
|
onChange: (file: File[]) => void;
|
||||||
|
isMultiple?: boolean;
|
||||||
|
isFile?: boolean,
|
||||||
|
preview?: string[],
|
||||||
|
onChangePreview?: (preview: string[]) => void,
|
||||||
|
getCover?: (url: string) => void,
|
||||||
|
coverUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const UploadBoxDraggble: FC<Props> = (props: Props) => {
|
||||||
|
|
||||||
|
const [files, setFiles] = useState<File[]>([])
|
||||||
|
const [perviews, setPerviews] = useState<string[]>(props.preview ? props.preview : [])
|
||||||
|
const [isFill, setIsFill] = useState<boolean>(false)
|
||||||
|
const [cover, setCover] = useState<string>(props.coverUrl ? props.coverUrl : '')
|
||||||
|
|
||||||
|
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||||
|
if (props.isMultiple) {
|
||||||
|
const array = [...files, ...acceptedFiles]
|
||||||
|
setFiles(array)
|
||||||
|
props.onChange(array)
|
||||||
|
} else {
|
||||||
|
setFiles([acceptedFiles[0]])
|
||||||
|
props.onChange([acceptedFiles[0]])
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [files])
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({ onDrop })
|
||||||
|
|
||||||
|
const handleDelete = (index: number) => {
|
||||||
|
const array = [...files]
|
||||||
|
array.splice(index, 1)
|
||||||
|
setFiles(array)
|
||||||
|
props.onChange(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeletePreview = (index: number) => {
|
||||||
|
const array = [...perviews];
|
||||||
|
array.splice(index, 1);
|
||||||
|
setPerviews(array);
|
||||||
|
if (props.onChangePreview) {
|
||||||
|
props.onChangePreview(array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (props.preview && props.preview.length > 0 && !isFill) {
|
||||||
|
setPerviews(props.preview)
|
||||||
|
console.log('preview', props.preview);
|
||||||
|
|
||||||
|
setIsFill(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [props.preview, isFill])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCover(props.coverUrl ? props.coverUrl : '')
|
||||||
|
}, [props.coverUrl])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div {...getRootProps()} className='w-full py-8 border border-dashed border-description flex flex-col justify-center items-center gap-4 rounded-2xl'>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
<Gallery
|
||||||
|
className='size-8'
|
||||||
|
color='#8C90A3'
|
||||||
|
/>
|
||||||
|
<div className='text-description text-xs'>
|
||||||
|
{props.label}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<DocumentUpload
|
||||||
|
className='size-4'
|
||||||
|
color='black'
|
||||||
|
/>
|
||||||
|
<div className='text-xs'>آپلود</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
files.length > 0 || perviews.length > 0 ? (
|
||||||
|
<div className='mt-4 flex gap-4 items-center'>
|
||||||
|
{
|
||||||
|
perviews && perviews.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className='flex items-center gap-2'>
|
||||||
|
<div key={index} className='flex relative items-center gap-2'>
|
||||||
|
<img onClick={() => {
|
||||||
|
if (props.getCover) {
|
||||||
|
setCover(item)
|
||||||
|
props.getCover(item)
|
||||||
|
}
|
||||||
|
}} src={item}
|
||||||
|
className={clx(
|
||||||
|
'size-10 rounded-full object-cover',
|
||||||
|
cover === item ? 'border-2 border-red-400' : ''
|
||||||
|
)} />
|
||||||
|
<div onClick={() => handleDeletePreview(index)} className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
|
||||||
|
<CloseCircle className='size-4 ' color='red' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
files.map((file, index) => {
|
||||||
|
if (props.isFile) {
|
||||||
|
return (
|
||||||
|
<div key={index} className='flex border p-2 rounded-lg items-center gap-2'>
|
||||||
|
<div className='flex relative items-center gap-2'>
|
||||||
|
<div className='text-xs'>{file.name}</div>
|
||||||
|
<div className='absolute -left-4 -top-4 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
|
||||||
|
<CloseCircle onClick={() => handleDelete(index)} className='size-4 ' color='red' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<div key={index} className='flex items-center gap-2'>
|
||||||
|
<div key={index} className='flex relative items-center gap-2'>
|
||||||
|
<img src={URL.createObjectURL(file)} alt={file.name} className='size-10 rounded-full object-cover' />
|
||||||
|
<div className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
|
||||||
|
<CloseCircle onClick={() => handleDelete(index)} className='size-4 ' color='red' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadBoxDraggble
|
||||||
@@ -85,6 +85,38 @@ const EditorCanvas = () => {
|
|||||||
setEditingCell(null);
|
setEditingCell(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Attach transformer to selected object when it changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!transformerRef.current || !selectedObjectId || !layerRef.current) return;
|
||||||
|
|
||||||
|
const selectedObject = objects.find((obj) => obj.id === selectedObjectId);
|
||||||
|
if (!selectedObject) return;
|
||||||
|
|
||||||
|
// For grid objects, handle separately
|
||||||
|
if (selectedObject.type === "grid") {
|
||||||
|
const tableNode = layerRef.current.findOne(`#${selectedObjectId}`);
|
||||||
|
if (tableNode && transformerRef.current) {
|
||||||
|
transformerRef.current.nodes([tableNode]);
|
||||||
|
transformerRef.current.getLayer()?.batchDraw();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other objects (like images), find the node by id and attach transformer
|
||||||
|
// Use a small delay to ensure the node is mounted (especially for images that need to load)
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
if (!layerRef.current || !transformerRef.current) return;
|
||||||
|
|
||||||
|
const node = layerRef.current.findOne(`#${selectedObjectId}`);
|
||||||
|
if (node && transformerRef.current) {
|
||||||
|
transformerRef.current.nodes([node]);
|
||||||
|
transformerRef.current.getLayer()?.batchDraw();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [selectedObjectId, objects, transformerRef, layerRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!transformerRef.current || !selectedObjectId) return;
|
if (!transformerRef.current || !selectedObjectId) return;
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ const tools: Array<{ icon: (color: string, variant?: "Bold" | "Outline" | "Broke
|
|||||||
{ icon: (color, variant) => <MouseSquare size={20} color={color} variant={variant} />, tool: "select", label: "انتخاب" },
|
{ icon: (color, variant) => <MouseSquare size={20} color={color} variant={variant} />, tool: "select", label: "انتخاب" },
|
||||||
{ icon: (color, variant) => <Shapes size={20} color={color} variant={variant} />, tool: "rectangle", label: "مستطیل" },
|
{ icon: (color, variant) => <Shapes size={20} color={color} variant={variant} />, tool: "rectangle", label: "مستطیل" },
|
||||||
{ icon: (color, variant) => <Text size={20} color={color} variant={variant} />, tool: "text", label: "متن" },
|
{ icon: (color, variant) => <Text size={20} color={color} variant={variant} />, tool: "text", label: "متن" },
|
||||||
{ icon: (color, variant) => <Minus size={20} color={color} variant={variant} />, tool: "line", label: "خط" },
|
|
||||||
{ icon: (color, variant) => <Grid8 size={20} color={color} variant={variant} />, tool: "grid", label: "گرید" },
|
{ icon: (color, variant) => <Grid8 size={20} color={color} variant={variant} />, tool: "grid", label: "گرید" },
|
||||||
{ icon: (color, variant) => <Sticker size={20} color={color} variant={variant} />, tool: "sticker", label: "استیکر" },
|
{ icon: (color, variant) => <Gallery size={20} color={color} variant={variant} />, tool: "image", label: "تصویر" },
|
||||||
|
{ icon: (color, variant) => <Link size={20} color={color} variant={variant} />, tool: "link", label: "لینک" },
|
||||||
{ icon: (color, variant) => <DocumentUpload size={20} color={color} variant={variant} />, tool: "document", label: "سند" },
|
{ icon: (color, variant) => <DocumentUpload size={20} color={color} variant={variant} />, tool: "document", label: "سند" },
|
||||||
{ icon: (color, variant) => <Element size={20} color={color} variant={variant} />, tool: "arrow", label: "پیکان" },
|
{ icon: (color, variant) => <Element size={20} color={color} variant={variant} />, tool: "arrow", label: "پیکان" },
|
||||||
{ icon: (color, variant) => <Gallery size={20} color={color} variant={variant} />, tool: "image", label: "تصویر" },
|
{ icon: (color, variant) => <Minus size={20} color={color} variant={variant} />, tool: "line", label: "خط" },
|
||||||
{ icon: (color, variant) => <VideoSquare size={20} color={color} variant={variant} />, tool: "video", label: "ویدیو" },
|
{ icon: (color, variant) => <VideoSquare size={20} color={color} variant={variant} />, tool: "video", label: "ویدیو" },
|
||||||
{ icon: (color, variant) => <Link size={20} color={color} variant={variant} />, tool: "link", label: "لینک" },
|
{ icon: (color, variant) => <Sticker size={20} color={color} variant={variant} />, tool: "sticker", label: "استیکر" },
|
||||||
];
|
];
|
||||||
|
|
||||||
type ToolsBarProps = {
|
type ToolsBarProps = {
|
||||||
|
|||||||
@@ -1,29 +1,64 @@
|
|||||||
import { Gallery } from "iconsax-react";
|
import { type FC, useState, useRef } from "react";
|
||||||
import Button from "@/components/Button";
|
import UploadBoxDraggble from "@/components/UploadBoxDraggble";
|
||||||
|
|
||||||
type ImageUploadProps = {
|
type ImageUploadProps = {
|
||||||
onImageUpload: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
onImageUpload: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImageUpload = ({ onImageUpload }: ImageUploadProps) => {
|
const ImageUpload: FC<ImageUploadProps> = ({ onImageUpload }) => {
|
||||||
|
const [uploadedImages, setUploadedImages] = useState<string[]>([]);
|
||||||
|
const previousFilesCountRef = useRef<number>(0);
|
||||||
|
|
||||||
|
const handleFileChange = (files: File[]) => {
|
||||||
|
// Update ref to track current files count
|
||||||
|
if (files.length > previousFilesCountRef.current) {
|
||||||
|
// Process only new files
|
||||||
|
const newFiles = files.slice(previousFilesCountRef.current);
|
||||||
|
|
||||||
|
newFiles.forEach((file) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const imageUrl = event.target?.result as string;
|
||||||
|
setUploadedImages((prev) => [...prev, imageUrl]);
|
||||||
|
|
||||||
|
// Create a synthetic event for onImageUpload
|
||||||
|
const syntheticEvent = {
|
||||||
|
target: {
|
||||||
|
files: [file],
|
||||||
|
},
|
||||||
|
} as unknown as React.ChangeEvent<HTMLInputElement>;
|
||||||
|
|
||||||
|
onImageUpload(syntheticEvent);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always update ref to track current files count
|
||||||
|
previousFilesCountRef.current = files.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePreviewChange = (previews: string[]) => {
|
||||||
|
setUploadedImages(previews);
|
||||||
|
// Update ref when previews change (e.g., when files are deleted)
|
||||||
|
previousFilesCountRef.current = previews.length;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm mb-2">آپلود تصویر</label>
|
<div className="text-base font-bold mb-4">تصویر</div>
|
||||||
<input
|
<UploadBoxDraggble
|
||||||
type="file"
|
label="تصویر مورد نظر را آپلود کنید"
|
||||||
accept="image/*"
|
onChange={handleFileChange}
|
||||||
onChange={onImageUpload}
|
isMultiple={true}
|
||||||
className="hidden"
|
preview={uploadedImages}
|
||||||
id="image-upload"
|
onChangePreview={handlePreviewChange}
|
||||||
/>
|
/>
|
||||||
<Button
|
{uploadedImages.length > 0 && (
|
||||||
variant="outline"
|
<div className="mt-4 text-description text-xs">
|
||||||
onClick={() => document.getElementById("image-upload")?.click()}
|
تصویر های آپلود شده
|
||||||
className="w-full"
|
</div>
|
||||||
>
|
)}
|
||||||
<Gallery size={20} />
|
|
||||||
انتخاب تصویر
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,17 +16,18 @@ const ImageObject = ({
|
|||||||
const shapeRef = useRef<Konva.Image>(null);
|
const shapeRef = useRef<Konva.Image>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSelected && shapeRef.current && transformerRef.current) {
|
if (isSelected && shapeRef.current && transformerRef.current && image) {
|
||||||
transformerRef.current.nodes([shapeRef.current]);
|
transformerRef.current.nodes([shapeRef.current]);
|
||||||
transformerRef.current.getLayer()?.batchDraw();
|
transformerRef.current.getLayer()?.batchDraw();
|
||||||
}
|
}
|
||||||
}, [isSelected, transformerRef]);
|
}, [isSelected, transformerRef, image]);
|
||||||
|
|
||||||
if (!image) return null;
|
if (!image) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KonvaImage
|
<KonvaImage
|
||||||
ref={shapeRef}
|
ref={shapeRef}
|
||||||
|
id={obj.id}
|
||||||
x={obj.x}
|
x={obj.x}
|
||||||
y={obj.y}
|
y={obj.y}
|
||||||
image={image}
|
image={image}
|
||||||
|
|||||||
Reference in New Issue
Block a user