| ... | @@ -3,7 +3,7 @@ import { | ... | @@ -3,7 +3,7 @@ import { |
| 3 | isAtomCommandInterrupted, | 3 | isAtomCommandInterrupted, |
| 4 | squashAtomCommandFailure, | 4 | squashAtomCommandFailure, |
| 5 | } from "@t3tools/client-runtime/state/runtime"; | 5 | } from "@t3tools/client-runtime/state/runtime"; |
| 6 | import type { EnvironmentId, VcsRef, ThreadId } from "@t3tools/contracts"; | 6 | import type { ContextMenuItem, EnvironmentId, VcsRef, ThreadId } from "@t3tools/contracts"; |
| 7 | import { LegendList, type LegendListRef } from "@legendapp/list/react"; | 7 | import { LegendList, type LegendListRef } from "@legendapp/list/react"; |
| 8 | import { ChevronDownIcon, GitBranchIcon, RefreshCwIcon, SearchIcon } from "lucide-react"; | 8 | import { ChevronDownIcon, GitBranchIcon, RefreshCwIcon, SearchIcon } from "lucide-react"; |
| 9 | import { | 9 | import { |
| ... | @@ -17,9 +17,12 @@ import { | ... | @@ -17,9 +17,12 @@ import { |
| 17 | useRef, | 17 | useRef, |
| 18 | useState, | 18 | useState, |
| 19 | useTransition, | 19 | useTransition, |
| | 20 | type MouseEvent as ReactMouseEvent, |
| 20 | } from "react"; | 21 | } from "react"; |
| 21 | | 22 | |
| 22 | import { useComposerDraftStore, type DraftId } from "../composerDraftStore"; | 23 | import { useComposerDraftStore, type DraftId } from "../composerDraftStore"; |
| | 24 | import { writeTextToClipboard } from "../hooks/useCopyToClipboard"; |
| | 25 | import { readLocalApi } from "../localApi"; |
| 23 | import { useOpenPrLink } from "../lib/openPullRequestLink"; | 26 | import { useOpenPrLink } from "../lib/openPullRequestLink"; |
| 24 | import { usePaginatedBranches } from "../state/queries"; | 27 | import { usePaginatedBranches } from "../state/queries"; |
| 25 | import { useProject, useThread } from "../state/entities"; | 28 | import { useProject, useThread } from "../state/entities"; |
| ... | @@ -317,6 +320,45 @@ export function BranchToolbarBranchSelector({ | ... | @@ -317,6 +320,45 @@ export function BranchToolbarBranchSelector({ |
| 317 | // --------------------------------------------------------------------------- | 320 | // --------------------------------------------------------------------------- |
| 318 | // Branch actions | 321 | // Branch actions |
| 319 | // --------------------------------------------------------------------------- | 322 | // --------------------------------------------------------------------------- |
| | 323 | const copyBranchName = useCallback((branchName: string) => { |
| | 324 | void writeTextToClipboard(branchName, "branch name").then( |
| | 325 | (didCopy) => { |
| | 326 | if (!didCopy) return; |
| | 327 | toastManager.add({ |
| | 328 | type: "success", |
| | 329 | title: "Branch name copied", |
| | 330 | description: branchName, |
| | 331 | }); |
| | 332 | }, |
| | 333 | (error: unknown) => { |
| | 334 | toastManager.add( |
| | 335 | stackedThreadToast({ |
| | 336 | type: "error", |
| | 337 | title: "Failed to copy branch name", |
| | 338 | description: toBranchActionErrorMessage(error), |
| | 339 | }), |
| | 340 | ); |
| | 341 | }, |
| | 342 | ); |
| | 343 | }, []); |
| | 344 | |
| | 345 | const handleBranchContextMenu = useCallback( |
| | 346 | (event: ReactMouseEvent, branchName: string | null) => { |
| | 347 | if (!branchName) return; |
| | 348 | const api = readLocalApi(); |
| | 349 | if (!api) return; |
| | 350 | event.preventDefault(); |
| | 351 | event.stopPropagation(); |
| | 352 | const items: ContextMenuItem<"copy-branch-name">[] = [ |
| | 353 | { id: "copy-branch-name", label: "Copy branch name", icon: "copy" }, |
| | 354 | ]; |
| | 355 | void api.contextMenu.show(items, { x: event.clientX, y: event.clientY }).then((action) => { |
| | 356 | if (action === "copy-branch-name") copyBranchName(branchName); |
| | 357 | }); |
| | 358 | }, |
| | 359 | [copyBranchName], |
| | 360 | ); |
| | 361 | |
| 320 | const runBranchAction = (action: () => Promise<void>) => { | 362 | const runBranchAction = (action: () => Promise<void>) => { |
| 321 | startBranchActionTransition(async () => { | 363 | startBranchActionTransition(async () => { |
| 322 | await action(); | 364 | await action(); |
| ... | @@ -624,6 +666,7 @@ export function BranchToolbarBranchSelector({ | ... | @@ -624,6 +666,7 @@ export function BranchToolbarBranchSelector({ |
| 624 | value={itemValue} | 666 | value={itemValue} |
| 625 | className="pe-1.5" | 667 | className="pe-1.5" |
| 626 | onClick={() => selectBranch(refName)} | 668 | onClick={() => selectBranch(refName)} |
| | 669 | onContextMenu={(event) => handleBranchContextMenu(event, itemValue)} |
| 627 | > | 670 | > |
| 628 | <div className="flex w-full min-w-0 items-center justify-between gap-2"> | 671 | <div className="flex w-full min-w-0 items-center justify-between gap-2"> |
| 629 | <span className="min-w-0 flex-1 truncate">{itemValue}</span> | 672 | <span className="min-w-0 flex-1 truncate">{itemValue}</span> |
| ... | @@ -674,15 +717,23 @@ export function BranchToolbarBranchSelector({ | ... | @@ -674,15 +717,23 @@ export function BranchToolbarBranchSelector({ |
| 674 | <TooltipPopup side="top">{branchPrTooltip}</TooltipPopup> | 717 | <TooltipPopup side="top">{branchPrTooltip}</TooltipPopup> |
| 675 | </Tooltip> | 718 | </Tooltip> |
| 676 | ) : null} | 719 | ) : null} |
| 677 | <ComboboxTrigger | 720 | {/* Context menu lives on the wrapper: the disabled Button has |
| 678 | render={<Button variant="ghost" size="xs" />} | 721 | pointer-events-none, so the trigger itself never sees right-clicks |
| 679 | className="min-w-0 text-muted-foreground/70 hover:text-foreground/80" | 722 | while refs are loading or a branch action is pending. */} |
| 680 | disabled={isInitialBranchesLoadPending || isBranchActionPending} | 723 | <span |
| | 724 | className="flex min-w-0" |
| | 725 | onContextMenu={(event) => handleBranchContextMenu(event, resolvedActiveBranch)} |
| 681 | > | 726 | > |
| 682 | <GitBranchIcon className="size-3 shrink-0 opacity-70" /> | 727 | <ComboboxTrigger |
| 683 | <span className="min-w-0 max-w-[240px] truncate">{triggerLabel}</span> | 728 | render={<Button variant="ghost" size="xs" />} |
| 684 | <ChevronDownIcon className="size-3 shrink-0 opacity-50" /> | 729 | className="min-w-0 text-muted-foreground/70 hover:text-foreground/80" |
| 685 | </ComboboxTrigger> | 730 | disabled={isInitialBranchesLoadPending || isBranchActionPending} |
| | 731 | > |
| | 732 | <GitBranchIcon className="size-3 shrink-0 opacity-70" /> |
| | 733 | <span className="min-w-0 max-w-[240px] truncate">{triggerLabel}</span> |
| | 734 | <ChevronDownIcon className="size-3 shrink-0 opacity-50" /> |
| | 735 | </ComboboxTrigger> |
| | 736 | </span> |
| 686 | </div> | 737 | </div> |
| 687 | <ComboboxPopup align="end" side="top" className="flex w-80 flex-col"> | 738 | <ComboboxPopup align="end" side="top" className="flex w-80 flex-col"> |
| 688 | <div className="shrink-0 px-3 pt-2.5"> | 739 | <div className="shrink-0 px-3 pt-2.5"> |