Adam 2 месяцев назад
Родитель
Сommit
2dad56c9a2
1 измененных файлов с 89 добавлено и 31 удалено
  1. 89 31
      packages/desktop/src/pages/session.tsx

+ 89 - 31
packages/desktop/src/pages/session.tsx

@@ -28,7 +28,7 @@ import {
 import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
 import type { JSX } from "solid-js"
 import { useSync } from "@/context/sync"
-import { useSession } from "@/context/session"
+import { useSession, type LocalPTY } from "@/context/session"
 import { useLayout } from "@/context/layout"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { Terminal } from "@/components/terminal"
@@ -43,6 +43,7 @@ export default function Page() {
     clickTimer: undefined as number | undefined,
     fileSelectOpen: false,
     activeDraggable: undefined as string | undefined,
+    activeTerminalDraggable: undefined as string | undefined,
   })
   let inputRef!: HTMLDivElement
 
@@ -178,6 +179,49 @@ export default function Page() {
     setStore("activeDraggable", undefined)
   }
 
+  const handleTerminalDragStart = (event: unknown) => {
+    const id = getDraggableId(event)
+    if (!id) return
+    setStore("activeTerminalDraggable", id)
+  }
+
+  const handleTerminalDragOver = (event: DragEvent) => {
+    const { draggable, droppable } = event
+    if (draggable && droppable) {
+      const terminals = session.terminal.all()
+      const fromIndex = terminals.findIndex((t) => t.id === draggable.id.toString())
+      const toIndex = terminals.findIndex((t) => t.id === droppable.id.toString())
+      if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) {
+        session.terminal.move(draggable.id.toString(), toIndex)
+      }
+    }
+  }
+
+  const handleTerminalDragEnd = () => {
+    setStore("activeTerminalDraggable", undefined)
+  }
+
+  const SortableTerminalTab = (props: { terminal: LocalPTY }): JSX.Element => {
+    const sortable = createSortable(props.terminal.id)
+    return (
+      // @ts-ignore
+      <div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
+        <div class="relative h-full">
+          <Tabs.Trigger
+            value={props.terminal.id}
+            closeButton={
+              session.terminal.all().length > 1 && (
+                <IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(props.terminal.id)} />
+              )
+            }
+          >
+            {props.terminal.title}
+          </Tabs.Trigger>
+        </div>
+      </div>
+    )
+  }
+
   const FileVisual = (props: { file: LocalFile; active?: boolean }): JSX.Element => {
     return (
       <div class="flex items-center gap-x-1.5">
@@ -618,40 +662,54 @@ export default function Page() {
             onResize={layout.terminal.resize}
             onCollapse={layout.terminal.close}
           />
-          <Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
-            <Tabs.List class="h-10">
+          <DragDropProvider
+            onDragStart={handleTerminalDragStart}
+            onDragEnd={handleTerminalDragEnd}
+            onDragOver={handleTerminalDragOver}
+            collisionDetector={closestCenter}
+          >
+            <DragDropSensors />
+            <ConstrainDragYAxis />
+            <Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
+              <Tabs.List class="h-10">
+                <SortableProvider ids={session.terminal.all().map((t) => t.id)}>
+                  <For each={session.terminal.all()}>{(terminal) => <SortableTerminalTab terminal={terminal} />}</For>
+                </SortableProvider>
+                <div class="h-full flex items-center justify-center">
+                  <Tooltip value="Open file" class="flex items-center">
+                    <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
+                  </Tooltip>
+                </div>
+              </Tabs.List>
               <For each={session.terminal.all()}>
                 {(terminal) => (
-                  <Tabs.Trigger
-                    value={terminal.id}
-                    closeButton={
-                      session.terminal.all().length > 1 && (
-                        <IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(terminal.id)} />
-                      )
-                    }
-                  >
-                    {terminal.title}
-                  </Tabs.Trigger>
+                  <Tabs.Content value={terminal.id}>
+                    <Terminal
+                      pty={terminal}
+                      onCleanup={session.terminal.update}
+                      onConnectError={() => session.terminal.clone(terminal.id)}
+                    />
+                  </Tabs.Content>
                 )}
               </For>
-              <div class="h-full flex items-center justify-center">
-                <Tooltip value="Open file" class="flex items-center">
-                  <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
-                </Tooltip>
-              </div>
-            </Tabs.List>
-            <For each={session.terminal.all()}>
-              {(terminal) => (
-                <Tabs.Content value={terminal.id}>
-                  <Terminal
-                    pty={terminal}
-                    onCleanup={session.terminal.update}
-                    onConnectError={() => session.terminal.clone(terminal.id)}
-                  />
-                </Tabs.Content>
-              )}
-            </For>
-          </Tabs>
+            </Tabs>
+            <DragOverlay>
+              <Show when={store.activeTerminalDraggable}>
+                {(draggedId) => {
+                  const terminal = createMemo(() => session.terminal.all().find((t) => t.id === draggedId()))
+                  return (
+                    <Show when={terminal()}>
+                      {(t) => (
+                        <div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
+                          {t().title}
+                        </div>
+                      )}
+                    </Show>
+                  )
+                }}
+              </Show>
+            </DragOverlay>
+          </DragDropProvider>
         </div>
       </Show>
     </div>