Feat: 프로젝트 캔버스 사이드바 상태 연결 - S11P21S002-193
This commit is contained in:
parent
9e3f7764c7
commit
584788c260
@ -1,12 +1,8 @@
|
||||
import api from '@/api/axiosConfig';
|
||||
import { FolderRequest, FolderResponse } from '@/types';
|
||||
|
||||
export async function getFolder(projectId: number, folderId: number, memberId: number) {
|
||||
return api
|
||||
.get<FolderResponse>(`/projects/${projectId}/folders/${folderId}`, {
|
||||
params: { memberId },
|
||||
})
|
||||
.then(({ data }) => data);
|
||||
export async function getFolder(projectId: string, folderId: number) {
|
||||
return api.get<FolderResponse>(`/projects/${projectId}/folders/${folderId}`).then(({ data }) => data);
|
||||
}
|
||||
|
||||
export async function updateFolder(projectId: number, folderId: number, memberId: number, folderData: FolderRequest) {
|
||||
|
@ -17,9 +17,9 @@ export default function ImageCanvas() {
|
||||
const stageRef = useRef<Konva.Stage>(null);
|
||||
const dragLayerRef = useRef<Konva.Layer>(null);
|
||||
const scale = useRef<number>(0);
|
||||
const imageUrl = '/sample.jpg';
|
||||
const imageUrl = useCanvasStore((state) => state.image);
|
||||
const labels = useCanvasStore((state) => state.labels) ?? [];
|
||||
const [image, imageStatus] = useImage(imageUrl);
|
||||
const [image] = useImage(imageUrl);
|
||||
const [rectPoints, setRectPoints] = useState<[number, number][]>([]);
|
||||
const [polygonPoints, setPolygonPoints] = useState<[number, number][]>([]);
|
||||
const drawState = useCanvasStore((state) => state.drawState);
|
||||
@ -183,6 +183,17 @@ export default function ImageCanvas() {
|
||||
return { x: scale.current, y: scale.current };
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!image) {
|
||||
scale.current = 0;
|
||||
return;
|
||||
}
|
||||
const widthRatio = stageWidth / image!.width;
|
||||
const heightRatio = stageHeight / image!.height;
|
||||
|
||||
scale.current = Math.min(widthRatio, heightRatio);
|
||||
}, [image, stageHeight, stageWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!stageRef.current) return;
|
||||
stageRef.current.container().style.cursor = drawState === 'pointer' ? 'default' : 'crosshair';
|
||||
@ -190,9 +201,9 @@ export default function ImageCanvas() {
|
||||
if (drawState !== 'pointer') {
|
||||
setSelectedLabelId(null);
|
||||
}
|
||||
}, [drawState]);
|
||||
}, [drawState, setSelectedLabelId]);
|
||||
|
||||
return imageStatus === 'loaded' ? (
|
||||
return image ? (
|
||||
<div>
|
||||
<Stage
|
||||
ref={stageRef}
|
||||
|
@ -2,7 +2,6 @@ import * as React from 'react';
|
||||
import ProjectCreateForm, { ProjectCreateFormValues } from './ProjectCreateForm';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { ProjectRequest } from '@/types';
|
||||
|
||||
interface ProjectCreateModalProps {
|
||||
@ -16,19 +15,6 @@ export default function ProjectCreateModal({ onSubmit, buttonClass = '' }: Proje
|
||||
const handleOpen = () => setIsOpen(true);
|
||||
const handleClose = () => setIsOpen(false);
|
||||
|
||||
const formatLabelType = (
|
||||
labelType: 'Classification' | 'Detection' | 'Segmentation'
|
||||
): ProjectRequest['projectType'] => {
|
||||
switch (labelType) {
|
||||
case 'Classification':
|
||||
return 'classification';
|
||||
case 'Detection':
|
||||
return 'detection';
|
||||
case 'Segmentation':
|
||||
return 'segmentation';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
@ -38,10 +24,10 @@ export default function ProjectCreateModal({ onSubmit, buttonClass = '' }: Proje
|
||||
<Button
|
||||
variant="outlinePrimary"
|
||||
className={`${buttonClass}`}
|
||||
size={'xs'}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<Plus size={16} />
|
||||
<span>프로젝트 추가</span>
|
||||
<span>새 프로젝트</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
@ -50,7 +36,8 @@ export default function ProjectCreateModal({ onSubmit, buttonClass = '' }: Proje
|
||||
onSubmit={(data: ProjectCreateFormValues) => {
|
||||
const formattedData: ProjectRequest = {
|
||||
title: data.projectName,
|
||||
projectType: formatLabelType(data.labelType),
|
||||
projectType: (data.labelType.charAt(0).toUpperCase() +
|
||||
data.labelType.slice(1)) as ProjectRequest['projectType'],
|
||||
};
|
||||
onSubmit(formattedData);
|
||||
handleClose();
|
||||
|
@ -1,58 +1,16 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, Outlet } from 'react-router-dom';
|
||||
import Header from '../Header';
|
||||
import { Label, Project } from '@/types';
|
||||
import { Project } from '@/types';
|
||||
import { ResizablePanelGroup } from '../ui/resizable';
|
||||
// import { ResizablePanel } from '../ui/resizable';
|
||||
import WorkspaceSidebar from '../WorkspaceSidebar';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import useCanvasStore from '@/stores/useCanvasStore';
|
||||
import useFolderQuery from '@/queries/folders/useFolderQuery';
|
||||
import useWorkspaceQuery from '@/queries/workspaces/useWorkspaceQuery';
|
||||
import useProjectListQuery from '@/queries/projects/useProjectListQuery';
|
||||
|
||||
const mockLabels: Label[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Label 1',
|
||||
color: '#FFaa33',
|
||||
coordinates: [
|
||||
[700, 100],
|
||||
[1200, 800],
|
||||
],
|
||||
type: 'rect',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Label 2',
|
||||
color: '#aaFF55',
|
||||
coordinates: [
|
||||
[200, 200],
|
||||
[400, 200],
|
||||
[500, 500],
|
||||
[400, 800],
|
||||
[200, 800],
|
||||
[100, 500],
|
||||
],
|
||||
type: 'polygon',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Label 3',
|
||||
color: '#77aaFF',
|
||||
coordinates: [
|
||||
[1000, 1000],
|
||||
[1800, 1800],
|
||||
],
|
||||
type: 'rect',
|
||||
},
|
||||
];
|
||||
|
||||
export default function WorkspaceLayout() {
|
||||
const setLabels = useCanvasStore((state) => state.setLabels);
|
||||
const params = useParams<{ workspaceId: string; projectId: string }>();
|
||||
const workspaceId = Number(params.workspaceId);
|
||||
const projectId = Number(params.projectId);
|
||||
const [workspace, setWorkspace] = useState<{ name: string; projects: Project[] }>({
|
||||
name: '',
|
||||
projects: [],
|
||||
@ -61,7 +19,6 @@ export default function WorkspaceLayout() {
|
||||
const memberId = profile?.id || 0;
|
||||
const { data: workspaceData } = useWorkspaceQuery(workspaceId, memberId);
|
||||
const { data: projectListData } = useProjectListQuery(workspaceId, memberId);
|
||||
const { data: folderData } = useFolderQuery(projectId, 0, memberId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspaceData) return;
|
||||
@ -73,7 +30,6 @@ export default function WorkspaceLayout() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectListData) return;
|
||||
console.log(folderData);
|
||||
const projects = projectListData.workspaceResponses.map(
|
||||
(project): Project => ({
|
||||
id: project.id,
|
||||
@ -89,11 +45,7 @@ export default function WorkspaceLayout() {
|
||||
...prev,
|
||||
projects,
|
||||
}));
|
||||
}, [folderData, projectListData]);
|
||||
|
||||
useEffect(() => {
|
||||
setLabels(mockLabels);
|
||||
}, [setLabels]);
|
||||
}, [projectListData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { DirectoryItem } from '@/types';
|
||||
import { ChildFolder } from '@/types';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import ProjectFileItem from './ProjectFileItem';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export default function ProjectDirectoryItem({
|
||||
className = '',
|
||||
item,
|
||||
depth = 1,
|
||||
depth = 0,
|
||||
initialExpanded = false,
|
||||
}: {
|
||||
className?: string;
|
||||
item: DirectoryItem;
|
||||
item: ChildFolder;
|
||||
depth?: number;
|
||||
initialExpanded?: boolean;
|
||||
}) {
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const [isExpanded, setIsExpanded] = useState(initialExpanded);
|
||||
const paddingLeft = depth * 12;
|
||||
|
||||
return (
|
||||
@ -31,28 +32,9 @@ export default function ProjectDirectoryItem({
|
||||
className={`stroke-gray-500 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{item.name}</span>
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{item.title}</span>
|
||||
</div>
|
||||
{item.children?.map((child) => {
|
||||
const childProps = {
|
||||
className: isExpanded ? '' : 'hidden',
|
||||
depth: depth + 1,
|
||||
};
|
||||
|
||||
return child.type === 'directory' ? (
|
||||
<ProjectDirectoryItem
|
||||
key={`${item.name}-${child.name}`}
|
||||
item={child}
|
||||
{...childProps}
|
||||
/>
|
||||
) : (
|
||||
<ProjectFileItem
|
||||
key={`${item.name}-${child.name}`}
|
||||
item={child}
|
||||
{...childProps}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* TODO: nested 폴더 구조 적용 */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { FileItem } from '@/types';
|
||||
import { ImageResponse } from '@/types';
|
||||
import { Check, Image, Minus } from 'lucide-react';
|
||||
import useCanvasStore from '@/stores/useCanvasStore';
|
||||
|
||||
@ -7,29 +7,38 @@ export default function ProjectFileItem({
|
||||
className = '',
|
||||
item,
|
||||
depth = 1,
|
||||
selected,
|
||||
}: {
|
||||
className?: string;
|
||||
item: FileItem;
|
||||
item: ImageResponse;
|
||||
depth?: number;
|
||||
selected: boolean;
|
||||
}) {
|
||||
const paddingLeft = depth * 12;
|
||||
const changeImage = useCanvasStore((state) => state.changeImage);
|
||||
|
||||
const handleClick = () => {
|
||||
changeImage(item.url, [
|
||||
// TODO: fetch image
|
||||
changeImage(item.imageUrl, [
|
||||
{
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
name: item.imageTitle,
|
||||
type: 'rect',
|
||||
color: '#FF0000',
|
||||
coordinates: [],
|
||||
coordinates: [
|
||||
[0, 0],
|
||||
[100, 100],
|
||||
],
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn('flex w-full gap-2 rounded-md py-0.5 pr-1 hover:bg-gray-200', className)}
|
||||
className={cn(
|
||||
`flex w-full gap-2 rounded-md py-0.5 pr-1 ${selected ? 'bg-gray-200' : 'hover:bg-gray-100'}`,
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
paddingLeft,
|
||||
}}
|
||||
@ -41,17 +50,17 @@ export default function ProjectFileItem({
|
||||
className="stroke-gray-500"
|
||||
/>
|
||||
</div>
|
||||
<span className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left">{item.name}</span>
|
||||
{item.status === 'idle' ? (
|
||||
<Minus
|
||||
size={16}
|
||||
className="shrink-0 stroke-gray-400"
|
||||
/>
|
||||
) : (
|
||||
<span className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left">{item.imageTitle}</span>
|
||||
{item.status === 'COMPLETED' ? (
|
||||
<Check
|
||||
size={16}
|
||||
className="shrink-0 stroke-green-500"
|
||||
/>
|
||||
) : (
|
||||
<Minus
|
||||
size={16}
|
||||
className="shrink-0 stroke-gray-400"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
@ -1,29 +1,19 @@
|
||||
import { Project } from '@/types';
|
||||
import { ChevronRight, SquarePenIcon, Upload } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { SquarePenIcon, Upload } from 'lucide-react';
|
||||
import ProjectFileItem from './ProjectFileItem';
|
||||
import ProjectDirectoryItem from './ProjectDirectoryItem';
|
||||
import useFolderQuery from '@/queries/folders/useFolderQuery';
|
||||
import useCanvasStore from '@/stores/useCanvasStore';
|
||||
|
||||
export default function ProjectStructure({ project }: { project: Project }) {
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const image = useCanvasStore((state) => state.image);
|
||||
const { data: folderData } = useFolderQuery(project.id.toString(), 0);
|
||||
|
||||
return (
|
||||
<div className="flex select-none flex-col px-1 pb-2">
|
||||
<header className="flex w-full items-center gap-2 rounded px-1 hover:bg-gray-200">
|
||||
<div
|
||||
className="flex w-full cursor-pointer items-center gap-1 overflow-hidden pr-1"
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
>
|
||||
<button>
|
||||
<ChevronRight
|
||||
size={16}
|
||||
className={`stroke-gray-500 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
<div className="flex flex-col overflow-hidden">
|
||||
<h2 className="body-small-strong overflow-hidden text-ellipsis whitespace-nowrap">{project.name}</h2>
|
||||
<h3 className="caption overflow-hidden text-ellipsis whitespace-nowrap">{project.type}</h3>
|
||||
</div>
|
||||
<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"
|
||||
@ -38,23 +28,28 @@ export default function ProjectStructure({ project }: { project: Project }) {
|
||||
<Upload size={16} />
|
||||
</button>
|
||||
</header>
|
||||
<div className={`caption flex flex-col ${isExpanded ? '' : 'hidden'}`}>
|
||||
{project.children.map((item) =>
|
||||
item.type === 'directory' ? (
|
||||
{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.name}`}
|
||||
key={`${project.id}-${item.title}`}
|
||||
item={item}
|
||||
className={isExpanded ? '' : 'hidden'}
|
||||
initialExpanded={true}
|
||||
/>
|
||||
) : (
|
||||
))}
|
||||
{folderData.images.map((item) => (
|
||||
<ProjectFileItem
|
||||
key={`${project.id}-${item.name}`}
|
||||
key={`${project.id}-${item.imageTitle}`}
|
||||
item={item}
|
||||
className={isExpanded ? '' : 'hidden'}
|
||||
selected={image === item.imageUrl}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { SquarePen } from 'lucide-react';
|
||||
import { ResizableHandle, ResizablePanel } from '../ui/resizable';
|
||||
@ -7,15 +6,18 @@ import { Project } from '@/types';
|
||||
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '../ui/select';
|
||||
import ProjectCreateModal from '../ProjectCreateModal';
|
||||
import useCanvasStore from '@/stores/useCanvasStore';
|
||||
import { webPath } from '@/router';
|
||||
|
||||
export default function WorkspaceSidebar({ workspaceName, projects }: { workspaceName: string; projects: Project[] }) {
|
||||
const { projectId: selectedProjectId } = useParams<{ projectId: string }>();
|
||||
const selectedProject = projects.find((project) => project.id.toString() === selectedProjectId);
|
||||
const setSidebarSize = useCanvasStore((state) => state.setSidebarSize);
|
||||
const navigate = useNavigate();
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>();
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string | undefined>();
|
||||
// const [selectedProjectId, setSelectedProjectId] = useState<string | undefined>();
|
||||
const handleSelectProject = (projectId: string) => {
|
||||
setSelectedProjectId(projectId);
|
||||
navigate(`/workspace/${workspaceId}/project/${projectId}`);
|
||||
// setSelectedProjectId(projectId);
|
||||
navigate(`${webPath.workspace()}/${workspaceId}/project/${projectId}`);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -24,7 +26,7 @@ export default function WorkspaceSidebar({ workspaceName, projects }: { workspac
|
||||
minSize={10}
|
||||
maxSize={35}
|
||||
defaultSize={20}
|
||||
className="flex h-full flex-col bg-gray-100"
|
||||
className="flex h-full flex-col bg-gray-50"
|
||||
onResize={(size) => setSidebarSize(size)}
|
||||
>
|
||||
<header className="body flex w-full items-center gap-2 p-2">
|
||||
@ -34,11 +36,14 @@ export default function WorkspaceSidebar({ workspaceName, projects }: { workspac
|
||||
</button>
|
||||
<ProjectCreateModal
|
||||
onSubmit={(data) => console.log('프로젝트 생성:', data)}
|
||||
buttonClass="caption border-gray-800 bg-gray-100 flex items-center gap-2"
|
||||
buttonClass="caption border-primary bg-gray-50"
|
||||
/>
|
||||
</header>
|
||||
<div className="p-2">
|
||||
<Select onValueChange={handleSelectProject}>
|
||||
<Select
|
||||
onValueChange={handleSelectProject}
|
||||
defaultValue={selectedProjectId}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="프로젝트를 선택해주세요" />
|
||||
</SelectTrigger>
|
||||
@ -54,16 +59,7 @@ export default function WorkspaceSidebar({ workspaceName, projects }: { workspac
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
{projects
|
||||
.filter((project) => project.id.toString() === selectedProjectId)
|
||||
.map((project) => (
|
||||
<ProjectStructure
|
||||
key={project.id}
|
||||
project={project}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{selectedProject && <ProjectStructure project={selectedProject} />}
|
||||
</ResizablePanel>
|
||||
<ResizableHandle className="bg-gray-300" />
|
||||
</>
|
||||
|
@ -1,30 +1,3 @@
|
||||
// import React from 'react';
|
||||
// import ReactDOM from 'react-dom/client';
|
||||
// import App from './App.tsx';
|
||||
// import './index.css';
|
||||
|
||||
// async function enableMocking() {
|
||||
// if (!import.meta.env.DEV) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const { worker } = await import('./mocks/browser.ts');
|
||||
// await worker.start();
|
||||
// console.log('[MSW] Mocking enabled. Service Worker is running.');
|
||||
// } catch (error) {
|
||||
// console.error('[MSW] Failed to start the Service Worker:', error);
|
||||
// }
|
||||
// }
|
||||
|
||||
// enableMocking().then(() => {
|
||||
// ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
// <React.StrictMode>
|
||||
// <App />
|
||||
// </React.StrictMode>
|
||||
// );
|
||||
// });
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
|
@ -1,14 +1,26 @@
|
||||
import ImageCanvas from '@/components/ImageCanvas';
|
||||
import { ResizablePanel } from '@/components/ui/resizable';
|
||||
import WorkspaceLabelBar from '@/components/WorkspaceLabelBar';
|
||||
import useCanvasStore from '@/stores/useCanvasStore';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default function LabelCanvas() {
|
||||
const imageUrl = useCanvasStore((state) => state.image);
|
||||
|
||||
return (
|
||||
<ResizablePanel className="flex w-full items-center">
|
||||
<main className="h-full grow">
|
||||
<ImageCanvas />
|
||||
</main>
|
||||
<WorkspaceLabelBar />
|
||||
<Suspense fallback={<div></div>}>
|
||||
<main className="h-full grow">
|
||||
{imageUrl ? (
|
||||
<ImageCanvas />
|
||||
) : (
|
||||
<div className="body flex h-full w-full select-none items-center justify-center bg-gray-200 text-gray-400">
|
||||
이미지를 선택하세요.
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
<WorkspaceLabelBar />
|
||||
</Suspense>
|
||||
</ResizablePanel>
|
||||
);
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ function HeaderSection({
|
||||
<div className="flex flex-col">
|
||||
<div className="flex gap-3">
|
||||
<ProjectCreateModal
|
||||
buttonClass="mt-4 flex items-center gap-2"
|
||||
buttonClass="mt-4 flex items-center gap-2 body-small-strong"
|
||||
onSubmit={onCreateProject}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { getFolder } from '@/api/folderApi';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
|
||||
export default function useFolderQuery(projectId: number, folderId: number, memberId: number) {
|
||||
export default function useFolderQuery(projectId: string, folderId: number) {
|
||||
return useSuspenseQuery({
|
||||
queryKey: ['folder', projectId, folderId, memberId],
|
||||
queryFn: () => getFolder(projectId, folderId, memberId),
|
||||
queryKey: ['folder', projectId, folderId],
|
||||
queryFn: () => getFolder(projectId, folderId),
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user