feat: add chat scroll debug and use alternative ways of forcing scroll in DOM!
This commit is contained in:
parent
25f2fdef4e
commit
674b6ab00d
5 changed files with 112 additions and 19 deletions
|
|
@ -605,6 +605,10 @@ export const guiOptionsScheme: {
|
|||
debugResponseTimeIndicator: {
|
||||
text: 'Debug Input Lag',
|
||||
},
|
||||
},
|
||||
{
|
||||
debugChatScroll: {
|
||||
},
|
||||
}
|
||||
],
|
||||
'export-import': [
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ const defaultOptions = {
|
|||
preventBackgroundTimeoutKick: false,
|
||||
preventSleep: false,
|
||||
debugContro: false,
|
||||
debugChatScroll: false,
|
||||
chatVanillaRestrictions: true,
|
||||
debugResponseTimeIndicator: false,
|
||||
// antiAliasing: false,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ type Props = {
|
|||
inputDisabled?: string
|
||||
placeholder?: string
|
||||
chatVanillaRestrictions?: boolean
|
||||
debugChatScroll?: boolean
|
||||
}
|
||||
|
||||
export const chatInputValueGlobal = proxy({
|
||||
|
|
@ -69,7 +70,8 @@ export default ({
|
|||
allowSelection,
|
||||
inputDisabled,
|
||||
placeholder,
|
||||
chatVanillaRestrictions
|
||||
chatVanillaRestrictions,
|
||||
debugChatScroll
|
||||
}: Props) => {
|
||||
const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]'))
|
||||
const [isInputFocused, setIsInputFocused] = useState(false)
|
||||
|
|
@ -86,7 +88,16 @@ export default ({
|
|||
const chatHistoryPos = useRef(sendHistoryRef.current.length)
|
||||
const inputCurrentlyEnteredValue = useRef('')
|
||||
|
||||
const { scrollToBottom } = useScrollBehavior(chatMessages, { messages, opened })
|
||||
const { scrollToBottom, isAtBottom, wasAtBottom, currentlyAtBottom } = useScrollBehavior(chatMessages, { messages, opened })
|
||||
const [rightNowAtBottom, setRightNowAtBottom] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!debugChatScroll) return
|
||||
const interval = setInterval(() => {
|
||||
setRightNowAtBottom(isAtBottom())
|
||||
}, 50)
|
||||
return () => clearInterval(interval)
|
||||
}, [debugChatScroll])
|
||||
|
||||
const setSendHistory = (newHistory: string[]) => {
|
||||
sendHistoryRef.current = newHistory
|
||||
|
|
@ -252,6 +263,55 @@ export default ({
|
|||
}}
|
||||
>
|
||||
{opacity && <div ref={chatMessages} className={`chat ${opened ? 'opened' : ''}`} id="chat-messages" style={{ opacity }}>
|
||||
{debugChatScroll && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 5,
|
||||
left: 5,
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
title="Right now is at bottom (updated every 50ms)"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: rightNowAtBottom ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
title="Currently at bottom"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: currentlyAtBottom ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
title="Was at bottom"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: wasAtBottom() ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
title="Chat opened"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: opened ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{messages.map((m) => (
|
||||
<MessageLine key={reactKeyForMessage(m)} message={m} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default () => {
|
|||
const isChatActive = useIsModalActive('chat')
|
||||
const lastMessageId = useRef(0)
|
||||
const usingTouch = useSnapshot(miscUiState).currentTouch
|
||||
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions } = useSnapshot(options)
|
||||
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions, debugChatScroll } = useSnapshot(options)
|
||||
const isUsingMicrosoftAuth = useMemo(() => !!lastConnectOptions.value?.authenticatedAccount, [])
|
||||
const { forwardChat } = useSnapshot(viewerVersionState)
|
||||
const { viewerConnection } = useSnapshot(gameAdditionalState)
|
||||
|
|
@ -48,6 +48,7 @@ export default () => {
|
|||
|
||||
return <Chat
|
||||
chatVanillaRestrictions={chatVanillaRestrictions}
|
||||
debugChatScroll={debugChatScroll}
|
||||
allowSelection={chatSelect}
|
||||
usingTouch={!!usingTouch}
|
||||
opacity={(isChatActive ? chatOpacityOpened : chatOpacity) / 100}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import { RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { pixelartIcons } from '../PixelartIcon'
|
||||
|
||||
export const useScrollBehavior = (
|
||||
|
|
@ -12,6 +12,8 @@ export const useScrollBehavior = (
|
|||
}
|
||||
) => {
|
||||
const openedWasAtBottom = useRef(true) // before new messages
|
||||
const [currentlyAtBottom, setCurrentlyAtBottom] = useState(true)
|
||||
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const isAtBottom = () => {
|
||||
if (!elementRef.current) return true
|
||||
|
|
@ -20,17 +22,30 @@ export const useScrollBehavior = (
|
|||
return distanceFromBottom < 1
|
||||
}
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (elementRef.current) {
|
||||
elementRef.current.scrollTop = elementRef.current.scrollHeight
|
||||
setTimeout(() => {
|
||||
if (!elementRef.current) return
|
||||
elementRef.current.scrollTo({
|
||||
top: elementRef.current.scrollHeight,
|
||||
behavior: 'instant'
|
||||
})
|
||||
}, 0)
|
||||
const scrollToBottom = (behavior: ScrollBehavior = 'instant') => {
|
||||
if (!elementRef.current) return
|
||||
|
||||
// Clear any existing scroll timeout
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current)
|
||||
}
|
||||
|
||||
const el = elementRef.current
|
||||
|
||||
// Immediate scroll
|
||||
el.scrollTop = el.scrollHeight
|
||||
|
||||
// Double-check after a short delay to ensure we're really at the bottom
|
||||
scrollTimeoutRef.current = setTimeout(() => {
|
||||
if (!elementRef.current) return
|
||||
const el = elementRef.current
|
||||
el.scrollTo({
|
||||
top: el.scrollHeight,
|
||||
behavior
|
||||
})
|
||||
setCurrentlyAtBottom(true)
|
||||
openedWasAtBottom.current = true
|
||||
}, 5)
|
||||
}
|
||||
|
||||
// Handle scroll position tracking
|
||||
|
|
@ -39,18 +54,28 @@ export const useScrollBehavior = (
|
|||
if (!element) return
|
||||
|
||||
const handleScroll = () => {
|
||||
openedWasAtBottom.current = isAtBottom()
|
||||
const atBottom = isAtBottom()
|
||||
openedWasAtBottom.current = atBottom
|
||||
setCurrentlyAtBottom(atBottom)
|
||||
}
|
||||
|
||||
element.addEventListener('scroll', handleScroll)
|
||||
return () => element.removeEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
element.removeEventListener('scroll', handleScroll)
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Handle opened state changes
|
||||
useLayoutEffect(() => {
|
||||
if (opened) {
|
||||
openedWasAtBottom.current = true
|
||||
} else {
|
||||
// Wait a frame before scrolling to ensure DOM has updated
|
||||
requestAnimationFrame(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
} else if (elementRef.current) {
|
||||
scrollToBottom()
|
||||
}
|
||||
}, [opened])
|
||||
|
|
@ -64,6 +89,8 @@ export const useScrollBehavior = (
|
|||
|
||||
return {
|
||||
scrollToBottom,
|
||||
isAtBottom
|
||||
isAtBottom,
|
||||
wasAtBottom: () => openedWasAtBottom.current,
|
||||
currentlyAtBottom
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue