diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx index 7afa1861c8f..0e2e180d51e 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx @@ -1,12 +1,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { cn } from "@superset/ui/utils"; -import { useMatchRoute, useNavigate } from "@tanstack/react-router"; -import { LuLayers, LuPlus } from "react-icons/lu"; +import { LuFolderPlus, LuPlus } from "react-icons/lu"; import { useHotkeyDisplay } from "renderer/hotkeys"; -import { - STROKE_WIDTH, - STROKE_WIDTH_THICK, -} from "renderer/screens/main/components/WorkspaceSidebar/constants"; +import { OrganizationDropdown } from "renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown"; +import { STROKE_WIDTH_THICK } from "renderer/screens/main/components/WorkspaceSidebar/constants"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; interface DashboardSidebarHeaderProps { @@ -16,35 +13,24 @@ interface DashboardSidebarHeaderProps { export function DashboardSidebarHeader({ isCollapsed = false, }: DashboardSidebarHeaderProps) { - const navigate = useNavigate(); - const matchRoute = useMatchRoute(); const openModal = useOpenNewWorkspaceModal(); const shortcutText = useHotkeyDisplay("NEW_WORKSPACE").text; - const isWorkspacesPageOpen = !!matchRoute({ to: "/v2-workspaces" }); - - const handleWorkspacesClick = () => { - navigate({ to: "/v2-workspaces" }); - }; if (isCollapsed) { return (
+ + - Workspaces + Add Repository @@ -67,21 +53,22 @@ export function DashboardSidebarHeader({ return (
- + + + + + Add Repository + +
- )} -
+ {!isRenaming && ( + + ({totalWorkspaceCount}) + + )} @@ -125,24 +121,6 @@ export const DashboardSidebarProjectRow = forwardRef< New workspace - - ); }, diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx index c87f5536e6e..56276b432f2 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx @@ -1,5 +1,5 @@ import { cn } from "@superset/ui/utils"; -import { LuCloud, LuFolderGit2, LuLaptop } from "react-icons/lu"; +import { LuCloud, LuGitMerge, LuLaptop } from "react-icons/lu"; import { AsciiSpinner } from "renderer/screens/main/components/AsciiSpinner"; import { StatusIndicator } from "renderer/screens/main/components/StatusIndicator"; import type { ActivePaneStatus } from "shared/tabs-types"; @@ -50,7 +50,7 @@ export function DashboardSidebarWorkspaceIcon({ strokeWidth={1.75} /> ) : ( - ) : null} - + {!isV2CloudEnabled && } + {isV2WorkspaceRoute && ( + + )} {!isMac && } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx index 04eb749c50f..d918461f319 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/OrganizationDropdown/OrganizationDropdown.tsx @@ -32,7 +32,11 @@ import { authClient } from "renderer/lib/auth-client"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; -export function OrganizationDropdown() { +export function OrganizationDropdown({ + variant = "topbar", +}: { + variant?: "topbar" | "expanded" | "collapsed"; +}) { const { data: session } = authClient.useSession(); const collections = useCollections(); const signOutMutation = electronTrpc.auth.signOut.useMutation(); @@ -65,27 +69,60 @@ export function OrganizationDropdown() { const userName = session?.user?.name; const displayName = activeOrganization?.name ?? userName ?? "Organization"; + const triggerButton = + variant === "collapsed" ? ( + + ) : variant === "expanded" ? ( + + ) : ( + + ); + + const contentAlign = variant === "topbar" ? "end" : "start"; + return ( - - - - + {triggerButton} + {/* Organization */} navigate({ to: "/settings/account" })} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle/RightSidebarToggle.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle/RightSidebarToggle.tsx new file mode 100644 index 00000000000..8484e02f3fa --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle/RightSidebarToggle.tsx @@ -0,0 +1,55 @@ +import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { + LuPanelRight, + LuPanelRightClose, + LuPanelRightOpen, +} from "react-icons/lu"; +import { HotkeyLabel } from "renderer/hotkeys"; +import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; + +export function RightSidebarToggle({ workspaceId }: { workspaceId: string }) { + const collections = useCollections(); + const localState = collections.v2WorkspaceLocalState.get(workspaceId); + const isOpen = localState?.rightSidebarOpen ?? false; + + const toggle = () => { + collections.v2WorkspaceLocalState.update(workspaceId, (draft) => { + draft.rightSidebarOpen = !draft.rightSidebarOpen; + }); + }; + + const getToggleIcon = (isHovering: boolean) => { + if (!isOpen) { + return isHovering ? ( + + ) : ( + + ); + } + return isHovering ? ( + + ) : ( + + ); + }; + + return ( + + + + + + + + + ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle/index.ts new file mode 100644 index 00000000000..8990c30415b --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/RightSidebarToggle/index.ts @@ -0,0 +1 @@ +export { RightSidebarToggle } from "./RightSidebarToggle"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx index a0b11900b00..2e1091ec1f8 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx @@ -1,37 +1,201 @@ -import { useQuery } from "@tanstack/react-query"; -import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; -import { OpenInMenuButton } from "../OpenInMenuButton"; +import type { ExternalApp } from "@superset/local-db"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@superset/ui/dropdown-menu"; +import { toast } from "@superset/ui/sonner"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { cn } from "@superset/ui/utils"; +import { useCallback, useMemo, useState } from "react"; +import { HiChevronDown } from "react-icons/hi2"; +import { + getAppOption, + OpenInExternalDropdownItems, +} from "renderer/components/OpenInExternalDropdown"; +import { HotkeyLabel, useHotkey, useHotkeyDisplay } from "renderer/hotkeys"; +import { electronTrpc } from "renderer/lib/electron-trpc"; +import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; +import { useThemeStore } from "renderer/stores"; interface V2OpenInMenuButtonProps { + worktreePath: string; branch: string; - hostUrl: string; - projectId: string; workspaceId: string; } export function V2OpenInMenuButton({ + worktreePath, branch, - hostUrl, - projectId, workspaceId, }: V2OpenInMenuButtonProps) { - const workspaceQuery = useQuery({ - queryKey: ["v2-open-in-workspace", hostUrl, workspaceId], - queryFn: () => - getHostServiceClientByUrl(hostUrl).workspace.get.query({ - id: workspaceId, - }), + const collections = useCollections(); + const activeTheme = useThemeStore((state) => state.activeTheme); + + const localState = collections.v2WorkspaceLocalState.get(workspaceId); + const [defaultApp, setDefaultApp] = useState( + (localState?.defaultOpenInApp as ExternalApp) ?? "finder", + ); + + const handleDefaultAppChange = useCallback( + (app: ExternalApp) => { + setDefaultApp(app); + collections.v2WorkspaceLocalState.update(workspaceId, (draft) => { + draft.defaultOpenInApp = app; + }); + }, + [collections, workspaceId], + ); + + const openInApp = electronTrpc.external.openInApp.useMutation({ + onSuccess: (_data, variables) => { + handleDefaultAppChange(variables.app); + }, + onError: (error) => toast.error(`Failed to open: ${error.message}`), + }); + const copyPath = electronTrpc.external.copyPath.useMutation({ + onSuccess: () => toast.success("Path copied to clipboard"), + onError: (error) => toast.error(`Failed to copy path: ${error.message}`), }); - if (!workspaceQuery.data?.worktreePath) { - return null; - } + const currentApp = useMemo( + () => getAppOption(defaultApp) ?? null, + [defaultApp], + ); + const openInDisplay = useHotkeyDisplay("OPEN_IN_APP"); + const copyPathDisplay = useHotkeyDisplay("COPY_PATH"); + const showOpenInShortcut = openInDisplay.text !== "Unassigned"; + const showCopyPathShortcut = copyPathDisplay.text !== "Unassigned"; + const isLoading = openInApp.isPending || copyPath.isPending; + const isDark = activeTheme?.type === "dark"; + + const handleOpenInEditor = useCallback(() => { + if (openInApp.isPending || copyPath.isPending) return; + openInApp.mutate({ path: worktreePath, app: defaultApp }); + }, [worktreePath, defaultApp, openInApp, copyPath.isPending]); + + const handleOpenInOtherApp = useCallback( + (appId: ExternalApp) => { + if (openInApp.isPending || copyPath.isPending) return; + openInApp.mutate({ path: worktreePath, app: appId }); + }, + [worktreePath, openInApp, copyPath.isPending], + ); + + const handleCopyPath = useCallback(() => { + if (openInApp.isPending || copyPath.isPending) return; + copyPath.mutate(worktreePath); + }, [worktreePath, copyPath, openInApp.isPending]); + + useHotkey("OPEN_IN_APP", handleOpenInEditor); return ( - +
+ + + + + + {currentApp ? ( + + ) : ( + "Select an editor from the dropdown" + )} + + + + + + + + + + { + if ( + appId !== defaultApp || + !showOpenInShortcut || + group === "jetbrains" + ) { + return null; + } + return ( + + {openInDisplay.text} + + ); + }} + copyPathTrailing={ + showCopyPathShortcut ? ( + + {copyPathDisplay.text} + + ) : null + } + subContentClassName="w-40" + appContentClassName="gap-0" + appIconClassName="size-4 object-contain mr-2" + subTriggerIconClassName="size-4 object-contain mr-2" + subTriggerContentClassName="flex items-center gap-0" + copyPathContentClassName="gap-0" + copyPathIconClassName="mr-2" + /> + + +
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx index ed03ea90592..ccfb65d3a1f 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx @@ -1,6 +1,8 @@ import { and, eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; +import { useQuery } from "@tanstack/react-query"; import { electronTrpc } from "renderer/lib/electron-trpc"; +import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; import { useCollections } from "../../../../../providers/CollectionsProvider"; import { useHostService } from "../../../../../providers/HostServiceProvider/HostServiceProvider"; import { V2OpenInMenuButton } from "../V2OpenInMenuButton"; @@ -42,15 +44,27 @@ export function V2WorkspaceOpenInButton({ const isLocalWorkspace = Boolean(workspace) && workspace.deviceId === currentDevice?.id; + const workspaceQuery = useQuery({ + queryKey: ["v2-open-in-workspace", hostUrl, workspaceId], + queryFn: () => + getHostServiceClientByUrl(hostUrl as string).workspace.get.query({ + id: workspaceId, + }), + enabled: !!workspace && !!hostUrl && isLocalWorkspace, + }); + if (!workspace || !hostUrl || !isLocalWorkspace) { return null; } + if (!workspaceQuery.data?.worktreePath) { + return null; + } + return ( ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts index f42d80b5597..a34167897b5 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts @@ -39,6 +39,7 @@ export const workspaceLocalStateSchema = z.object({ }), paneLayout: paneWorkspaceStateSchema, rightSidebarOpen: z.boolean().default(false), + defaultOpenInApp: z.string().nullable().default(null), }); export const dashboardSidebarSectionSchema = z.object({