From 9898967ebbba5ededc827534e1e6700c39b41e9a Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sat, 15 Aug 2026 20:49:19 +0000 Subject: [PATCH] fix(app): use tree directory picker everywhere --- .../components/dialog-select-directory.tsx | 205 ------------------ .../app/src/components/directory-picker.tsx | 9 +- 2 files changed, 1 insertion(+), 213 deletions(-) delete mode 100644 packages/app/src/components/dialog-select-directory.tsx diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx deleted file mode 100644 index 29e0c1c9c36..00000000000 --- a/packages/app/src/components/dialog-select-directory.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { useDialog } from "@opencode-ai/ui/context/dialog" -import { Dialog } from "@opencode-ai/ui/dialog" -import { FileIcon } from "@opencode-ai/ui/file-icon" -import { List } from "@opencode-ai/ui/list" -import type { ListRef } from "@opencode-ai/ui/list" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" -import { createMemo, createResource, createSignal } from "solid-js" -import { useLanguage } from "@/context/language" -import { ServerConnection } from "@/context/servers" -import { useGlobal } from "@/context/global" -import { cleanPickerInput, createDirectorySearch, displayPickerPath } from "./directory-picker-domain" -import type { Path } from "@/types" - -interface DialogSelectDirectoryProps { - title?: string - multiple?: boolean - onSelect: (result: string | string[] | null) => void - server: ServerConnection.Any -} - -const RECENT_PROJECT_LIMIT = 5 - -type Row = { - absolute: string - search: string - group: "recent" | "folders" -} - -function toRow(absolute: string, home: string, group: Row["group"]): Row { - const full = displayPickerPath(absolute, "", "") - const tilde = displayPickerPath(full, "~", home) - const withSlash = (value: string) => { - if (!value) return "" - if (value.endsWith("/")) return value - return value + "/" - } - - const search = Array.from( - new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)), - ).join("\n") - return { absolute: full, search, group } -} - -function uniqueRows(rows: Row[]) { - const seen = new Set() - return rows.filter((row) => { - if (seen.has(row.absolute)) return false - seen.add(row.absolute) - return true - }) -} - -export function DialogSelectDirectory(props: DialogSelectDirectoryProps) { - const global = useGlobal() - const { sync, sdk, ...serverCtx } = global.ensureServerCtx(props.server) - const dialog = useDialog() - const language = useLanguage() - - const [filter, setFilter] = createSignal("") - let list: ListRef | undefined - - const [fallbackPath] = createResource( - () => (!(sync.data.path.home || sync.data.path.directory) ? true : undefined), - () => - sdk.api.location - .get() - .then( - (location): Path => ({ - state: "", - config: "", - worktree: location.project.directory, - directory: location.directory, - home: "", - }), - ) - .catch(() => undefined), - { initialValue: undefined }, - ) - - const home = createMemo(() => sync.data.path.home || fallbackPath()?.home || "") - const start = createMemo( - () => sync.data.path.home || sync.data.path.directory || fallbackPath()?.home || fallbackPath()?.directory, - ) - - const directories = createDirectorySearch({ - sdk, - home, - base: start, - }) - - const recentProjects = createMemo(() => { - const projects = serverCtx.projects.list() - const byProject = new Map() - - for (const project of projects) { - let at = 0 - const dirs = [project.worktree, ...(project.sandboxes ?? [])] - for (const directory of dirs) { - const sessions = sync.child(directory, { bootstrap: false })[0].session - for (const session of sessions) { - if (session.time.archived) continue - const updated = session.time.updated ?? session.time.created - if (updated > at) at = updated - } - } - byProject.set(project.worktree, at) - } - - return projects - .map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index })) - .sort((a, b) => b.at - a.at || a.index - b.index) - .map(({ project }) => { - const row = toRow(project.worktree, home(), "recent") - const name = project.name || getFilename(project.worktree) - return { - ...row, - search: `${row.search}\n${name}`, - } - }) - }) - - const items = async (value: string) => { - const results = await directories(value) - const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders")) - // Cap the idle list only. Once a query narrows the results, every project stays searchable. - const recent = recentProjects() - const visible = value ? recent : recent.slice(0, RECENT_PROJECT_LIMIT) - return uniqueRows([...visible, ...directoryRows]) - } - - function resolve(absolute: string) { - props.onSelect(props.multiple ? [absolute] : absolute) - dialog.close() - } - - return ( - - x.absolute} - filterKeys={["search"]} - groupBy={(item) => item.group} - sortGroupsBy={(a, b) => { - if (a.category === b.category) return 0 - return a.category === "recent" ? -1 : 1 - }} - groupHeader={(group) => - group.category === "recent" ? language.t("home.recentProjects") : language.t("command.project.open") - } - ref={(r) => (list = r)} - onFilter={(value) => setFilter(cleanPickerInput(value))} - onKeyEvent={(e, item) => { - if (e.key !== "Tab") return - if (e.shiftKey) return - if (!item) return - - e.preventDefault() - e.stopPropagation() - - const value = displayPickerPath(item.absolute, filter(), home()) - list?.setFilter(value.endsWith("/") ? value : value + "/") - }} - onSelect={(path) => { - if (!path) return - resolve(path.absolute) - }} - > - {(item) => { - const path = displayPickerPath(item.absolute, filter(), home()) - if (path === "~") { - return ( -
-
- -
- ~ - / -
-
-
- ) - } - return ( -
-
- -
- - {getDirectory(path)} - - {getFilename(path)} - / -
-
-
- ) - }} -
-
- ) -} diff --git a/packages/app/src/components/directory-picker.tsx b/packages/app/src/components/directory-picker.tsx index b815e9a9a2a..74bc2aa4843 100644 --- a/packages/app/src/components/directory-picker.tsx +++ b/packages/app/src/components/directory-picker.tsx @@ -1,9 +1,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { ServerConnection } from "@/context/servers" import { usePlatform } from "@/context/platform" -import { useSettings } from "@/context/settings" import { lazy } from "solid-js" -import { DialogSelectDirectory } from "./dialog-select-directory" import { directoryPickerKind } from "./directory-picker-policy" const DialogSelectDirectoryV2 = lazy(() => @@ -19,7 +17,6 @@ type DirectoryPickerInput = { export function useDirectoryPicker() { const platform = usePlatform() - const settings = useSettings() const dialog = useDialog() return (input: DirectoryPickerInput) => { @@ -36,10 +33,6 @@ export function useDirectoryPicker() { const cancel = () => { if (!selected) input.onSelect(null) } - if (platform.platform === "desktop" && settings.general.newLayoutDesigns()) { - dialog.show(() => , cancel) - return - } - dialog.show(() => , cancel) + dialog.show(() => , cancel) } }