diff options
| author | Adam <[email protected]> | 2025-11-18 11:00:27 -0600 |
|---|---|---|
| committer | opencode <[email protected]> | 2025-11-18 17:07:34 +0000 |
| commit | fc5fc2c570e6ac5a6f9647080b80f7fec0c4b605 (patch) | |
| tree | a3971bfbe17308df9f2d3e1771c0ab544900c099 /packages/desktop/src/pages/layout.tsx | |
| parent | 4069999b782cc00d4e707f5eca32082bdfad45bc (diff) | |
| download | opencode-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.tsx | 266 |
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> |
