|
|
@@ -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>
|