Skip to content

Commit

Permalink
Merge pull request #730 from Roguelike-Celebration/hacking-on-virtual…
Browse files Browse the repository at this point in the history
…ization

Hacking on virtualization
  • Loading branch information
lazerwalker authored Oct 22, 2022
2 parents 86c860a + b969d0f commit c3c5fff
Show file tree
Hide file tree
Showing 26 changed files with 1,469 additions and 576 deletions.
291 changes: 223 additions & 68 deletions src/Actions.ts

Large diffs are not rendered by default.

340 changes: 199 additions & 141 deletions src/App.tsx

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions src/Deferred.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* based loosely on the following:
*
* @see https://stackoverflow.com/questions/44728779/deferred-that-extends-promise
* @see https://github.com/BabylonJS/Babylon.js/blob/master/packages/dev/core/src/Misc/deferred.ts
*/
export class Deferred<T> {
private _promise: Promise<T>;
private _resolve: (value: T | PromiseLike<T>) => void;
private _reject: (reason?: any) => void;

constructor () {
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve
this._reject = reject
})
}

public get promise () {
return this._promise
}

public get resolve () {
return this._resolve
}

public get reject () {
return this._reject
}
}
130 changes: 0 additions & 130 deletions src/components/ChatView.tsx

This file was deleted.

8 changes: 8 additions & 0 deletions src/components/MessageItem/MessageItem.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.message-item {
width: 100%;

margin: 0;
padding: 0;

position: absolute;
}
73 changes: 73 additions & 0 deletions src/components/MessageItem/MessageItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { FC, memo, useContext, useEffect, useRef } from 'react'
import { MessagesContext } from '../../App'
import { MessageType } from '../../message'
import MessageView from '../MessageView'
import { VirtualizationContext } from '../VirtualizationProvider'
import './MessageItem.css'

interface MessageItemProps {
messageId: string;
hideTimestamp: boolean;
msgIndex: number;
}

const { getComputedStyle: getStyle } = window

const outerHeight: (el: HTMLElement) => number = (el) => {
const { marginTop, height, marginBottom } = getStyle(el)
return [marginTop, height, marginBottom].reduce(
(acc, px) => acc + parseInt(px, 10),
0
)
}

export const MessageItem: FC<MessageItemProps> = memo(
({ messageId, hideTimestamp, msgIndex }) => {
const [{ messagePositions, viewportScrollHeight }, virtualizationDispatch] =
useContext(VirtualizationContext)
const { entities } = useContext(MessagesContext)
const message = entities[messageId]

const messageItemRef = useRef<HTMLLIElement>(null)
useEffect(() => {
if (!messageItemRef.current) {
return
}

const { top, height } = messagePositions[messageId] ?? {}

if (top === undefined && height === undefined) {
virtualizationDispatch({
type: 'setMessagePosition',
payload: {
id: messageId,
top: viewportScrollHeight + messageItemRef.current.offsetTop,
height: outerHeight(messageItemRef.current)
}
})
}
}, [
messageId,
messagePositions[messageId],
viewportScrollHeight,
virtualizationDispatch
])

return message ? (
<li
className="message-item"
style={{ top: messagePositions[messageId]?.top }}
ref={messageItemRef}
>
{message.type === MessageType.MovedRoom && <hr />}
<MessageView
message={message}
hideTimestamp={hideTimestamp}
msgIndex={msgIndex}
/>
</li>
) : null
}
)

MessageItem.displayName = 'MessageItem'
1 change: 1 addition & 0 deletions src/components/MessageItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MessageItem } from './MessageItem'
31 changes: 31 additions & 0 deletions src/components/MessageList/MessageList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.message-list {
height: 40vh;
list-style-type: none;

margin: 0;
padding: 0;

overflow-y: scroll;
overflow-x: hidden;

position: relative;
}

.messages-load-progress {
background: var(--main-background);

width: 100%;
height: 40vh;

display: flex;
align-items: center;
justify-content: center;

position: absolute;
}

.sentinel {
width: 100%;
height: 1px;
position: absolute;
}
71 changes: 71 additions & 0 deletions src/components/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { FC, ReactNodeArray, useContext } from 'react'
import { MessagesContext } from '../../App'
import { MessageItem } from '../MessageItem'
import { VirtualizationContext } from '../VirtualizationProvider'
import { useAutoscroll, useShouldHideTimestamp } from './hooks'
import './MessageList.css'

interface MessageListProps {
autoscrollChat: boolean;
messagesLoadProgress: number;
}

export const MessageList: FC<MessageListProps> = ({
autoscrollChat,
messagesLoadProgress
}) => {
const { ids, entities } = useContext(MessagesContext)
const [{ messagePositions, viewportScrollTop, viewportScrollHeight }] =
useContext(VirtualizationContext)

const isLoading = ids.length > 0 && messagesLoadProgress < 1
const autoscrollAndNotLoading = autoscrollChat && !isLoading
const [scrollContainerRef, toggleAutoscroll] = useAutoscroll(autoscrollAndNotLoading)
const shouldHideTimestamp = useShouldHideTimestamp()

return (
<>
<ol
className="message-list"
ref={scrollContainerRef}
onScroll={toggleAutoscroll}
>
{ids.reduce<ReactNodeArray>((acc, id, i) => {
if (!scrollContainerRef.current) {
return acc
}

const { clientHeight } = scrollContainerRef.current
const { top, height } = messagePositions[id] ?? {}
const didMeasure = top !== undefined && height !== undefined
const isInBounds =
didMeasure &&
top < viewportScrollTop + clientHeight &&
top + height > viewportScrollTop

if (!didMeasure || isInBounds) {
acc.push(
<MessageItem
key={id}
messageId={id}
hideTimestamp={shouldHideTimestamp(
entities[id],
entities[ids[i - 1]]
)}
msgIndex={i}
/>
)
}

return acc
}, [])}
<li className="sentinel" style={{ top: viewportScrollHeight }} />
</ol>
{isLoading && (
<div className="messages-load-progress">
Loading {Math.floor(messagesLoadProgress * 100)}%
</div>
)}
</>
)
}
2 changes: 2 additions & 0 deletions src/components/MessageList/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useAutoscroll } from './useAutoscroll'
export { useShouldHideTimestamp } from './useShouldHideTimestamp'
Loading

0 comments on commit c3c5fff

Please sign in to comment.