Refactor: 레이블링 페이지 레이아웃 변경

This commit is contained in:
정현조 2024-09-13 08:23:15 +09:00
parent ebc548a1d9
commit 7ca4012f68
3 changed files with 161 additions and 70 deletions

View File

@ -1,65 +1,114 @@
import { Outlet } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { useParams, Outlet } from 'react-router-dom';
import Header from '../Header';
import { Label, Project } from '@/types';
import { Label, Project, DirectoryItem, FileItem } from '@/types';
import { ResizablePanelGroup, ResizablePanel } from '../ui/resizable';
import WorkspaceSidebar from '../WorkspaceSidebar';
import WorkspaceLabelBar from '../WorkspaceLabelBar';
import { fetchFolderApi } from '@/api/folderApi';
import { getWorkspaceApi } from '@/api/workspaceApi';
import { getAllProjectsApi } from '@/api/projectApi';
import useAuthStore from '@/stores/useAuthStore';
export default function WorkspaceLayout() {
const workspace: { name: string; projects: Project[] } = {
name: 'Workspace-name-1',
projects: [
{
id: 1,
name: 'project-111',
type: 'Segmentation',
children: [
{
id: 12,
type: 'directory',
name: 'directory-1',
children: [
{
id: 123,
type: 'directory',
name: 'directory-2',
children: [
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' },
{ id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'idle' },
],
},
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'idle' },
{ id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'done' },
],
},
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' },
],
},
{
id: 2,
name: 'very-extremely-long-long-project-name-222',
type: 'Classification',
children: [
{
id: 23,
type: 'directory',
name: 'this-is-my-very-very-long-directory-name-that-will-be-overflow',
children: [
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' },
{ id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'done' },
],
},
{
id: 1,
url: '',
type: 'image',
name: 'wow-this-is-my-very-very-long-image-name-so-this-will-be-overflow-too.jpg',
status: 'idle',
},
],
},
],
const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();
const [workspace, setWorkspace] = useState<{ name: string; projects: Project[] }>({
name: '',
projects: [],
});
const profile = useAuthStore((state) => state.profile);
const memberId = profile?.id || 0;
useEffect(() => {
const fetchWorkspaceData = async (workspaceId: number, memberId: number) => {
try {
const workspaceResponse = await getWorkspaceApi(workspaceId, memberId);
if (workspaceResponse.isSuccess) {
const workspaceTitle = workspaceResponse.data.title;
setWorkspace((prev) => ({
...prev,
name: workspaceTitle,
}));
fetchProjects(workspaceId, memberId);
}
} catch (error) {
console.error('워크스페이스 조회 실패:', error);
}
};
const fetchProjects = async (workspaceId: number, memberId: number) => {
try {
const projectResponse = await getAllProjectsApi(workspaceId, memberId);
if (projectResponse.isSuccess) {
const projects = await Promise.all(
projectResponse.data.workspaceResponses.map(async (project) => {
const children = await fetchFolderWithImages(project.id, memberId);
return {
id: project.id,
name: project.title,
type: capitalizeType(project.projectType),
children,
};
})
);
setWorkspace((prev) => ({
...prev,
projects: projects as Project[],
}));
}
} catch (error) {
console.error('프로젝트 목록 조회 실패:', error);
}
};
const fetchFolderWithImages = async (projectId: number, memberId: number): Promise<DirectoryItem[]> => {
try {
const folderResponse = await fetchFolderApi(projectId, 0, memberId);
if (folderResponse.isSuccess) {
const files: FileItem[] = folderResponse.data.images.map((image) => ({
id: image.id,
name: image.imageTitle,
url: image.imageUrl,
type: 'image',
status: image.status === 'COMPLETED' ? 'done' : 'idle',
}));
return [
{
id: folderResponse.data.id,
name: folderResponse.data.title,
type: 'directory',
children: files,
},
];
}
return [];
} catch (error) {
console.error('폴더 및 이미지 조회 실패:', error);
return [];
}
};
const capitalizeType = (
type: 'classification' | 'detection' | 'segmentation'
): 'Classification' | 'Detection' | 'Segmentation' => {
switch (type) {
case 'classification':
return 'Classification';
case 'detection':
return 'Detection';
case 'segmentation':
return 'Segmentation';
default:
throw new Error(`Unknown project type: ${type}`);
}
};
if (workspaceId && memberId) {
fetchWorkspaceData(Number(workspaceId), memberId);
}
}, [workspaceId, projectId, memberId]);
const labels: Label[] = [
{
id: 1,

View File

@ -1,6 +1,7 @@
import { cn } from '@/lib/utils';
import { FileItem } from '@/types';
import { Check, Image, Minus } from 'lucide-react';
import useCanvasStore from '@/stores/useCanvasStore';
export default function ProjectFileItem({
className = '',
@ -12,6 +13,19 @@ export default function ProjectFileItem({
depth?: number;
}) {
const paddingLeft = depth * 12;
const changeImage = useCanvasStore((state) => state.changeImage);
const handleClick = () => {
changeImage(item.url, [
{
id: item.id,
name: item.name,
type: 'rect',
color: '#FF0000',
coordinates: [],
},
]);
};
return (
<button
@ -19,6 +33,7 @@ export default function ProjectFileItem({
style={{
paddingLeft,
}}
onClick={handleClick}
>
<div className="flex items-center">
<Image

View File

@ -1,10 +1,22 @@
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { SquarePen } from 'lucide-react';
import { ResizableHandle, ResizablePanel } from '../ui/resizable';
import ProjectStructure from './ProjectStructure';
import { Button } from '../ui/button';
import { Project } from '@/types';
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '../ui/select';
import ProjectCreateModal from '../ProjectCreateModal';
export default function WorkspaceSidebar({ workspaceName, projects }: { workspaceName: string; projects: Project[] }) {
const navigate = useNavigate();
const { workspaceId } = useParams<{ workspaceId: string }>();
const [selectedProjectId, setSelectedProjectId] = useState<string | undefined>();
const handleSelectProject = (projectId: string) => {
setSelectedProjectId(projectId);
navigate(`/workspace/${workspaceId}/project/${projectId}`);
};
return (
<>
<ResizablePanel
@ -21,17 +33,32 @@ export default function WorkspaceSidebar({ workspaceName, projects }: { workspac
<button>
<SquarePen size={16} />
</button>
<Button
variant="outline"
size="xs"
className="caption border-gray-800 bg-gray-100"
onClick={() => console.log('New project')}
>
</Button>
<ProjectCreateModal
onSubmit={(data) => console.log('프로젝트 생성:', data)}
buttonClass="caption border-gray-800 bg-gray-100 flex items-center gap-2"
/>
</header>
<div>
<div className="p-2">
<Select onValueChange={handleSelectProject}>
<SelectTrigger>
<SelectValue placeholder="프로젝트를 선택해주세요" />
</SelectTrigger>
<SelectContent>
{projects.map((project) => (
<SelectItem
key={project.id}
value={project.id.toString()}
>
{project.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
{projects
.filter((project) => project.id.toString() === selectedProjectId)
.map((project) => (
<ProjectStructure
key={project.id}
project={project}