import type { JSX } from "solid-js" import { Show, createEffect, onCleanup } from "solid-js" import { createStore } from "solid-js/store" 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; onClose?: () => void }): JSX.Element { const terminal = useTerminal() const language = useLanguage() const sortable = createSortable(props.terminal.id) const [store, setStore] = createStore({ editing: false, title: props.terminal.title, menuOpen: false, menuPosition: { x: 0, y: 0 }, blurEnabled: false, }) let input: HTMLInputElement | undefined let blurFrame: number | undefined const isDefaultTitle = () => { const number = props.terminal.titleNumber if (!Number.isFinite(number) || number <= 0) return false const match = props.terminal.title.match(/^Terminal (\d+)$/) if (!match) return false const parsed = Number(match[1]) if (!Number.isFinite(parsed) || parsed <= 0) return false return parsed === number } const label = () => { language.locale() if (props.terminal.title && !isDefaultTitle()) return props.terminal.title const number = props.terminal.titleNumber if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number }) 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 (store.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() } setStore("blurEnabled", false) setStore("title", props.terminal.title) setStore("editing", true) } const save = () => { if (!store.blurEnabled) return const value = store.title.trim() if (value && value !== props.terminal.title) { terminal.update({ id: props.terminal.id, title: value }) } setStore("editing", false) } const keydown = (e: KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault() save() return } if (e.key === "Escape") { e.preventDefault() setStore("editing", false) } } const menu = (e: MouseEvent) => { e.preventDefault() setStore("menuPosition", { x: e.clientX, y: e.clientY }) setStore("menuOpen", true) } createEffect(() => { if (!store.editing) return if (!input) return input.focus() input.select() if (blurFrame !== undefined) cancelAnimationFrame(blurFrame) blurFrame = requestAnimationFrame(() => { blurFrame = undefined setStore("blurEnabled", true) }) }) onCleanup(() => { if (blurFrame === undefined) return cancelAnimationFrame(blurFrame) }) return (