diff --git a/src/hooks/useScrollAnchor.tsx b/src/hooks/useScrollAnchor.tsx index a8ec2ad..c2d72a2 100644 --- a/src/hooks/useScrollAnchor.tsx +++ b/src/hooks/useScrollAnchor.tsx @@ -1,87 +1,106 @@ import { useCallback, useEffect, useRef, useState } from "react" +import { useMessageOption } from "./useMessageOption" export const useScrollAnchor = () => { - const messagesRef = useRef(null) - const scrollRef = useRef(null) - const visibilityRef = useRef(null) + const { isProcessing, messages } = useMessageOption() + const [isAtTop, setIsAtTop] = useState(false) const [isAtBottom, setIsAtBottom] = useState(true) - const [isVisible, setIsVisible] = useState(false) + const [userScrolled, setUserScrolled] = useState(false) + const [isOverflowing, setIsOverflowing] = useState(false) + + const messagesStartRef = useRef(null) + const messagesEndRef = useRef(null) + const containerRef = useRef(null) + const isAutoScrolling = useRef(false) + + console.log(`isAtTop: ${isAtTop}, isAtBottom: ${isAtBottom}, userScrolled: ${userScrolled}, isOverflowing: ${isOverflowing}`) + + useEffect(() => { + if (!isProcessing && userScrolled) { + console.log("userScrolled") + setUserScrolled(false) + } + }, [isProcessing]) + + useEffect(() => { + if (isProcessing && !userScrolled) { + scrollToBottom() + } + }, [messages]) + + useEffect(() => { + const container = containerRef.current + if (!container) return + + const topObserver = new IntersectionObserver( + ([entry]) => { + setIsAtTop(entry.isIntersecting) + }, + { threshold: 1 } + ) + + const bottomObserver = new IntersectionObserver( + ([entry]) => { + setIsAtBottom(entry.isIntersecting) + if (entry.isIntersecting) { + setUserScrolled(false) + } else if (!isAutoScrolling.current) { + setUserScrolled(true) + } + }, + { threshold: 1 } + ) + + if (messagesStartRef.current) { + topObserver.observe(messagesStartRef.current) + } + + if (messagesEndRef.current) { + bottomObserver.observe(messagesEndRef.current) + } + + const resizeObserver = new ResizeObserver(() => { + setIsOverflowing(container.scrollHeight > container.clientHeight) + }) + + resizeObserver.observe(container) + + return () => { + topObserver.disconnect() + bottomObserver.disconnect() + resizeObserver.disconnect() + } + }, []) + + const scrollToTop = useCallback(() => { + if (messagesStartRef.current) { + messagesStartRef.current.scrollIntoView({ behavior: "smooth" }) + } + }, []) const scrollToBottom = useCallback(() => { - if (messagesRef.current) { - messagesRef.current.scrollIntoView({ - block: "end", - behavior: "smooth" - }) - } + isAutoScrolling.current = true + + setTimeout(() => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ behavior: "smooth" }) + } + + isAutoScrolling.current = false + }, 100) }, []) - useEffect(() => { - if (messagesRef.current) { - if (isAtBottom && !isVisible) { - messagesRef.current.scrollIntoView({ - block: "end" - }) - } - } - }, [isAtBottom, isVisible]) - - useEffect(() => { - const { current } = scrollRef - - if (current) { - const handleScroll = (event: Event) => { - const target = event.target as HTMLDivElement - const offset = 25 - const isAtBottom = - target.scrollTop + target.clientHeight >= target.scrollHeight - offset - console.log(target.scrollTop, target.clientHeight, target.scrollHeight) - setIsAtBottom(isAtBottom) - } - - current.addEventListener("scroll", handleScroll, { - passive: true - }) - - return () => { - current.removeEventListener("scroll", handleScroll) - } - } - }, []) - - useEffect(() => { - if (visibilityRef.current) { - let observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - console.log(entry.isIntersecting) - if (entry.isIntersecting) { - setIsVisible(true) - } else { - setIsVisible(false) - } - }) - }, - { - rootMargin: "0px 0px -100px 0px" - } - ) - - observer.observe(visibilityRef.current) - - return () => { - observer.disconnect() - } - } - }) - return { - messagesRef, - scrollRef, - visibilityRef, - scrollToBottom, + messagesStartRef, + messagesEndRef, + containerRef, + isAtTop, isAtBottom, - isVisible + userScrolled, + isOverflowing, + scrollToTop, + scrollToBottom, + setIsAtBottom } -} +} \ No newline at end of file diff --git a/src/services/app.ts b/src/services/app.ts index e7d871a..9fb2ccd 100644 --- a/src/services/app.ts +++ b/src/services/app.ts @@ -38,4 +38,18 @@ export const getAdvancedOllamaSettings = async () => { export const copilotResumeLastChat = async () => { return await storage.get("copilotResumeLastChat") +} + + +export const defaultSidebarOpen = async () => { + const sidebarOpen = await storage.get("sidebarOpen") + if (!sidebarOpen || sidebarOpen === "") { + return "right_clk" + } + return sidebarOpen +} + + +export const setSidebarOpen = async (sidebarOpen: string) => { + await storage.set("sidebarOpen", sidebarOpen) } \ No newline at end of file