| 1 | import { ComposerSelectControl } from "./chat/ComposerControl"; |
| 2 | import { ComposerContextLabel } from "./ComposerContextLabel"; |
| 3 | import { FolderGit2Icon, FolderGitIcon, FolderIcon } from "lucide-react"; |
| 4 | import { memo, useMemo, type MouseEvent as ReactMouseEvent } from "react"; |
| 5 | |
| 6 | import { |
| 7 | resolveCurrentWorkspaceLabel, |
| 8 | resolveEnvModeLabel, |
| 9 | resolveLockedWorkspaceLabel, |
| 10 | type EnvMode, |
| 11 | } from "./BranchToolbar.logic"; |
| 12 | import { useComposerMenuProps } from "./chat/composerEventScope"; |
| 13 | import { PreviousWorktreeItemContent } from "./PreviousWorktreeItemContent"; |
| 14 | import { |
| 15 | Select, |
| 16 | SelectGroup, |
| 17 | SelectGroupLabel, |
| 18 | SelectItem, |
| 19 | SelectPopup, |
| 20 | SelectValue, |
| 21 | } from "./ui/select"; |
| 22 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; |
| 23 | |
| 24 | const PREVIOUS_WORKTREE_SELECT_VALUE = "previous-worktree"; |
| 25 | |
| 26 | interface BranchToolbarEnvModeSelectorProps { |
| 27 | forceNewWorktree?: boolean; |
| 28 | envLocked: boolean; |
| 29 | effectiveEnvMode: EnvMode; |
| 30 | activeWorktreePath: string | null; |
| 31 | onEnvModeChange: (mode: EnvMode) => void; |
| 32 | previousWorktreeLabel?: string | null; |
| 33 | previousWorktreeBranch?: string | null; |
| 34 | onUsePreviousWorktree?: () => void; |
| 35 | } |
| 36 | |
| 37 | export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSelector({ |
| 38 | forceNewWorktree = false, |
| 39 | envLocked, |
| 40 | effectiveEnvMode, |
| 41 | activeWorktreePath, |
| 42 | onEnvModeChange, |
| 43 | previousWorktreeLabel, |
| 44 | previousWorktreeBranch = null, |
| 45 | onUsePreviousWorktree, |
| 46 | }: BranchToolbarEnvModeSelectorProps) { |
| 47 | const composerFloatingLayerProps = useComposerMenuProps(); |
| 48 | const showPreviousWorktree = Boolean(previousWorktreeLabel && onUsePreviousWorktree); |
| 49 | const envModeItems = useMemo( |
| 50 | () => [ |
| 51 | { |
| 52 | value: "local", |
| 53 | label: resolveCurrentWorkspaceLabel(activeWorktreePath), |
| 54 | }, |
| 55 | { value: "worktree", label: resolveEnvModeLabel("worktree") }, |
| 56 | ...(showPreviousWorktree && previousWorktreeLabel |
| 57 | ? [{ value: PREVIOUS_WORKTREE_SELECT_VALUE, label: previousWorktreeLabel }] |
| 58 | : []), |
| 59 | ], |
| 60 | [activeWorktreePath, previousWorktreeLabel, showPreviousWorktree], |
| 61 | ); |
| 62 | |
| 63 | const stopContextMenuMouseDown = (event: ReactMouseEvent) => { |
| 64 | if (event.button !== 0 || event.ctrlKey) { |
| 65 | event.stopPropagation(); |
| 66 | } |
| 67 | }; |
| 68 | |
| 69 | if (envLocked || forceNewWorktree) { |
| 70 | const lockedRow = ( |
| 71 | <span |
| 72 | className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-1.75 font-normal text-muted-foreground/70 text-xs sm:h-6" |
| 73 | data-composer-context-control |
| 74 | > |
| 75 | {activeWorktreePath ? ( |
| 76 | <FolderGitIcon className="size-3" /> |
| 77 | ) : effectiveEnvMode === "worktree" ? ( |
| 78 | <FolderGit2Icon className="size-3" /> |
| 79 | ) : ( |
| 80 | <FolderIcon className="size-3" /> |
| 81 | )} |
| 82 | <ComposerContextLabel> |
| 83 | {forceNewWorktree |
| 84 | ? resolveEnvModeLabel("worktree") |
| 85 | : resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)} |
| 86 | </ComposerContextLabel> |
| 87 | </span> |
| 88 | ); |
| 89 | |
| 90 | return ( |
| 91 | <Tooltip> |
| 92 | <TooltipTrigger render={lockedRow} /> |
| 93 | <TooltipPopup> |
| 94 | {forceNewWorktree |
| 95 | ? "Each model starts in its own worktree." |
| 96 | : resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)} |
| 97 | </TooltipPopup> |
| 98 | </Tooltip> |
| 99 | ); |
| 100 | } |
| 101 | |
| 102 | return ( |
| 103 | <Select |
| 104 | modal={false} |
| 105 | value={effectiveEnvMode} |
| 106 | onValueChange={(value: string | null) => { |
| 107 | if (value === PREVIOUS_WORKTREE_SELECT_VALUE) { |
| 108 | onUsePreviousWorktree?.(); |
| 109 | return; |
| 110 | } |
| 111 | onEnvModeChange(value as EnvMode); |
| 112 | }} |
| 113 | items={envModeItems} |
| 114 | > |
| 115 | <Tooltip> |
| 116 | <TooltipTrigger |
| 117 | render={ |
| 118 | <ComposerSelectControl |
| 119 | size="xs" |
| 120 | className="min-w-0 shrink" |
| 121 | aria-label="Workspace" |
| 122 | data-composer-shortcut="composer.workspace" |
| 123 | data-composer-context-control |
| 124 | onMouseDownCapture={stopContextMenuMouseDown} |
| 125 | /> |
| 126 | } |
| 127 | > |
| 128 | {effectiveEnvMode === "worktree" ? ( |
| 129 | <FolderGit2Icon className="size-3" /> |
| 130 | ) : activeWorktreePath ? ( |
| 131 | <FolderGitIcon className="size-3" /> |
| 132 | ) : ( |
| 133 | <FolderIcon className="size-3" /> |
| 134 | )} |
| 135 | <ComposerContextLabel> |
| 136 | <SelectValue /> |
| 137 | </ComposerContextLabel> |
| 138 | </TooltipTrigger> |
| 139 | <TooltipPopup> |
| 140 | {effectiveEnvMode === "worktree" |
| 141 | ? resolveEnvModeLabel("worktree") |
| 142 | : resolveCurrentWorkspaceLabel(activeWorktreePath)} |
| 143 | </TooltipPopup> |
| 144 | </Tooltip> |
| 145 | <SelectPopup |
| 146 | alignItemWithTrigger={false} |
| 147 | {...composerFloatingLayerProps} |
| 148 | className={showPreviousWorktree ? "w-[min(21rem,calc(100vw-2rem))]" : undefined} |
| 149 | > |
| 150 | <SelectGroup> |
| 151 | <SelectGroupLabel>Workspace</SelectGroupLabel> |
| 152 | <SelectItem value="local"> |
| 153 | <span className="inline-flex items-center gap-1.5"> |
| 154 | {activeWorktreePath ? ( |
| 155 | <FolderGitIcon className="size-3" /> |
| 156 | ) : ( |
| 157 | <FolderIcon className="size-3" /> |
| 158 | )} |
| 159 | {resolveCurrentWorkspaceLabel(activeWorktreePath)} |
| 160 | </span> |
| 161 | </SelectItem> |
| 162 | <SelectItem value="worktree"> |
| 163 | <span className="inline-flex items-center gap-1.5"> |
| 164 | <FolderGit2Icon className="size-3" /> |
| 165 | {resolveEnvModeLabel("worktree")} |
| 166 | </span> |
| 167 | </SelectItem> |
| 168 | {showPreviousWorktree && previousWorktreeLabel ? ( |
| 169 | <SelectItem value={PREVIOUS_WORKTREE_SELECT_VALUE}> |
| 170 | <PreviousWorktreeItemContent branch={previousWorktreeBranch} /> |
| 171 | </SelectItem> |
| 172 | ) : null} |
| 173 | </SelectGroup> |
| 174 | </SelectPopup> |
| 175 | </Select> |
| 176 | ); |
| 177 | }); |