Make floating terminal trigger more obvious (#1943)
This commit is contained in:
parent
da95082fd7
commit
7c384a36e3
|
|
@ -1,10 +1,8 @@
|
|||
/* eslint-disable max-lines -- Why: the floating surface owns both terminal chrome and local notes tabs until the shared floating shell is extracted. */
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Maximize2, Minimize2, Minus } from 'lucide-react'
|
||||
import TabBar from '@/components/tab-bar/TabBar'
|
||||
import TerminalPane from '@/components/terminal-pane/TerminalPane'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface'
|
||||
import {
|
||||
ORCHESTRATION_SETUP_DISMISSED_STORAGE_KEY,
|
||||
|
|
@ -23,6 +21,7 @@ import type { TerminalTab } from '../../../../shared/types'
|
|||
import { FloatingTerminalOrchestrationDialog } from './FloatingTerminalOrchestrationDialog'
|
||||
import ProjectNotesTabContent from '@/components/notes/ProjectNotesTabContent'
|
||||
import { FloatingTerminalResizeHandles } from './FloatingTerminalResizeHandles'
|
||||
import { FloatingTerminalWindowControls } from './FloatingTerminalWindowControls'
|
||||
export { FloatingTerminalToggleButton } from './FloatingTerminalToggleButton'
|
||||
import {
|
||||
clampFloatingTerminalBounds,
|
||||
|
|
@ -361,7 +360,7 @@ export function FloatingTerminalPanel({
|
|||
>
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div
|
||||
className="flex h-9 shrink-0 items-center border-b border-border bg-[var(--bg-titlebar,var(--card))]"
|
||||
className="flex h-9 shrink-0 cursor-grab items-center border-b border-border bg-[var(--bg-titlebar,var(--card))] active:cursor-grabbing"
|
||||
onPointerDown={handleDragStart}
|
||||
onPointerMove={handleDragMove}
|
||||
onPointerUp={handleDragEnd}
|
||||
|
|
@ -401,47 +400,11 @@ export function FloatingTerminalPanel({
|
|||
tabBarOrder={tabBarOrder}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 px-2" data-floating-terminal-no-drag>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={
|
||||
maximized ? 'Restore floating terminal' : 'Maximize floating terminal'
|
||||
}
|
||||
aria-pressed={maximized}
|
||||
onClick={toggleMaximized}
|
||||
>
|
||||
{maximized ? (
|
||||
<Minimize2 className="size-3.5" />
|
||||
) : (
|
||||
<Maximize2 className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{maximized ? 'Restore' : 'Maximize'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label="Minimize floating terminal"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<Minus className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
Minimize
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<FloatingTerminalWindowControls
|
||||
maximized={maximized}
|
||||
onToggleMaximized={toggleMaximized}
|
||||
onMinimize={() => onOpenChange(false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative min-h-0 flex-1 overflow-hidden bg-background">
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function FloatingTerminalToggleButton({
|
|||
type="button"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
className="bg-card/95 shadow-xs"
|
||||
className="border-border bg-secondary text-secondary-foreground shadow-xs hover:bg-accent hover:text-accent-foreground"
|
||||
data-floating-terminal-toggle
|
||||
aria-label={open ? 'Minimize floating terminal' : 'Show floating terminal'}
|
||||
aria-pressed={open}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
import { useCallback, useMemo } from 'react'
|
||||
import { Maximize2, Minimize2, Minus } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { AGENT_CATALOG, AgentIcon } from '@/lib/agent-catalog'
|
||||
import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface'
|
||||
import { CLIENT_PLATFORM } from '@/lib/new-workspace'
|
||||
import { buildAgentStartupPlan } from '@/lib/tui-agent-startup'
|
||||
import { tuiAgentToAgentKind } from '@/lib/telemetry'
|
||||
import { useAppStore } from '@/store'
|
||||
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
|
||||
|
||||
type FloatingTerminalWindowControlsProps = {
|
||||
maximized: boolean
|
||||
onToggleMaximized: () => void
|
||||
onMinimize: () => void
|
||||
}
|
||||
|
||||
const controlButtonClassName =
|
||||
'border-border bg-secondary text-secondary-foreground shadow-xs hover:bg-accent hover:text-accent-foreground'
|
||||
|
||||
export function FloatingTerminalWindowControls({
|
||||
maximized,
|
||||
onToggleMaximized,
|
||||
onMinimize
|
||||
}: FloatingTerminalWindowControlsProps): React.JSX.Element {
|
||||
const defaultTuiAgent = useAppStore((s) => s.settings?.defaultTuiAgent ?? null)
|
||||
const createTab = useAppStore((s) => s.createTab)
|
||||
const setActiveTabForWorktree = useAppStore((s) => s.setActiveTabForWorktree)
|
||||
|
||||
const defaultAgent = defaultTuiAgent && defaultTuiAgent !== 'blank' ? defaultTuiAgent : null
|
||||
const defaultAgentLabel = useMemo(
|
||||
() =>
|
||||
defaultAgent
|
||||
? (AGENT_CATALOG.find((agent) => agent.id === defaultAgent)?.label ?? defaultAgent)
|
||||
: null,
|
||||
[defaultAgent]
|
||||
)
|
||||
|
||||
const launchDefaultAgent = useCallback(() => {
|
||||
if (!defaultAgent) {
|
||||
return
|
||||
}
|
||||
const state = useAppStore.getState()
|
||||
const startupPlan = buildAgentStartupPlan({
|
||||
agent: defaultAgent,
|
||||
prompt: '',
|
||||
cmdOverrides: state.settings?.agentCmdOverrides ?? {},
|
||||
platform: CLIENT_PLATFORM,
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
if (!startupPlan) {
|
||||
toast.error(`Could not build launch command for ${defaultAgentLabel ?? defaultAgent}.`)
|
||||
return
|
||||
}
|
||||
const tab = createTab(FLOATING_TERMINAL_WORKTREE_ID, undefined, undefined, { activate: false })
|
||||
state.queueTabStartupCommand(tab.id, {
|
||||
command: startupPlan.launchCommand,
|
||||
...(startupPlan.env ? { env: startupPlan.env } : {}),
|
||||
telemetry: {
|
||||
agent_kind: tuiAgentToAgentKind(defaultAgent),
|
||||
launch_source: 'shortcut',
|
||||
request_kind: 'new'
|
||||
}
|
||||
})
|
||||
setActiveTabForWorktree(FLOATING_TERMINAL_WORKTREE_ID, tab.id)
|
||||
const fresh = useAppStore.getState()
|
||||
const currentTabs = fresh.tabsByWorktree[FLOATING_TERMINAL_WORKTREE_ID] ?? []
|
||||
const stored = fresh.tabBarOrderByWorktree[FLOATING_TERMINAL_WORKTREE_ID] ?? []
|
||||
const validIds = new Set(currentTabs.map((entry) => entry.id))
|
||||
const order = stored.filter((id) => validIds.has(id) && id !== tab.id)
|
||||
for (const entry of currentTabs) {
|
||||
if (entry.id !== tab.id && !order.includes(entry.id)) {
|
||||
order.push(entry.id)
|
||||
}
|
||||
}
|
||||
order.push(tab.id)
|
||||
fresh.setTabBarOrder(FLOATING_TERMINAL_WORKTREE_ID, order)
|
||||
focusTerminalTabSurface(tab.id)
|
||||
}, [createTab, defaultAgent, defaultAgentLabel, setActiveTabForWorktree])
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 px-2" data-floating-terminal-no-drag>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-xs"
|
||||
className={controlButtonClassName}
|
||||
aria-label={
|
||||
defaultAgentLabel
|
||||
? `Open ${defaultAgentLabel} in floating terminal`
|
||||
: 'No default agent configured'
|
||||
}
|
||||
disabled={!defaultAgent}
|
||||
onClick={launchDefaultAgent}
|
||||
>
|
||||
<AgentIcon agent={defaultAgent} size={14} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{defaultAgentLabel ? `Open ${defaultAgentLabel}` : 'Choose a default agent first'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-xs"
|
||||
className={controlButtonClassName}
|
||||
aria-label={maximized ? 'Restore floating terminal' : 'Maximize floating terminal'}
|
||||
aria-pressed={maximized}
|
||||
onClick={onToggleMaximized}
|
||||
>
|
||||
{maximized ? <Minimize2 className="size-3.5" /> : <Maximize2 className="size-3.5" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{maximized ? 'Restore' : 'Maximize'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-xs"
|
||||
className={controlButtonClassName}
|
||||
aria-label="Minimize floating terminal"
|
||||
onClick={onMinimize}
|
||||
>
|
||||
<Minus className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
Minimize
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -908,7 +908,7 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
|
|||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex size-5 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
className="inline-flex size-5 cursor-pointer items-center justify-center rounded border border-border bg-secondary text-secondary-foreground shadow-xs transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
aria-label={floatingTerminalActionLabel}
|
||||
onClick={() => {
|
||||
window.dispatchEvent(new CustomEvent(TOGGLE_FLOATING_TERMINAL_EVENT))
|
||||
|
|
|
|||
Loading…
Reference in New Issue