| ... | ... | @@ -6,11 +6,11 @@ import { |
| 6 | 6 | import { type TerminalSessionState } from "@t3tools/client-runtime/state/terminal"; |
| 7 | 7 | import { |
| 8 | 8 | Plus, |
| 9 | Square, |
| 9 | 10 | SquareSplitHorizontal, |
| 10 | 11 | SquareSplitVertical, |
| 11 | 12 | TerminalSquare, |
| 12 | 13 | Trash2, |
| 13 | | XIcon, |
| 14 | 14 | } from "lucide-react"; |
| 15 | 15 | import { |
| 16 | 16 | type ContextMenuItem, |
| ... | ... | @@ -33,6 +33,7 @@ import { |
| 33 | 33 | } from "react"; |
| 34 | 34 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; |
| 35 | 35 | import { Button } from "~/components/ui/button"; |
| 36 | import { PanelTabCloseButton } from "~/components/ui/panel-tab-close-button"; |
| 36 | 37 | import { readTextFromClipboard, writeTextToClipboard } from "~/hooks/useCopyToClipboard"; |
| 37 | 38 | import { cn } from "~/lib/utils"; |
| 38 | 39 | import { type TerminalContextSelection } from "~/lib/terminalContext"; |
| ... | ... | @@ -1622,85 +1623,76 @@ export default function ThreadTerminalDrawer({ |
| 1622 | 1623 | </div> |
| 1623 | 1624 | |
| 1624 | 1625 | <div className="min-h-0 flex-1 overflow-y-auto px-1 py-1"> |
| 1625 | | {resolvedTerminalGroups.map((terminalGroup, groupIndex) => { |
| 1626 | {resolvedTerminalGroups.map((terminalGroup) => { |
| 1626 | 1627 | const isGroupActive = |
| 1627 | 1628 | terminalGroup.terminalIds.includes(resolvedActiveTerminalId); |
| 1628 | 1629 | const groupActiveTerminalId = isGroupActive |
| 1629 | 1630 | ? resolvedActiveTerminalId |
| 1630 | 1631 | : (terminalGroup.terminalIds[0] ?? resolvedActiveTerminalId); |
| 1632 | const terminalCount = terminalGroup.terminalIds.length; |
| 1633 | const isSplitGroup = terminalCount > 1; |
| 1634 | const groupLabel = !isSplitGroup |
| 1635 | ? "Single" |
| 1636 | : terminalGroup.splitDirection === "vertical" |
| 1637 | ? "Stacked" |
| 1638 | : "Side by side"; |
| 1639 | const GroupIcon = !isSplitGroup |
| 1640 | ? Square |
| 1641 | : terminalGroup.splitDirection === "vertical" |
| 1642 | ? SquareSplitVertical |
| 1643 | : SquareSplitHorizontal; |
| 1631 | 1644 | |
| 1632 | 1645 | return ( |
| 1633 | 1646 | <div key={terminalGroup.id} className="pb-0.5"> |
| 1634 | 1647 | {showGroupHeaders && ( |
| 1635 | 1648 | <button |
| 1636 | 1649 | type="button" |
| 1637 | | className={`flex w-full items-center rounded px-1 py-0.5 text-[10px] uppercase tracking-[0.08em] ${ |
| 1650 | className={`flex h-[22px] w-full cursor-pointer items-center gap-1 rounded px-1.5 text-[11px] ${ |
| 1638 | 1651 | isGroupActive |
| 1639 | | ? "bg-accent/70 text-foreground" |
| 1640 | | : "text-muted-foreground hover:bg-accent/50 hover:text-foreground" |
| 1652 | ? "bg-accent/50 text-foreground" |
| 1653 | : "text-muted-foreground hover:bg-accent/40 hover:text-foreground" |
| 1641 | 1654 | }`} |
| 1642 | 1655 | onClick={() => onActiveTerminalChange(groupActiveTerminalId)} |
| 1643 | 1656 | > |
| 1644 | | Group {groupIndex + 1} |
| 1657 | <GroupIcon className="size-3 shrink-0" /> |
| 1658 | <span className="min-w-0 flex-1 truncate text-left">{groupLabel}</span> |
| 1659 | <span className="text-muted-foreground/70 text-[10px] tabular-nums"> |
| 1660 | {terminalCount} |
| 1661 | </span> |
| 1645 | 1662 | </button> |
| 1646 | 1663 | )} |
| 1647 | 1664 | |
| 1648 | | <div |
| 1649 | | className={showGroupHeaders ? "ml-1 border-l border-border/60 pl-1.5" : ""} |
| 1650 | | > |
| 1665 | <div className="flex flex-col gap-0.5"> |
| 1651 | 1666 | {terminalGroup.terminalIds.map((terminalId) => { |
| 1652 | 1667 | const isActive = terminalId === resolvedActiveTerminalId; |
| 1653 | | const closeTerminalLabel = `Close ${ |
| 1654 | | terminalLabelById.get(terminalId) ?? "terminal" |
| 1655 | | }${isActive && closeShortcutLabel ? ` (${closeShortcutLabel})` : ""}`; |
| 1668 | const terminalLabel = terminalLabelById.get(terminalId) ?? "Terminal"; |
| 1669 | const closeTerminalLabel = `Close ${terminalLabel}${ |
| 1670 | isActive && closeShortcutLabel ? ` (${closeShortcutLabel})` : "" |
| 1671 | }`; |
| 1656 | 1672 | return ( |
| 1657 | 1673 | <div |
| 1658 | 1674 | key={terminalId} |
| 1659 | | className={`group flex items-center gap-1 rounded px-1 py-0.5 text-[11px] ${ |
| 1675 | className={cn( |
| 1676 | "group/tab flex h-6 w-full items-center gap-0.5 rounded-md pr-2 pl-1.5 text-xs", |
| 1660 | 1677 | isActive |
| 1661 | 1678 | ? "bg-accent text-foreground" |
| 1662 | | : "text-muted-foreground hover:bg-accent/50 hover:text-foreground" |
| 1663 | | }`} |
| 1664 | | > |
| 1665 | | {showGroupHeaders && ( |
| 1666 | | <span className="text-[10px] text-muted-foreground/80">└</span> |
| 1679 | : "text-muted-foreground hover:bg-accent/60 hover:text-foreground", |
| 1667 | 1680 | )} |
| 1681 | > |
| 1682 | <PanelTabCloseButton |
| 1683 | label={closeTerminalLabel} |
| 1684 | onClick={() => confirmCloseTerminal(terminalId)} |
| 1685 | tooltip={closeTerminalLabel} |
| 1686 | > |
| 1687 | <TerminalSquare className="size-3 shrink-0" /> |
| 1688 | </PanelTabCloseButton> |
| 1668 | 1689 | <button |
| 1669 | 1690 | type="button" |
| 1670 | | className="flex min-w-0 flex-1 items-center gap-1 text-left" |
| 1691 | className="flex min-w-0 flex-1 cursor-pointer items-center gap-1 text-left" |
| 1671 | 1692 | onClick={() => onActiveTerminalChange(terminalId)} |
| 1672 | 1693 | > |
| 1673 | | <TerminalSquare className="size-3 shrink-0" /> |
| 1674 | | <span className="truncate"> |
| 1675 | | {terminalLabelById.get(terminalId) ?? "Terminal"} |
| 1676 | | </span> |
| 1694 | <span className="truncate">{terminalLabel}</span> |
| 1677 | 1695 | </button> |
| 1678 | | {normalizedTerminalIds.length > 1 && ( |
| 1679 | | <Popover> |
| 1680 | | <PopoverTrigger |
| 1681 | | openOnHover |
| 1682 | | render={ |
| 1683 | | <button |
| 1684 | | type="button" |
| 1685 | | className="inline-flex size-3.5 items-center justify-center rounded text-xs font-medium leading-none text-muted-foreground opacity-0 transition hover:bg-accent hover:text-foreground group-hover:opacity-100" |
| 1686 | | onClick={() => confirmCloseTerminal(terminalId)} |
| 1687 | | aria-label={closeTerminalLabel} |
| 1688 | | /> |
| 1689 | | } |
| 1690 | | > |
| 1691 | | <XIcon className="size-2.5" /> |
| 1692 | | </PopoverTrigger> |
| 1693 | | <PopoverPopup |
| 1694 | | tooltipStyle |
| 1695 | | side="bottom" |
| 1696 | | sideOffset={6} |
| 1697 | | align="center" |
| 1698 | | className="pointer-events-none select-none" |
| 1699 | | > |
| 1700 | | {closeTerminalLabel} |
| 1701 | | </PopoverPopup> |
| 1702 | | </Popover> |
| 1703 | | )} |
| 1704 | 1696 | </div> |
| 1705 | 1697 | ); |
| 1706 | 1698 | })} |