diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx index ddf0d93bee6..3f616552ef9 100644 --- a/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx @@ -1,4 +1,9 @@ import { Button } from "@superset/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@superset/ui/collapsible"; import { Command, CommandEmpty, @@ -9,7 +14,6 @@ import { import { Dialog, DialogContent, - DialogFooter, DialogHeader, DialogTitle, } from "@superset/ui/dialog"; @@ -24,9 +28,14 @@ import { } from "@superset/ui/select"; import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { GoGitBranch } from "react-icons/go"; -import { HiCheck, HiChevronUpDown, HiPlus } from "react-icons/hi2"; +import { + HiCheck, + HiChevronDown, + HiChevronUpDown, + HiPlus, +} from "react-icons/hi2"; import { formatRelativeTime } from "renderer/lib/formatRelativeTime"; import { trpc } from "renderer/lib/trpc"; import { useOpenNew } from "renderer/react-query/projects"; @@ -68,6 +77,8 @@ export function NewWorkspaceModal() { const [baseBranch, setBaseBranch] = useState(null); const [baseBranchOpen, setBaseBranchOpen] = useState(false); const [branchSearch, setBranchSearch] = useState(""); + const [showAdvanced, setShowAdvanced] = useState(false); + const titleInputRef = useRef(null); const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); const { data: recentProjects = [] } = trpc.projects.getRecents.useQuery(); @@ -126,6 +137,30 @@ export function NewWorkspaceModal() { setMode("new"); setBaseBranch(null); setBranchSearch(""); + setShowAdvanced(false); + }; + + // Focus title input when modal opens and project is selected + useEffect(() => { + if (isOpen && selectedProjectId && mode === "new") { + // Small delay to ensure dialog is fully rendered + const timer = setTimeout(() => { + titleInputRef.current?.focus(); + }, 50); + return () => clearTimeout(timer); + } + }, [isOpen, selectedProjectId, mode]); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if ( + e.key === "Enter" && + !e.shiftKey && + mode === "new" && + selectedProjectId + ) { + e.preventDefault(); + handleCreateWorkspace(); + } }; const handleClose = () => { @@ -195,12 +230,14 @@ export function NewWorkspaceModal() { return ( !open && handleClose()}> - + Open Workspace - {/* Project Selector */}
setTitle(e.target.value)} + /> + + {title && !showAdvanced && ( +

+ + + {branchName || generateBranchFromTitle(title)} + - (optional) + from {effectiveBaseBranch} - - setTitle(e.target.value)} - /> -

- -
- - handleBranchNameChange(e.target.value)} - /> -
+

+ )} -
- - Base branch - - {isBranchesError ? ( -
- Failed to load branches + + + + Advanced options + + +
+ + + handleBranchNameChange(e.target.value) + } + />
- ) : ( - - - - - - - - - No branches found - {filteredBranches.map((branch) => ( - { - setBaseBranch(branch.name); - setBaseBranchOpen(false); - setBranchSearch(""); - }} - className="flex items-center justify-between" - > - - - - {branch.name} + + + + + + + + No branches found + {filteredBranches.map((branch) => ( + { + setBaseBranch(branch.name); + setBaseBranchOpen(false); + setBranchSearch(""); + }} + className="flex items-center justify-between" + > + + + + {branch.name} + + {branch.name === + branchData?.defaultBranch && ( + + default + + )} - )} - - - {branch.lastCommitDate > 0 && ( - - {formatRelativeTime( - branch.lastCommitDate, + + {branch.lastCommitDate > 0 && ( + + {formatRelativeTime( + branch.lastCommitDate, + )} + + )} + {effectiveBaseBranch === + branch.name && ( + )} - )} - {effectiveBaseBranch === branch.name && ( - - )} - - - ))} - - - - - )} -

- Your new branch will be created from this branch -

-
+ + ))} + + + + + )} +
+ + + +
) : ( )} - - {mode === "new" && selectedProjectId && ( - - - - )}
); diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx index e303aaf3d45..cb9280161eb 100644 --- a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx @@ -1,3 +1,4 @@ +import { Button } from "@superset/ui/button"; import { toast } from "@superset/ui/sonner"; import { formatDistanceToNow } from "date-fns"; import { LuGitBranch } from "react-icons/lu"; @@ -36,6 +37,28 @@ export function ExistingWorktreesList({ }); }; + const handleOpenAll = async () => { + if (closedWorktrees.length === 0) return; + + const count = closedWorktrees.length; + toast.promise( + (async () => { + for (const wt of closedWorktrees) { + await openWorktree.mutateAsync({ worktreeId: wt.id }); + } + })(), + { + loading: `Opening ${count} workspaces...`, + success: () => { + onOpenSuccess(); + return `Opened ${count} workspaces`; + }, + error: (err) => + err instanceof Error ? err.message : "Failed to open workspaces", + }, + ); + }; + if (isLoading) { return (
@@ -64,6 +87,19 @@ export function ExistingWorktreesList({ return (
+ {closedWorktrees.length > 1 && ( +
+ +
+ )} {closedWorktrees.map((wt) => (