Merge branch 'fe/develop' of https://lab.ssafy.com/s11-s-project/S11P21S002 into fe/refactor/admin-page
This commit is contained in:
commit
05f8a760ed
@ -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);
|
||||
formData.append('folderZip', 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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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(() => {
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
})
|
@ -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>
|
@ -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);
|
||||
})
|
@ -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>
|
@ -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,46 +23,62 @@ export default function ProjectStructure({ project }: { project: Project }) {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex h-full 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>
|
||||
</div>
|
||||
<button
|
||||
className="flex gap-1"
|
||||
onClick={() => console.log('edit project')}
|
||||
<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>
|
||||
</div>
|
||||
<button
|
||||
className="flex gap-1"
|
||||
onClick={() => console.log('edit project')}
|
||||
>
|
||||
<SquarePenIcon size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="flex gap-1"
|
||||
onClick={() => console.log('upload image')}
|
||||
>
|
||||
<Upload size={16} />
|
||||
</button>
|
||||
</header>
|
||||
{folderData.children.length === 0 && folderData.images.length === 0 ? (
|
||||
<div className="body-small flex h-full select-none items-center justify-center text-gray-400">
|
||||
빈 프로젝트입니다.
|
||||
</div>
|
||||
) : (
|
||||
<div className="caption flex flex-col">
|
||||
{folderData.children.map((item) => (
|
||||
<ProjectDirectoryItem
|
||||
key={`${project.id}-${item.title}`}
|
||||
item={item}
|
||||
initialExpanded={true}
|
||||
/>
|
||||
))}
|
||||
{folderData.images.map((item) => (
|
||||
<ProjectFileItem
|
||||
key={`${project.id}-${item.imageTitle}`}
|
||||
item={item}
|
||||
selected={image?.id === item.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex p-2.5">
|
||||
<Button
|
||||
variant="outlinePrimary"
|
||||
className="w-full"
|
||||
onClick={() => console.log('autolabel')}
|
||||
>
|
||||
<SquarePenIcon size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="flex gap-1"
|
||||
onClick={() => console.log('upload image')}
|
||||
>
|
||||
<Upload size={16} />
|
||||
</button>
|
||||
</header>
|
||||
{folderData.children.length === 0 && folderData.images.length === 0 ? (
|
||||
<div className="body-small flex h-full select-none items-center justify-center text-gray-400">
|
||||
빈 프로젝트입니다.
|
||||
</div>
|
||||
) : (
|
||||
<div className="caption flex flex-col">
|
||||
{folderData.children.map((item) => (
|
||||
<ProjectDirectoryItem
|
||||
key={`${project.id}-${item.title}`}
|
||||
item={item}
|
||||
initialExpanded={true}
|
||||
/>
|
||||
))}
|
||||
{folderData.images.map((item) => (
|
||||
<ProjectFileItem
|
||||
key={`${project.id}-${item.imageTitle}`}
|
||||
item={item}
|
||||
selected={image?.id === item.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Play
|
||||
size={16}
|
||||
className="mr-1"
|
||||
/>
|
||||
<span>자동 레이블링</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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}`);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user