feat(app): add v2 home tab toggle (#32191)
This commit is contained in:
@@ -7,12 +7,11 @@ import {
|
|||||||
Match,
|
Match,
|
||||||
onMount,
|
onMount,
|
||||||
Show,
|
Show,
|
||||||
startTransition,
|
|
||||||
Switch,
|
Switch,
|
||||||
untrack,
|
untrack,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useLocation, useMatch, useNavigate, useParams } from "@solidjs/router"
|
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
@@ -20,6 +19,8 @@ import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
|||||||
import { useTheme } from "@opencode-ai/ui/theme/context"
|
import { useTheme } from "@opencode-ai/ui/theme/context"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||||
|
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||||
|
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||||
|
|
||||||
import { getProjectAvatarVariant, LayoutRoute, useLayout, type LocalProject } from "@/context/layout"
|
import { getProjectAvatarVariant, LayoutRoute, useLayout, type LocalProject } from "@/context/layout"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
@@ -253,7 +254,6 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
{(_) => {
|
{(_) => {
|
||||||
const serverSync = useServerSync()
|
const serverSync = useServerSync()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const homeMatch = useMatch(() => "/")
|
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
|
|
||||||
const newSessionHref = () => {
|
const newSessionHref = () => {
|
||||||
@@ -268,17 +268,6 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const tabsStore = tabs.store
|
const tabsStore = tabs.store
|
||||||
const tabsStoreActions = tabs
|
const tabsStoreActions = tabs
|
||||||
const navigateTab = (tab: Tab) => {
|
|
||||||
const href = tabHref(tab)
|
|
||||||
if (tab.server === server.key) {
|
|
||||||
navigate(href)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
void startTransition(() => {
|
|
||||||
server.setActive(tab.server)
|
|
||||||
navigate(href)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchRoute = (route: LayoutRoute) => {
|
const matchRoute = (route: LayoutRoute) => {
|
||||||
if (route.type === "home") return
|
if (route.type === "home") return
|
||||||
@@ -309,7 +298,10 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
const route = layout.route()
|
const route = layout.route()
|
||||||
if (!tabs.ready()) return
|
if (!tabs.ready()) return
|
||||||
const tab = currentTab()
|
const tab = currentTab()
|
||||||
if (tab) return
|
if (tab) {
|
||||||
|
tabs.remember(tab)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (route.type === "session") {
|
if (route.type === "session") {
|
||||||
const sync = serverSync().createDirSyncContext(route.dir)
|
const sync = serverSync().createDirSyncContext(route.dir)
|
||||||
@@ -332,6 +324,18 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const openNewTab = () => navigate(newSessionHref())
|
const openNewTab = () => navigate(newSessionHref())
|
||||||
|
const toggleHome = () => tabs.toggleHome({ home: layout.route().type === "home", current: currentTab() })
|
||||||
|
|
||||||
|
command.register("titlebar-home", () => [
|
||||||
|
{
|
||||||
|
id: "home.toggle",
|
||||||
|
title: language.t("home.title"),
|
||||||
|
category: language.t("command.category.view"),
|
||||||
|
keybind: "mod+b",
|
||||||
|
hidden: true,
|
||||||
|
onSelect: toggleHome,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
command.register("tabs", () => {
|
command.register("tabs", () => {
|
||||||
const current = currentTab()
|
const current = currentTab()
|
||||||
@@ -369,7 +373,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
if (index === -1) index = tabsStore.length - 1
|
if (index === -1) index = tabsStore.length - 1
|
||||||
|
|
||||||
const next = tabsStore[index]
|
const next = tabsStore[index]
|
||||||
if (next) navigateTab(next)
|
if (next) tabs.select(next)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -386,7 +390,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
if (index === tabsStore.length) index = 0
|
if (index === tabsStore.length) index = 0
|
||||||
|
|
||||||
const next = tabsStore[index]
|
const next = tabsStore[index]
|
||||||
if (next) navigateTab(next)
|
if (next) tabs.select(next)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...Array.from({ length: 9 }, (_, i) => {
|
...Array.from({ length: 9 }, (_, i) => {
|
||||||
@@ -401,7 +405,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const tab = tabsStore[index]
|
const tab = tabsStore[index]
|
||||||
if (tab) navigateTab(tab)
|
if (tab) tabs.select(tab)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -427,15 +431,28 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
<Show when={windows() || linux()}>
|
<Show when={windows() || linux()}>
|
||||||
<WindowsAppMenu command={command} platform={platform} variant="v2" />
|
<WindowsAppMenu command={command} platform={platform} variant="v2" />
|
||||||
</Show>
|
</Show>
|
||||||
<IconButtonV2
|
<TooltipV2
|
||||||
variant="ghost-muted"
|
placement="bottom"
|
||||||
size="large"
|
value={
|
||||||
as="a"
|
<>
|
||||||
href="/"
|
{language.t("home.title")}
|
||||||
class="!w-9 shrink-0"
|
<KeybindV2 keys={command.keybindParts("home.toggle")} variant="neutral" />
|
||||||
icon={<IconV2 name="grid-plus" />}
|
</>
|
||||||
state={!!homeMatch() ? "pressed" : undefined}
|
}
|
||||||
/>
|
class="shrink-0"
|
||||||
|
>
|
||||||
|
<IconButtonV2
|
||||||
|
type="button"
|
||||||
|
variant="ghost-muted"
|
||||||
|
size="large"
|
||||||
|
class="!w-9 shrink-0"
|
||||||
|
icon={<IconV2 name="grid-plus" />}
|
||||||
|
state={layout.route().type === "home" ? "pressed" : undefined}
|
||||||
|
onClick={toggleHome}
|
||||||
|
aria-label={language.t("home.title")}
|
||||||
|
aria-pressed={layout.route().type === "home"}
|
||||||
|
/>
|
||||||
|
</TooltipV2>
|
||||||
|
|
||||||
<div data-slot="titlebar-tabs" class="relative min-w-0">
|
<div data-slot="titlebar-tabs" class="relative min-w-0">
|
||||||
<div
|
<div
|
||||||
@@ -469,7 +486,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
title={language.t("command.session.new")}
|
title={language.t("command.session.new")}
|
||||||
active={currentTab() === tab}
|
active={currentTab() === tab}
|
||||||
onNavigate={() => {
|
onNavigate={() => {
|
||||||
navigateTab(tab)
|
tabs.select(tab)
|
||||||
ref.scrollIntoView({ behavior: "instant" })
|
ref.scrollIntoView({ behavior: "instant" })
|
||||||
}}
|
}}
|
||||||
onClose={() => tabsStoreActions.removeTab(i())}
|
onClose={() => tabsStoreActions.removeTab(i())}
|
||||||
@@ -488,7 +505,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
directory={decode64(tab.dirBase64)!}
|
directory={decode64(tab.dirBase64)!}
|
||||||
sessionId={tab.sessionId}
|
sessionId={tab.sessionId}
|
||||||
onNavigate={() => {
|
onNavigate={() => {
|
||||||
navigateTab(tab)
|
tabs.select(tab)
|
||||||
|
|
||||||
ref.scrollIntoView({ behavior: "instant" })
|
ref.scrollIntoView({ behavior: "instant" })
|
||||||
}}
|
}}
|
||||||
@@ -518,7 +535,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
title={language.t("command.session.new")}
|
title={language.t("command.session.new")}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
const tab = tabsStore.at(-1)
|
const tab = tabsStore.at(-1)
|
||||||
if (tab) navigateTab(tab)
|
if (tab) tabs.select(tab)
|
||||||
else navigate("/")
|
else navigate("/")
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -170,13 +170,7 @@ export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatKeybind(config: string, t?: (key: KeyLabel) => string): string {
|
function displayKeybindParts(kb: Keybind, t?: (key: KeyLabel) => string) {
|
||||||
if (!config || config === "none") return ""
|
|
||||||
|
|
||||||
const keybinds = parseKeybind(config)
|
|
||||||
if (keybinds.length === 0) return ""
|
|
||||||
|
|
||||||
const kb = keybinds[0]
|
|
||||||
const parts: string[] = []
|
const parts: string[] = []
|
||||||
|
|
||||||
if (kb.ctrl) parts.push(IS_MAC ? "⌃" : keyText("common.key.ctrl", t))
|
if (kb.ctrl) parts.push(IS_MAC ? "⌃" : keyText("common.key.ctrl", t))
|
||||||
@@ -184,40 +178,52 @@ export function formatKeybind(config: string, t?: (key: KeyLabel) => string): st
|
|||||||
if (kb.shift) parts.push(IS_MAC ? "⇧" : keyText("common.key.shift", t))
|
if (kb.shift) parts.push(IS_MAC ? "⇧" : keyText("common.key.shift", t))
|
||||||
if (kb.meta) parts.push(IS_MAC ? "⌘" : keyText("common.key.meta", t))
|
if (kb.meta) parts.push(IS_MAC ? "⌘" : keyText("common.key.meta", t))
|
||||||
|
|
||||||
if (kb.key) {
|
if (!kb.key) return parts
|
||||||
const keys: Record<string, string> = {
|
|
||||||
arrowup: "↑",
|
|
||||||
arrowdown: "↓",
|
|
||||||
arrowleft: "←",
|
|
||||||
arrowright: "→",
|
|
||||||
comma: ",",
|
|
||||||
plus: "+",
|
|
||||||
}
|
|
||||||
const named: Record<string, KeyLabel> = {
|
|
||||||
backspace: "common.key.backspace",
|
|
||||||
delete: "common.key.delete",
|
|
||||||
end: "common.key.end",
|
|
||||||
enter: "common.key.enter",
|
|
||||||
esc: "common.key.esc",
|
|
||||||
escape: "common.key.esc",
|
|
||||||
home: "common.key.home",
|
|
||||||
insert: "common.key.insert",
|
|
||||||
pagedown: "common.key.pageDown",
|
|
||||||
pageup: "common.key.pageUp",
|
|
||||||
space: "common.key.space",
|
|
||||||
tab: "common.key.tab",
|
|
||||||
}
|
|
||||||
const key = kb.key.toLowerCase()
|
|
||||||
const displayKey =
|
|
||||||
keys[key] ??
|
|
||||||
(named[key]
|
|
||||||
? keyText(named[key], t)
|
|
||||||
: key.length === 1
|
|
||||||
? key.toUpperCase()
|
|
||||||
: key.charAt(0).toUpperCase() + key.slice(1))
|
|
||||||
parts.push(displayKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const keys: Record<string, string> = {
|
||||||
|
arrowup: "↑",
|
||||||
|
arrowdown: "↓",
|
||||||
|
arrowleft: "←",
|
||||||
|
arrowright: "→",
|
||||||
|
comma: ",",
|
||||||
|
plus: "+",
|
||||||
|
}
|
||||||
|
const named: Record<string, KeyLabel> = {
|
||||||
|
backspace: "common.key.backspace",
|
||||||
|
delete: "common.key.delete",
|
||||||
|
end: "common.key.end",
|
||||||
|
enter: "common.key.enter",
|
||||||
|
esc: "common.key.esc",
|
||||||
|
escape: "common.key.esc",
|
||||||
|
home: "common.key.home",
|
||||||
|
insert: "common.key.insert",
|
||||||
|
pagedown: "common.key.pageDown",
|
||||||
|
pageup: "common.key.pageUp",
|
||||||
|
space: "common.key.space",
|
||||||
|
tab: "common.key.tab",
|
||||||
|
}
|
||||||
|
const key = kb.key.toLowerCase()
|
||||||
|
const displayKey =
|
||||||
|
keys[key] ??
|
||||||
|
(named[key]
|
||||||
|
? keyText(named[key], t)
|
||||||
|
: key.length === 1
|
||||||
|
? key.toUpperCase()
|
||||||
|
: key.charAt(0).toUpperCase() + key.slice(1))
|
||||||
|
parts.push(displayKey)
|
||||||
|
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatKeybindParts(config: string, t?: (key: KeyLabel) => string): string[] {
|
||||||
|
if (!config || config === "none") return []
|
||||||
|
const keybind = parseKeybind(config)[0]
|
||||||
|
return keybind ? displayKeybindParts(keybind, t) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatKeybind(config: string, t?: (key: KeyLabel) => string): string {
|
||||||
|
const parts = formatKeybindParts(config, t)
|
||||||
|
if (parts.length === 0) return ""
|
||||||
return IS_MAC ? parts.join("") : parts.join("+")
|
return IS_MAC ? parts.join("") : parts.join("+")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,25 +408,26 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keybindConfig = (id: string) => {
|
||||||
|
if (id === PALETTE_ID) return settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND
|
||||||
|
const base = actionId(id)
|
||||||
|
return options().find((x) => actionId(x.id) === base)?.keybind ?? bind(base, catalog[base]?.keybind)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
register,
|
register,
|
||||||
trigger(id: string, source?: CommandSource) {
|
trigger(id: string, source?: CommandSource) {
|
||||||
run(id, source)
|
run(id, source)
|
||||||
},
|
},
|
||||||
keybind(id: string) {
|
keybind(id: string) {
|
||||||
if (id === PALETTE_ID) {
|
const config = keybindConfig(id)
|
||||||
return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND, language.t)
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = actionId(id)
|
|
||||||
const option = options().find((x) => actionId(x.id) === base)
|
|
||||||
if (option?.keybind) return formatKeybind(option.keybind, language.t)
|
|
||||||
|
|
||||||
const meta = catalog[base]
|
|
||||||
const config = bind(base, meta?.keybind)
|
|
||||||
if (!config) return ""
|
if (!config) return ""
|
||||||
return formatKeybind(config, language.t)
|
return formatKeybind(config, language.t)
|
||||||
},
|
},
|
||||||
|
keybindParts(id: string) {
|
||||||
|
const config = keybindConfig(id)
|
||||||
|
return config ? formatKeybindParts(config, language.t) : []
|
||||||
|
},
|
||||||
show: showPalette,
|
show: showPalette,
|
||||||
keybinds(enabled: boolean) {
|
keybinds(enabled: boolean) {
|
||||||
setStore("suspendCount", (count) => Math.max(0, count + (enabled ? -1 : 1)))
|
setStore("suspendCount", (count) => Math.max(0, count + (enabled ? -1 : 1)))
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ export type DraftTab = {
|
|||||||
|
|
||||||
export type Tab = SessionTab | DraftTab
|
export type Tab = SessionTab | DraftTab
|
||||||
|
|
||||||
|
type RecentTab = {
|
||||||
|
key?: string
|
||||||
|
}
|
||||||
|
|
||||||
export const draftHref = (draftID: string) => `/new-session?draftId=${encodeURIComponent(draftID)}`
|
export const draftHref = (draftID: string) => `/new-session?draftId=${encodeURIComponent(draftID)}`
|
||||||
|
|
||||||
export const tabHref = (tab: Tab) =>
|
export const tabHref = (tab: Tab) =>
|
||||||
@@ -62,26 +66,45 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
},
|
},
|
||||||
createStore<Tab[]>([]),
|
createStore<Tab[]>([]),
|
||||||
)
|
)
|
||||||
|
const [recent, setRecent, , recentReady] = persisted(Persist.global("tabs.recent"), createStore<RecentTab>({}))
|
||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const closing = new Set<string>()
|
const closing = new Set<string>()
|
||||||
|
let recentWrite = 0
|
||||||
|
let recentValue: string | undefined
|
||||||
|
|
||||||
|
const recentKey = () => (recentWrite ? recentValue : recent.key)
|
||||||
|
|
||||||
|
const setRecentKey = (key: string | undefined) => {
|
||||||
|
const write = ++recentWrite
|
||||||
|
recentValue = key
|
||||||
|
if (recentReady()) {
|
||||||
|
setRecent("key", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
void recentReady.promise?.then(() => {
|
||||||
|
if (write === recentWrite) setRecent("key", key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const removeDraftPersisted = (draftID: string) => {
|
const removeDraftPersisted = (draftID: string) => {
|
||||||
for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
|
for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!ready()) return
|
if (!ready() || !recentReady()) return
|
||||||
const servers = new Set(server.list.map(ServerConnection.key))
|
const servers = new Set(server.list.map(ServerConnection.key))
|
||||||
if (store.every((tab) => servers.has(tab.server))) return
|
const next = store.filter((tab) => servers.has(tab.server))
|
||||||
setStore((tabs) => tabs.filter((tab) => servers.has(tab.server)))
|
if (next.length !== store.length) setStore(() => next)
|
||||||
|
if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
const navigateTab = (tab: Tab) => {
|
const navigateTab = (tab: Tab) => {
|
||||||
const href = tabHref(tab)
|
const href = tabHref(tab)
|
||||||
|
setRecentKey(tabKey(tab))
|
||||||
if (tab.server === server.key) {
|
if (tab.server === server.key) {
|
||||||
navigate(href)
|
navigate(href)
|
||||||
return
|
return
|
||||||
@@ -129,14 +152,16 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
// and fall back home. Navigate to the new session first so we leave /new-session
|
// and fall back home. Navigate to the new session first so we leave /new-session
|
||||||
// before the draft is removed from the store.
|
// before the draft is removed from the store.
|
||||||
const active = location.pathname === "/new-session" && location.query.draftId === draftID
|
const active = location.pathname === "/new-session" && location.query.draftId === draftID
|
||||||
|
const next = { type: "session" as const, ...session }
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
setStore(
|
setStore(
|
||||||
produce((tabs) => {
|
produce((tabs) => {
|
||||||
const index = tabs.findIndex((tab) => tab.type === "draft" && tab.draftID === draftID)
|
const index = tabs.findIndex((tab) => tab.type === "draft" && tab.draftID === draftID)
|
||||||
if (index !== -1) tabs[index] = { type: "session", ...session }
|
if (index !== -1) tabs[index] = next
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
if (active) navigateTab({ type: "session", ...session })
|
if (recent.key === `draft:${draftID}`) setRecentKey(tabKey(next))
|
||||||
|
if (active) navigateTab(next)
|
||||||
})
|
})
|
||||||
removeDraftPersisted(draftID)
|
removeDraftPersisted(draftID)
|
||||||
},
|
},
|
||||||
@@ -153,6 +178,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
tabs.splice(index, 1)
|
tabs.splice(index, 1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
if (recent.key === key) setRecentKey(nextTab && tabKey(nextTab))
|
||||||
if (nextTab) navigateTab(nextTab)
|
if (nextTab) navigateTab(nextTab)
|
||||||
else navigate("/")
|
else navigate("/")
|
||||||
}).finally(() => closing.delete(key))
|
}).finally(() => closing.delete(key))
|
||||||
@@ -160,11 +186,22 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
},
|
},
|
||||||
removeServer(key: ServerConnection.Key) {
|
removeServer(key: ServerConnection.Key) {
|
||||||
const drafts = store.flatMap((tab) => (tab.type === "draft" && tab.server === key ? [tab.draftID] : []))
|
const drafts = store.flatMap((tab) => (tab.type === "draft" && tab.server === key ? [tab.draftID] : []))
|
||||||
|
const removed = store.filter((tab) => tab.server === key).map(tabKey)
|
||||||
setStore((tabs) => tabs.filter((tab) => tab.server !== key))
|
setStore((tabs) => tabs.filter((tab) => tab.server !== key))
|
||||||
|
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
|
||||||
for (const draftID of drafts) removeDraftPersisted(draftID)
|
for (const draftID of drafts) removeDraftPersisted(draftID)
|
||||||
if (server.key === key) navigate("/")
|
if (server.key === key) navigate("/")
|
||||||
},
|
},
|
||||||
removeSessions: (input: SessionTabsRemovedDetail) => {
|
removeSessions: (input: SessionTabsRemovedDetail) => {
|
||||||
|
const removed = store
|
||||||
|
.filter(
|
||||||
|
(tab) =>
|
||||||
|
tab.type === "session" &&
|
||||||
|
tab.server === server.key &&
|
||||||
|
atob(tab.dirBase64) === input.directory &&
|
||||||
|
input.sessionIDs.includes(tab.sessionId),
|
||||||
|
)
|
||||||
|
.map(tabKey)
|
||||||
void startTransition(() => {
|
void startTransition(() => {
|
||||||
setStore(
|
setStore(
|
||||||
produce((tabs) => {
|
produce((tabs) => {
|
||||||
@@ -207,10 +244,29 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
else navigate("/")
|
else navigate("/")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
select: navigateTab,
|
||||||
|
remember(tab: Tab) {
|
||||||
|
const key = tabKey(tab)
|
||||||
|
if (recentKey() !== key) setRecentKey(key)
|
||||||
|
},
|
||||||
|
toggleHome(input: { home: boolean; current?: Tab }) {
|
||||||
|
if (input.home) {
|
||||||
|
const tab = store.find((tab) => tabKey(tab) === recentKey())
|
||||||
|
if (tab) navigateTab(tab)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (input.current) {
|
||||||
|
setRecentKey(tabKey(input.current))
|
||||||
|
navigate("/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
navigate("/")
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...actions, store, ready }
|
return { ...actions, store, ready, recentReady }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export const DESKTOP_MENU: DesktopMenu[] = [
|
|||||||
id: "view",
|
id: "view",
|
||||||
label: "View",
|
label: "View",
|
||||||
items: [
|
items: [
|
||||||
{ type: "item", label: "Toggle Sidebar", command: "sidebar.toggle", accelerator: { macos: "Cmd+B" } },
|
{ type: "item", label: "Toggle Sidebar", command: "sidebar.toggle" },
|
||||||
{ type: "item", label: "Toggle Terminal", command: "terminal.toggle", accelerator: { macos: "Ctrl+`" } },
|
{ type: "item", label: "Toggle Terminal", command: "terminal.toggle", accelerator: { macos: "Ctrl+`" } },
|
||||||
{ type: "item", label: "Toggle File Tree", command: "fileTree.toggle" },
|
{ type: "item", label: "Toggle File Tree", command: "fileTree.toggle" },
|
||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
|
|||||||
@@ -996,7 +996,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
id: "sidebar.toggle",
|
id: "sidebar.toggle",
|
||||||
title: language.t("command.sidebar.toggle"),
|
title: language.t("command.sidebar.toggle"),
|
||||||
category: language.t("command.category.view"),
|
category: language.t("command.category.view"),
|
||||||
keybind: "mod+b",
|
keybind: newDesign() ? undefined : "mod+b",
|
||||||
onSelect: () => layout.sidebar.toggle(),
|
onSelect: () => layout.sidebar.toggle(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -137,6 +137,7 @@
|
|||||||
|
|
||||||
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) {
|
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:active, [data-state="pressed"]):not(:disabled) {
|
||||||
background-color: var(--v2-overlay-simple-overlay-pressed);
|
background-color: var(--v2-overlay-simple-overlay-pressed);
|
||||||
|
color: var(--v2-icon-icon-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) {
|
[data-component="icon-button-v2"][data-variant="ghost-muted"]:is(:disabled, [data-state="disabled"]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user