refactor(sidebar): scope folder-header menu to per-folder new/import, drop import from card and blank-area menus
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -796,8 +796,6 @@
|
|||||||
"removeFolderConfirmTitle": "إزالة المجلد من مساحة العمل؟",
|
"removeFolderConfirmTitle": "إزالة المجلد من مساحة العمل؟",
|
||||||
"removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.",
|
"removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"focus": "تركيز",
|
|
||||||
"closeFolderTabs": "إغلاق جميع علامات تبويب هذا المجلد",
|
|
||||||
"removeFromWorkspace": "إزالة من مساحة العمل"
|
"removeFromWorkspace": "إزالة من مساحة العمل"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -796,8 +796,6 @@
|
|||||||
"removeFolderConfirmTitle": "このフォルダをワークスペースから削除しますか?",
|
"removeFolderConfirmTitle": "このフォルダをワークスペースから削除しますか?",
|
||||||
"removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。",
|
"removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"focus": "フォーカス",
|
|
||||||
"closeFolderTabs": "このフォルダのすべてのタブを閉じる",
|
|
||||||
"removeFromWorkspace": "ワークスペースから削除"
|
"removeFromWorkspace": "ワークスペースから削除"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -796,8 +796,6 @@
|
|||||||
"removeFolderConfirmTitle": "이 폴더를 워크스페이스에서 제거하시겠습니까?",
|
"removeFolderConfirmTitle": "이 폴더를 워크스페이스에서 제거하시겠습니까?",
|
||||||
"removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.",
|
"removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"focus": "포커스",
|
|
||||||
"closeFolderTabs": "이 폴더의 모든 탭 닫기",
|
|
||||||
"removeFromWorkspace": "워크스페이스에서 제거"
|
"removeFromWorkspace": "워크스페이스에서 제거"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -796,8 +796,6 @@
|
|||||||
"removeFolderConfirmTitle": "从工作区移除该文件夹?",
|
"removeFolderConfirmTitle": "从工作区移除该文件夹?",
|
||||||
"removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。",
|
"removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"focus": "聚焦到此",
|
|
||||||
"closeFolderTabs": "关闭此文件夹的所有 Tab",
|
|
||||||
"removeFromWorkspace": "从工作区移除"
|
"removeFromWorkspace": "从工作区移除"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -796,8 +796,6 @@
|
|||||||
"removeFolderConfirmTitle": "從工作區移除此資料夾?",
|
"removeFolderConfirmTitle": "從工作區移除此資料夾?",
|
||||||
"removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。",
|
"removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"focus": "聚焦至此",
|
|
||||||
"closeFolderTabs": "關閉此資料夾的所有分頁",
|
|
||||||
"removeFromWorkspace": "從工作區移除"
|
"removeFromWorkspace": "從工作區移除"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user