|
|
@@ -7,7 +7,7 @@ import { useSpring } from "@opencode-ai/ui/motion-spring"
|
|
|
import { TextReveal } from "@opencode-ai/ui/text-reveal"
|
|
|
import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
|
|
|
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
|
|
-import { Index, createEffect, createMemo, on, onCleanup } from "solid-js"
|
|
|
+import { Index, createEffect, createMemo, onCleanup } from "solid-js"
|
|
|
import { createStore } from "solid-js/store"
|
|
|
import { composerEnabled, composerProbe } from "@/testing/session-composer"
|
|
|
import { useLanguage } from "@/context/language"
|
|
|
@@ -210,76 +210,25 @@ export function SessionTodoDock(props: {
|
|
|
opacity: `${Math.max(0, Math.min(1, 1 - hide()))}`,
|
|
|
}}
|
|
|
>
|
|
|
- <TodoList todos={props.todos} open={!store.collapsed} />
|
|
|
+ <TodoList todos={props.todos} />
|
|
|
</div>
|
|
|
</div>
|
|
|
</DockTray>
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-function TodoList(props: { todos: Todo[]; open: boolean }) {
|
|
|
+function TodoList(props: { todos: Todo[] }) {
|
|
|
const [store, setStore] = createStore({
|
|
|
stuck: false,
|
|
|
- scrolling: false,
|
|
|
- })
|
|
|
- let scrollRef!: HTMLDivElement
|
|
|
- let timer: number | undefined
|
|
|
-
|
|
|
- const inProgress = createMemo(() => props.todos.findIndex((todo) => todo.status === "in_progress"))
|
|
|
-
|
|
|
- const ensure = () => {
|
|
|
- if (!props.open) return
|
|
|
- if (store.scrolling) return
|
|
|
- if (!scrollRef || scrollRef.offsetParent === null) return
|
|
|
-
|
|
|
- const el = scrollRef.querySelector("[data-in-progress]")
|
|
|
- if (!(el instanceof HTMLElement)) return
|
|
|
-
|
|
|
- const topFade = 16
|
|
|
- const bottomFade = 44
|
|
|
- const container = scrollRef.getBoundingClientRect()
|
|
|
- const rect = el.getBoundingClientRect()
|
|
|
- const top = rect.top - container.top + scrollRef.scrollTop
|
|
|
- const bottom = rect.bottom - container.top + scrollRef.scrollTop
|
|
|
- const viewTop = scrollRef.scrollTop + topFade
|
|
|
- const viewBottom = scrollRef.scrollTop + scrollRef.clientHeight - bottomFade
|
|
|
-
|
|
|
- if (top < viewTop) {
|
|
|
- scrollRef.scrollTop = Math.max(0, top - topFade)
|
|
|
- } else if (bottom > viewBottom) {
|
|
|
- scrollRef.scrollTop = bottom - (scrollRef.clientHeight - bottomFade)
|
|
|
- }
|
|
|
-
|
|
|
- setStore("stuck", scrollRef.scrollTop > 0)
|
|
|
- }
|
|
|
-
|
|
|
- createEffect(
|
|
|
- on([() => props.open, inProgress], () => {
|
|
|
- if (!props.open || inProgress() < 0) return
|
|
|
- requestAnimationFrame(ensure)
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- onCleanup(() => {
|
|
|
- if (!timer) return
|
|
|
- window.clearTimeout(timer)
|
|
|
})
|
|
|
|
|
|
return (
|
|
|
<div class="relative">
|
|
|
<div
|
|
|
class="px-3 pb-11 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar"
|
|
|
- ref={scrollRef}
|
|
|
style={{ "overflow-anchor": "none" }}
|
|
|
onScroll={(e) => {
|
|
|
setStore("stuck", e.currentTarget.scrollTop > 0)
|
|
|
- setStore("scrolling", true)
|
|
|
- if (timer) window.clearTimeout(timer)
|
|
|
- timer = window.setTimeout(() => {
|
|
|
- setStore("scrolling", false)
|
|
|
- if (inProgress() < 0) return
|
|
|
- requestAnimationFrame(ensure)
|
|
|
- }, 250)
|
|
|
}}
|
|
|
>
|
|
|
<Index each={props.todos}>
|