201 lines
6.2 KiB
TypeScript
201 lines
6.2 KiB
TypeScript
/**
|
|
* @license
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @license
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import { AnimatePresence, motion } from 'motion/react';
|
|
import Login from './components/Login';
|
|
import Sidebar from './components/Sidebar';
|
|
import Overview from './components/Overview';
|
|
import ProjectLibrary from './components/ProjectLibrary';
|
|
import ReverseWorkspace from './components/ReverseWorkspace';
|
|
import UserManagement from './components/UserManagement';
|
|
import { ViewType } from './types';
|
|
import { api } from './lib/api';
|
|
|
|
export default function App() {
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
const [sessionLoading, setSessionLoading] = useState(true);
|
|
const [activeView, setActiveView] = useState<ViewType>(ViewType.OVERVIEW);
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
const [activeProjectId, setActiveProjectId] = useState('head-ct-demo');
|
|
const [projectLibraryInitialView, setProjectLibraryInitialView] = useState<'dicom' | 'model' | 'mask'>('dicom');
|
|
const workspaceLeaveGuardRef = useRef<(() => Promise<boolean>) | null>(null);
|
|
const bootSessionResetRef = useRef(false);
|
|
|
|
// Automatically collapse main sidebar when entering Project Library or Workspace
|
|
useEffect(() => {
|
|
if (activeView === ViewType.PROJECTS || activeView === ViewType.WORKSPACE) {
|
|
setSidebarCollapsed(true);
|
|
} else {
|
|
setSidebarCollapsed(false);
|
|
}
|
|
}, [activeView]);
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
|
|
const syncSession = async () => {
|
|
try {
|
|
if (!bootSessionResetRef.current) {
|
|
bootSessionResetRef.current = true;
|
|
const session = await api.logout();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setIsAuthenticated(session.authenticated);
|
|
setActiveView(ViewType.OVERVIEW);
|
|
return;
|
|
}
|
|
const session = await api.getSession();
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setIsAuthenticated(session.authenticated);
|
|
if (!session.authenticated) {
|
|
setActiveView(ViewType.OVERVIEW);
|
|
}
|
|
} catch {
|
|
if (mounted) {
|
|
setIsAuthenticated(false);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setSessionLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
syncSession();
|
|
const interval = window.setInterval(syncSession, 2500);
|
|
return () => {
|
|
mounted = false;
|
|
window.clearInterval(interval);
|
|
};
|
|
}, []);
|
|
|
|
const handleLogin = () => {
|
|
setIsAuthenticated(true);
|
|
};
|
|
|
|
const requestActiveView = (nextView: ViewType) => {
|
|
if (nextView === activeView) {
|
|
return;
|
|
}
|
|
|
|
const leaveWorkspace = activeView === ViewType.WORKSPACE && nextView !== ViewType.WORKSPACE;
|
|
const switchView = () => {
|
|
if (leaveWorkspace && nextView === ViewType.PROJECTS) {
|
|
setProjectLibraryInitialView('mask');
|
|
}
|
|
setActiveView(nextView);
|
|
};
|
|
|
|
if (!leaveWorkspace || !workspaceLeaveGuardRef.current) {
|
|
switchView();
|
|
return;
|
|
}
|
|
|
|
workspaceLeaveGuardRef.current()
|
|
.then((canLeave) => {
|
|
if (canLeave) {
|
|
switchView();
|
|
}
|
|
})
|
|
.catch(() => undefined);
|
|
};
|
|
|
|
const handleLogout = async () => {
|
|
if (activeView === ViewType.WORKSPACE && workspaceLeaveGuardRef.current) {
|
|
const canLeave = await workspaceLeaveGuardRef.current();
|
|
if (!canLeave) {
|
|
return;
|
|
}
|
|
}
|
|
await api.logout();
|
|
setIsAuthenticated(false);
|
|
setActiveView(ViewType.OVERVIEW);
|
|
};
|
|
|
|
if (sessionLoading) {
|
|
return (
|
|
<div className="min-h-screen bg-neutral-50 flex items-center justify-center text-slate-500 font-medium">
|
|
正在同步登录状态...
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return <Login onLogin={handleLogin} />;
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-screen bg-[#f8fafc] overflow-hidden font-sans antialiased text-slate-900">
|
|
<Sidebar
|
|
activeView={activeView}
|
|
setActiveView={requestActiveView}
|
|
onLogout={handleLogout}
|
|
collapsed={sidebarCollapsed}
|
|
setCollapsed={setSidebarCollapsed}
|
|
/>
|
|
|
|
<main className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
{/* Top Navigation */}
|
|
<header className="h-16 bg-white border-b border-slate-200 px-8 flex items-center justify-between z-10 shrink-0">
|
|
<div className="flex items-center gap-4 text-sm font-medium">
|
|
<span className="text-slate-900 font-bold capitalize">
|
|
{activeView === ViewType.OVERVIEW && '总体概况'}
|
|
{activeView === ViewType.PROJECTS && '项目库'}
|
|
{activeView === ViewType.WORKSPACE && '逆向工作区'}
|
|
{activeView === ViewType.SYSTEM && '系统管理工作区'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-6">
|
|
</div>
|
|
</header>
|
|
|
|
{/* Content Area */}
|
|
<div className="flex-1 overflow-y-auto overflow-x-hidden p-8">
|
|
<AnimatePresence mode="wait">
|
|
<motion.div
|
|
key={activeView}
|
|
initial={{ opacity: 0, x: 10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -10 }}
|
|
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
className="h-full"
|
|
>
|
|
{activeView === ViewType.OVERVIEW && <Overview />}
|
|
{activeView === ViewType.PROJECTS && (
|
|
<ProjectLibrary
|
|
initialViewMode={projectLibraryInitialView}
|
|
onReverse={(projectId) => {
|
|
setActiveProjectId(projectId);
|
|
setActiveView(ViewType.WORKSPACE);
|
|
}}
|
|
/>
|
|
)}
|
|
{activeView === ViewType.WORKSPACE && (
|
|
<ReverseWorkspace
|
|
projectId={activeProjectId}
|
|
onLeaveGuardChange={(handler) => {
|
|
workspaceLeaveGuardRef.current = handler;
|
|
}}
|
|
/>
|
|
)}
|
|
{activeView === ViewType.SYSTEM && <UserManagement />}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|