fix(app): terminal issues/regression
This commit is contained in:
@@ -18,12 +18,22 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
|
|||||||
const [menuPosition, setMenuPosition] = createSignal({ x: 0, y: 0 })
|
const [menuPosition, setMenuPosition] = createSignal({ x: 0, y: 0 })
|
||||||
const [blurEnabled, setBlurEnabled] = createSignal(false)
|
const [blurEnabled, setBlurEnabled] = createSignal(false)
|
||||||
|
|
||||||
|
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 = () => {
|
const label = () => {
|
||||||
language.locale()
|
language.locale()
|
||||||
|
if (props.terminal.title && !isDefaultTitle()) return props.terminal.title
|
||||||
|
|
||||||
const number = props.terminal.titleNumber
|
const number = props.terminal.titleNumber
|
||||||
if (Number.isFinite(number) && number > 0) {
|
if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number })
|
||||||
return language.t("terminal.title.numbered", { number })
|
|
||||||
}
|
|
||||||
if (props.terminal.title) return props.terminal.title
|
if (props.terminal.title) return props.terminal.title
|
||||||
return language.t("terminal.title")
|
return language.t("terminal.title")
|
||||||
}
|
}
|
||||||
@@ -102,8 +112,15 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
<div
|
||||||
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
|
// @ts-ignore
|
||||||
|
use:sortable
|
||||||
|
class="outline-none focus:outline-none focus-visible:outline-none"
|
||||||
|
classList={{
|
||||||
|
"h-full": true,
|
||||||
|
"opacity-0": sortable.isActiveDraggable,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div class="relative h-full">
|
<div class="relative h-full">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
classes={{ button: "border-0" }}
|
classes={{ button: "border-0" }}
|
||||||
@@ -111,6 +128,8 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
|
|||||||
onClick={focus}
|
onClick={focus}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
onContextMenu={menu}
|
onContextMenu={menu}
|
||||||
|
class="!shadow-none"
|
||||||
|
classes={{ button: "outline-none focus:outline-none focus-visible:outline-none !shadow-none !ring-0" }}
|
||||||
closeButton={
|
closeButton={
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="close"
|
icon="close"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface TerminalProps extends ComponentProps<"div"> {
|
|||||||
pty: LocalPTY
|
pty: LocalPTY
|
||||||
onSubmit?: () => void
|
onSubmit?: () => void
|
||||||
onCleanup?: (pty: LocalPTY) => void
|
onCleanup?: (pty: LocalPTY) => void
|
||||||
|
onConnect?: () => void
|
||||||
onConnectError?: (error: unknown) => void
|
onConnectError?: (error: unknown) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
let container!: HTMLDivElement
|
let container!: HTMLDivElement
|
||||||
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnectError"])
|
const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnect", "onConnectError"])
|
||||||
let ws: WebSocket | undefined
|
let ws: WebSocket | undefined
|
||||||
let term: Term | undefined
|
let term: Term | undefined
|
||||||
let ghostty: Ghostty
|
let ghostty: Ghostty
|
||||||
@@ -241,6 +242,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
// console.log("Scroll position:", ydisp)
|
// console.log("Scroll position:", ydisp)
|
||||||
// })
|
// })
|
||||||
socket.addEventListener("open", () => {
|
socket.addEventListener("open", () => {
|
||||||
|
local.onConnect?.()
|
||||||
sdk.client.pty
|
sdk.client.pty
|
||||||
.update({
|
.update({
|
||||||
ptyID: local.pty.id,
|
ptyID: local.pty.id,
|
||||||
@@ -255,10 +257,12 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
t.write(event.data)
|
t.write(event.data)
|
||||||
})
|
})
|
||||||
socket.addEventListener("error", (error) => {
|
socket.addEventListener("error", (error) => {
|
||||||
|
if (disposed) return
|
||||||
console.error("WebSocket error:", error)
|
console.error("WebSocket error:", error)
|
||||||
local.onConnectError?.(error)
|
local.onConnectError?.(error)
|
||||||
})
|
})
|
||||||
socket.addEventListener("close", (event) => {
|
socket.addEventListener("close", (event) => {
|
||||||
|
if (disposed) return
|
||||||
// Normal closure (code 1000) means PTY process exited - server event handles cleanup
|
// Normal closure (code 1000) means PTY process exited - server event handles cleanup
|
||||||
// For other codes (network issues, server restart), trigger error handler
|
// For other codes (network issues, server restart), trigger error handler
|
||||||
if (event.code !== 1000) {
|
if (event.code !== 1000) {
|
||||||
@@ -268,6 +272,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
|
disposed = true
|
||||||
if (handleResize) {
|
if (handleResize) {
|
||||||
window.removeEventListener("resize", handleResize)
|
window.removeEventListener("resize", handleResize)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1231,11 +1231,15 @@ export default function Page() {
|
|||||||
language.locale()
|
language.locale()
|
||||||
|
|
||||||
const label = (pty: LocalPTY) => {
|
const label = (pty: LocalPTY) => {
|
||||||
|
const title = pty.title
|
||||||
const number = pty.titleNumber
|
const number = pty.titleNumber
|
||||||
if (Number.isFinite(number) && number > 0) {
|
const match = title.match(/^Terminal (\d+)$/)
|
||||||
return language.t("terminal.title.numbered", { number })
|
const parsed = match ? Number(match[1]) : undefined
|
||||||
}
|
const isDefaultTitle = Number.isFinite(number) && number > 0 && Number.isFinite(parsed) && parsed === number
|
||||||
if (pty.title) return pty.title
|
|
||||||
|
if (title && !isDefaultTitle) return title
|
||||||
|
if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number })
|
||||||
|
if (title) return title
|
||||||
return language.t("terminal.title")
|
return language.t("terminal.title")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2002,7 +2006,12 @@ export default function Page() {
|
|||||||
<Terminal
|
<Terminal
|
||||||
pty={pty}
|
pty={pty}
|
||||||
onCleanup={(data) => terminal.update({ ...data, id: pty.id })}
|
onCleanup={(data) => terminal.update({ ...data, id: pty.id })}
|
||||||
|
onConnect={() => {
|
||||||
|
terminal.update({ id: pty.id, error: false })
|
||||||
|
setDismissed(false)
|
||||||
|
}}
|
||||||
onConnectError={() => {
|
onConnectError={() => {
|
||||||
|
setDismissed(false)
|
||||||
terminal.update({ id: pty.id, error: true })
|
terminal.update({ id: pty.id, error: true })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -2056,11 +2065,17 @@ export default function Page() {
|
|||||||
{(t) => (
|
{(t) => (
|
||||||
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
|
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
|
||||||
{(() => {
|
{(() => {
|
||||||
|
const title = t().title
|
||||||
const number = t().titleNumber
|
const number = t().titleNumber
|
||||||
if (Number.isFinite(number) && number > 0) {
|
const match = title.match(/^Terminal (\d+)$/)
|
||||||
|
const parsed = match ? Number(match[1]) : undefined
|
||||||
|
const isDefaultTitle =
|
||||||
|
Number.isFinite(number) && number > 0 && Number.isFinite(parsed) && parsed === number
|
||||||
|
|
||||||
|
if (title && !isDefaultTitle) return title
|
||||||
|
if (Number.isFinite(number) && number > 0)
|
||||||
return language.t("terminal.title.numbered", { number })
|
return language.t("terminal.title.numbered", { number })
|
||||||
}
|
if (title) return title
|
||||||
if (t().title) return t().title
|
|
||||||
return language.t("terminal.title")
|
return language.t("terminal.title")
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user