fix(app): session title turn spinner (#16764)
This commit is contained in:
@@ -8,6 +8,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
|||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||||
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
|
import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
@@ -235,6 +236,40 @@ export function MessageTimeline(props: {
|
|||||||
if (!id) return idle
|
if (!id) return idle
|
||||||
return sync.data.session_status[id] ?? idle
|
return sync.data.session_status[id] ?? idle
|
||||||
})
|
})
|
||||||
|
const working = createMemo(() => !!pending() || sessionStatus().type !== "idle")
|
||||||
|
|
||||||
|
const [slot, setSlot] = createStore({
|
||||||
|
open: false,
|
||||||
|
show: false,
|
||||||
|
fade: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
let f: number | undefined
|
||||||
|
const clear = () => {
|
||||||
|
if (f !== undefined) window.clearTimeout(f)
|
||||||
|
f = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
onCleanup(clear)
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
working,
|
||||||
|
(on, prev) => {
|
||||||
|
clear()
|
||||||
|
if (on) {
|
||||||
|
setSlot({ open: true, show: true, fade: false })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (prev) {
|
||||||
|
setSlot({ open: false, show: true, fade: true })
|
||||||
|
f = window.setTimeout(() => setSlot({ show: false, fade: false }), 260)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSlot({ open: false, show: false, fade: false })
|
||||||
|
},
|
||||||
|
{ defer: true },
|
||||||
|
),
|
||||||
|
)
|
||||||
const activeMessageID = createMemo(() => {
|
const activeMessageID = createMemo(() => {
|
||||||
const parentID = pending()?.parentID
|
const parentID = pending()?.parentID
|
||||||
if (parentID) {
|
if (parentID) {
|
||||||
@@ -573,43 +608,64 @@ export function MessageTimeline(props: {
|
|||||||
aria-label={language.t("common.goBack")}
|
aria-label={language.t("common.goBack")}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={titleValue() || title.editing}>
|
<div class="flex items-center min-w-0 grow-1">
|
||||||
<Show
|
<div
|
||||||
when={title.editing}
|
class="shrink-0 flex items-center justify-center overflow-hidden transition-[width,margin] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]"
|
||||||
fallback={
|
style={{
|
||||||
<h1
|
width: slot.open ? "16px" : "0px",
|
||||||
class="text-14-medium text-text-strong truncate grow-1 min-w-0 pl-2"
|
"margin-right": slot.open ? "8px" : "0px",
|
||||||
onDblClick={openTitleEditor}
|
}}
|
||||||
>
|
aria-hidden="true"
|
||||||
{titleValue()}
|
|
||||||
</h1>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<InlineInput
|
<Show when={slot.show}>
|
||||||
ref={(el) => {
|
<div
|
||||||
titleRef = el
|
class="transition-opacity duration-200 ease-out"
|
||||||
}}
|
classList={{
|
||||||
value={title.draft}
|
"opacity-0": slot.fade,
|
||||||
disabled={title.saving}
|
}}
|
||||||
class="text-14-medium text-text-strong grow-1 min-w-0 pl-2 rounded-[6px]"
|
>
|
||||||
style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }}
|
<Spinner class="size-4" style={{ color: "var(--icon-interactive-base)" }} />
|
||||||
onInput={(event) => setTitle("draft", event.currentTarget.value)}
|
</div>
|
||||||
onKeyDown={(event) => {
|
</Show>
|
||||||
event.stopPropagation()
|
</div>
|
||||||
if (event.key === "Enter") {
|
<Show when={titleValue() || title.editing}>
|
||||||
event.preventDefault()
|
<Show
|
||||||
void saveTitleEditor()
|
when={title.editing}
|
||||||
return
|
fallback={
|
||||||
}
|
<h1
|
||||||
if (event.key === "Escape") {
|
class="text-14-medium text-text-strong truncate grow-1 min-w-0"
|
||||||
event.preventDefault()
|
onDblClick={openTitleEditor}
|
||||||
closeTitleEditor()
|
>
|
||||||
}
|
{titleValue()}
|
||||||
}}
|
</h1>
|
||||||
onBlur={closeTitleEditor}
|
}
|
||||||
/>
|
>
|
||||||
|
<InlineInput
|
||||||
|
ref={(el) => {
|
||||||
|
titleRef = el
|
||||||
|
}}
|
||||||
|
value={title.draft}
|
||||||
|
disabled={title.saving}
|
||||||
|
class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px]"
|
||||||
|
style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }}
|
||||||
|
onInput={(event) => setTitle("draft", event.currentTarget.value)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault()
|
||||||
|
void saveTitleEditor()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
event.preventDefault()
|
||||||
|
closeTitleEditor()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={closeTitleEditor}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={sessionID()}>
|
<Show when={sessionID()}>
|
||||||
{(id) => (
|
{(id) => (
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export function Spinner(props: {
|
|||||||
animation: square.corner
|
animation: square.corner
|
||||||
? undefined
|
? undefined
|
||||||
: `${square.outer ? "pulse-opacity-dim" : "pulse-opacity"} ${square.duration}s ease-in-out infinite`,
|
: `${square.outer ? "pulse-opacity-dim" : "pulse-opacity"} ${square.duration}s ease-in-out infinite`,
|
||||||
|
"animation-fill-mode": square.corner ? undefined : "both",
|
||||||
"animation-delay": square.corner ? undefined : `${square.delay}s`,
|
"animation-delay": square.corner ? undefined : `${square.delay}s`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,10 +26,10 @@
|
|||||||
@keyframes pulse-opacity-dim {
|
@keyframes pulse-opacity-dim {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0.15;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
opacity: 0.2;
|
opacity: 0.35;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user