diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceDropdown.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceDropdown.tsx index 0ec504a01a..29b14a50f2 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceDropdown.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceDropdown.tsx @@ -1,32 +1,92 @@ import { Button } from "@superset/ui/button"; +import { ButtonGroup } from "@superset/ui/button-group"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "@superset/ui/dropdown-menu"; import { toast } from "@superset/ui/sonner"; -import { useState } from "react"; -import { HiMiniFolderOpen, HiMiniPlus } from "react-icons/hi2"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; +import { useRef, useState } from "react"; +import { + HiChevronDown, + HiChevronUp, + HiMiniFolderOpen, + HiMiniPlus, +} from "react-icons/hi2"; import { trpc } from "renderer/lib/trpc"; import { useOpenNew } from "renderer/react-query/projects"; import { useCreateWorkspace } from "renderer/react-query/workspaces"; +const INITIAL_PROJECTS_LIMIT = 5; + +/** + * Formats a path for display, replacing the home directory with ~ and + * removing the trailing project name directory. + */ +function formatPath( + path: string, + projectName: string, + homeDir: string | undefined, +): string { + const normalizedPath = path.replace(/\\/g, "/"); + const normalizedHome = homeDir ? homeDir.replace(/\\/g, "/") : null; + + let displayPath = normalizedPath; + if ( + normalizedHome && + (normalizedPath === normalizedHome || + normalizedPath.startsWith(`${normalizedHome}/`)) + ) { + displayPath = `~${normalizedPath.slice(normalizedHome.length)}`; + } else { + displayPath = normalizedPath.replace(/^\/(?:Users|home)\/[^/]+/, "~"); + } + + const escapedProjectName = projectName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const suffixPattern = new RegExp(`/${escapedProjectName}$`); + return displayPath.replace(suffixPattern, ""); +} + export interface WorkspaceDropdownProps { className?: string; } export function WorkspaceDropdown({ className }: WorkspaceDropdownProps) { const [isOpen, setIsOpen] = useState(false); + const [showAllProjects, setShowAllProjects] = useState(false); + const primaryButtonRef = useRef(null); + const dropdownTriggerRef = useRef(null); + const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); const { data: recentProjects = [] } = trpc.projects.getRecents.useQuery(); + const { data: homeDir } = trpc.window.getHomeDir.useQuery(); const createWorkspace = useCreateWorkspace(); const openNew = useOpenNew(); + const currentProject = recentProjects.find( + (p) => p.id === activeWorkspace?.projectId, + ); + const otherProjects = recentProjects.filter( + (p) => p.id !== activeWorkspace?.projectId, + ); + const visibleProjects = showAllProjects + ? otherProjects + : otherProjects.slice(0, INITIAL_PROJECTS_LIMIT); + const hasMoreProjects = otherProjects.length > INITIAL_PROJECTS_LIMIT; + + const closeDropdown = () => { + setIsOpen(false); + setShowAllProjects(false); + primaryButtonRef.current?.blur(); + dropdownTriggerRef.current?.blur(); + }; + const handleCreateWorkspace = async (projectId: string) => { toast.promise(createWorkspace.mutateAsync({ projectId }), { loading: "Creating workspace...", success: () => { - setIsOpen(false); + closeDropdown(); return "Workspace created"; }, error: (err) => @@ -35,6 +95,7 @@ export function WorkspaceDropdown({ className }: WorkspaceDropdownProps) { }; const handleOpenNewProject = async () => { + closeDropdown(); try { const result = await openNew.mutateAsync(undefined); if (!result.canceled && result.project) { @@ -48,54 +109,155 @@ export function WorkspaceDropdown({ className }: WorkspaceDropdownProps) { } }; + const handlePrimaryAction = () => { + primaryButtonRef.current?.blur(); + if (currentProject) { + handleCreateWorkspace(currentProject.id); + } else { + handleOpenNewProject(); + } + }; + + const handleOpenChange = (open: boolean) => { + if (open) { + setIsOpen(true); + dropdownTriggerRef.current?.blur(); + } else { + closeDropdown(); + } + }; + return ( - - - - - -
- {recentProjects.length > 0 && ( -
-

- Recent Projects + + + + + + + {currentProject + ? `New workspace in ${currentProject.name}` + : "New workspace"} + + + + + + + + + + + More options + + + +

+

New Workspace

+

+ Select a project to create a workspace +

+
+ {currentProject && ( +
+

+ Current project

- {recentProjects.map((project) => ( +
- ))} +
+
+ )} + {otherProjects.length > 0 && ( +
+

+ {currentProject ? "Other projects" : "Recent projects"} +

+
+ {visibleProjects.map((project) => ( + + ))} +
+ {hasMoreProjects && ( + + )}
)} -
+
-
- - + + + ); }