summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src/pages/layout.tsx
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-11-18 11:00:27 -0600
committeropencode <[email protected]>2025-11-18 17:07:34 +0000
commitfc5fc2c570e6ac5a6f9647080b80f7fec0c4b605 (patch)
treea3971bfbe17308df9f2d3e1771c0ab544900c099 /packages/desktop/src/pages/layout.tsx
parent4069999b782cc00d4e707f5eca32082bdfad45bc (diff)
downloadopencode-fc5fc2c570e6ac5a6f9647080b80f7fec0c4b605.tar.gz
opencode-fc5fc2c570e6ac5a6f9647080b80f7fec0c4b605.zip
wip(desktop): new layout work
Diffstat (limited to 'packages/desktop/src/pages/layout.tsx')
-rw-r--r--packages/desktop/src/pages/layout.tsx266
1 files changed, 145 insertions, 121 deletions
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx
index 6e0078a16..33cf7b66f 100644
--- a/packages/desktop/src/pages/layout.tsx
+++ b/packages/desktop/src/pages/layout.tsx
@@ -1,19 +1,25 @@
-import { Button, Tooltip, DiffChanges, IconButton, Mark, Icon } from "@opencode-ai/ui"
-import { createMemo, For, Match, ParentProps, Show, Switch } from "solid-js"
+import { Button, Tooltip, DiffChanges, IconButton, Mark, Icon, Collapsible } from "@opencode-ai/ui"
+import { createMemo, For, ParentProps, Show } from "solid-js"
import { DateTime } from "luxon"
-import { useSync } from "@/context/sync"
import { A, useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
+import { useGlobalSync } from "@/context/global-sync"
+import { base64Encode, getFilename } from "@/utils"
export default function Layout(props: ParentProps) {
const params = useParams()
- const sync = useSync()
+ const globalSync = useGlobalSync()
const layout = useLayout()
+ const handleOpenProject = async () => {
+ // layout.projects.open(dir.)
+ }
+
return (
<div class="relative h-screen flex flex-col">
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base">
- <div
+ <A
+ href="/"
classList={{
"w-12 shrink-0 px-4 py-3.5": true,
"flex items-center justify-start self-stretch": true,
@@ -22,7 +28,7 @@ export default function Layout(props: ParentProps) {
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
<Mark class="shrink-0" />
- </div>
+ </A>
</header>
<div class="h-[calc(100vh-3rem)] flex">
<div
@@ -33,136 +39,154 @@ export default function Layout(props: ParentProps) {
}}
style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
>
- <div class="flex flex-col justify-center items-start gap-4 self-stretch p-2 overflow-hidden mx-auto @[4rem]:mx-0">
- <Switch>
- <Match when={layout.sidebar.opened()}>
- <Button
- variant="ghost"
- size="large"
- class="group/sidebar-toggle w-full text-left justify-start"
- onClick={layout.sidebar.toggle}
- >
- <div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
- <Icon name="layout-left" size="small" class="group-hover/sidebar-toggle:hidden" />
- <Icon
- name="layout-left-partial"
- size="small"
- class="hidden group-hover/sidebar-toggle:inline-block"
- />
- <Icon
- name="layout-left-full"
- size="small"
- class="hidden group-active/sidebar-toggle:inline-block"
- />
- </div>
+ <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
+ variant="ghost"
+ size="large"
+ class="group/sidebar-toggle shrink-0 w-full text-left justify-start"
+ onClick={layout.sidebar.toggle}
+ >
+ <div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
+ <Icon
+ name={layout.sidebar.opened() ? "layout-left" : "layout-right"}
+ size="small"
+ class="group-hover/sidebar-toggle:hidden"
+ />
+ <Icon
+ name={layout.sidebar.opened() ? "layout-left-partial" : "layout-right-partial"}
+ size="small"
+ class="hidden group-hover/sidebar-toggle:inline-block"
+ />
+ <Icon
+ name={layout.sidebar.opened() ? "layout-left-full" : "layout-right-full"}
+ size="small"
+ class="hidden group-active/sidebar-toggle:inline-block"
+ />
+ </div>
+ <Show when={layout.sidebar.opened()}>
<div class="hidden group-hover/sidebar-toggle:block group-active/sidebar-toggle:block text-text-base">
Toggle sidebar
</div>
- </Button>
- </Match>
- <Match when={!layout.sidebar.opened()}>
- <Tooltip placement="right" value="Toggle sidebar">
- <Button variant="ghost" size="large" class="group/sidebar-toggle" onClick={layout.sidebar.toggle}>
- <div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
- <Icon name="layout-right" size="small" class="group-hover/sidebar-toggle:hidden" />
- <Icon
- name="layout-right-partial"
- size="small"
- class="hidden group-hover/sidebar-toggle:inline-block"
- />
- <Icon
- name="layout-right-full"
- size="small"
- class="hidden group-active/sidebar-toggle:inline-block"
- />
- </div>
- </Button>
- </Tooltip>
- </Match>
- </Switch>
- <div class="w-full px-3">
- <Button as={A} href="/session" class="hidden @[4rem]:flex w-full" size="large" icon="edit-small-2">
- New Session
+ </Show>
</Button>
- <Tooltip placement="right" value="New session">
- <IconButton as={A} href="/session" icon="edit-small-2" size="large" class="@[4rem]:hidden" />
- </Tooltip>
- </div>
- <div class="hidden @[4rem]:flex size-full overflow-y-auto no-scrollbar flex-col flex-1 px-3">
- <nav class="w-full">
- <For each={sync.data.session}>
- {(session) => {
- const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
+ </Tooltip>
+ <div class="flex flex-col justify-center items-start gap-4 self-stretch min-h-0">
+ <div class="hidden @[4rem]:flex size-full flex-col grow overflow-y-auto no-scrollbar">
+ <For each={layout.projects.list()}>
+ {(project) => {
+ const [store] = globalSync.child(project.directory)
+ const slug = createMemo(() => base64Encode(project.directory))
return (
- <A
- data-active={session.id === params.id}
- href={`/session/${session.id}`}
- class="group/session focus:outline-none cursor-default"
- >
- <Tooltip placement="right" value={session.title}>
- <div
- class="w-full mb-1.5 px-3 py-1 rounded-md
- group-data-[active=true]/session:bg-surface-raised-base-hover
- group-hover/session:bg-surface-raised-base-hover
- group-focus/session:bg-surface-raised-base-hover"
- >
- <div class="flex items-center self-stretch gap-6 justify-between">
- <span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
- {session.title}
- </span>
- <span class="text-12-regular text-text-weak text-right whitespace-nowrap">
- {Math.abs(updated().diffNow().as("seconds")) < 60
- ? "Now"
- : updated()
- .toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
- ?.replace(" ago", "")
- ?.replace(/ days?/, "d")
- ?.replace(" min.", "m")
- ?.replace(" hr.", "h")}
- </span>
- </div>
- <div class="flex justify-between items-center self-stretch">
- <span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
- <Show when={session.summary}>{(summary) => <DiffChanges changes={summary()} />}</Show>
- </div>
- </div>
- </Tooltip>
- </A>
+ <Collapsible variant="ghost" defaultOpen class="gap-2">
+ <Button
+ as={"div"}
+ variant="ghost"
+ class="flex items-center justify-between gap-3 w-full h-8 pl-2 pr-2.25 self-stretch"
+ >
+ <Collapsible.Trigger class="p-0 text-left text-14-medium text-text-strong grow min-w-0 truncate">
+ {getFilename(project.directory)}
+ </Collapsible.Trigger>
+ <IconButton as={A} href={`${slug()}/session`} icon="plus-small" size="normal" />
+ </Button>
+ <Collapsible.Content>
+ <nav class="w-full flex flex-col gap-1.5">
+ <For each={store.session}>
+ {(session) => {
+ const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
+ return (
+ <A
+ data-active={session.id === params.id}
+ href={`${slug()}/session/${session.id}`}
+ class="group/session focus:outline-none cursor-default"
+ >
+ <Tooltip placement="right" value={session.title}>
+ <div
+ class="w-full px-2 py-1 rounded-md
+ group-data-[active=true]/session:bg-surface-raised-base-hover
+ group-hover/session:bg-surface-raised-base-hover
+ group-focus/session:bg-surface-raised-base-hover"
+ >
+ <div class="flex items-center self-stretch gap-6 justify-between">
+ <span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
+ {session.title}
+ </span>
+ <span class="text-12-regular text-text-weak text-right whitespace-nowrap">
+ {Math.abs(updated().diffNow().as("seconds")) < 60
+ ? "Now"
+ : updated()
+ .toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
+ ?.replace(" ago", "")
+ ?.replace(/ days?/, "d")
+ ?.replace(" min.", "m")
+ ?.replace(" hr.", "h")}
+ </span>
+ </div>
+ <div class="hidden flex justify-between items-center self-stretch">
+ <span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
+ <Show when={session.summary}>
+ {(summary) => <DiffChanges changes={summary()} />}
+ </Show>
+ </div>
+ </div>
+ </Tooltip>
+ </A>
+ )
+ }}
+ </For>
+ </nav>
+ {/* <Show when={sync.session.more()}> */}
+ {/* <button */}
+ {/* class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong" */}
+ {/* onClick={() => sync.session.fetch()} */}
+ {/* > */}
+ {/* Show more */}
+ {/* </button> */}
+ {/* </Show> */}
+ </Collapsible.Content>
+ </Collapsible>
)
}}
</For>
- </nav>
- <Show when={sync.session.more()}>
- <button
- class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong"
- onClick={() => sync.session.fetch()}
- >
- Show more
- </button>
- </Show>
+ </div>
</div>
</div>
- <div class="flex flex-col items-start shrink-0 px-3 py-1 mx-auto @[4rem]:mx-0">
- <Button
- as={"a"}
- href="https://opencode.ai/desktop-feedback"
- target="_blank"
- class="hidden @[4rem]:flex w-full text-12-medium text-text-base stroke-[1.5px]"
- variant="ghost"
- icon="speech-bubble"
- >
- Share feedback
- </Button>
- <Tooltip placement="right" value="Share feedback">
- <IconButton
+ <div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
+ <Tooltip placement="right" value="Open project" inactive={layout.sidebar.opened()}>
+ <Button
+ disabled
+ class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
+ variant="ghost"
+ size="large"
+ icon="folder-add-left"
+ onClick={handleOpenProject}
+ >
+ <Show when={layout.sidebar.opened()}>Open project</Show>
+ </Button>
+ </Tooltip>
+ <Tooltip placement="right" value="Settings" inactive={layout.sidebar.opened()}>
+ <Button
+ disabled
+ class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
+ variant="ghost"
+ size="large"
+ icon="settings-gear"
+ >
+ <Show when={layout.sidebar.opened()}>Settings</Show>
+ </Button>
+ </Tooltip>
+ <Tooltip placement="right" value="Share feedback" inactive={layout.sidebar.opened()}>
+ <Button
as={"a"}
href="https://opencode.ai/desktop-feedback"
target="_blank"
- icon="speech-bubble"
+ class="flex w-full text-left justify-start text-12-medium text-text-base stroke-[1.5px]"
variant="ghost"
size="large"
- class="@[4rem]:hidden stroke-[1.5px]"
- />
+ icon="bubble-5"
+ >
+ <Show when={layout.sidebar.opened()}>Share feedback</Show>
+ </Button>
</Tooltip>
</div>
</div>