refactor(sidebar): scope folder-header menu to per-folder new/import, drop import from card and blank-area menus

This commit is contained in:
xintaofei
2026-04-22 08:16:39 +08:00
parent fc174d8692
commit ae88b2256f
12 changed files with 54 additions and 114 deletions

View File

@@ -9,7 +9,6 @@ import {
CircleCheck, CircleCheck,
CircleDashed, CircleDashed,
CircleX, CircleX,
Download,
Plus, Plus,
type LucideIcon, type LucideIcon,
} from "lucide-react" } from "lucide-react"
@@ -75,8 +74,6 @@ interface SidebarConversationCardProps {
onDelete: (id: number, agentType: string) => Promise<void> onDelete: (id: number, agentType: string) => Promise<void>
onStatusChange: (id: number, status: ConversationStatus) => Promise<void> onStatusChange: (id: number, status: ConversationStatus) => Promise<void>
onNewConversation?: () => void onNewConversation?: () => void
onImport?: () => void
importing?: boolean
} }
export const SidebarConversationCard = memo(function SidebarConversationCard({ export const SidebarConversationCard = memo(function SidebarConversationCard({
@@ -89,8 +86,6 @@ export const SidebarConversationCard = memo(function SidebarConversationCard({
onDelete, onDelete,
onStatusChange, onStatusChange,
onNewConversation, onNewConversation,
onImport,
importing,
}: SidebarConversationCardProps) { }: SidebarConversationCardProps) {
const t = useTranslations("Folder.conversationCard") const t = useTranslations("Folder.conversationCard")
const tSidebar = useTranslations("Folder.sidebar") const tSidebar = useTranslations("Folder.sidebar")
@@ -254,15 +249,6 @@ export const SidebarConversationCard = memo(function SidebarConversationCard({
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
{t("delete")} {t("delete")}
</ContextMenuItem> </ContextMenuItem>
{onImport && (
<>
<ContextMenuSeparator />
<ContextMenuItem disabled={importing} onSelect={onImport}>
<Download className="h-4 w-4" />
{importing ? t("importing") : t("importLocalSessions")}
</ContextMenuItem>
</>
)}
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>

View File

@@ -107,22 +107,22 @@ const FolderHeader = memo(function FolderHeader({
folderName, folderName,
count, count,
expanded, expanded,
importing,
onToggle, onToggle,
onFocus,
onCloseFolderTabs,
onRemoveFromWorkspace, onRemoveFromWorkspace,
onNewConversation, onNewConversation,
onImport,
t, t,
}: { }: {
folderId: number folderId: number
folderName: string folderName: string
count: number count: number
expanded: boolean expanded: boolean
importing: boolean
onToggle: (folderId: number) => void onToggle: (folderId: number) => void
onFocus: (folderId: number) => void
onCloseFolderTabs: (folderId: number) => void
onRemoveFromWorkspace: (folderId: number) => void onRemoveFromWorkspace: (folderId: number) => void
onNewConversation: (folderId: number) => void onNewConversation: (folderId: number) => void
onImport: (folderId: number) => void
t: ReturnType<typeof useTranslations> t: ReturnType<typeof useTranslations>
}) { }) {
return ( return (
@@ -204,11 +204,16 @@ const FolderHeader = memo(function FolderHeader({
</div> </div>
</ContextMenuTrigger> </ContextMenuTrigger>
<ContextMenuContent> <ContextMenuContent>
<ContextMenuItem onSelect={() => onFocus(folderId)}> <ContextMenuItem onSelect={() => onNewConversation(folderId)}>
{t("folderHeaderMenu.focus")} <Plus className="h-4 w-4" />
{t("newConversation")}
</ContextMenuItem> </ContextMenuItem>
<ContextMenuItem onSelect={() => onCloseFolderTabs(folderId)}> <ContextMenuItem
{t("folderHeaderMenu.closeFolderTabs")} disabled={importing}
onSelect={() => onImport(folderId)}
>
<Download className="h-4 w-4" />
{importing ? t("importing") : t("importLocalSessions")}
</ContextMenuItem> </ContextMenuItem>
<ContextMenuSeparator /> <ContextMenuSeparator />
<ContextMenuItem <ContextMenuItem
@@ -470,28 +475,6 @@ export function SidebarConversationList({
}) })
}, []) }, [])
const focusFolder = useCallback(
(folderId: number) => {
const idx = flatItems.findIndex(
(item) => item.type === "folder_header" && item.folderId === folderId
)
if (idx >= 0) {
virtualizerRef.current?.scrollToIndex(idx, {
align: "start",
smooth: true,
})
}
},
[flatItems]
)
const handleCloseFolderTabs = useCallback(
(folderId: number) => {
closeTabsByFolder(folderId)
},
[closeTabsByFolder]
)
const handleRemoveFolder = useCallback( const handleRemoveFolder = useCallback(
(folderId: number) => { (folderId: number) => {
const name = folderIndex.get(folderId)?.name ?? String(folderId) const name = folderIndex.get(folderId)?.name ?? String(folderId)
@@ -595,35 +578,44 @@ export function SidebarConversationList({
[folderIndex, openNewConversationTab] [folderIndex, openNewConversationTab]
) )
const handleImport = useCallback(async () => { const handleImportForFolder = useCallback(
if (importing) return async (folderId: number) => {
if (!activeFolder) return if (importing) return
setImporting(true) setImporting(true)
const taskId = `import-${activeFolder.id}-${Date.now()}` const taskId = `import-${folderId}-${Date.now()}`
addTask(taskId, t("importLocalSessions")) addTask(taskId, t("importLocalSessions"))
updateTask(taskId, { status: "running" }) updateTask(taskId, { status: "running" })
try { try {
const result = await importLocalConversations(activeFolder.id) const result = await importLocalConversations(folderId)
updateTask(taskId, { status: "completed" }) updateTask(taskId, { status: "completed" })
refreshConversations() refreshConversations()
if (result.imported > 0) { if (result.imported > 0) {
toast.success( toast.success(
t("toasts.importedSessions", { t("toasts.importedSessions", {
imported: result.imported, imported: result.imported,
skipped: result.skipped, skipped: result.skipped,
}) })
) )
} else { } else {
toast.info(t("toasts.noNewSessionsFound", { skipped: result.skipped })) toast.info(
t("toasts.noNewSessionsFound", { skipped: result.skipped })
)
}
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
updateTask(taskId, { status: "failed", error: msg })
toast.error(t("toasts.importFailed", { message: msg }))
} finally {
setImporting(false)
} }
} catch (e) { },
const msg = e instanceof Error ? e.message : String(e) [importing, addTask, updateTask, refreshConversations, t]
updateTask(taskId, { status: "failed", error: msg }) )
toast.error(t("toasts.importFailed", { message: msg }))
} finally { const handleImport = useCallback(async () => {
setImporting(false) if (!activeFolder) return
} await handleImportForFolder(activeFolder.id)
}, [importing, activeFolder, addTask, updateTask, refreshConversations, t]) }, [activeFolder, handleImportForFolder])
const emptyAfterFilter = const emptyAfterFilter =
filteredConversations.length === 0 && conversations.length > 0 filteredConversations.length === 0 && conversations.length > 0
@@ -678,14 +670,6 @@ export function SidebarConversationList({
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
{t("newConversation")} {t("newConversation")}
</ContextMenuItem> </ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
disabled={importing || !activeFolder}
onSelect={handleImport}
>
<Download className="h-4 w-4" />
{importing ? t("importing") : t("importLocalSessions")}
</ContextMenuItem>
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
) : emptyAfterFilter ? ( ) : emptyAfterFilter ? (
@@ -716,11 +700,11 @@ export function SidebarConversationList({
folderName={stickyFolderItem.folderName} folderName={stickyFolderItem.folderName}
count={stickyFolderItem.count} count={stickyFolderItem.count}
expanded={stickyFolderItem.expanded} expanded={stickyFolderItem.expanded}
importing={importing}
onToggle={toggleFolder} onToggle={toggleFolder}
onFocus={focusFolder}
onCloseFolderTabs={handleCloseFolderTabs}
onRemoveFromWorkspace={handleRemoveFolder} onRemoveFromWorkspace={handleRemoveFolder}
onNewConversation={handleNewConversationForFolder} onNewConversation={handleNewConversationForFolder}
onImport={handleImportForFolder}
t={t} t={t}
/> />
</div> </div>
@@ -746,11 +730,11 @@ export function SidebarConversationList({
folderName={item.folderName} folderName={item.folderName}
count={item.count} count={item.count}
expanded={item.expanded} expanded={item.expanded}
importing={importing}
onToggle={toggleFolder} onToggle={toggleFolder}
onFocus={focusFolder}
onCloseFolderTabs={handleCloseFolderTabs}
onRemoveFromWorkspace={handleRemoveFolder} onRemoveFromWorkspace={handleRemoveFolder}
onNewConversation={handleNewConversationForFolder} onNewConversation={handleNewConversationForFolder}
onImport={handleImportForFolder}
t={t} t={t}
/> />
) )
@@ -771,8 +755,6 @@ export function SidebarConversationList({
onDelete={handleDelete} onDelete={handleDelete}
onStatusChange={handleStatusChange} onStatusChange={handleStatusChange}
onNewConversation={handleNewConversation} onNewConversation={handleNewConversation}
onImport={handleImport}
importing={importing}
/> />
) )
})} })}
@@ -788,14 +770,6 @@ export function SidebarConversationList({
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
{t("newConversation")} {t("newConversation")}
</ContextMenuItem> </ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
disabled={importing || !activeFolder}
onSelect={handleImport}
>
<Download className="h-4 w-4" />
{importing ? t("importing") : t("importLocalSessions")}
</ContextMenuItem>
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
)} )}

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "إزالة المجلد من مساحة العمل؟", "removeFolderConfirmTitle": "إزالة المجلد من مساحة العمل؟",
"removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.", "removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "تركيز",
"closeFolderTabs": "إغلاق جميع علامات تبويب هذا المجلد",
"removeFromWorkspace": "إزالة من مساحة العمل" "removeFromWorkspace": "إزالة من مساحة العمل"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "Ordner aus Arbeitsbereich entfernen?", "removeFolderConfirmTitle": "Ordner aus Arbeitsbereich entfernen?",
"removeFolderConfirmDescription": "\"{name}\" aus dem Arbeitsbereich entfernen? Zugehörige Tabs und Terminals werden geschlossen.", "removeFolderConfirmDescription": "\"{name}\" aus dem Arbeitsbereich entfernen? Zugehörige Tabs und Terminals werden geschlossen.",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "Fokussieren",
"closeFolderTabs": "Alle Tabs dieses Ordners schließen",
"removeFromWorkspace": "Aus Arbeitsbereich entfernen" "removeFromWorkspace": "Aus Arbeitsbereich entfernen"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "Remove folder from workspace?", "removeFolderConfirmTitle": "Remove folder from workspace?",
"removeFolderConfirmDescription": "Remove \"{name}\" from the workspace? Its tabs and terminals will close.", "removeFolderConfirmDescription": "Remove \"{name}\" from the workspace? Its tabs and terminals will close.",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "Focus",
"closeFolderTabs": "Close all tabs of this folder",
"removeFromWorkspace": "Remove from workspace" "removeFromWorkspace": "Remove from workspace"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "¿Eliminar carpeta del espacio de trabajo?", "removeFolderConfirmTitle": "¿Eliminar carpeta del espacio de trabajo?",
"removeFolderConfirmDescription": "¿Eliminar \"{name}\" del espacio de trabajo? Sus pestañas y terminales se cerrarán.", "removeFolderConfirmDescription": "¿Eliminar \"{name}\" del espacio de trabajo? Sus pestañas y terminales se cerrarán.",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "Enfocar",
"closeFolderTabs": "Cerrar todas las pestañas de esta carpeta",
"removeFromWorkspace": "Quitar del espacio de trabajo" "removeFromWorkspace": "Quitar del espacio de trabajo"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "Retirer le dossier de l'espace de travail ?", "removeFolderConfirmTitle": "Retirer le dossier de l'espace de travail ?",
"removeFolderConfirmDescription": "Retirer \"{name}\" de l'espace de travail ? Les onglets et terminaux associés seront fermés.", "removeFolderConfirmDescription": "Retirer \"{name}\" de l'espace de travail ? Les onglets et terminaux associés seront fermés.",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "Focaliser",
"closeFolderTabs": "Fermer tous les onglets de ce dossier",
"removeFromWorkspace": "Retirer de l'espace de travail" "removeFromWorkspace": "Retirer de l'espace de travail"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "このフォルダをワークスペースから削除しますか?", "removeFolderConfirmTitle": "このフォルダをワークスペースから削除しますか?",
"removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。", "removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "フォーカス",
"closeFolderTabs": "このフォルダのすべてのタブを閉じる",
"removeFromWorkspace": "ワークスペースから削除" "removeFromWorkspace": "ワークスペースから削除"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "이 폴더를 워크스페이스에서 제거하시겠습니까?", "removeFolderConfirmTitle": "이 폴더를 워크스페이스에서 제거하시겠습니까?",
"removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.", "removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "포커스",
"closeFolderTabs": "이 폴더의 모든 탭 닫기",
"removeFromWorkspace": "워크스페이스에서 제거" "removeFromWorkspace": "워크스페이스에서 제거"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "Remover pasta do espaço de trabalho?", "removeFolderConfirmTitle": "Remover pasta do espaço de trabalho?",
"removeFolderConfirmDescription": "Remover \"{name}\" do espaço de trabalho? As abas e terminais relacionados serão fechados.", "removeFolderConfirmDescription": "Remover \"{name}\" do espaço de trabalho? As abas e terminais relacionados serão fechados.",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "Focar",
"closeFolderTabs": "Fechar todas as abas desta pasta",
"removeFromWorkspace": "Remover do espaço de trabalho" "removeFromWorkspace": "Remover do espaço de trabalho"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "从工作区移除该文件夹?", "removeFolderConfirmTitle": "从工作区移除该文件夹?",
"removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。", "removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "聚焦到此",
"closeFolderTabs": "关闭此文件夹的所有 Tab",
"removeFromWorkspace": "从工作区移除" "removeFromWorkspace": "从工作区移除"
} }
}, },

View File

@@ -796,8 +796,6 @@
"removeFolderConfirmTitle": "從工作區移除此資料夾?", "removeFolderConfirmTitle": "從工作區移除此資料夾?",
"removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。", "removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。",
"folderHeaderMenu": { "folderHeaderMenu": {
"focus": "聚焦至此",
"closeFolderTabs": "關閉此資料夾的所有分頁",
"removeFromWorkspace": "從工作區移除" "removeFromWorkspace": "從工作區移除"
} }
}, },