Răsfoiți Sursa

chore: cleanup

Adam 2 luni în urmă
părinte
comite
9629f7464b

+ 113 - 0
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>
+  )
+}

+ 34 - 28
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
           }

+ 7 - 94
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={{