diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx index 5743b63022b..546734e1248 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx @@ -9,7 +9,7 @@ import { useChangesTab } from "./hooks/useChangesTab"; import type { SidebarTabDefinition } from "./types"; interface WorkspaceSidebarProps { - onSelectFile: (absolutePath: string) => void; + onSelectFile: (absolutePath: string, openInNewTab?: boolean) => void; onSelectDiffFile?: (path: string) => void; onSearch?: () => void; selectedFilePath?: string; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx index 849cc99d8f9..9e71568eaa1 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx @@ -1,9 +1,12 @@ import type { AppRouter } from "@superset/host-service"; +import type { ExternalApp } from "@superset/local-db"; import { alert } from "@superset/ui/atoms/Alert"; import { Button } from "@superset/ui/button"; import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { workspaceTrpc } from "@superset/workspace-client"; +import { eq } from "@tanstack/db"; +import { useLiveQuery } from "@tanstack/react-db"; import type { inferRouterOutputs } from "@trpc/server"; import { FilePlus, FolderPlus, FoldVertical, RefreshCw } from "lucide-react"; import { Fragment, useCallback, useEffect, useRef, useState } from "react"; @@ -16,6 +19,9 @@ import { useGitStatusMap, } from "renderer/hooks/host-service/useGitStatusMap"; import { useWorkspaceEvent } from "renderer/hooks/host-service/useWorkspaceEvent"; +import { electronTrpcClient } from "renderer/lib/trpc-client"; +import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; +import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { ROW_HEIGHT, TREE_INDENT, @@ -31,7 +37,7 @@ type InlineEditState = | null; interface FilesTabProps { - onSelectFile: (absolutePath: string) => void; + onSelectFile: (absolutePath: string, openInNewTab?: boolean) => void; selectedFilePath?: string; workspaceId: string; workspaceName?: string; @@ -55,6 +61,7 @@ function TreeNode({ folderStatusByPath, ignoredPaths, onSelectFile, + onOpenInEditor, onToggleDirectory, onInlineEditSubmit, onInlineEditCancel, @@ -74,7 +81,8 @@ function TreeNode({ fileStatusByPath: Map; folderStatusByPath: Map; ignoredPaths: Set; - onSelectFile: (absolutePath: string) => void; + onSelectFile: (absolutePath: string, openInNewTab?: boolean) => void; + onOpenInEditor: (absolutePath: string) => void; onToggleDirectory: (absolutePath: string) => void; onInlineEditSubmit: (name: string) => void; onInlineEditCancel: () => void; @@ -127,6 +135,7 @@ function TreeNode({ decoration={decoration} isMuted={isMuted} onSelectFile={onSelectFile} + onOpenInEditor={onOpenInEditor} onToggleDirectory={onToggleDirectory} onNewFile={onNewFile} onNewFolder={onNewFolder} @@ -162,6 +171,7 @@ function TreeNode({ folderStatusByPath={folderStatusByPath} ignoredPaths={ignoredPaths} onSelectFile={onSelectFile} + onOpenInEditor={onOpenInEditor} onToggleDirectory={onToggleDirectory} onInlineEditSubmit={onInlineEditSubmit} onInlineEditCancel={onInlineEditCancel} @@ -210,6 +220,53 @@ export function FilesTab({ id: workspaceId, }); const rootPath = workspaceQuery.data?.worktreePath ?? ""; + const projectId = workspaceQuery.data?.projectId; + + const collections = useCollections(); + const { machineId } = useLocalHostService(); + const { data: workspacesWithHost = [] } = useLiveQuery( + (q) => + q + .from({ workspaces: collections.v2Workspaces }) + .leftJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => + eq(workspaces.hostId, hosts.id), + ) + .where(({ workspaces }) => eq(workspaces.id, workspaceId)) + .select(({ hosts }) => ({ + hostMachineId: hosts?.machineId ?? null, + })), + [collections, workspaceId], + ); + const workspaceHost = workspacesWithHost[0]; + + const { data: sidebarProjectRows = [] } = useLiveQuery( + (q) => + q + .from({ sp: collections.v2SidebarProjects }) + .where(({ sp }) => eq(sp.projectId, projectId ?? "")) + .select(({ sp }) => ({ defaultOpenInApp: sp.defaultOpenInApp })), + [collections, projectId], + ); + const resolvedOpenInApp: ExternalApp = + (sidebarProjectRows[0]?.defaultOpenInApp as ExternalApp | null) ?? "finder"; + + const handleOpenInEditor = useCallback( + (absolutePath: string) => { + if (!workspaceHost) return; + if (workspaceHost.hostMachineId !== machineId) { + toast.error("Opening in editor is only supported on local workspaces"); + return; + } + electronTrpcClient.external.openInApp + .mutate({ path: absolutePath, app: resolvedOpenInApp }) + .catch((err) => { + toast.error("Couldn't open file", { + description: err instanceof Error ? err.message : String(err), + }); + }); + }, + [workspaceHost, machineId, resolvedOpenInApp], + ); const writeFile = workspaceTrpc.filesystem.writeFile.useMutation(); const createDirectory = @@ -566,6 +623,7 @@ export function FilesTab({ folderStatusByPath={folderStatusByPath} ignoredPaths={ignoredPaths} onSelectFile={onSelectFile} + onOpenInEditor={handleOpenInEditor} onToggleDirectory={(absolutePath) => void fileTree.toggle(absolutePath) } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/components/WorkspaceFilesTreeItem/WorkspaceFilesTreeItem.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/components/WorkspaceFilesTreeItem/WorkspaceFilesTreeItem.tsx index b158cc9c566..57c08caca16 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/components/WorkspaceFilesTreeItem/WorkspaceFilesTreeItem.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/components/WorkspaceFilesTreeItem/WorkspaceFilesTreeItem.tsx @@ -39,7 +39,8 @@ interface WorkspaceFilesTreeItemProps { isHovered?: boolean; decoration?: FileStatus; isMuted?: boolean; - onSelectFile: (absolutePath: string) => void; + onSelectFile: (absolutePath: string, openInNewTab?: boolean) => void; + onOpenInEditor: (absolutePath: string) => void; onToggleDirectory: (absolutePath: string) => void; onNewFile: (parentPath: string) => void; onNewFolder: (parentPath: string) => void; @@ -57,6 +58,7 @@ function WorkspaceFilesTreeItemComponent({ decoration, isMuted, onSelectFile, + onOpenInEditor, onToggleDirectory, onNewFile, onNewFolder, @@ -88,11 +90,17 @@ function WorkspaceFilesTreeItemComponent({ : undefined, isSelected ? "!bg-accent" : undefined, )} - onClick={() => - isFolder - ? onToggleDirectory(node.absolutePath) - : onSelectFile(node.absolutePath) - } + onClick={(e) => { + if (e.metaKey || e.ctrlKey) { + onOpenInEditor(node.absolutePath); + } else if (isFolder) { + onToggleDirectory(node.absolutePath); + } else if (e.shiftKey) { + onSelectFile(node.absolutePath, true); + } else { + onSelectFile(node.absolutePath); + } + }} style={{ height: rowHeight, paddingLeft: 8 + (depth - 1) * indent, diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx index 21b1f806317..ca7271c5c81 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx @@ -104,8 +104,24 @@ function WorkspaceContent({ }); const openFilePane = useCallback( - (filePath: string) => { + (filePath: string, openInNewTab?: boolean) => { const state = store.getState(); + if (openInNewTab) { + state.addTab({ + titleOverride: filePath.split(/[/\\]/).pop(), + panes: [ + { + kind: "file", + data: { + filePath, + mode: "editor", + hasChanges: false, + } as FilePaneData, + }, + ], + }); + return; + } const active = state.getActivePane(); if ( active?.pane.kind === "file" && diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileSearchResultItem/FileSearchResultItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileSearchResultItem/FileSearchResultItem.tsx index 911d42432ba..a36ec967acd 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileSearchResultItem/FileSearchResultItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileSearchResultItem/FileSearchResultItem.tsx @@ -85,7 +85,9 @@ export function FileSearchResultItem({ const handleClick = (e: React.MouseEvent) => { if (!entry.isDirectory) { - if (e.metaKey || e.ctrlKey) { + if (e.shiftKey) { + onActivate(entry, true); + } else if (e.metaKey || e.ctrlKey) { onOpenInEditor(entry); } else { onActivate(entry); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileTreeItem/FileTreeItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileTreeItem/FileTreeItem.tsx index 72a3acdc25d..b49a70fa3ee 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileTreeItem/FileTreeItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/FilesView/components/FileTreeItem/FileTreeItem.tsx @@ -72,14 +72,16 @@ export function FileTreeItem({ const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); - if (isFolder) { + if (e.metaKey || e.ctrlKey) { + onOpenInEditor(entry); + } else if (isFolder) { if (isExpanded) { item.collapse(); } else { item.expand(); } - } else if (e.metaKey || e.ctrlKey) { - onOpenInEditor(entry); + } else if (e.shiftKey) { + onActivate(entry, true); } else { onActivate(entry); }