From 89c2fac3c6262ee57db35431b6dc44319af8dd2d Mon Sep 17 00:00:00 2001 From: Kamil Date: Tue, 28 Jul 2026 00:48:29 +0200 Subject: [PATCH] Open project and workspace folders from the sidebar (#2491) * feat(app): open project folder from project context menu (#2487) * refactor(app): give file manager action a home --------- Co-authored-by: Mohamed Boudra --- .../src/components/sidebar-workspace-list.tsx | 7 +++ .../sidebar/sidebar-workspace-menu.tsx | 7 +++ packages/app/src/i18n/resources/ar.ts | 2 + packages/app/src/i18n/resources/en.ts | 2 + packages/app/src/i18n/resources/es.ts | 2 + packages/app/src/i18n/resources/fr.ts | 2 + packages/app/src/i18n/resources/ja.ts | 2 + packages/app/src/i18n/resources/pt-BR.ts | 2 + packages/app/src/i18n/resources/ru.ts | 2 + packages/app/src/i18n/resources/zh-CN.ts | 2 + .../open-in-file-manager/menu-item.tsx | 54 +++++++++++++++++++ 11 files changed, 84 insertions(+) create mode 100644 packages/app/src/workspace/open-in-file-manager/menu-item.tsx diff --git a/packages/app/src/components/sidebar-workspace-list.tsx b/packages/app/src/components/sidebar-workspace-list.tsx index c349487db..d988b880a 100644 --- a/packages/app/src/components/sidebar-workspace-list.tsx +++ b/packages/app/src/components/sidebar-workspace-list.tsx @@ -131,6 +131,7 @@ import { getIsElectron, } from "@/constants/platform"; import { getDesktopHost } from "@/desktop/host"; +import { OpenInFileManagerMenuItem } from "@/workspace/open-in-file-manager/menu-item"; const workspaceKeyExtractor = (workspace: SidebarWorkspacePlacement) => workspace.workspaceKey; @@ -609,6 +610,10 @@ function ProjectKebabMenu({ {t("sidebar.project.actions.openNewWindow")} ) : null} + void; }) { + const workspacePath = workspace.workspaceDirectory ?? workspace.projectRootPath; const { t } = useTranslation(); const showShortcut = showShortcutBadge && shortcutNumber !== null; const showKebab = Boolean(onArchive && (isHovered || isTouchPlatform)); @@ -698,6 +704,7 @@ function WorkspaceRowRightGroup({ archiveShortcutKeys={archiveShortcutKeys} isPinned={isPinned} onTogglePin={onTogglePin} + openInFileManagerPath={workspacePath} /> ) : null} diff --git a/packages/app/src/components/sidebar/sidebar-workspace-menu.tsx b/packages/app/src/components/sidebar/sidebar-workspace-menu.tsx index e04fc7ab6..f31489d7b 100644 --- a/packages/app/src/components/sidebar/sidebar-workspace-menu.tsx +++ b/packages/app/src/components/sidebar/sidebar-workspace-menu.tsx @@ -13,6 +13,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Shortcut } from "@/components/ui/shortcut"; +import { OpenInFileManagerMenuItem } from "@/workspace/open-in-file-manager/menu-item"; const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground }); const foregroundMutedColorMapping = (theme: Theme) => ({ @@ -58,6 +59,7 @@ interface SidebarWorkspaceMenuProps { archiveShortcutKeys?: ShortcutKey[][] | null; isPinned?: boolean; onTogglePin?: () => void; + openInFileManagerPath?: string | null; } export function SidebarWorkspaceMenu({ @@ -73,6 +75,7 @@ export function SidebarWorkspaceMenu({ archiveShortcutKeys, isPinned, onTogglePin, + openInFileManagerPath, }: SidebarWorkspaceMenuProps) { const { t } = useTranslation(); const archiveTrailing = useMemo( @@ -137,6 +140,10 @@ export function SidebarWorkspaceMenu({ {isPinned ? t("sidebar.workspace.actions.unpin") : t("sidebar.workspace.actions.pin")} ) : null} + ({ + color: theme.colors.foregroundMuted, +}); + +const leadingIcon = ; + +export function OpenInFileManagerMenuItem({ path, testID }: OpenInFileManagerMenuItemProps) { + const { t } = useTranslation(); + const toast = useToast(); + const isElectron = getIsElectron(); + const workspacePath = path?.trim() ?? ""; + const { targets } = useDesktopOpenTargets({ + isLocalExecution: isElectron && workspacePath.length > 0, + }); + const fileManagerTarget = targets.find((target) => target.kind === "file-manager"); + + const openInFileManager = useCallback(() => { + if (!fileManagerTarget || workspacePath.length === 0) return; + void openDesktopTarget({ + editorId: fileManagerTarget.id, + workspacePath, + }).catch((error) => { + console.warn("[open-in-file-manager] open failed", error); + toast.error(t("sidebar.project.actions.openFolderFailed")); + }); + }, [fileManagerTarget, t, toast, workspacePath]); + + if (!isElectron || !fileManagerTarget || workspacePath.length === 0) { + return null; + } + + return ( + + {t("sidebar.project.actions.openFolder")} + + ); +}