diff --git a/packages/app/src/components/session/session-sortable-terminal-tab.tsx b/packages/app/src/components/session/session-sortable-terminal-tab.tsx index 0e387b9fb1a..63efa54a846 100644 --- a/packages/app/src/components/session/session-sortable-terminal-tab.tsx +++ b/packages/app/src/components/session/session-sortable-terminal-tab.tsx @@ -1,14 +1,22 @@ import type { JSX } from "solid-js" +import { createSignal, Show } from "solid-js" import { createSortable } from "@thisbeyond/solid-dnd" import { IconButton } from "@opencode-ai/ui/icon-button" import { Tabs } from "@opencode-ai/ui/tabs" +import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" +import { Icon } from "@opencode-ai/ui/icon" import { useTerminal, type LocalPTY } from "@/context/terminal" import { useLanguage } from "@/context/language" -export function SortableTerminalTab(props: { terminal: LocalPTY }): JSX.Element { +export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => void }): JSX.Element { const terminal = useTerminal() const language = useLanguage() const sortable = createSortable(props.terminal.id) + const [editing, setEditing] = createSignal(false) + const [title, setTitle] = createSignal(props.terminal.title) + const [menuOpen, setMenuOpen] = createSignal(false) + const [menuPosition, setMenuPosition] = createSignal({ x: 0, y: 0 }) + const [blurEnabled, setBlurEnabled] = createSignal(false) const label = () => { language.locale() @@ -19,20 +27,138 @@ export function SortableTerminalTab(props: { terminal: LocalPTY }): JSX.Element if (props.terminal.title) return props.terminal.title return language.t("terminal.title") } + + const close = () => { + const count = terminal.all().length + terminal.close(props.terminal.id) + if (count === 1) { + props.onClose?.() + } + } + + const focus = () => { + if (editing()) return + + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur() + } + const wrapper = document.getElementById(`terminal-wrapper-${props.terminal.id}`) + const element = wrapper?.querySelector('[data-component="terminal"]') as HTMLElement + if (!element) return + + const textarea = element.querySelector("textarea") as HTMLTextAreaElement + if (textarea) { + textarea.focus() + return + } + element.focus() + element.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true })) + } + + const edit = (e?: Event) => { + if (e) { + e.stopPropagation() + e.preventDefault() + } + + setBlurEnabled(false) + setTitle(props.terminal.title) + setEditing(true) + setTimeout(() => { + const input = document.getElementById(`terminal-title-input-${props.terminal.id}`) as HTMLInputElement + if (!input) return + input.focus() + input.select() + setTimeout(() => setBlurEnabled(true), 100) + }, 10) + } + + const save = () => { + if (!blurEnabled()) return + + const value = title().trim() + if (value && value !== props.terminal.title) { + terminal.update({ id: props.terminal.id, title: value }) + } + setEditing(false) + } + + const keydown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault() + save() + return + } + if (e.key === "Escape") { + e.preventDefault() + setEditing(false) + } + } + + const menu = (e: MouseEvent) => { + e.preventDefault() + setMenuPosition({ x: e.clientX, y: e.clientY }) + setMenuOpen(true) + } + return ( // @ts-ignore