summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src/pages/layout.tsx
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-04 20:32:08 -0600
committerAdam <[email protected]>2025-12-04 20:32:08 -0600
commit09f522f0aa698be60c954e58bb7eee0e460c4439 (patch)
tree8b936f4ab3cbafab391551e898412d1617dbd66b /packages/desktop/src/pages/layout.tsx
parentd82bd430f68b8227a93c39e0b7b617c9463ceea8 (diff)
downloadopencode-09f522f0aa698be60c954e58bb7eee0e460c4439.tar.gz
opencode-09f522f0aa698be60c954e58bb7eee0e460c4439.zip
Reapply "feat(desktop): terminal pane (#5081)"
This reverts commit f9dcd979364acc5172fd0044c1c8b04dcaec9229.
Diffstat (limited to 'packages/desktop/src/pages/layout.tsx')
-rw-r--r--packages/desktop/src/pages/layout.tsx114
1 files changed, 109 insertions, 5 deletions
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx
index 15180c885..106a2e733 100644
--- a/packages/desktop/src/pages/layout.tsx
+++ b/packages/desktop/src/pages/layout.tsx
@@ -1,9 +1,9 @@
import { createMemo, For, ParentProps, Show } from "solid-js"
import { DateTime } from "luxon"
-import { A, useParams } from "@solidjs/router"
+import { A, useNavigate, useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync"
-import { base64Encode } from "@/utils"
+import { base64Decode, base64Encode } from "@/utils"
import { Mark } from "@opencode-ai/ui/logo"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
@@ -12,11 +12,21 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Collapsible } from "@opencode-ai/ui/collapsible"
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { getFilename } from "@opencode-ai/util/path"
+import { Select } from "@opencode-ai/ui/select"
+import { Session } from "@opencode-ai/sdk/client"
export default function Layout(props: ParentProps) {
+ const navigate = useNavigate()
const params = useParams()
const globalSync = useGlobalSync()
const layout = useLayout()
+ const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
+ const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
+ const currentSession = createMemo(() => sessions().find((s) => s.id === params.id) ?? sessions().at(0))
+
+ function navigateToSession(session: Session | undefined) {
+ navigate(`/${params.dir}/session/${session?.id}`)
+ }
const handleOpenProject = async () => {
// layout.projects.open(dir.)
@@ -24,7 +34,7 @@ export default function Layout(props: ParentProps) {
return (
<div class="relative h-screen flex flex-col">
- <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base">
+ <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex">
<A
href="/"
classList={{
@@ -36,16 +46,110 @@ export default function Layout(props: ParentProps) {
>
<Mark class="shrink-0" />
</A>
+ <div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
+ <div class="flex items-center gap-3">
+ <div class="flex items-center gap-2">
+ <Select
+ options={layout.projects.list().map((project) => getFilename(project.directory))}
+ current={getFilename(currentDirectory())}
+ class="text-14-regular text-text-base"
+ variant="ghost"
+ />
+ <div class="text-text-weaker">/</div>
+ <Select
+ options={sessions()}
+ current={currentSession()}
+ label={(x) => x.title}
+ value={(x) => x.id}
+ onSelect={navigateToSession}
+ class="text-14-regular text-text-base max-w-3xs"
+ variant="ghost"
+ />
+ </div>
+ <Button as={A} href={`/${params.dir}/session`} icon="plus-small">
+ New session
+ </Button>
+ </div>
+ <div class="flex items-center gap-4">
+ <Tooltip
+ class="shrink-0"
+ value={
+ <div class="flex items-center gap-2">
+ <span>Toggle terminal</span>
+ <span class="text-icon-base text-12-medium">Ctrl `</span>
+ </div>
+ }
+ >
+ <Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
+ <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
+ <Icon
+ size="small"
+ name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
+ class="group-hover/terminal-toggle:hidden"
+ />
+ <Icon
+ size="small"
+ name="layout-bottom-partial"
+ class="hidden group-hover/terminal-toggle:inline-block"
+ />
+ <Icon
+ size="small"
+ name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
+ class="hidden group-active/terminal-toggle:inline-block"
+ />
+ </div>
+ </Button>
+ </Tooltip>
+ </div>
+ </div>
</header>
<div class="h-[calc(100vh-3rem)] flex">
<div
classList={{
- "@container w-12 pb-5 shrink-0 bg-background-base": true,
+ "relative @container w-12 pb-5 shrink-0 bg-background-base": true,
"flex flex-col gap-5.5 items-start self-stretch justify-between": true,
"border-r border-border-weak-base": true,
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
+ <Show when={layout.sidebar.opened()}>
+ <div
+ class="absolute inset-y-0 right-0 z-10 w-2 translate-x-1/2 cursor-ew-resize"
+ onMouseDown={(e) => {
+ e.preventDefault()
+ const startX = e.clientX
+ const startWidth = layout.sidebar.width()
+ const maxWidth = window.innerWidth * 0.3
+ const minWidth = 150
+ const collapseThreshold = 80
+ let currentWidth = startWidth
+
+ document.body.style.userSelect = "none"
+ document.body.style.overflow = "hidden"
+
+ const onMouseMove = (moveEvent: MouseEvent) => {
+ const deltaX = moveEvent.clientX - startX
+ currentWidth = startWidth + deltaX
+ const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth))
+ layout.sidebar.resize(clampedWidth)
+ }
+
+ const onMouseUp = () => {
+ document.body.style.userSelect = ""
+ document.body.style.overflow = ""
+ document.removeEventListener("mousemove", onMouseMove)
+ document.removeEventListener("mouseup", onMouseUp)
+
+ if (currentWidth < collapseThreshold) {
+ layout.sidebar.close()
+ }
+ }
+
+ document.addEventListener("mousemove", onMouseMove)
+ document.addEventListener("mouseup", onMouseUp)
+ }}
+ />
+ </Show>
<div class="grow flex flex-col items-start self-stretch gap-4 p-2 min-h-0">
<Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
<Button
@@ -197,7 +301,7 @@ export default function Layout(props: ParentProps) {
</Tooltip>
</div>
</div>
- <main class="size-full overflow-x-hidden">{props.children}</main>
+ <main class="size-full overflow-x-hidden flex flex-col items-start">{props.children}</main>
</div>
</div>
)