diff options
| author | Adam <[email protected]> | 2026-02-12 09:49:14 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-12 09:49:14 -0600 |
| commit | ff4414bb152acfddb5c0eb073c38bedc1df4ae14 (patch) | |
| tree | 78381c67d21ef6f089647f6b19e7aa2976840dbc /packages/app/src/pages/layout/sidebar-items.tsx | |
| parent | 56ad2db02055955f926fda0e4a89055b22ead6f9 (diff) | |
| download | opencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.tar.gz opencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.zip | |
chore: refactor packages/app files (#13236)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Frank <[email protected]>
Diffstat (limited to 'packages/app/src/pages/layout/sidebar-items.tsx')
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-items.tsx | 238 |
1 files changed, 152 insertions, 86 deletions
diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 678bfa0d8..d55090370 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -13,7 +13,7 @@ import { MessageNav } from "@opencode-ai/ui/message-nav" import { Spinner } from "@opencode-ai/ui/spinner" import { Tooltip } from "@opencode-ai/ui/tooltip" import { getFilename } from "@opencode-ai/util/path" -import { type Message, type Session, type TextPart } from "@opencode-ai/sdk/v2/client" +import { type Message, type Session, type TextPart, type UserMessage } from "@opencode-ai/sdk/v2/client" import { For, Match, Show, Switch, createMemo, onCleanup, type Accessor, type JSX } from "solid-js" import { agentColor } from "@/utils/agent" @@ -70,6 +70,116 @@ export type SessionItemProps = { archiveSession: (session: Session) => Promise<void> } +const SessionRow = (props: { + session: Session + slug: string + mobile?: boolean + dense?: boolean + tint: Accessor<string | undefined> + isWorking: Accessor<boolean> + hasPermissions: Accessor<boolean> + hasError: Accessor<boolean> + unseenCount: Accessor<number> + setHoverSession: (id: string | undefined) => void + clearHoverProjectSoon: () => void + sidebarOpened: Accessor<boolean> + prefetchSession: (session: Session, priority?: "high" | "low") => void + scheduleHoverPrefetch: () => void + cancelHoverPrefetch: () => void +}): JSX.Element => ( + <A + href={`/${props.slug}/session/${props.session.id}`} + class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`} + onPointerEnter={props.scheduleHoverPrefetch} + onPointerLeave={props.cancelHoverPrefetch} + onMouseEnter={props.scheduleHoverPrefetch} + onMouseLeave={props.cancelHoverPrefetch} + onFocus={() => props.prefetchSession(props.session, "high")} + onClick={() => { + props.setHoverSession(undefined) + if (props.sidebarOpened()) return + props.clearHoverProjectSoon() + }} + > + <div class="flex items-center gap-1 w-full"> + <div + class="shrink-0 size-6 flex items-center justify-center" + style={{ color: props.tint() ?? "var(--icon-interactive-base)" }} + > + <Switch fallback={<Icon name="dash" size="small" class="text-icon-weak" />}> + <Match when={props.isWorking()}> + <Spinner class="size-[15px]" /> + </Match> + <Match when={props.hasPermissions()}> + <div class="size-1.5 rounded-full bg-surface-warning-strong" /> + </Match> + <Match when={props.hasError()}> + <div class="size-1.5 rounded-full bg-text-diff-delete-base" /> + </Match> + <Match when={props.unseenCount() > 0}> + <div class="size-1.5 rounded-full bg-text-interactive-base" /> + </Match> + </Switch> + </div> + <span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"> + {props.session.title} + </span> + <Show when={props.session.summary}> + {(summary) => ( + <div class="group-hover/session:hidden group-active/session:hidden group-focus-within/session:hidden"> + <DiffChanges changes={summary()} /> + </div> + )} + </Show> + </div> + </A> +) + +const SessionHoverPreview = (props: { + mobile?: boolean + nav: Accessor<HTMLElement | undefined> + hoverSession: Accessor<string | undefined> + session: Session + sidebarHovering: Accessor<boolean> + hoverReady: Accessor<boolean> + hoverMessages: Accessor<UserMessage[] | undefined> + language: ReturnType<typeof useLanguage> + isActive: Accessor<boolean> + slug: string + setHoverSession: (id: string | undefined) => void + messageLabel: (message: Message) => string | undefined + onMessageSelect: (message: Message) => void + trigger: JSX.Element +}): JSX.Element => ( + <HoverCard + openDelay={1000} + closeDelay={props.sidebarHovering() ? 600 : 0} + placement="right-start" + gutter={16} + shift={-2} + trigger={props.trigger} + mount={!props.mobile ? props.nav() : undefined} + open={props.hoverSession() === props.session.id} + onOpenChange={(open) => props.setHoverSession(open ? props.session.id : undefined)} + > + <Show + when={props.hoverReady()} + fallback={<div class="text-12-regular text-text-weak">{props.language.t("session.messages.loading")}</div>} + > + <div class="overflow-y-auto max-h-72 h-full"> + <MessageNav + messages={props.hoverMessages() ?? []} + current={undefined} + getLabel={props.messageLabel} + onMessageSelect={props.onMessageSelect} + size="normal" + class="w-60" + /> + </div> + </Show> + </HoverCard> +) + export const SessionItem = (props: SessionItemProps): JSX.Element => { const params = useParams() const navigate = useNavigate() @@ -113,7 +223,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { }) const hoverMessages = createMemo(() => - sessionStore.message[props.session.id]?.filter((message) => message.role === "user"), + sessionStore.message[props.session.id]?.filter((message): message is UserMessage => message.role === "user"), ) const hoverReady = createMemo(() => sessionStore.message[props.session.id] !== undefined) const hoverAllowed = createMemo(() => !props.mobile && props.sidebarExpanded()) @@ -141,54 +251,24 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { const text = parts.find((part): part is TextPart => part?.type === "text" && !part.synthetic && !part.ignored) return text?.text } - const item = ( - <A - href={`/${props.slug}/session/${props.session.id}`} - class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`} - onPointerEnter={scheduleHoverPrefetch} - onPointerLeave={cancelHoverPrefetch} - onMouseEnter={scheduleHoverPrefetch} - onMouseLeave={cancelHoverPrefetch} - onFocus={() => props.prefetchSession(props.session, "high")} - onClick={() => { - props.setHoverSession(undefined) - if (layout.sidebar.opened()) return - props.clearHoverProjectSoon() - }} - > - <div class="flex items-center gap-1 w-full"> - <div - class="shrink-0 size-6 flex items-center justify-center" - style={{ color: tint() ?? "var(--icon-interactive-base)" }} - > - <Switch fallback={<Icon name="dash" size="small" class="text-icon-weak" />}> - <Match when={isWorking()}> - <Spinner class="size-[15px]" /> - </Match> - <Match when={hasPermissions()}> - <div class="size-1.5 rounded-full bg-surface-warning-strong" /> - </Match> - <Match when={hasError()}> - <div class="size-1.5 rounded-full bg-text-diff-delete-base" /> - </Match> - <Match when={unseenCount() > 0}> - <div class="size-1.5 rounded-full bg-text-interactive-base" /> - </Match> - </Switch> - </div> - <span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"> - {props.session.title} - </span> - <Show when={props.session.summary}> - {(summary) => ( - <div class="group-hover/session:hidden group-active/session:hidden group-focus-within/session:hidden"> - <DiffChanges changes={summary()} /> - </div> - )} - </Show> - </div> - </A> + <SessionRow + session={props.session} + slug={props.slug} + mobile={props.mobile} + dense={props.dense} + tint={tint} + isWorking={isWorking} + hasPermissions={hasPermissions} + hasError={hasError} + unseenCount={unseenCount} + setHoverSession={props.setHoverSession} + clearHoverProjectSoon={props.clearHoverProjectSoon} + sidebarOpened={layout.sidebar.opened} + prefetchSession={props.prefetchSession} + scheduleHoverPrefetch={scheduleHoverPrefetch} + cancelHoverPrefetch={cancelHoverPrefetch} + /> ) return ( @@ -205,44 +285,30 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { </Tooltip> } > - <HoverCard - openDelay={1000} - closeDelay={props.sidebarHovering() ? 600 : 0} - placement="right-start" - gutter={16} - shift={-2} + <SessionHoverPreview + mobile={props.mobile} + nav={props.nav} + hoverSession={props.hoverSession} + session={props.session} + sidebarHovering={props.sidebarHovering} + hoverReady={hoverReady} + hoverMessages={hoverMessages} + language={language} + isActive={isActive} + slug={props.slug} + setHoverSession={props.setHoverSession} + messageLabel={messageLabel} + onMessageSelect={(message) => { + if (!isActive()) { + layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id) + navigate(`${props.slug}/session/${props.session.id}`) + return + } + window.history.replaceState(null, "", `#message-${message.id}`) + window.dispatchEvent(new HashChangeEvent("hashchange")) + }} trigger={item} - mount={!props.mobile ? props.nav() : undefined} - open={props.hoverSession() === props.session.id} - onOpenChange={(open) => props.setHoverSession(open ? props.session.id : undefined)} - > - <Show - when={hoverReady()} - fallback={<div class="text-12-regular text-text-weak">{language.t("session.messages.loading")}</div>} - > - <div class="overflow-y-auto max-h-72 h-full"> - <MessageNav - messages={hoverMessages() ?? []} - current={undefined} - getLabel={messageLabel} - onMessageSelect={(message) => { - if (!isActive()) { - layout.pendingMessage.set( - `${base64Encode(props.session.directory)}/${props.session.id}`, - message.id, - ) - navigate(`${props.slug}/session/${props.session.id}`) - return - } - window.history.replaceState(null, "", `#message-${message.id}`) - window.dispatchEvent(new HashChangeEvent("hashchange")) - }} - size="normal" - class="w-60" - /> - </div> - </Show> - </HoverCard> + /> </Show> <div class={`absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"} flex items-center gap-0.5 transition-opacity`} |
