Merge branch 'fe/develop' of https://lab.ssafy.com/s11-s-project/S11P21S002 into fe/refactor/admin-page

This commit is contained in:
정현조 2024-09-23 14:02:53 +09:00
commit 05f8a760ed
13 changed files with 167 additions and 138 deletions

View File

@ -42,7 +42,7 @@ export async function changeImageStatus(
export async function uploadImageList(projectId: number, folderId: number, memberId: number, imageList: File[]) {
return api
.post(
`/projects/${projectId}/folders/${folderId}/images`,
`/projects/${projectId}/folders/${folderId}/images/file`,
{ imageList },
{
params: { memberId },
@ -51,43 +51,26 @@ export async function uploadImageList(projectId: number, folderId: number, membe
.then(({ data }) => data);
}
export async function uploadImageFolder(memberId: number, projectId: number, files: File[], parentId: number = 0) {
export async function uploadImageFolder(memberId: number, projectId: number, files: File[]) {
const formData = new FormData();
files.forEach((file) => {
formData.append('files', file);
});
return api
.post(
`/projects/${projectId}/folders/${0}/images/upload`,
{ folderZip: files, parentId },
{
params: { memberId },
}
)
.then(({ data }) => data)
.catch((error) => {
return Promise.reject(error);
});
}
export async function uploadImageFolderZip(memberId: number, projectId: number, file: File, parentId: number = 0) {
const formData = new FormData();
formData.append('folderZip', file);
formData.append('parentId', parentId.toString());
// const jsonData = {
// parentId,
// };
// const blob = new Blob([JSON.stringify(jsonData)], { type: 'application/json' });
// formData.append('parentId', blob);
});
return api
.post(`/projects/${projectId}/folders/${0}/images/upload`, formData, {
.post(`/projects/${projectId}/folders/${0}/images/zip`, formData, {
params: { memberId },
})
.then(({ data }) => data)
.catch((error) => {
return Promise.reject(error);
});
.then(({ data }) => data);
}
export async function uploadImageZip(memberId: number, projectId: number, file: File) {
const formData = new FormData();
formData.append('folderZip', file);
return api
.post(`/projects/${projectId}/folders/${0}/images/zip`, formData, {
params: { memberId },
})
.then(({ data }) => data);
}

View File

@ -1,12 +1,13 @@
import api from '@/api/axiosConfig';
import { LabelingRequest } from '@/types';
export async function saveImageLabels(projectId: number, imageId: number, memberId: number, data: LabelingRequest) {
return api
.post(`/projects/${projectId}/label/image/${imageId}`, data, {
params: { memberId },
})
.then(({ data }) => data);
export async function saveImageLabels(
projectId: number,
imageId: number,
data: {
data: string;
}
) {
return api.post(`/projects/${projectId}/label/image/${imageId}`, data).then(({ data }) => data);
}
export async function runAutoLabel(projectId: number, memberId: number) {

View File

@ -1,8 +1,8 @@
import useCanvasStore from '@/stores/useCanvasStore';
import { LucideIcon, MousePointer2, PenTool, Square } from 'lucide-react';
import { LucideIcon, MousePointer2, PenTool, Save, Square } from 'lucide-react';
import { cn } from '@/lib/utils';
export default function CanvasControlBar() {
export default function CanvasControlBar({ saveJson }: { saveJson: () => void }) {
const drawState = useCanvasStore((state) => state.drawState);
const setDrawState = useCanvasStore((state) => state.setDrawState);
const buttonBaseClassName = 'rounded-lg p-2 transition-colors ';
@ -31,6 +31,16 @@ export default function CanvasControlBar() {
</button>
);
})}
<button
className={cn(buttonClassName, buttonBaseClassName)}
onClick={saveJson}
>
<Save
size={20}
color="black"
/>
</button>
</div>
);
}

View File

@ -7,11 +7,13 @@ export default function LabelRect({
isSelected,
onSelect,
info,
setLabel,
dragLayer,
}: {
isSelected: boolean;
onSelect: (evt: Konva.KonvaEventObject<TouchEvent | MouseEvent>) => void;
info: Label;
setLabel: (coordinate: [number, number][]) => void;
dragLayer: Konva.Layer;
}) {
const rectRef = useRef<Konva.Line>(null);
@ -28,13 +30,19 @@ export default function LabelRect({
trRef.current?.moveToTop();
};
const handleMoveEnd = () => {
const rectPoints = rectRef.current?.points();
const points = [
[rectPoints![0], rectPoints![1]],
[rectPoints![4], rectPoints![5]],
const rect = rectRef.current?.getPosition();
const scale = rectRef.current?.scale();
if (!rect || !scale) return;
const points: [number, number][] = [
[info.coordinates[0][0] * scale.x + rect.x, info.coordinates[0][1] * scale.y + rect.y],
[info.coordinates[1][0] * scale.x + rect.x, info.coordinates[1][1] * scale.y + rect.y],
];
console.log(points);
setLabel(points);
rectRef.current?.setAbsolutePosition({ x: 0, y: 0 });
rectRef.current?.scale({ x: 1, y: 1 });
};
useEffect(() => {

View File

@ -1,4 +1,5 @@
import Konva from 'konva';
import { Vector2d } from 'konva/lib/types';
import { useEffect, useRef } from 'react';
import { Circle, Group, Line } from 'react-konva';
@ -24,6 +25,24 @@ const TRANSFORM_CHANGE_STR = [
export default function PolygonTransformer({ coordinates, setCoordinates, stage, dragLayer }: PolygonTransformerProps) {
const anchorsRef = useRef<Konva.Group>(null);
const scale: Vector2d = { x: 1 / stage.getAbsoluteScale().x, y: 1 / stage.getAbsoluteScale().y };
const handleClick = (index: number) => (e: Konva.KonvaEventObject<MouseEvent>) => {
if (e.evt.button === 0 && e.evt.detail === 2) {
const pos = stage.getRelativePointerPosition()!;
const newCoordinates: [number, number][] = [
...coordinates.slice(0, index + 1),
[pos.x, pos.y],
...coordinates.slice(index + 1),
];
setCoordinates(newCoordinates);
return;
}
if (e.evt.button !== 2) return;
const newCoordinates = [...coordinates.slice(0, index), ...coordinates.slice(index + 1)];
setCoordinates(newCoordinates);
};
const handleDragMove = (index: number) => (e: Konva.KonvaEventObject<DragEvent>) => {
const circle = e.target as Konva.Circle;
const pos = circle.position();
@ -31,7 +50,7 @@ export default function PolygonTransformer({ coordinates, setCoordinates, stage,
newCoordinates[index] = [pos.x, pos.y];
setCoordinates(newCoordinates);
stage.batchDraw();
// stage.batchDraw();
};
const handleMouseOver = (e: Konva.KonvaEventObject<MouseEvent>) => {
const circle = e.target as Konva.Circle;
@ -94,10 +113,11 @@ export default function PolygonTransformer({ coordinates, setCoordinates, stage,
fill="white"
draggable
strokeScaleEnabled={false}
onClick={handleClick(index)}
onDragMove={handleDragMove(index)}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
scale={{ x: 1 / stage.getAbsoluteScale().x, y: 1 / stage.getAbsoluteScale().y }}
scale={scale}
perfectDrawEnabled={false}
shadowForStrokeEnabled={false}
/>

View File

@ -9,9 +9,11 @@ import LabelPolygon from './LabelPolygon';
import CanvasControlBar from '../CanvasControlBar';
import useLabelJsonQuery from '@/queries/labelJson/useLabelJsonQuery';
import { Label } from '@/types';
import { useParams } from 'react-router-dom';
export default function ImageCanvas() {
const { imagePath, dataPath } = useCanvasStore((state) => state.image)!;
const { projectId } = useParams<{ projectId: string }>();
const { id: imageId, imagePath, dataPath } = useCanvasStore((state) => state.image)!;
const { data: labelData } = useLabelJsonQuery(dataPath);
const { shapes } = labelData;
const selectedLabelId = useCanvasStore((state) => state.selectedLabelId);
@ -43,6 +45,25 @@ export default function ImageCanvas() {
);
}, [setLabels, shapes]);
const setLabel = (index: number) => (coordinates: [number, number][]) => {
const newLabels = [...labels];
newLabels[index].coordinates = coordinates;
setLabels(newLabels);
};
const saveJson = () => {
const json = JSON.stringify({
...labelData,
shapes: labels.map(({ name, color, coordinates, type }) => ({
label: name,
color,
shape_type: type === 'polygon' ? 'polygon' : 'rectangle',
points: coordinates,
})),
});
console.log(projectId, imageId, json);
// TOOD: api 연결
};
const startDrawRect = () => {
const { x, y } = stageRef.current!.getRelativePointerPosition()!;
setRectPoints([
@ -250,6 +271,7 @@ export default function ImageCanvas() {
isSelected={label.id === selectedLabelId}
onSelect={() => setSelectedLabelId(label.id)}
info={label}
setLabel={setLabel(label.id)}
dragLayer={dragLayerRef.current as Konva.Layer}
/>
) : (
@ -312,7 +334,7 @@ export default function ImageCanvas() {
<Layer ref={dragLayerRef} />
</Stage>
<CanvasControlBar />
<CanvasControlBar saveJson={saveJson} />
</div>
) : (
<div></div>

View File

@ -5,15 +5,7 @@ import { uploadImageFolder } from '@/api/imageApi';
import useAuthStore from '@/stores/useAuthStore';
import { X } from 'lucide-react';
export default function ImageFolderUploadForm({
onClose,
projectId,
parentId,
}: {
onClose: () => void;
projectId: number;
parentId: number;
}) {
export default function ImageUploadFolderForm({ onClose, projectId }: { onClose: () => void; projectId: number }) {
const profile = useAuthStore((state) => state.profile);
const memberId = profile?.id || 0;
@ -57,7 +49,7 @@ export default function ImageFolderUploadForm({
setIsUploading(true);
setProgress(0);
await uploadImageFolder(memberId, projectId, files, parentId)
await uploadImageFolder(memberId, projectId, files)
.then(() => {
setProgress(100);
})

View File

@ -1,9 +1,9 @@
import React from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
import { Plus } from 'lucide-react';
import ImageFolderUploadForm from './ImageFolderUploadForm';
import ImageUploadFolderForm from './ImageUploadFolderForm';
export default function ImageFolderUploadModal({ projectId, parentId = 0 }: { projectId: number; parentId: number }) {
export default function ImageUploadFolderModal({ projectId }: { projectId: number }) {
const [isOpen, setIsOpen] = React.useState(false);
const handleOpen = () => setIsOpen(true);
@ -24,10 +24,9 @@ export default function ImageFolderUploadModal({ projectId, parentId = 0 }: { pr
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader title="폴더 업로드" />
<ImageFolderUploadForm
<ImageUploadFolderForm
onClose={handleClose}
projectId={projectId}
parentId={parentId}
/>
</DialogContent>
</Dialog>

View File

@ -1,19 +1,11 @@
import { useState } from 'react';
import { Button } from '../ui/button';
import { cn } from '@/lib/utils';
import { uploadImageFolderZip } from '@/api/imageApi';
import { uploadImageZip } from '@/api/imageApi';
import useAuthStore from '@/stores/useAuthStore';
import { X } from 'lucide-react';
export default function ImageFolderZipUploadForm({
onClose,
projectId,
parentId,
}: {
onClose: () => void;
projectId: number;
parentId: number;
}) {
export default function ImageUploadZipForm({ onClose, projectId }: { onClose: () => void; projectId: number }) {
const profile = useAuthStore((state) => state.profile);
const memberId = profile?.id || 0;
@ -58,7 +50,7 @@ export default function ImageFolderZipUploadForm({
setIsUploading(true);
setProgress(0);
await uploadImageFolderZip(memberId, projectId, file, parentId)
await uploadImageZip(memberId, projectId, file)
.then(() => {
setProgress(100);
})

View File

@ -1,15 +1,9 @@
import React from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
import { Plus } from 'lucide-react';
import ImageFolderZipUploadForm from './ImageFolderZipUploadForm';
import ImageUploadZipForm from './ImageUploadZipForm';
export default function ImageFolderZipUploadModal({
projectId,
parentId = 0,
}: {
projectId: number;
parentId: number;
}) {
export default function ImageUploadZipModal({ projectId }: { projectId: number }) {
const [isOpen, setIsOpen] = React.useState(false);
const handleOpen = () => setIsOpen(true);
@ -30,10 +24,9 @@ export default function ImageFolderZipUploadModal({
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader title="폴더 압축파일 업로드" />
<ImageFolderZipUploadForm
<ImageUploadZipForm
onClose={handleClose}
projectId={projectId}
parentId={parentId}
/>
</DialogContent>
</Dialog>

View File

@ -1,9 +1,10 @@
import { Project } from '@/types';
import { SquarePenIcon, Upload } from 'lucide-react';
import { Play, SquarePenIcon, Upload } from 'lucide-react';
import ProjectFileItem from './ProjectFileItem';
import ProjectDirectoryItem from './ProjectDirectoryItem';
import useFolderQuery from '@/queries/folders/useFolderQuery';
import useCanvasStore from '@/stores/useCanvasStore';
import { Button } from '../ui/button';
export default function ProjectStructure({ project }: { project: Project }) {
const image = useCanvasStore((state) => state.image);
@ -22,7 +23,8 @@ export default function ProjectStructure({ project }: { project: Project }) {
];
return (
<div className="flex h-full flex-col overflow-y-auto px-1 pb-2">
<div className="flex h-full flex-col justify-between">
<div className="flex flex-col overflow-y-auto px-1 pb-2">
<header className="flex w-full items-center gap-2 rounded p-1">
<div className="flex w-full items-center gap-1 overflow-hidden pr-1">
<h3 className="caption overflow-hidden text-ellipsis whitespace-nowrap">{project.type}</h3>
@ -63,5 +65,20 @@ export default function ProjectStructure({ project }: { project: Project }) {
</div>
)}
</div>
<div className="flex p-2.5">
<Button
variant="outlinePrimary"
className="w-full"
onClick={() => console.log('autolabel')}
>
<Play
size={16}
className="mr-1"
/>
<span> </span>
</Button>
</div>
</div>
);
}

View File

@ -14,9 +14,7 @@ export default function WorkspaceSidebar({ workspaceName, projects }: { workspac
const setSidebarSize = useCanvasStore((state) => state.setSidebarSize);
const navigate = useNavigate();
const { workspaceId } = useParams<{ workspaceId: string }>();
// const [selectedProjectId, setSelectedProjectId] = useState<string | undefined>();
const handleSelectProject = (projectId: string) => {
// setSelectedProjectId(projectId);
navigate(`${webPath.workspace()}/${workspaceId}/${projectId}`);
};

View File

@ -1,5 +1,5 @@
import ImageFolderUploadModal from '@/components/ImageFolderUploadModal';
import ImageFolderZipUploadModal from '@/components/ImageFolderZipUploadModal';
import ImageUploadFolderModal from '@/components/ImageUploadFolderModal';
import ImageUploadZipModal from '@/components/ImageUploadZipModal';
import { useParams } from 'react-router-dom';
export default function ImageFolderUploadTest() {
@ -8,14 +8,8 @@ export default function ImageFolderUploadTest() {
return (
<div className="min-h-screen w-full">
<ImageFolderUploadModal
projectId={projectId}
parentId={0}
/>
<ImageFolderZipUploadModal
projectId={projectId}
parentId={0}
/>
<ImageUploadFolderModal projectId={projectId} />
<ImageUploadZipModal projectId={projectId} />
</div>
);
}