diff options
| author | Adam <[email protected]> | 2025-12-12 10:45:02 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-12 10:45:34 -0600 |
| commit | 9629f7464b3a1a6077e7c6b04923c4c13993ac08 (patch) | |
| tree | d803cca8c47b65c199c00d68c406a80452831d10 /packages | |
| parent | 9017d103031fa252f5b3fce23df651d1b4ef5bc5 (diff) | |
| download | opencode-9629f7464b3a1a6077e7c6b04923c4c13993ac08.tar.gz opencode-9629f7464b3a1a6077e7c6b04923c4c13993ac08.zip | |
chore: cleanup
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/desktop/src/components/header.tsx | 113 | ||||
| -rw-r--r-- | packages/desktop/src/context/global-sync.tsx | 62 | ||||
| -rw-r--r-- | packages/desktop/src/pages/layout.tsx | 101 |
3 files changed, 154 insertions, 122 deletions
diff --git a/packages/desktop/src/components/header.tsx b/packages/desktop/src/components/header.tsx new file mode 100644 index 000000000..cc4d01816 --- /dev/null +++ b/packages/desktop/src/components/header.tsx @@ -0,0 +1,113 @@ +import { useGlobalSync } from "@/context/global-sync" +import { useLayout } from "@/context/layout" +import { Session } from "@opencode-ai/sdk/v2/client" +import { Button } from "@opencode-ai/ui/button" +import { Icon } from "@opencode-ai/ui/icon" +import { Mark } from "@opencode-ai/ui/logo" +import { Select } from "@opencode-ai/ui/select" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { base64Decode } from "@opencode-ai/util/encode" +import { getFilename } from "@opencode-ai/util/path" +import { A, useParams } from "@solidjs/router" +import { createMemo, Show } from "solid-js" + +export function Header(props: { + navigateToProject: (directory: string) => void + navigateToSession: (session: Session | undefined) => void +}) { + const globalSync = useGlobalSync() + const layout = useLayout() + const params = useParams() + const currentDirectory = createMemo(() => base64Decode(params.dir ?? "")) + const store = createMemo(() => globalSync.child(currentDirectory())[0]) + const sessions = createMemo(() => store().session ?? []) + const currentSession = createMemo(() => sessions().find((s) => s.id === params.id)) + + return ( + <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region> + <A + href="/" + classList={{ + "w-12 shrink-0 px-4 py-3.5": true, + "flex items-center justify-start self-stretch": true, + "border-r border-border-weak-base": true, + }} + style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }} + data-tauri-drag-region + > + <Mark class="shrink-0" /> + </A> + <div class="pl-4 px-6 flex items-center justify-between gap-4 w-full"> + <Show when={params.dir && layout.projects.list().length > 0}> + <div class="flex items-center gap-3"> + <div class="flex items-center gap-2"> + <Select + options={layout.projects.list().map((project) => project.worktree)} + current={currentDirectory()} + label={(x) => getFilename(x)} + onSelect={(x) => (x ? props.navigateToProject(x) : undefined)} + class="text-14-regular text-text-base" + variant="ghost" + > + {/* @ts-ignore */} + {(i) => ( + <div class="flex items-center gap-2"> + <Icon name="folder" size="small" /> + <div class="text-text-strong">{getFilename(i)}</div> + </div> + )} + </Select> + <div class="text-text-weaker">/</div> + <Select + options={sessions()} + current={currentSession()} + placeholder="New session" + label={(x) => x.title} + value={(x) => x.id} + onSelect={props.navigateToSession} + class="text-14-regular text-text-base max-w-md" + variant="ghost" + /> + </div> + <Show when={currentSession()}> + <Button as={A} href={`/${params.dir}/session`} icon="plus-small"> + New session + </Button> + </Show> + </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> + </Show> + </div> + </header> + ) +} diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx index 4c461a4e9..8151a2c6f 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/desktop/src/context/global-sync.tsx @@ -69,6 +69,32 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple children: {}, }) + const children: Record<string, ReturnType<typeof createStore<State>>> = {} + function child(directory: string) { + if (!children[directory]) { + setGlobalStore("children", directory, { + project: "", + provider: { all: [], connected: [], default: {} }, + config: {}, + path: { state: "", config: "", worktree: "", directory: "", home: "" }, + ready: false, + agent: [], + session: [], + session_status: {}, + session_diff: {}, + todo: {}, + limit: 5, + message: {}, + part: {}, + node: [], + changes: [], + }) + children[directory] = createStore(globalStore.children[directory]) + bootstrapInstance(directory) + } + return children[directory] + } + async function loadSessions(directory: string) { globalSDK.client.session.list({ directory }).then((x) => { const sessions = (x.data ?? []) @@ -76,7 +102,8 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple .filter((s) => !s.time.archived) .sort((a, b) => a.id.localeCompare(b.id)) .slice(0, 5) - setGlobalStore("children", directory, "session", sessions) + const [, setStore] = child(directory) + setStore("session", sessions) }) } @@ -100,32 +127,6 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple await Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true)) } - const children: Record<string, ReturnType<typeof createStore<State>>> = {} - function child(directory: string) { - if (!children[directory]) { - setGlobalStore("children", directory, { - project: "", - provider: { all: [], connected: [], default: {} }, - config: {}, - path: { state: "", config: "", worktree: "", directory: "", home: "" }, - ready: false, - agent: [], - session: [], - session_status: {}, - session_diff: {}, - todo: {}, - limit: 5, - message: {}, - part: {}, - node: [], - changes: [], - }) - children[directory] = createStore(globalStore.children[directory]) - bootstrapInstance(directory) - } - return children[directory] - } - globalSDK.event.listen((e) => { const directory = e.name const event = e.details @@ -164,7 +165,12 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple const result = Binary.search(store.session, event.properties.info.id, (s) => s.id) if (event.properties.info.time.archived) { if (result.found) { - setStore("session", (s) => s.filter((x) => x.id !== event.properties.info.id)) + setStore( + "session", + produce((draft) => { + draft.splice(result.index, 1) + }), + ) } break } diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index d27006835..41b1977c8 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -16,7 +16,6 @@ import { A, useNavigate, useParams } from "@solidjs/router" import { useLayout, getAvatarColors } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { base64Decode, base64Encode } from "@opencode-ai/util/encode" -import { Mark } from "@opencode-ai/ui/logo" import { Avatar } from "@opencode-ai/ui/avatar" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Button } from "@opencode-ai/ui/button" @@ -27,7 +26,6 @@ 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 { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Session, Project, ProviderAuthMethod, ProviderAuthAuthorization } from "@opencode-ai/sdk/v2/client" import { usePlatform } from "@/context/platform" @@ -56,6 +54,7 @@ import { useGlobalSDK } from "@/context/global-sdk" import { Spinner } from "@opencode-ai/ui/spinner" import { useNotification } from "@/context/notification" import { Binary } from "@opencode-ai/util/binary" +import { Header } from "@/components/header" export default function Layout(props: ParentProps) { const [store, setStore] = createStore({ @@ -70,9 +69,6 @@ export default function Layout(props: ParentProps) { const platform = usePlatform() const notification = useNotification() const navigate = useNavigate() - const currentDirectory = createMemo(() => base64Decode(params.dir ?? "")) - const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? []) - const currentSession = createMemo(() => sessions().find((s) => s.id === params.id)) const providers = useProviders() function navigateToProject(directory: string | undefined) { @@ -226,6 +222,7 @@ export default function Layout(props: ParentProps) { const ProjectVisual = (props: { project: Project & { expanded: boolean }; class?: string }): JSX.Element => { const name = createMemo(() => getFilename(props.project.worktree)) + const current = createMemo(() => base64Decode(params.dir ?? "")) return ( <Switch> <Match when={layout.sidebar.opened()}> @@ -246,7 +243,7 @@ export default function Layout(props: ParentProps) { variant="ghost" size="large" class="flex items-center justify-center p-0 aspect-square border-none rounded-lg" - data-selected={props.project.worktree === currentDirectory()} + data-selected={props.project.worktree === current()} onClick={() => navigateToProject(props.project.worktree)} > <ProjectAvatar project={props.project} notify /> @@ -259,10 +256,10 @@ export default function Layout(props: ParentProps) { const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => { const notification = useNotification() const sortable = createSortable(props.project.worktree) - const [projectStore, setProjectStore] = globalSync.child(props.project.worktree) - const sessions = createMemo(() => projectStore.session.filter((s) => !s.time.archived)) const slug = createMemo(() => base64Encode(props.project.worktree)) const name = createMemo(() => getFilename(props.project.worktree)) + const [store, setStore] = globalSync.child(props.project.worktree) + const sessions = createMemo(() => store.session ?? []) const [expanded, setExpanded] = createSignal(true) return ( // @ts-ignore @@ -313,7 +310,7 @@ export default function Layout(props: ParentProps) { sessionID: session.id, time: { archived: Date.now() }, }) - setProjectStore( + setStore( produce((draft) => { const match = Binary.search(draft.session, session.id, (s) => s.id) if (match.found) draft.session.splice(match.index, 1) @@ -427,91 +424,7 @@ export default function Layout(props: ParentProps) { return ( <div class="relative flex-1 flex flex-col"> - <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region> - <A - href="/" - classList={{ - "w-12 shrink-0 px-4 py-3.5": true, - "flex items-center justify-start self-stretch": true, - "border-r border-border-weak-base": true, - }} - style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }} - data-tauri-drag-region - > - <Mark class="shrink-0" /> - </A> - <div class="pl-4 px-6 flex items-center justify-between gap-4 w-full"> - <Show when={params.dir && layout.projects.list().length > 0}> - <div class="flex items-center gap-3"> - <div class="flex items-center gap-2"> - <Select - options={layout.projects.list().map((project) => project.worktree)} - current={currentDirectory()} - label={(x) => getFilename(x)} - onSelect={(x) => (x ? navigateToProject(x) : undefined)} - class="text-14-regular text-text-base" - variant="ghost" - > - {/* @ts-ignore */} - {(i) => ( - <div class="flex items-center gap-2"> - <Icon name="folder" size="small" /> - <div class="text-text-strong">{getFilename(i)}</div> - </div> - )} - </Select> - <div class="text-text-weaker">/</div> - <Select - options={sessions()} - current={currentSession()} - placeholder="New session" - label={(x) => x.title} - value={(x) => x.id} - onSelect={navigateToSession} - class="text-14-regular text-text-base max-w-md" - variant="ghost" - /> - </div> - <Show when={currentSession()}> - <Button as={A} href={`/${params.dir}/session`} icon="plus-small"> - New session - </Button> - </Show> - </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> - </Show> - </div> - </header> + <Header navigateToProject={navigateToProject} navigateToSession={navigateToSession} /> <div class="h-[calc(100%-3rem)] flex"> <div classList={{ |
