import { useEffect, useMemo, useState } from 'react'; import { FolderKanban, Loader2, Pencil, Plus, RefreshCw, Trash2, } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { IMAGE_WORKSPACE_CREATE_PROJECT_EVENT } from '@/lib/image-workspace'; import { cn } from '@/lib/utils'; import { useImageWorkspaceStore } from '@/stores/image-workspace'; import { useAuthStore } from '@/stores/auth'; import type { DesignWorkspaceSummary } from '../../../shared/image-workspace'; type ImageWorkspaceSidebarProps = { sidebarCollapsed: boolean; }; type ProjectDialogState = | { mode: 'create'; project: null } | { mode: 'rename'; project: DesignWorkspaceSummary }; function formatUpdatedAt(value: string): string { const timestamp = Date.parse(value); if (!Number.isFinite(timestamp)) return ''; const date = new Date(timestamp); const now = new Date(); if (date.toDateString() === now.toDateString()) { return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } return date.toLocaleDateString([], { month: 'numeric', day: 'numeric' }); } export function ImageWorkspaceSidebar({ sidebarCollapsed }: ImageWorkspaceSidebarProps) { const authenticated = useAuthStore((state) => state.isAuthenticated()); const navigate = useNavigate(); const status = useImageWorkspaceStore((state) => state.status); const bootstrap = useImageWorkspaceStore((state) => state.bootstrap); const activeWorkspaceId = useImageWorkspaceStore((state) => state.activeWorkspaceId); const workspace = useImageWorkspaceStore((state) => state.workspace); const deletingWorkspaceId = useImageWorkspaceStore((state) => state.deletingWorkspaceId); const workspaceError = useImageWorkspaceStore((state) => state.error); const load = useImageWorkspaceStore((state) => state.load); const createProject = useImageWorkspaceStore((state) => state.createProject); const deleteProject = useImageWorkspaceStore((state) => state.deleteProject); const renameProject = useImageWorkspaceStore((state) => state.renameProject); const selectProject = useImageWorkspaceStore((state) => state.selectProject); const [dialog, setDialog] = useState(null); const [projectName, setProjectName] = useState(''); const [dialogError, setDialogError] = useState(null); const [saving, setSaving] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deleteConfirmation, setDeleteConfirmation] = useState(''); const [deleteError, setDeleteError] = useState(null); useEffect(() => { if (status === 'idle' || (status === 'auth-required' && authenticated)) void load(); }, [authenticated, load, status]); useEffect(() => { const openCreateDialog = () => { setProjectName(''); setDialogError(null); setDialog({ mode: 'create', project: null }); }; window.addEventListener(IMAGE_WORKSPACE_CREATE_PROJECT_EVENT, openCreateDialog); return () => window.removeEventListener(IMAGE_WORKSPACE_CREATE_PROJECT_EVENT, openCreateDialog); }, []); const sortedProjects = useMemo( () => [...(bootstrap?.workspaces ?? [])].sort( (left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt), ), [bootstrap?.workspaces], ); const openCreateDialog = () => { setProjectName(''); setDialogError(null); setDialog({ mode: 'create', project: null }); }; const openRenameDialog = (project: DesignWorkspaceSummary) => { setProjectName(project.title); setDialogError(null); setDialog({ mode: 'rename', project }); }; const saveProject = async () => { const title = projectName.trim(); if (!title) { setDialogError('请输入项目名称'); return; } setSaving(true); setDialogError(null); try { if (dialog?.mode === 'rename') { await renameProject(dialog.project.workspaceId, title); toast.success('项目名称已更新'); } else { await createProject(title); navigate('/image-canvas'); } setDialog(null); } catch (error) { setDialogError(error instanceof Error ? error.message : '保存项目失败'); } finally { setSaving(false); } }; const confirmDelete = async () => { if (!deleteTarget) return; if (deleteConfirmation !== deleteTarget.title) { setDeleteError('请输入完整项目名称'); return; } setDeleteError(null); try { await deleteProject(deleteTarget.workspaceId); setDeleteTarget(null); setDeleteConfirmation(''); toast.success('设计项目已删除'); } catch (error) { setDeleteError(error instanceof Error ? error.message : '删除项目失败'); } }; const chooseProject = (project: DesignWorkspaceSummary) => { void selectProject(project.workspaceId); navigate('/image-canvas'); }; if (sidebarCollapsed) { return (
); } return (
设计项目 {(status === 'error' || status === 'unavailable') && ( )}
{status === 'loading' && (
{[0, 1, 2].map((item) => (
))}
)} {(status === 'error' || status === 'unavailable') && (
{workspaceError ?? 'AI 设计暂时不可用'}
)} {status === 'ready' && sortedProjects.length === 0 && ( )} {sortedProjects.map((project) => { const active = project.workspaceId === activeWorkspaceId; const pending = project.workspaceId === activeWorkspaceId && !workspace; return (
); })}
!open && !saving && setDialog(null)}> {dialog?.mode === 'rename' ? '重命名设计项目' : '新建设计项目'} 创建后会立即生成一份空白设计表单,可通过对话或直接编辑完善。
setProjectName(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && !saving) void saveProject(); }} /> {dialogError &&

{dialogError}

}
{ if (!open && !deletingWorkspaceId) setDeleteTarget(null); }} > 删除设计项目 这会隐藏项目、设计表单、任务与资产。已另存到电脑的文件不受影响。
setDeleteConfirmation(event.target.value)} /> {deleteError &&

{deleteError}

}
); }