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
15 changes: 15 additions & 0 deletions packages/app/src/pages/layout-new.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { createEffect, Suspense, type ParentProps } from "solid-js"
import { createStore } from "solid-js/store"
import { useNavigate } from "@solidjs/router"
import { DebugBar } from "@/components/debug-bar"
import { TabsInfoPopup } from "@/components/help-button"
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
import { usePlatform } from "@/context/platform"
import { useServer } from "@/context/server"
import { setV2Toast, ToastRegion } from "@/utils/toast"
import { useOpenProject } from "@/pages/layout/open-project"
import { useDeepLinks } from "@/pages/layout/use-deep-links"

export default function NewLayout(props: ParentProps) {
const platform = usePlatform()
const server = useServer()
const navigate = useNavigate()
const [state, setState] = createStore({ debugTools: true })
const openProject = useOpenProject({
open: (directory) => server.projects.open(directory),
touch: (directory) => server.projects.touch(directory),
})
useDeepLinks({
enabled: () => server.isLocal(),
openProject: openProject.ensureProject,
navigate,
})

createEffect(() => setV2Toast(true))

Expand Down
56 changes: 18 additions & 38 deletions packages/app/src/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,9 @@ import {
latestRootSession,
sortedRootSessions,
} from "./layout/helpers"
import {
collectNewSessionDeepLinks,
collectOpenProjectDeepLinks,
deepLinkEvent,
drainPendingDeepLinks,
} from "./layout/deep-links"
import { createInlineEditorController } from "./layout/inline-editor"
import { useOpenProject } from "./layout/open-project"
import { useDeepLinks } from "./layout/use-deep-links"
import {
LocalWorkspace,
SortableWorkspace,
Expand Down Expand Up @@ -118,6 +114,10 @@ export default function LegacyLayout(props: ParentProps) {
const navigate = useNavigate()
const providers = useProviders(() => undefined)
const dialog = useDialog()
const openProjects = useOpenProject({
projectDirectory: (directory) => projectRoot(directory),
open: (directory) => layout.projects.open(directory),
})
const command = useCommand()
const theme = useTheme()
const language = useLanguage()
Expand Down Expand Up @@ -1255,41 +1255,21 @@ export default function LegacyLayout(props: ParentProps) {
navigateWithSidebarReset(`/${base64Encode(session.directory)}/session/${session.id}`)
}

function openProject(directory: string, navigate = true) {
layout.projects.open(directory)
async function openProject(directory: string, navigate = true) {
await openProjects.ensureProject(directory)
if (navigate) return navigateToProject(directory)
}

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

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

for (const link of collectNewSessionDeepLinks(urls)) {
void openProject(link.directory, false)
const slug = base64Encode(link.directory)
if (link.prompt) {
setSessionHandoff(SessionStateKey.from(server.scope(), SessionRouteKey.fromLegacy(slug)), {
prompt: link.prompt,
})
}
const href = link.prompt ? `/${slug}/session?prompt=${encodeURIComponent(link.prompt)}` : `/${slug}/session`
navigateWithSidebarReset(href)
}
}

onMount(() => {
const handler = (event: Event) => {
const detail = (event as CustomEvent<{ urls: string[] }>).detail
const urls = detail?.urls ?? []
if (urls.length === 0) return
handleDeepLinks(urls)
}

handleDeepLinks(drainPendingDeepLinks(window))
makeEventListener(window, deepLinkEvent, handler as EventListener)
useDeepLinks({
enabled: () => server.isLocal() && !settings.general.newLayoutDesigns(),
openProject: (directory) => openProjects.ensureProject(directory),
prepareNewSession: (link) => {
if (!link.prompt) return
setSessionHandoff(SessionStateKey.from(server.scope(), SessionRouteKey.fromLegacy(base64Encode(link.directory))), {
prompt: link.prompt,
})
},
navigate: navigateWithSidebarReset,
})

async function renameProject(project: LocalProject, next: string) {
Expand Down
25 changes: 25 additions & 0 deletions packages/app/src/pages/layout/open-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"

export function useOpenProject(options: {
projectDirectory?: (directory: string) => string
open: (directory: string) => void
touch?: (directory: string) => void
}) {
const serverSDK = useServerSDK()
const serverSync = useServerSync()

async function ensureProject(directory: string) {
const projectDirectory = options.projectDirectory?.(directory) ?? directory
const known = serverSync().data.project.some((project) => project.worktree === projectDirectory)
if (!known) {
await serverSDK()
.api.project.current({ location: { directory: projectDirectory } })
.catch(() => undefined)
}
options.open(directory)
options.touch?.(directory)
}

return { ensureProject }
}
48 changes: 48 additions & 0 deletions packages/app/src/pages/layout/use-deep-links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { base64Encode } from "@opencode-ai/core/util/encode"
import { makeEventListener } from "@solid-primitives/event-listener"
import { onMount } from "solid-js"
import {
collectNewSessionDeepLinks,
collectOpenProjectDeepLinks,
deepLinkEvent,
drainPendingDeepLinks,
} from "./deep-links"

export function useDeepLinks(options: {
enabled: () => boolean
openProject: (directory: string) => Promise<void>
navigate: (href: string) => void
prepareNewSession?: (link: { directory: string; prompt?: string }) => void
}) {
function handleDeepLinks(urls: string[]) {
if (!options.enabled()) return

for (const directory of collectOpenProjectDeepLinks(urls)) {
void options.openProject(directory).then(() => {
options.navigate(`/${base64Encode(directory)}/session`)
})
}

for (const link of collectNewSessionDeepLinks(urls)) {
void options.openProject(link.directory).then(() => {
options.prepareNewSession?.(link)
const slug = base64Encode(link.directory)
const href = link.prompt
? `/${slug}/session?prompt=${encodeURIComponent(link.prompt)}`
: `/${slug}/session`
options.navigate(href)
})
}
}

onMount(() => {
const handler = (event: Event) => {
const detail = (event as CustomEvent<{ urls: string[] }>).detail
handleDeepLinks(detail?.urls ?? [])
}
handleDeepLinks(drainPendingDeepLinks(window))
makeEventListener(window, deepLinkEvent, handler as EventListener)
})

return { handleDeepLinks }
}
Loading