Skip to content

Commit 674b6ab

Browse files
committed
feat: add chat scroll debug and use alternative ways of forcing scroll in DOM!
1 parent 25f2fde commit 674b6ab

File tree

5 files changed

+112
-19
lines changed

5 files changed

+112
-19
lines changed

src/optionsGuiScheme.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,10 @@ export const guiOptionsScheme: {
605605
debugResponseTimeIndicator: {
606606
text: 'Debug Input Lag',
607607
},
608+
},
609+
{
610+
debugChatScroll: {
611+
},
608612
}
609613
],
610614
'export-import': [

src/optionsStorage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const defaultOptions = {
7272
preventBackgroundTimeoutKick: false,
7373
preventSleep: false,
7474
debugContro: false,
75+
debugChatScroll: false,
7576
chatVanillaRestrictions: true,
7677
debugResponseTimeIndicator: false,
7778
// antiAliasing: false,

src/react/Chat.tsx

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Props = {
4141
inputDisabled?: string
4242
placeholder?: string
4343
chatVanillaRestrictions?: boolean
44+
debugChatScroll?: boolean
4445
}
4546

4647
export const chatInputValueGlobal = proxy({
@@ -69,7 +70,8 @@ export default ({
6970
allowSelection,
7071
inputDisabled,
7172
placeholder,
72-
chatVanillaRestrictions
73+
chatVanillaRestrictions,
74+
debugChatScroll
7375
}: Props) => {
7476
const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]'))
7577
const [isInputFocused, setIsInputFocused] = useState(false)
@@ -86,7 +88,16 @@ export default ({
8688
const chatHistoryPos = useRef(sendHistoryRef.current.length)
8789
const inputCurrentlyEnteredValue = useRef('')
8890

89-
const { scrollToBottom } = useScrollBehavior(chatMessages, { messages, opened })
91+
const { scrollToBottom, isAtBottom, wasAtBottom, currentlyAtBottom } = useScrollBehavior(chatMessages, { messages, opened })
92+
const [rightNowAtBottom, setRightNowAtBottom] = useState(false)
93+
94+
useEffect(() => {
95+
if (!debugChatScroll) return
96+
const interval = setInterval(() => {
97+
setRightNowAtBottom(isAtBottom())
98+
}, 50)
99+
return () => clearInterval(interval)
100+
}, [debugChatScroll])
90101

91102
const setSendHistory = (newHistory: string[]) => {
92103
sendHistoryRef.current = newHistory
@@ -252,6 +263,55 @@ export default ({
252263
}}
253264
>
254265
{opacity && <div ref={chatMessages} className={`chat ${opened ? 'opened' : ''}`} id="chat-messages" style={{ opacity }}>
266+
{debugChatScroll && (
267+
<div
268+
style={{
269+
position: 'absolute',
270+
top: 5,
271+
left: 5,
272+
display: 'flex',
273+
gap: 4,
274+
zIndex: 100,
275+
}}
276+
>
277+
<div
278+
title="Right now is at bottom (updated every 50ms)"
279+
style={{
280+
width: 12,
281+
height: 12,
282+
backgroundColor: rightNowAtBottom ? '#00ff00' : '#ff0000',
283+
border: '1px solid #fff',
284+
}}
285+
/>
286+
<div
287+
title="Currently at bottom"
288+
style={{
289+
width: 12,
290+
height: 12,
291+
backgroundColor: currentlyAtBottom ? '#00ff00' : '#ff0000',
292+
border: '1px solid #fff',
293+
}}
294+
/>
295+
<div
296+
title="Was at bottom"
297+
style={{
298+
width: 12,
299+
height: 12,
300+
backgroundColor: wasAtBottom() ? '#00ff00' : '#ff0000',
301+
border: '1px solid #fff',
302+
}}
303+
/>
304+
<div
305+
title="Chat opened"
306+
style={{
307+
width: 12,
308+
height: 12,
309+
backgroundColor: opened ? '#00ff00' : '#ff0000',
310+
border: '1px solid #fff',
311+
}}
312+
/>
313+
</div>
314+
)}
255315
{messages.map((m) => (
256316
<MessageLine key={reactKeyForMessage(m)} message={m} />
257317
))}

src/react/ChatProvider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default () => {
1717
const isChatActive = useIsModalActive('chat')
1818
const lastMessageId = useRef(0)
1919
const usingTouch = useSnapshot(miscUiState).currentTouch
20-
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions } = useSnapshot(options)
20+
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions, debugChatScroll } = useSnapshot(options)
2121
const isUsingMicrosoftAuth = useMemo(() => !!lastConnectOptions.value?.authenticatedAccount, [])
2222
const { forwardChat } = useSnapshot(viewerVersionState)
2323
const { viewerConnection } = useSnapshot(gameAdditionalState)
@@ -48,6 +48,7 @@ export default () => {
4848

4949
return <Chat
5050
chatVanillaRestrictions={chatVanillaRestrictions}
51+
debugChatScroll={debugChatScroll}
5152
allowSelection={chatSelect}
5253
usingTouch={!!usingTouch}
5354
opacity={(isChatActive ? chatOpacityOpened : chatOpacity) / 100}

src/react/hooks/useScrollBehavior.ts

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react'
1+
import { RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react'
22
import { pixelartIcons } from '../PixelartIcon'
33

44
export const useScrollBehavior = (
@@ -12,6 +12,8 @@ export const useScrollBehavior = (
1212
}
1313
) => {
1414
const openedWasAtBottom = useRef(true) // before new messages
15+
const [currentlyAtBottom, setCurrentlyAtBottom] = useState(true)
16+
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null)
1517

1618
const isAtBottom = () => {
1719
if (!elementRef.current) return true
@@ -20,17 +22,30 @@ export const useScrollBehavior = (
2022
return distanceFromBottom < 1
2123
}
2224

23-
const scrollToBottom = () => {
24-
if (elementRef.current) {
25-
elementRef.current.scrollTop = elementRef.current.scrollHeight
26-
setTimeout(() => {
27-
if (!elementRef.current) return
28-
elementRef.current.scrollTo({
29-
top: elementRef.current.scrollHeight,
30-
behavior: 'instant'
31-
})
32-
}, 0)
25+
const scrollToBottom = (behavior: ScrollBehavior = 'instant') => {
26+
if (!elementRef.current) return
27+
28+
// Clear any existing scroll timeout
29+
if (scrollTimeoutRef.current) {
30+
clearTimeout(scrollTimeoutRef.current)
3331
}
32+
33+
const el = elementRef.current
34+
35+
// Immediate scroll
36+
el.scrollTop = el.scrollHeight
37+
38+
// Double-check after a short delay to ensure we're really at the bottom
39+
scrollTimeoutRef.current = setTimeout(() => {
40+
if (!elementRef.current) return
41+
const el = elementRef.current
42+
el.scrollTo({
43+
top: el.scrollHeight,
44+
behavior
45+
})
46+
setCurrentlyAtBottom(true)
47+
openedWasAtBottom.current = true
48+
}, 5)
3449
}
3550

3651
// Handle scroll position tracking
@@ -39,18 +54,28 @@ export const useScrollBehavior = (
3954
if (!element) return
4055

4156
const handleScroll = () => {
42-
openedWasAtBottom.current = isAtBottom()
57+
const atBottom = isAtBottom()
58+
openedWasAtBottom.current = atBottom
59+
setCurrentlyAtBottom(atBottom)
4360
}
4461

4562
element.addEventListener('scroll', handleScroll)
46-
return () => element.removeEventListener('scroll', handleScroll)
63+
return () => {
64+
element.removeEventListener('scroll', handleScroll)
65+
if (scrollTimeoutRef.current) {
66+
clearTimeout(scrollTimeoutRef.current)
67+
}
68+
}
4769
}, [])
4870

4971
// Handle opened state changes
5072
useLayoutEffect(() => {
5173
if (opened) {
52-
openedWasAtBottom.current = true
53-
} else {
74+
// Wait a frame before scrolling to ensure DOM has updated
75+
requestAnimationFrame(() => {
76+
scrollToBottom()
77+
})
78+
} else if (elementRef.current) {
5479
scrollToBottom()
5580
}
5681
}, [opened])
@@ -64,6 +89,8 @@ export const useScrollBehavior = (
6489

6590
return {
6691
scrollToBottom,
67-
isAtBottom
92+
isAtBottom,
93+
wasAtBottom: () => openedWasAtBottom.current,
94+
currentlyAtBottom
6895
}
6996
}

0 commit comments

Comments
 (0)