Feat: 리뷰와 레이블을 같이 볼 수 있게 컴포넌트 추가

This commit is contained in:
정현조 2024-09-23 21:45:53 +09:00
parent 4e148ccce3
commit 67b4ac78bb
2 changed files with 106 additions and 4 deletions

View File

@ -0,0 +1,102 @@
import { useEffect, useState } from 'react';
import { Image, Layer, Stage, Line, Rect } from 'react-konva';
import useImage from 'use-image';
import { Label } from '@/types';
interface Shape {
label: string;
color: string;
points: [number, number][];
shape_type: 'polygon' | 'rectangle';
}
interface ImageWithLabelsProps {
imagePath: string;
labelData: string;
}
export default function ImageWithLabels({ imagePath, labelData }: ImageWithLabelsProps) {
const [image] = useImage(imagePath);
const [labels, setLabels] = useState<Label[]>([]);
const [stageDimensions, setStageDimensions] = useState({ width: window.innerWidth, height: window.innerHeight });
useEffect(() => {
const fetchLabelData = async () => {
try {
const response = await fetch(labelData);
const json: { shapes: Shape[] } = await response.json();
const shapes = json.shapes.map((shape, index) => ({
id: index,
name: shape.label,
color: shape.color,
type: shape.shape_type === 'polygon' ? 'polygon' : 'rect',
coordinates: shape.points,
})) as Label[];
setLabels(shapes);
} catch (error) {
console.error('Failed to fetch label data:', error);
}
};
fetchLabelData();
}, [labelData]);
useEffect(() => {
const updateDimensions = () => {
setStageDimensions({
width: window.innerWidth - 280,
height: window.innerHeight - 64,
});
};
window.addEventListener('resize', updateDimensions);
return () => {
window.removeEventListener('resize', updateDimensions);
};
}, []);
const getScale = () => {
if (!image) return { x: 1, y: 1 };
const widthRatio = stageDimensions.width / image.width;
const heightRatio = stageDimensions.height / image.height;
const scale = Math.min(widthRatio, heightRatio);
return { x: scale, y: scale };
};
return image ? (
<Stage
width={stageDimensions.width}
height={stageDimensions.height}
className="overflow-hidden bg-gray-200"
scale={getScale()}
>
<Layer>{image && <Image image={image} />}</Layer>
<Layer>
{labels.map((label) =>
label.type === 'rect' ? (
<Rect
key={label.id}
x={label.coordinates[0][0]}
y={label.coordinates[0][1]}
width={label.coordinates[1][0] - label.coordinates[0][0]}
height={label.coordinates[1][1] - label.coordinates[0][1]}
stroke={label.color}
strokeWidth={2}
listening={false}
/>
) : (
<Line
key={label.id}
points={label.coordinates.flat()}
stroke={label.color}
strokeWidth={2}
closed
listening={false}
/>
)
)}
</Layer>
</Stage>
) : (
<div></div>
);
}

View File

@ -7,6 +7,7 @@ import useAuthStore from '@/stores/useAuthStore';
import { Button } from '@/components/ui/button';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
import ImageWithLabels from '@/components/ImageWithLabels';
export default function ReviewDetail(): JSX.Element {
const { workspaceId, projectId, reviewId } = useParams<{
@ -99,10 +100,9 @@ export default function ReviewDetail(): JSX.Element {
<Slider {...settings}>
{reviewDetail.images.map((image) => (
<div key={image.id}>
<img
src={image.imagePath}
alt="리뷰 이미지"
className="h-auto w-full rounded"
<ImageWithLabels
imagePath={image.imagePath}
labelData={image.dataPath}
/>
</div>
))}