Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions packages/app/src/context/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,25 @@ export function pruneSessionKeys(input: {
.slice(input.max)
}

type RootResult = {
root: string
project?: {
id?: string
worktree?: string
vcs?: string
sandboxes?: string[]
}
}

const norm = (dir: string) => {
const drive = dir.match(/^([A-Za-z]:)[\\/]+$/)
if (drive) return `${drive[1].toLowerCase()}/`
if (/^[\\/]+$/.test(dir)) return "/"
const key = dir.replace(/[\\/]+$/, "").replaceAll("\\", "/")
if (/^[A-Za-z]:\//.test(key) || key.startsWith("//")) return key.toLowerCase()
return key
}

function nextSessionTabsForOpen(current: SessionTabs | undefined, tab: string): SessionTabs {
const all = current?.all ?? []
if (tab === "review") return { all: all.filter((x) => x !== "review"), active: tab }
Expand Down Expand Up @@ -457,9 +476,30 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
return directory
}

const resolveRoot = (directory: string): Promise<RootResult> => {
return globalSdk
.createClient({ directory, throwOnError: true })
.project.current()
.then((x) => {
const project = x.data
return {
root: project?.worktree && project.id !== "global" ? project.worktree : directory,
project: project
? {
id: project.id,
worktree: project.worktree,
vcs: project.vcs,
sandboxes: project.sandboxes,
}
: undefined,
}
})
.catch(() => ({ root: directory }))
}

createEffect(() => {
const projects = server.projects.list()
const seen = new Set(projects.map((project) => project.worktree))
const seen = new Set(projects.map((project) => norm(project.worktree)))

batch(() => {
for (const project of projects) {
Expand All @@ -468,9 +508,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(

server.projects.close(project.worktree)

if (!seen.has(root)) {
const key = norm(root)
if (!seen.has(key)) {
server.projects.open(root)
seen.add(root)
seen.add(key)
}

if (project.expanded) server.projects.expand(root)
Expand Down Expand Up @@ -565,11 +606,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
projects: {
list,
open(directory: string) {
const root = rootFor(directory)
if (server.projects.list().find((x) => x.worktree === root)) return
resolve(directory: string) {
return resolveRoot(directory)
},
async open(directory: string, resolved?: RootResult) {
const value = resolved ?? (await resolveRoot(directory))
const root = value.root
const key = norm(root)
const exists = server.projects.list().some((x) => norm(x.worktree) === key)
if (exists) return value
globalSync.project.loadSessions(root)
server.projects.open(root)
return value
},
close(directory: string) {
server.projects.close(directory)
Expand Down
35 changes: 21 additions & 14 deletions packages/app/src/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1262,20 +1262,28 @@ export default function Layout(props: ParentProps) {
navigateWithSidebarReset(`/${base64Encode(session.directory)}/session/${session.id}`)
}

function openProject(directory: string, navigate = true) {
layout.projects.open(directory)
if (navigate) navigateToProject(directory)
async function openProject(directory: string, navigate = true) {
const match = layout.projects.list().find((item) => workspaceKey(item.worktree) === workspaceKey(directory))
if (match) {
if (navigate) await navigateToProject(match.worktree)
return match.worktree
}

const { root, project } = await layout.projects.resolve(directory)
await layout.projects.open(directory, { root, project })
if (navigate) await navigateToProject(root)
return root
}

const handleDeepLinks = (urls: string[]) => {
if (!server.isLocal()) return

for (const directory of collectOpenProjectDeepLinks(urls)) {
openProject(directory)
void openProject(directory)
}

for (const link of collectNewSessionDeepLinks(urls)) {
openProject(link.directory, false)
void openProject(link.directory, false)
const slug = base64Encode(link.directory)
if (link.prompt) {
setSessionHandoff(slug, { prompt: link.prompt })
Expand Down Expand Up @@ -1355,14 +1363,13 @@ export default function Layout(props: ParentProps) {
const showEditProjectDialog = (project: LocalProject) => dialog.show(() => <DialogEditProject project={project} />)

async function chooseProject() {
function resolve(result: string | string[] | null) {
async function resolve(result: string | string[] | null) {
if (Array.isArray(result)) {
for (const directory of result) {
openProject(directory, false)
}
navigateToProject(result[0])
const roots = await Promise.all(result.map((directory) => openProject(directory, false)))
if (!roots[0]) return
await navigateToProject(roots[0])
} else if (result) {
openProject(result)
await openProject(result)
}
}

Expand All @@ -1371,11 +1378,11 @@ export default function Layout(props: ParentProps) {
title: language.t("command.project.open"),
multiple: true,
})
resolve(result)
void resolve(result)
} else {
dialog.show(
() => <DialogSelectDirectory multiple={true} onSelect={resolve} />,
() => resolve(null),
() => <DialogSelectDirectory multiple={true} onSelect={(result) => void resolve(result)} />,
() => void resolve(null),
)
}
}
Expand Down
Loading