This commit is contained in:
Dax Raad
2025-12-06 18:21:32 -05:00
parent 2ac8dd6361
commit dfea6780d9
9 changed files with 251 additions and 170 deletions
@@ -1,7 +1,9 @@
import { createMemo, Match, Show, Switch } from "solid-js"
import { createMemo, Match, onCleanup, onMount, Show, Switch } from "solid-js"
import { useTheme } from "../../context/theme"
import { useSync } from "../../context/sync"
import { useDirectory } from "../../context/directory"
import { useConnected } from "../../component/dialog-model"
import { createStore } from "solid-js/store"
export function Footer() {
const { theme } = useTheme()
@@ -10,27 +12,64 @@ export function Footer() {
const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed"))
const lsp = createMemo(() => Object.keys(sync.data.lsp))
const directory = useDirectory()
const connected = useConnected()
const [store, setStore] = createStore({
welcome: false,
})
onMount(() => {
function tick() {
if (connected()) return
if (!store.welcome) {
setStore("welcome", true)
timeout = setTimeout(() => tick(), 5000)
return
}
if (store.welcome) {
setStore("welcome", false)
timeout = setTimeout(() => tick(), 10_000)
return
}
}
let timeout = setTimeout(() => tick(), 10_000)
onCleanup(() => {
clearTimeout(timeout)
})
})
return (
<box flexDirection="row" justifyContent="space-between" gap={1} flexShrink={0}>
<text fg={theme.textMuted}>{directory()}</text>
<box gap={2} flexDirection="row" flexShrink={0}>
<text fg={theme.text}>
<span style={{ fg: theme.success }}>•</span> {lsp().length} LSP
</text>
<Show when={mcp().length}>
<text fg={theme.text}>
<Switch>
<Match when={mcpError()}>
<span style={{ fg: theme.error }}>⊙ </span>
</Match>
<Match when={true}>
<span style={{ fg: theme.success }}>⊙ </span>
</Match>
</Switch>
{mcp().length} MCP
</text>
</Show>
<text fg={theme.textMuted}>/status</text>
<Switch>
<Match when={store.welcome}>
<text fg={theme.text}>
Get started <span style={{ fg: theme.textMuted }}>/connect</span>
</text>
</Match>
<Match when={connected()}>
<text fg={theme.text}>
<span style={{ fg: theme.success }}>•</span> {lsp().length} LSP
</text>
<Show when={mcp().length}>
<text fg={theme.text}>
<Switch>
<Match when={mcpError()}>
<span style={{ fg: theme.error }}>⊙ </span>
</Match>
<Match when={true}>
<span style={{ fg: theme.success }}>⊙ </span>
</Match>
</Switch>
{mcp().length} MCP
</text>
</Show>
<text fg={theme.textMuted}>/status</text>
</Match>
</Switch>
</box>
</box>
)
@@ -91,31 +91,29 @@ export function Header() {
<ContextInfo context={context} cost={cost} />
</box>
</Match>
<Match when={!shareEnabled()}>
<Match when={true}>
<box flexDirection="row" justifyContent="space-between" gap={1}>
<Title session={session} />
<ContextInfo context={context} cost={cost} />
</box>
</Match>
<Match when={true}>
<Title session={session} />
<box flexDirection="row" justifyContent="space-between" gap={1}>
<box flexGrow={1} flexShrink={1}>
<Switch>
<Match when={session().share?.url}>
<text fg={theme.textMuted} wrapMode="word">
{session().share!.url}
</text>
</Match>
<Match when={true}>
<text fg={theme.text} wrapMode="word">
/share <span style={{ fg: theme.textMuted }}>to create a shareable link</span>
</text>
</Match>
</Switch>
<Show when={shareEnabled()}>
<box flexDirection="row" justifyContent="space-between" gap={1}>
<box flexGrow={1} flexShrink={1}>
<Switch>
<Match when={session().share?.url}>
<text fg={theme.textMuted} wrapMode="word">
{session().share!.url}
</text>
</Match>
<Match when={true}>
<text fg={theme.text} wrapMode="word">
/share <span style={{ fg: theme.textMuted }}>copy link</span>
</text>
</Match>
</Switch>
</box>
</box>
<ContextInfo context={context} cost={cost} />
</box>
</Show>
</Match>
</Switch>
</box>
@@ -242,6 +242,34 @@ export function Session() {
const command = useCommandDialog()
command.register(() => [
...(sync.data.config.share !== "disabled"
? [
{
title: "Share session",
value: "session.share",
suggested: route.type === "session",
keybind: "session_share" as const,
disabled: !!session()?.share?.url,
category: "Session",
onSelect: async (dialog: any) => {
await sdk.client.session
.share({
path: {
id: route.sessionID,
},
})
.then((res) =>
Clipboard.copy(res.data!.share!.url).catch(() =>
toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }),
),
)
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
.catch(() => toast.show({ message: "Failed to share session", variant: "error" }))
dialog.clear()
},
},
]
: []),
{
title: "Rename session",
value: "session.rename",
@@ -297,33 +325,6 @@ export function Session() {
dialog.clear()
},
},
...(sync.data.config.share !== "disabled"
? [
{
title: "Share session",
value: "session.share",
keybind: "session_share" as const,
disabled: !!session()?.share?.url,
category: "Session",
onSelect: async (dialog: any) => {
await sdk.client.session
.share({
path: {
id: route.sessionID,
},
})
.then((res) =>
Clipboard.copy(res.data!.share!.url).catch(() =>
toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }),
),
)
.then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
.catch(() => toast.show({ message: "Failed to share session", variant: "error" }))
dialog.clear()
},
},
]
: []),
{
title: "Unshare session",
value: "session.unshare",