From 3eb2db98ed0a9c266e1bf00544e460cb0633b368 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:27:31 -0500 Subject: wip: desktop work --- packages/desktop/src/pages/index.tsx | 372 ++++++++++++++++++++++++++++++++--- 1 file changed, 349 insertions(+), 23 deletions(-) (limited to 'packages/desktop/src/pages') diff --git a/packages/desktop/src/pages/index.tsx b/packages/desktop/src/pages/index.tsx index 2ddf7c18a..e7f94ad8d 100644 --- a/packages/desktop/src/pages/index.tsx +++ b/packages/desktop/src/pages/index.tsx @@ -1,15 +1,26 @@ -import { Button, Icon, List, SelectDialog, Tooltip } from "@opencode-ai/ui" +import { Button, List, SelectDialog, Tooltip, IconButton, Tabs } from "@opencode-ai/ui" import { FileIcon } from "@/ui" import FileTree from "@/components/file-tree" -import EditorPane from "@/components/editor-pane" -import { For, onCleanup, onMount, Show } from "solid-js" -import { useSync, useSDK, useLocal } from "@/context" -import type { LocalFile, TextSelection } from "@/context/local" -import SessionTimeline from "@/components/session-timeline" +import { For, onCleanup, onMount, Show, Match, Switch, createSignal, createEffect } from "solid-js" +import { useLocal, type LocalFile, type TextSelection } from "@/context/local" import { createStore } from "solid-js/store" import { getDirectory, getFilename } from "@/utils" import { ContentPart, PromptInput } from "@/components/prompt-input" import { DateTime } from "luxon" +import { + DragDropProvider, + DragDropSensors, + DragOverlay, + SortableProvider, + closestCenter, + createSortable, + useDragDropContext, +} from "@thisbeyond/solid-dnd" +import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd" +import type { JSX } from "solid-js" +import { Code } from "@/components/code" +import { useSync } from "@/context/sync" +import { useSDK } from "@/context/sdk" export default function Page() { const local = useLocal() @@ -17,10 +28,18 @@ export default function Page() { const sdk = useSDK() const [store, setStore] = createStore({ clickTimer: undefined as number | undefined, - modelSelectOpen: false, fileSelectOpen: false, }) let inputRef!: HTMLDivElement + let messageScrollElement!: HTMLDivElement + const [activeItem, setActiveItem] = createSignal(undefined) + + createEffect(() => { + if (!local.session.activeMessage()) return + if (!messageScrollElement) return + const element = messageScrollElement.querySelector(`[data-message="${local.session.activeMessage()?.id}"]`) + element?.scrollIntoView({ block: "start", behavior: "instant" }) + }) const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control" @@ -101,11 +120,50 @@ export default function Page() { } } + const navigateChange = (dir: 1 | -1) => { + const active = local.file.active() + if (!active) return + const current = local.file.changeIndex(active.path) + const next = current === undefined ? (dir === 1 ? 0 : -1) : current + dir + local.file.setChangeIndex(active.path, next) + } + + const handleTabChange = (path: string) => { + if (path === "chat" || path === "review") return + local.file.open(path) + } + + const handleTabClose = (file: LocalFile) => { + local.file.close(file.path) + } + + const handleDragStart = (event: unknown) => { + const id = getDraggableId(event) + if (!id) return + setActiveItem(id) + } + + const handleDragOver = (event: DragEvent) => { + const { draggable, droppable } = event + if (draggable && droppable) { + const currentFiles = local.file.opened().map((file) => file.path) + const fromIndex = currentFiles.indexOf(draggable.id.toString()) + const toIndex = currentFiles.indexOf(droppable.id.toString()) + if (fromIndex !== toIndex) { + local.file.move(draggable.id.toString(), toIndex) + } + } + } + + const handleDragEnd = () => { + setActiveItem(undefined) + } + const handlePromptSubmit = async (parts: ContentPart[]) => { const existingSession = local.session.active() let session = existingSession if (!session) { - const created = await sdk.session.create() + const created = await sdk.client.session.create() session = created.data ?? undefined } if (!session) return @@ -187,7 +245,7 @@ export default function Page() { } }) - await sdk.session.prompt({ + await sdk.client.session.prompt({ path: { id: session.id }, body: { agent: local.agent.current()!.name, @@ -211,6 +269,93 @@ export default function Page() { inputRef?.focus() } + const TabVisual = (props: { file: LocalFile }): JSX.Element => { + return ( +
+ + + {props.file.name} + + +
+ ) + } + + const SortableTab = (props: { + file: LocalFile + onTabClick: (file: LocalFile) => void + onTabClose: (file: LocalFile) => void + }): JSX.Element => { + const sortable = createSortable(props.file.path) + + return ( + // @ts-ignore +
+ +
+ props.onTabClick(props.file)}> + + + props.onTabClose(props.file)} + /> +
+
+
+ ) + } + + const ConstrainDragYAxis = (): JSX.Element => { + const context = useDragDropContext() + if (!context) return <> + const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context + const transformer: Transformer = { + id: "constrain-y-axis", + order: 100, + callback: (transform) => ({ ...transform, y: 0 }), + } + onDragStart((event) => { + const id = getDraggableId(event) + if (!id) return + addTransformer("draggables", id, transformer) + }) + onDragEnd((event) => { + const id = getDraggableId(event) + if (!id) return + removeTransformer("draggables", id, transformer.id) + }) + return <> + } + + const getDraggableId = (event: unknown): string | undefined => { + if (typeof event !== "object" || event === null) return undefined + if (!("draggable" in event)) return undefined + const draggable = (event as { draggable?: { id?: unknown } }).draggable + if (!draggable) return undefined + return typeof draggable.id === "string" ? draggable.id : undefined + } + return (
@@ -253,22 +398,203 @@ export default function Page() {
-
-
- - {(activeSession) => } - -
-
- - - -
+
+ + + + +
+ + +
Chat
+ +
+ {local.session.context()}% +
+
+
+ {/* Review */} + file.path)}> + + {(file) => } + + +
+ setStore("fileSelectOpen", true)} + /> +
+
+ +
+ + No active session
}> + {(activeSession) => ( +
+
+
+
    + + {(message) => ( +
  • local.session.setActiveMessage(message.id)} + > +
    + + + + + + + + + +
    +
    + {local.session.getMessageText(message)} +
    +
  • + )} +
    +
+
+
+ + {(message) => ( +
+
+
+ {local.session.getMessageText(message)} +
+
+ {message.summary?.text || + local.session.getMessageText(local.session.activeAssistantMessagesWithText())} +
+
+
+
+ )} +
+
+
+
+
+
+ )} + + + {/* */} + + {(file) => ( + + {(() => { + const view = local.file.view(file.path) + const showRaw = view === "raw" || !file.content?.diff + const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "") + return + })()} + + )} + + + + {(() => { + const id = activeItem() + if (!id) return null + const draggedFile = local.file.node(id) + if (!draggedFile) return null + return ( +
+ +
+ ) + })()} +
+