diff --git a/packages/app/src/components/session/index.ts b/packages/app/src/components/session/index.ts index f827ae641a..20124b6fde 100644 --- a/packages/app/src/components/session/index.ts +++ b/packages/app/src/components/session/index.ts @@ -1,3 +1,4 @@ +export { SessionHeader } from "./session-header" export { SessionContextTab } from "./session-context-tab" export { SortableTab, FileVisual } from "./session-sortable-tab" export { SortableTerminalTab } from "./session-sortable-terminal-tab" diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx new file mode 100644 index 0000000000..7cadc8e600 --- /dev/null +++ b/packages/app/src/components/session/session-header.tsx @@ -0,0 +1,133 @@ +import { createMemo, Show } from "solid-js" +import { A, useNavigate, useParams } from "@solidjs/router" +import { useLayout } from "@/context/layout" +import { useCommand } from "@/context/command" +import { useSync } from "@/context/sync" +import { getFilename } from "@opencode-ai/util/path" +import { base64Decode, base64Encode } from "@opencode-ai/util/encode" +import { Icon } from "@opencode-ai/ui/icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" +import { Select } from "@opencode-ai/ui/select" +import type { Session } from "@opencode-ai/sdk/v2/client" +import { same } from "@/utils/same" + +export function SessionHeader() { + const layout = useLayout() + const params = useParams() + const navigate = useNavigate() + const command = useCommand() + const sync = useSync() + + const projectDirectory = createMemo(() => base64Decode(params.dir ?? "")) + + const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID)) + const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id)) + const parentSession = createMemo(() => { + const current = currentSession() + if (!current?.parentID) return undefined + return sync.data.session.find((s) => s.id === current.parentID) + }) + const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same }) + + function navigateToProject(directory: string) { + navigate(`/${base64Encode(directory)}`) + } + + function navigateToSession(session: Session | undefined) { + if (!session) return + // Only navigate if we're actually changing to a different session + if (session.id === params.id) return + navigate(`/${params.dir}/session/${session.id}`) + } + + return ( + + + + + + + + + getFilename(x)} + onSelect={(x) => (x ? navigateToProject(x) : undefined)} + class="text-14-regular text-text-base" + variant="ghost" + > + {/* @ts-ignore */} + {(i) => ( + + + {getFilename(i)} + + )} + + / + + + x.title} + value={(x) => x.id} + onSelect={navigateToSession} + class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md" + variant="ghost" + /> + > + } + > + + x.title} + value={(x) => x.id} + onSelect={(session) => { + // Only navigate if selecting a different session than current parent + const currentParent = parentSession() + if (session && currentParent && session.id !== currentParent.id) { + navigateToSession(session) + } + }} + class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md" + variant="ghost" + /> + / + + + navigateToSession(parentSession())} + > + + + + + + + + + + + + + + + + ) +} diff --git a/packages/app/src/components/toolbar/index.tsx b/packages/app/src/components/toolbar/index.tsx index dd7b649452..b16e8e001b 100644 --- a/packages/app/src/components/toolbar/index.tsx +++ b/packages/app/src/components/toolbar/index.tsx @@ -8,7 +8,6 @@ import { usePlatform } from "@/context/platform" const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) -// ID for the portal mount target export const TOOLBAR_PORTAL_ID = "toolbar-content-portal" export const Toolbar: Component> = ({ class: className, ...props }) => { @@ -21,7 +20,7 @@ export const Toolbar: Component> = ({ class: className, .. classList={{ "pl-[80px]": IS_MAC && platform.platform !== "web", "pl-2": !IS_MAC || platform.platform === "web", - "py-2 mx-px bg-background-base border-b border-border-weak-base flex items-center justify-between w-full border-box relative": true, + "py-2 min-h-[41px] mx-px bg-background-base border-b border-border-weak-base flex items-center justify-between w-full border-box relative": true, ...(className ? { [className]: true } : {}), }} data-tauri-drag-region @@ -59,21 +58,9 @@ export const Toolbar: Component> = ({ class: className, .. - - - - - - ) } -// Re-export for use in DirectoryLayout export { ToolbarSession } from "./session" diff --git a/packages/app/src/components/toolbar/session.tsx b/packages/app/src/components/toolbar/session.tsx index 6134552fb3..442e78679a 100644 --- a/packages/app/src/components/toolbar/session.tsx +++ b/packages/app/src/components/toolbar/session.tsx @@ -1,256 +1,147 @@ -import { ComponentProps, createMemo, createResource, Show, Component } from "solid-js" -import { A, useNavigate, useParams } from "@solidjs/router" +import { createMemo, createResource, Show, Component, type ComponentProps } from "solid-js" +import { useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" import { useCommand } from "@/context/command" import { useServer } from "@/context/server" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useSync } from "@/context/sync" import { useGlobalSDK } from "@/context/global-sdk" -import { getFilename } from "@opencode-ai/util/path" -import { base64Decode, base64Encode } from "@opencode-ai/util/encode" +import { base64Decode } from "@opencode-ai/util/encode" import { iife } from "@opencode-ai/util/iife" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Button } from "@opencode-ai/ui/button" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" -import { Select } from "@opencode-ai/ui/select" import { Popover } from "@opencode-ai/ui/popover" import { TextField } from "@opencode-ai/ui/text-field" import { DialogSelectServer } from "@/components/dialog-select-server" import { SessionLspIndicator } from "@/components/session-lsp-indicator" import { SessionMcpIndicator } from "@/components/session-mcp-indicator" -import type { Session } from "@opencode-ai/sdk/v2/client" -import { same } from "@/utils/same" -export const ToolbarSession: Component> = ({ class: className, ...props }) => { +export const ToolbarSession: Component> = ({ class: className, ...props }) => { const globalSDK = useGlobalSDK() const layout = useLayout() const params = useParams() - const navigate = useNavigate() const command = useCommand() const server = useServer() const dialog = useDialog() const sync = useSync() const projectDirectory = createMemo(() => base64Decode(params.dir ?? "")) - - const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID)) const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id)) - const parentSession = createMemo(() => { - const current = currentSession() - if (!current?.parentID) return undefined - return sync.data.session.find((s) => s.id === current.parentID) - }) const shareEnabled = createMemo(() => sync.data.config.share !== "disabled") - const worktrees = createMemo(() => layout.projects.list().map((p) => p.worktree), [], { equals: same }) - - function navigateToProject(directory: string) { - navigate(`/${base64Encode(directory)}`) - } - - function navigateToSession(session: Session | undefined) { - if (!session) return - // Only navigate if we're actually changing to a different session - if (session.id === params.id) return - navigate(`/${params.dir}/session/${session.id}`) - } return ( - - - - - - getFilename(x)} - onSelect={(x) => (x ? navigateToProject(x) : undefined)} - class="text-14-regular text-text-base" - variant="ghost" - > - {/* @ts-ignore */} - {(i) => ( - - - {getFilename(i)} - - )} - - / - - - x.title} - value={(x) => x.id} - onSelect={navigateToSession} - class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md" - variant="ghost" - /> - > - } - > - - x.title} - value={(x) => x.id} - onSelect={(session) => { - // Only navigate if selecting a different session than current parent - const currentParent = parentSession() - if (session && currentParent && session.id !== currentParent.id) { - navigateToSession(session) - } - }} - class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md" - variant="ghost" - /> - / - - - navigateToSession(parentSession())} - > - - - - - - - - - - - - - - - - - - { - dialog.show(() => ) + + + { + dialog.show(() => ) + }} + > + - - - {server.name} - - - - - - - - - - - - - - - - + /> + + {server.name} + + + + + + - + - - - - - - } - > - {iife(() => { - const [url] = createResource( - () => currentSession(), - async (session) => { - if (!session) return - let shareURL = session.share?.url - if (!shareURL) { - shareURL = await globalSDK.client.session - .share({ sessionID: session.id, directory: projectDirectory() }) - .then((r) => r.data?.share?.url) - .catch((e) => { - console.error("Failed to share session", e) - return undefined - }) - } - return shareURL - }, - { initialValue: "" }, - ) - return ( - - {(shareUrl) => } - - ) - })} - + + + + + + + + + - + + + + + } + > + {iife(() => { + const [url] = createResource( + () => currentSession(), + async (session) => { + if (!session) return + let shareURL = session.share?.url + if (!shareURL) { + shareURL = await globalSDK.client.session + .share({ sessionID: session.id, directory: projectDirectory() }) + .then((r) => r.data?.share?.url) + .catch((e) => { + console.error("Failed to share session", e) + return undefined + }) + } + return shareURL + }, + { initialValue: "" }, + ) + return ( + + {(shareUrl) => } + + ) + })} + + + ) } diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index f2e3953a4f..c2f76d2de7 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -40,7 +40,14 @@ import { extractPromptFromParts } from "@/utils/prompt" import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" import { usePermission } from "@/context/permission" import { showToast } from "@opencode-ai/ui/toast" -import { SessionContextTab, SortableTab, FileVisual, SortableTerminalTab, NewSessionView } from "@/components/session" +import { + SessionHeader, + SessionContextTab, + SortableTab, + FileVisual, + SortableTerminalTab, + NewSessionView, +} from "@/components/session" import { usePlatform } from "@/context/platform" import { same } from "@/utils/same" @@ -769,6 +776,7 @@ export default function Page() { return ( + {/* Mobile tab bar - only shown on mobile when there are diffs */} 0}>