fix(app): move session search to command palette
This commit is contained in:
@@ -1,17 +1,22 @@
|
|||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Keybind } from "@opencode-ai/ui/keybind"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { createMemo, createSignal, onCleanup, Show } from "solid-js"
|
import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js"
|
||||||
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
||||||
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { decode64 } from "@/utils/base64"
|
||||||
|
|
||||||
type EntryType = "command" | "file"
|
type EntryType = "command" | "file" | "session"
|
||||||
|
|
||||||
type Entry = {
|
type Entry = {
|
||||||
id: string
|
id: string
|
||||||
@@ -22,6 +27,9 @@ type Entry = {
|
|||||||
category: string
|
category: string
|
||||||
option?: CommandOption
|
option?: CommandOption
|
||||||
path?: string
|
path?: string
|
||||||
|
directory?: string
|
||||||
|
sessionID?: string
|
||||||
|
archived?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type DialogSelectFileMode = "all" | "files"
|
type DialogSelectFileMode = "all" | "files"
|
||||||
@@ -33,6 +41,9 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
const file = useFile()
|
const file = useFile()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const globalSDK = useGlobalSDK()
|
||||||
|
const globalSync = useGlobalSync()
|
||||||
const filesOnly = () => props.mode === "files"
|
const filesOnly = () => props.mode === "files"
|
||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const tabs = createMemo(() => layout.tabs(sessionKey))
|
const tabs = createMemo(() => layout.tabs(sessionKey))
|
||||||
@@ -73,6 +84,52 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
path,
|
path,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
|
||||||
|
const project = createMemo(() => {
|
||||||
|
const directory = projectDirectory()
|
||||||
|
if (!directory) return
|
||||||
|
return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
|
||||||
|
})
|
||||||
|
const workspaces = createMemo(() => {
|
||||||
|
const directory = projectDirectory()
|
||||||
|
const current = project()
|
||||||
|
if (!current) return directory ? [directory] : []
|
||||||
|
|
||||||
|
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
|
||||||
|
if (directory && !dirs.includes(directory)) return [...dirs, directory]
|
||||||
|
return dirs
|
||||||
|
})
|
||||||
|
const homedir = createMemo(() => globalSync.data.path.home)
|
||||||
|
const label = (directory: string) => {
|
||||||
|
const current = project()
|
||||||
|
const kind =
|
||||||
|
current && directory === current.worktree
|
||||||
|
? language.t("workspace.type.local")
|
||||||
|
: language.t("workspace.type.sandbox")
|
||||||
|
const [store] = globalSync.child(directory, { bootstrap: false })
|
||||||
|
const home = homedir()
|
||||||
|
const path = home ? directory.replace(home, "~") : directory
|
||||||
|
const name = store.vcs?.branch ?? getFilename(directory)
|
||||||
|
return `${kind} : ${name || path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionItem = (input: {
|
||||||
|
directory: string
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
archived?: number
|
||||||
|
}): Entry => ({
|
||||||
|
id: `session:${input.directory}:${input.id}`,
|
||||||
|
type: "session",
|
||||||
|
title: input.title,
|
||||||
|
description: input.description,
|
||||||
|
category: language.t("command.category.session"),
|
||||||
|
directory: input.directory,
|
||||||
|
sessionID: input.id,
|
||||||
|
archived: input.archived,
|
||||||
|
})
|
||||||
|
|
||||||
const list = createMemo(() => allowed().map(commandItem))
|
const list = createMemo(() => allowed().map(commandItem))
|
||||||
|
|
||||||
const picks = createMemo(() => {
|
const picks = createMemo(() => {
|
||||||
@@ -122,6 +179,68 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sessionToken = { value: 0 }
|
||||||
|
let sessionInflight: Promise<Entry[]> | undefined
|
||||||
|
let sessionAll: Entry[] | undefined
|
||||||
|
|
||||||
|
const sessions = (text: string) => {
|
||||||
|
const query = text.trim()
|
||||||
|
if (!query) {
|
||||||
|
sessionToken.value += 1
|
||||||
|
sessionInflight = undefined
|
||||||
|
sessionAll = undefined
|
||||||
|
return [] as Entry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionAll) return sessionAll
|
||||||
|
if (sessionInflight) return sessionInflight
|
||||||
|
|
||||||
|
const current = sessionToken.value
|
||||||
|
const dirs = workspaces()
|
||||||
|
if (dirs.length === 0) return [] as Entry[]
|
||||||
|
|
||||||
|
sessionInflight = Promise.all(
|
||||||
|
dirs.map((directory) => {
|
||||||
|
const description = label(directory)
|
||||||
|
return globalSDK.client.session
|
||||||
|
.list({ directory, roots: true })
|
||||||
|
.then((x) =>
|
||||||
|
(x.data ?? [])
|
||||||
|
.filter((s) => !!s?.id)
|
||||||
|
.map((s) => ({
|
||||||
|
id: s.id,
|
||||||
|
title: s.title ?? language.t("command.session.new"),
|
||||||
|
description,
|
||||||
|
directory,
|
||||||
|
archived: s.time?.archived,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.catch(() => [] as { id: string; title: string; description: string; directory: string; archived?: number }[])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then((results) => {
|
||||||
|
if (sessionToken.value !== current) return [] as Entry[]
|
||||||
|
const seen = new Set<string>()
|
||||||
|
const next = results
|
||||||
|
.flat()
|
||||||
|
.filter((item) => {
|
||||||
|
const key = `${item.directory}:${item.id}`
|
||||||
|
if (seen.has(key)) return false
|
||||||
|
seen.add(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.map(sessionItem)
|
||||||
|
sessionAll = next
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
.catch(() => [] as Entry[])
|
||||||
|
.finally(() => {
|
||||||
|
sessionInflight = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
return sessionInflight
|
||||||
|
}
|
||||||
|
|
||||||
const items = async (text: string) => {
|
const items = async (text: string) => {
|
||||||
const query = text.trim()
|
const query = text.trim()
|
||||||
setGrouped(query.length > 0)
|
setGrouped(query.length > 0)
|
||||||
@@ -146,9 +265,10 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
const files = await file.searchFiles(query)
|
const files = await file.searchFiles(query)
|
||||||
return files.map(fileItem)
|
return files.map(fileItem)
|
||||||
}
|
}
|
||||||
const files = await file.searchFiles(query)
|
|
||||||
|
const [files, nextSessions] = await Promise.all([file.searchFiles(query), Promise.resolve(sessions(query))])
|
||||||
const entries = files.map(fileItem)
|
const entries = files.map(fileItem)
|
||||||
return [...list(), ...entries]
|
return [...list(), ...nextSessions, ...entries]
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMove = (item: Entry | undefined) => {
|
const handleMove = (item: Entry | undefined) => {
|
||||||
@@ -178,6 +298,12 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.type === "session") {
|
||||||
|
if (!item.directory || !item.sessionID) return
|
||||||
|
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!item.path) return
|
if (!item.path) return
|
||||||
open(item.path)
|
open(item.path)
|
||||||
}
|
}
|
||||||
@@ -202,13 +328,12 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
items={items}
|
items={items}
|
||||||
key={(item) => item.id}
|
key={(item) => item.id}
|
||||||
filterKeys={["title", "description", "category"]}
|
filterKeys={["title", "description", "category"]}
|
||||||
groupBy={(item) => item.category}
|
groupBy={grouped() ? (item) => item.category : () => ""}
|
||||||
onMove={handleMove}
|
onMove={handleMove}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
>
|
>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<Show
|
<Switch
|
||||||
when={item.type === "command"}
|
|
||||||
fallback={
|
fallback={
|
||||||
<div class="w-full flex items-center justify-between rounded-md pl-1">
|
<div class="w-full flex items-center justify-between rounded-md pl-1">
|
||||||
<div class="flex items-center gap-x-3 grow min-w-0">
|
<div class="flex items-center gap-x-3 grow min-w-0">
|
||||||
@@ -223,18 +348,43 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="w-full flex items-center justify-between gap-4">
|
<Match when={item.type === "command"}>
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="w-full flex items-center justify-between gap-4">
|
||||||
<span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<Show when={item.description}>
|
<span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
|
||||||
<span class="text-14-regular text-text-weak truncate">{item.description}</span>
|
<Show when={item.description}>
|
||||||
|
<span class="text-14-regular text-text-weak truncate">{item.description}</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<Show when={item.keybind}>
|
||||||
|
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "")}</Keybind>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={item.keybind}>
|
</Match>
|
||||||
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "")}</Keybind>
|
<Match when={item.type === "session"}>
|
||||||
</Show>
|
<div class="w-full flex items-center justify-between rounded-md pl-1">
|
||||||
</div>
|
<div class="flex items-center gap-x-3 grow min-w-0">
|
||||||
</Show>
|
<Icon name="bubble-5" size="small" class="shrink-0 text-icon-weak" />
|
||||||
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
|
<span
|
||||||
|
class="text-14-regular text-text-strong truncate"
|
||||||
|
classList={{ "opacity-70": !!item.archived }}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</span>
|
||||||
|
<Show when={item.description}>
|
||||||
|
<span
|
||||||
|
class="text-14-regular text-text-weak truncate"
|
||||||
|
classList={{ "opacity-70": !!item.archived }}
|
||||||
|
>
|
||||||
|
{item.description}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import { Button } from "@opencode-ai/ui/button"
|
|||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||||
import { List, type ListRef } from "@opencode-ai/ui/list"
|
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { HoverCard } from "@opencode-ai/ui/hover-card"
|
import { HoverCard } from "@opencode-ai/ui/hover-card"
|
||||||
import { MessageNav } from "@opencode-ai/ui/message-nav"
|
import { MessageNav } from "@opencode-ai/ui/message-nav"
|
||||||
@@ -2706,14 +2705,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SidebarPanel = (panelProps: { project: LocalProject | undefined; mobile?: boolean }) => {
|
const SidebarPanel = (panelProps: { project: LocalProject | undefined; mobile?: boolean }) => {
|
||||||
type SearchItem = {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
directory: string
|
|
||||||
label: string
|
|
||||||
archived?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectName = createMemo(() => {
|
const projectName = createMemo(() => {
|
||||||
const project = panelProps.project
|
const project = panelProps.project
|
||||||
if (!project) return ""
|
if (!project) return ""
|
||||||
@@ -2729,107 +2720,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
})
|
})
|
||||||
const homedir = createMemo(() => globalSync.data.path.home)
|
const homedir = createMemo(() => globalSync.data.path.home)
|
||||||
|
|
||||||
const [search, setSearch] = createStore({
|
|
||||||
value: "",
|
|
||||||
})
|
|
||||||
const searching = createMemo(() => search.value.trim().length > 0)
|
|
||||||
let searchRef: HTMLInputElement | undefined
|
|
||||||
let listRef: ListRef | undefined
|
|
||||||
|
|
||||||
const token = { value: 0 }
|
|
||||||
let inflight: Promise<SearchItem[]> | undefined
|
|
||||||
let all: SearchItem[] | undefined
|
|
||||||
|
|
||||||
const reset = () => {
|
|
||||||
token.value += 1
|
|
||||||
inflight = undefined
|
|
||||||
all = undefined
|
|
||||||
setSearch({ value: "" })
|
|
||||||
listRef = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const open = (item: SearchItem | undefined) => {
|
|
||||||
if (!item) return
|
|
||||||
|
|
||||||
const href = `/${base64Encode(item.directory)}/session/${item.id}`
|
|
||||||
if (!layout.sidebar.opened()) {
|
|
||||||
setState("hoverSession", undefined)
|
|
||||||
setState("hoverProject", undefined)
|
|
||||||
}
|
|
||||||
reset()
|
|
||||||
navigate(href)
|
|
||||||
layout.mobileSidebar.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = (filter: string) => {
|
|
||||||
const query = filter.trim()
|
|
||||||
if (!query) {
|
|
||||||
token.value += 1
|
|
||||||
inflight = undefined
|
|
||||||
all = undefined
|
|
||||||
return [] as SearchItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const project = panelProps.project
|
|
||||||
if (!project) return [] as SearchItem[]
|
|
||||||
if (all) return all
|
|
||||||
if (inflight) return inflight
|
|
||||||
|
|
||||||
const current = token.value
|
|
||||||
const dirs = workspaceIds(project)
|
|
||||||
inflight = Promise.all(
|
|
||||||
dirs.map((input) => {
|
|
||||||
const directory = workspaceKey(input)
|
|
||||||
const [workspaceStore] = globalSync.child(directory, { bootstrap: false })
|
|
||||||
const kind =
|
|
||||||
directory === project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
|
|
||||||
const name = workspaceLabel(directory, workspaceStore.vcs?.branch, project.id)
|
|
||||||
const label = `${kind} : ${name}`
|
|
||||||
return globalSDK.client.session
|
|
||||||
.list({ directory, roots: true })
|
|
||||||
.then((x) =>
|
|
||||||
(x.data ?? [])
|
|
||||||
.filter((s) => !!s?.id)
|
|
||||||
.map((s) => ({
|
|
||||||
id: s.id,
|
|
||||||
title: s.title ?? language.t("command.session.new"),
|
|
||||||
directory,
|
|
||||||
label,
|
|
||||||
archived: s.time?.archived,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.catch(() => [] as SearchItem[])
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then((results) => {
|
|
||||||
if (token.value !== current) return [] as SearchItem[]
|
|
||||||
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const next = results.flat().filter((item) => {
|
|
||||||
const key = `${item.directory}:${item.id}`
|
|
||||||
if (seen.has(key)) return false
|
|
||||||
seen.add(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
all = next
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
.catch(() => [] as SearchItem[])
|
|
||||||
.finally(() => {
|
|
||||||
inflight = undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
return inflight
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
() => panelProps.project?.worktree,
|
|
||||||
() => reset(),
|
|
||||||
{ defer: true },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
@@ -2918,105 +2808,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shrink-0 px-2 pt-2">
|
<div class="flex-1 min-h-0 flex flex-col">
|
||||||
<div
|
|
||||||
class="flex items-center gap-2 p-2 rounded-md bg-surface-base shadow-xs-border-base focus-within:shadow-xs-border-select"
|
|
||||||
onPointerDown={(event) => {
|
|
||||||
const target = event.target
|
|
||||||
if (!(target instanceof Element)) return
|
|
||||||
if (target.closest("input, textarea, [contenteditable='true']")) return
|
|
||||||
searchRef?.focus()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon name="magnifying-glass" />
|
|
||||||
<InlineInput
|
|
||||||
ref={(el) => {
|
|
||||||
searchRef = el
|
|
||||||
}}
|
|
||||||
class="flex-1 min-w-0 text-14-regular text-text-strong placeholder:text-text-weak"
|
|
||||||
style={{ "box-shadow": "none" }}
|
|
||||||
value={search.value}
|
|
||||||
onInput={(event) => setSearch("value", event.currentTarget.value)}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
event.preventDefault()
|
|
||||||
setSearch("value", "")
|
|
||||||
queueMicrotask(() => searchRef?.focus())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!searching()) return
|
|
||||||
|
|
||||||
if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Enter") {
|
|
||||||
const ref = listRef
|
|
||||||
if (!ref) return
|
|
||||||
event.stopPropagation()
|
|
||||||
ref.onKeyDown(event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
|
|
||||||
if (event.key === "n" || event.key === "p") {
|
|
||||||
const ref = listRef
|
|
||||||
if (!ref) return
|
|
||||||
event.stopPropagation()
|
|
||||||
ref.onKeyDown(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder={language.t("session.header.search.placeholder", { project: projectName() })}
|
|
||||||
spellcheck={false}
|
|
||||||
autocorrect="off"
|
|
||||||
autocomplete="off"
|
|
||||||
autocapitalize="off"
|
|
||||||
/>
|
|
||||||
<Show when={search.value}>
|
|
||||||
<IconButton
|
|
||||||
icon="circle-x"
|
|
||||||
variant="ghost"
|
|
||||||
class="size-5"
|
|
||||||
aria-label={language.t("common.close")}
|
|
||||||
onClick={() => {
|
|
||||||
setSearch("value", "")
|
|
||||||
queueMicrotask(() => searchRef?.focus())
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Show when={searching()}>
|
|
||||||
<List
|
|
||||||
class="flex-1 min-h-0 pb-2 pt-2 !px-2 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
|
|
||||||
items={items}
|
|
||||||
filter={search.value}
|
|
||||||
filterKeys={["title", "label", "id"]}
|
|
||||||
key={(item) => `${item.directory}:${item.id}`}
|
|
||||||
onSelect={open}
|
|
||||||
ref={(ref) => {
|
|
||||||
listRef = ref
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(item) => (
|
|
||||||
<div class="flex flex-col gap-0.5 min-w-0 pr-2 text-left">
|
|
||||||
<span
|
|
||||||
class="text-14-medium text-text-strong truncate"
|
|
||||||
classList={{ "opacity-70": !!item.archived }}
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="text-12-regular text-text-weak truncate"
|
|
||||||
classList={{ "opacity-70": !!item.archived }}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div class="flex-1 min-h-0 flex flex-col" classList={{ hidden: searching() }}>
|
|
||||||
<Show
|
<Show
|
||||||
when={workspacesEnabled()}
|
when={workspacesEnabled()}
|
||||||
fallback={
|
fallback={
|
||||||
@@ -3100,7 +2892,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
<div
|
<div
|
||||||
class="shrink-0 px-2 py-3 border-t border-border-weak-base"
|
class="shrink-0 px-2 py-3 border-t border-border-weak-base"
|
||||||
classList={{
|
classList={{
|
||||||
hidden: searching() || !(providers.all().length > 0 && providers.paid().length === 0),
|
hidden: !(providers.all().length > 0 && providers.paid().length === 0),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="rounded-md bg-background-base shadow-xs-border-base">
|
<div class="rounded-md bg-background-base shadow-xs-border-base">
|
||||||
|
|||||||
Reference in New Issue
Block a user