Skip to content

Commit e08d1ed

Browse files
committed
refactor: simplify sidebar layout and add directory picker dialog
1 parent 6028bff commit e08d1ed

4 files changed

Lines changed: 257 additions & 210 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { useDialog } from "@opencode-ai/ui/context/dialog"
2+
import { Dialog } from "@opencode-ai/ui/dialog"
3+
import { FileIcon } from "@opencode-ai/ui/file-icon"
4+
import { List } from "@opencode-ai/ui/list"
5+
import { getDirectory, getFilename } from "@opencode-ai/util/path"
6+
import { createMemo } from "solid-js"
7+
import { useGlobalSDK } from "@/context/global-sdk"
8+
import { useGlobalSync } from "@/context/global-sync"
9+
import { useLanguage } from "@/context/language"
10+
11+
interface DialogSelectDirectoryProps {
12+
title?: string
13+
multiple?: boolean
14+
onSelect: (result: string | string[] | null) => void
15+
}
16+
17+
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
18+
const sync = useGlobalSync()
19+
const sdk = useGlobalSDK()
20+
const dialog = useDialog()
21+
const language = useLanguage()
22+
23+
const home = createMemo(() => sync.data.path.home)
24+
const root = createMemo(() => sync.data.path.home || sync.data.path.directory)
25+
26+
function join(base: string | undefined, rel: string) {
27+
const b = (base ?? "").replace(/[\\/]+$/, "")
28+
const r = rel.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")
29+
if (!b) return r
30+
if (!r) return b
31+
return b + "/" + r
32+
}
33+
34+
function display(rel: string) {
35+
const full = join(root(), rel)
36+
const h = home()
37+
if (!h) return full
38+
if (full === h) return "~"
39+
if (full.startsWith(h + "/") || full.startsWith(h + "\\")) {
40+
return "~" + full.slice(h.length)
41+
}
42+
return full
43+
}
44+
45+
function normalizeQuery(query: string) {
46+
const h = home()
47+
48+
if (!query) return query
49+
if (query.startsWith("~/")) return query.slice(2)
50+
51+
if (h) {
52+
const lc = query.toLowerCase()
53+
const hc = h.toLowerCase()
54+
if (lc === hc || lc.startsWith(hc + "/") || lc.startsWith(hc + "\\")) {
55+
return query.slice(h.length).replace(/^[\\/]+/, "")
56+
}
57+
}
58+
59+
return query
60+
}
61+
62+
async function fetchDirs(query: string) {
63+
const directory = root()
64+
if (!directory) return [] as string[]
65+
66+
const results = await sdk.client.find
67+
.files({ directory, query, type: "directory", limit: 50 })
68+
.then((x) => x.data ?? [])
69+
.catch(() => [])
70+
71+
return results.map((x) => x.replace(/[\\/]+$/, ""))
72+
}
73+
74+
const directories = async (filter: string) => {
75+
const query = normalizeQuery(filter.trim())
76+
return fetchDirs(query)
77+
}
78+
79+
function resolve(rel: string) {
80+
const absolute = join(root(), rel)
81+
props.onSelect(props.multiple ? [absolute] : absolute)
82+
dialog.close()
83+
}
84+
85+
return (
86+
<Dialog title={props.title ?? language.t("command.project.open")}>
87+
<List
88+
search={{ placeholder: language.t("dialog.directory.search.placeholder"), autofocus: true }}
89+
emptyMessage={language.t("dialog.directory.empty")}
90+
loadingMessage={language.t("common.loading")}
91+
items={directories}
92+
key={(x) => x}
93+
onSelect={(path) => {
94+
if (!path) return
95+
resolve(path)
96+
}}
97+
>
98+
{(rel) => {
99+
const path = display(rel)
100+
return (
101+
<div class="w-full flex items-center justify-between rounded-md">
102+
<div class="flex items-center gap-x-3 grow min-w-0">
103+
<FileIcon node={{ path: rel, type: "directory" }} class="shrink-0 size-4" />
104+
<div class="flex items-center text-14-regular min-w-0">
105+
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
106+
{getDirectory(path)}
107+
</span>
108+
<span class="text-text-strong whitespace-nowrap">{getFilename(path)}</span>
109+
</div>
110+
</div>
111+
</div>
112+
)
113+
}}
114+
</List>
115+
</Dialog>
116+
)
117+
}

packages/app/src/components/titlebar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Icon } from "@opencode-ai/ui/icon"
44
import { Button } from "@opencode-ai/ui/button"
55
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
66
import { useTheme } from "@opencode-ai/ui/theme"
7+
import { AsciiMark } from "@opencode-ai/ui/logo"
78

89
import { useLayout } from "@/context/layout"
910
import { usePlatform } from "@/context/platform"
@@ -88,6 +89,11 @@ export function Titlebar() {
8889
>
8990
<Show when={mac()}>
9091
<div class="w-[72px] h-full shrink-0" data-tauri-drag-region />
92+
</Show>
93+
<div class="shrink-0 w-8 h-8 flex items-center justify-center" data-tauri-drag-region>
94+
<AsciiMark scale={0.45} />
95+
</div>
96+
<Show when={mac()}>
9197
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
9298
<IconButton
9399
icon="menu"

0 commit comments

Comments
 (0)