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

This commit is contained in:
정현조 2024-09-25 17:39:25 +09:00
commit aace66275f
14 changed files with 68 additions and 58 deletions

View File

@ -6,9 +6,9 @@ export async function reissueToken() {
} }
export async function getProfile() { export async function getProfile() {
return api return api.get<MemberResponse>('/auth/profile').then(({ data }) => data);
.get<MemberResponse>('/auth/profile', { }
withCredentials: true,
}) export async function logout() {
.then(({ data }) => data); return api.post('/auth/logout').then(({ data }) => data);
} }

View File

@ -27,9 +27,8 @@ api.interceptors.response.use(
return api return api
.post<RefreshTokenResponse>(REFRESH_URL) .post<RefreshTokenResponse>(REFRESH_URL)
.then(({ data }) => { .then(({ data }) => {
console.log(data);
const { accessToken } = data; const { accessToken } = data;
useAuthStore.getState().setLoggedIn(true, accessToken); useAuthStore.getState().setToken(accessToken);
if (error.config) { if (error.config) {
return api(error.config); return api(error.config);
} }

View File

@ -10,14 +10,6 @@ export async function saveImageLabels(
return api.post(`/projects/${projectId}/images/${imageId}/label`, data).then(({ data }) => data); return api.post(`/projects/${projectId}/images/${imageId}/label`, data).then(({ data }) => data);
} }
export async function runAutoLabel(projectId: number, memberId: number) { export async function runAutoLabel(projectId: number, modelId = 1) {
return api return api.post(`/projects/${projectId}/auto`, { modelId }).then(({ data }) => data);
.post(
`/projects/${projectId}/label/auto`,
{},
{
params: { memberId },
}
)
.then(({ data }) => data);
} }

View File

@ -6,4 +6,9 @@ export default {
component: CanvasControlBar, component: CanvasControlBar,
}; };
export const Default = () => <CanvasControlBar saveJson={() => {}} />; export const Default = () => (
<CanvasControlBar
saveJson={() => {}}
projectType="segmentation"
/>
);

View File

@ -2,16 +2,22 @@ import useCanvasStore from '@/stores/useCanvasStore';
import { LucideIcon, MousePointer2, PenTool, Save, Square } from 'lucide-react'; import { LucideIcon, MousePointer2, PenTool, Save, Square } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
export default function CanvasControlBar({ saveJson }: { saveJson: () => void }) { export default function CanvasControlBar({
saveJson,
projectType,
}: {
saveJson: () => void;
projectType: 'classification' | 'detection' | 'segmentation';
}) {
const drawState = useCanvasStore((state) => state.drawState); const drawState = useCanvasStore((state) => state.drawState);
const setDrawState = useCanvasStore((state) => state.setDrawState); const setDrawState = useCanvasStore((state) => state.setDrawState);
const buttonBaseClassName = 'rounded-lg p-2 transition-colors '; const buttonBaseClassName = 'rounded-lg p-2 transition-colors ';
const buttonClassName = 'hover:bg-gray-100'; const buttonClassName = 'hover:bg-gray-100';
const activeButtonClassName = 'bg-primary stroke-white'; const activeButtonClassName = 'bg-primary stroke-white';
const controls: { [key: string]: LucideIcon } = { const controls: { [key: string]: LucideIcon } = {
pointer: MousePointer2, pointer: MousePointer2,
rect: Square, ...(projectType === 'segmentation' ? { pen: PenTool } : { rect: Square }),
pen: PenTool,
}; };
return ( return (
@ -31,7 +37,7 @@ export default function CanvasControlBar({ saveJson }: { saveJson: () => void })
</button> </button>
); );
})} })}
<div className="h-5 w-0.5 rounded bg-gray-400" />
<button <button
className={cn(buttonClassName, buttonBaseClassName)} className={cn(buttonClassName, buttonBaseClassName)}
onClick={saveJson} onClick={saveJson}

View File

@ -20,7 +20,7 @@ export default function ImageCanvas() {
const selectedLabelId = useCanvasStore((state) => state.selectedLabelId); const selectedLabelId = useCanvasStore((state) => state.selectedLabelId);
const setSelectedLabelId = useCanvasStore((state) => state.setSelectedLabelId); const setSelectedLabelId = useCanvasStore((state) => state.setSelectedLabelId);
const sidebarSize = useCanvasStore((state) => state.sidebarSize); const sidebarSize = useCanvasStore((state) => state.sidebarSize);
const stageWidth = window.innerWidth * ((100 - sidebarSize) / 100) - 280; const stageWidth = window.innerWidth * ((100 - sidebarSize) / 100) - 200;
const stageHeight = window.innerHeight - 64; const stageHeight = window.innerHeight - 64;
const stageRef = useRef<Konva.Stage>(null); const stageRef = useRef<Konva.Stage>(null);
const dragLayerRef = useRef<Konva.Layer>(null); const dragLayerRef = useRef<Konva.Layer>(null);
@ -345,7 +345,10 @@ export default function ImageCanvas() {
<Layer ref={dragLayerRef} /> <Layer ref={dragLayerRef} />
</Stage> </Stage>
<CanvasControlBar saveJson={saveJson} /> <CanvasControlBar
saveJson={saveJson}
projectType={project.type}
/>
</div> </div>
) : ( ) : (
<div></div> <div></div>

View File

@ -8,15 +8,15 @@ import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery';
import useCreateWorkspaceQuery from '@/queries/workspaces/useCreateWorkspaceQuery'; import useCreateWorkspaceQuery from '@/queries/workspaces/useCreateWorkspaceQuery';
export default function WorkspaceBrowseLayout() { export default function WorkspaceBrowseLayout() {
const { profile, isLoggedIn } = useAuthStore(); const { profile } = useAuthStore();
const memberId = profile?.id ?? 0; const memberId = profile?.id ?? 0;
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (!isLoggedIn || memberId == 0) { if (memberId == 0) {
navigate('/'); navigate('/');
} }
}, [isLoggedIn, memberId, navigate]); }, [memberId, navigate]);
const { data: workspacesResponse } = useWorkspaceListQuery(memberId ?? 0); const { data: workspacesResponse } = useWorkspaceListQuery(memberId ?? 0);
const createWorkspace = useCreateWorkspaceQuery(); const createWorkspace = useCreateWorkspaceQuery();

View File

@ -7,12 +7,14 @@ import useCanvasStore from '@/stores/useCanvasStore';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { useEffect } from 'react'; import { useEffect } from 'react';
import WorkspaceDropdownMenu from '../WorkspaceDropdownMenu'; import WorkspaceDropdownMenu from '../WorkspaceDropdownMenu';
import useAutoLabelQuery from '@/queries/projects/useAutoLabelQuery';
import useProjectStore from '@/stores/useProjectStore'; import useProjectStore from '@/stores/useProjectStore';
export default function ProjectStructure({ project }: { project: Project }) { export default function ProjectStructure({ project }: { project: Project }) {
const setProject = useProjectStore((state) => state.setProject); const setProject = useProjectStore((state) => state.setProject);
const image = useCanvasStore((state) => state.image); const image = useCanvasStore((state) => state.image);
const { data: folderData, refetch } = useFolderQuery(project.id.toString(), 0); const { data: folderData, refetch } = useFolderQuery(project.id.toString(), 0);
const requestAutoLabel = useAutoLabelQuery();
useEffect(() => { useEffect(() => {
setProject(project); setProject(project);
@ -60,7 +62,17 @@ export default function ProjectStructure({ project }: { project: Project }) {
<Button <Button
variant="outlinePrimary" variant="outlinePrimary"
className="w-full" className="w-full"
onClick={() => console.log('autolabel')} onClick={() => {
requestAutoLabel.mutate(
{ projectId: project.id },
{
onSuccess: refetch,
onError: () => {
alert('자동 레이블링을 요청하는 중 오류가 발생했습니다.');
},
}
);
}}
> >
<Play <Play
size={16} size={16}

View File

@ -4,11 +4,11 @@ import useProfileQuery from '@/queries/auth/useProfileQuery';
export default function useHandleOAuthCallback() { export default function useHandleOAuthCallback() {
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
const accessToken = queryParams.get('accessToken'); const accessToken = queryParams.get('accessToken');
const setLoggedIn = useAuthStore((state) => state.setLoggedIn); const setToken = useAuthStore((state) => state.setToken);
const setProfile = useAuthStore((state) => state.setProfile); const setProfile = useAuthStore((state) => state.setProfile);
if (accessToken) { if (accessToken) {
setLoggedIn(true, accessToken); setToken(accessToken);
} }
const { data: profile } = useProfileQuery(); const { data: profile } = useProfileQuery();

View File

@ -1,25 +1,12 @@
import { useRef } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import GoogleLogo from '@/assets/icons/web_neutral_rd_ctn@1x.png'; import GoogleLogo from '@/assets/icons/web_neutral_rd_ctn@1x.png';
import useAuthStore from '@/stores/useAuthStore'; import useAuthStore from '@/stores/useAuthStore';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { getProfile } from '@/api/authApi';
const BASE_URL = import.meta.env.VITE_API_URL; const BASE_URL = import.meta.env.VITE_API_URL;
export default function Home() { export default function Home() {
const { isLoggedIn, accessToken, setLoggedIn, profile, setProfile } = useAuthStore(); const { accessToken } = useAuthStore();
const hasFetchedProfile = useRef(false);
if (!isLoggedIn && !profile && !hasFetchedProfile.current && accessToken) {
setLoggedIn(true, accessToken);
getProfile().then((data) => {
setProfile(data);
hasFetchedProfile.current = true;
});
}
const handleGoogleSignIn = () => {
window.location.href = `${BASE_URL}/login/oauth2/authorization/google`;
};
return ( return (
<div className="flex h-full flex-col items-center justify-center bg-gray-50 p-8"> <div className="flex h-full flex-col items-center justify-center bg-gray-50 p-8">
@ -42,9 +29,10 @@ export default function Home() {
</p> </p>
</div> </div>
{!isLoggedIn ? ( {!accessToken ? (
<button <Link
onClick={handleGoogleSignIn} to={`${BASE_URL}/login/oauth2/authorization/google`}
replace
className="mb-4 transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-gray-300 active:opacity-80" className="mb-4 transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-gray-300 active:opacity-80"
> >
<img <img
@ -52,7 +40,7 @@ export default function Home() {
alt="Sign in with Google" alt="Sign in with Google"
className="h-auto w-full" className="h-auto w-full"
/> />
</button> // 404 에러 방지 </Link> // 404 에러 방지
) : ( ) : (
<> <>
<Button <Button

View File

@ -4,12 +4,12 @@ import { reissueToken } from '@/api/authApi';
export default function useReissueTokenQuery() { export default function useReissueTokenQuery() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { setLoggedIn } = useAuthStore(); const { setToken } = useAuthStore();
return useMutation({ return useMutation({
mutationFn: reissueToken, mutationFn: reissueToken,
onSuccess: (data) => { onSuccess: (data) => {
setLoggedIn(true, data.accessToken); setToken(data.accessToken);
queryClient.invalidateQueries({ queryKey: ['profile'] }); queryClient.invalidateQueries({ queryKey: ['profile'] });
}, },
}); });

View File

@ -0,0 +1,9 @@
import { runAutoLabel } from '@/api/lablingApi';
import { useMutation } from '@tanstack/react-query';
export default function useAutoLabelQuery() {
return useMutation({
mutationFn: ({ projectId, modelId = 1 }: { projectId: number; modelId?: number }) =>
runAutoLabel(projectId, modelId),
});
}

View File

@ -45,7 +45,6 @@ const router = createBrowserRouter([
], ],
}, },
{ {
// FIXME: index에서 오류나지 않게 수정
path: webPath.browse(), path: webPath.browse(),
element: ( element: (
<Suspense fallback={<PageLayout />}> <Suspense fallback={<PageLayout />}>
@ -64,7 +63,6 @@ const router = createBrowserRouter([
], ],
}, },
{ {
// FIXME: index에서 오류나지 않게 수정
path: `${webPath.workspace()}/:workspaceId`, path: `${webPath.workspace()}/:workspaceId`,
element: ( element: (
<Suspense fallback={<div></div>}> <Suspense fallback={<div></div>}>

View File

@ -3,10 +3,9 @@ import { persist } from 'zustand/middleware';
import { MemberResponse } from '@/types'; import { MemberResponse } from '@/types';
interface AuthState { interface AuthState {
isLoggedIn: boolean;
accessToken: string; accessToken: string;
profile: MemberResponse | null; profile: MemberResponse | null;
setLoggedIn: (status: boolean, token: string) => void; setToken: (token: string) => void;
setProfile: (profile: MemberResponse) => void; setProfile: (profile: MemberResponse) => void;
clearAuth: () => void; clearAuth: () => void;
} }
@ -14,12 +13,11 @@ interface AuthState {
const useAuthStore = create<AuthState>()( const useAuthStore = create<AuthState>()(
persist( persist(
(set) => ({ (set) => ({
isLoggedIn: false,
accessToken: '', accessToken: '',
profile: null, profile: null,
setLoggedIn: (status: boolean, token: string) => set({ isLoggedIn: status, accessToken: token }), setToken: (token: string) => set({ accessToken: token }),
setProfile: (profile: MemberResponse) => set({ profile }), setProfile: (profile: MemberResponse) => set({ profile }),
clearAuth: () => set({ isLoggedIn: false, accessToken: '', profile: null }), clearAuth: () => set({ accessToken: '', profile: null }),
}), }),
{ {
name: 'auth-storage', name: 'auth-storage',