mirror of
https://github.com/urbit/shrub.git
synced 2024-12-20 09:21:42 +03:00
chat-fe: convert virtualscroller to bigInt
This commit is contained in:
parent
6bb62d802b
commit
a1cf88faba
@ -170,6 +170,42 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
};
|
||||
return inner(nod);
|
||||
}
|
||||
|
||||
peekLargest(): [BigInteger, V] | undefined {
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if(!node) {
|
||||
return undefined;
|
||||
}
|
||||
if(node.l) {
|
||||
return inner(node.l);
|
||||
}
|
||||
return node.n;
|
||||
}
|
||||
return inner(this.root);
|
||||
}
|
||||
|
||||
peekSmallest(): [BigInteger, V] | undefined {
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if(!node) {
|
||||
return undefined;
|
||||
}
|
||||
if(node.r) {
|
||||
return inner(node.r);
|
||||
}
|
||||
return node.n;
|
||||
}
|
||||
return inner(this.root);
|
||||
}
|
||||
|
||||
keys(): BigInteger[] {
|
||||
const list = Array.from(this);
|
||||
return list.map(([key]) => key);
|
||||
}
|
||||
|
||||
forEach(f: (value: V, key: BigInteger) => void) {
|
||||
const list = Array.from(this);
|
||||
return list.forEach(([k,v]) => f(v,k));
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[BigInteger, V]> {
|
||||
let result: [BigInteger, V][] = [];
|
||||
|
10
pkg/interface/src/logic/lib/bigInt.ts
Normal file
10
pkg/interface/src/logic/lib/bigInt.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
|
||||
export function max(a: BigInteger, b: BigInteger) {
|
||||
return a.gt(b) ? a : b;
|
||||
}
|
||||
|
||||
export function min(a: BigInteger, b: BigInteger) {
|
||||
return a.lt(b) ? a : b;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import RemoteContent from '~/views/components/RemoteContent';
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
|
||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
||||
<Row ref={ref} color='blue' alignItems='center' fontSize='0' position='absolute' width='100%' py='2'>
|
||||
<Row flexShrink={0} ref={ref} color='blue' alignItems='center' fontSize='0' position='absolute' width='100%' py='2'>
|
||||
<Rule borderColor='blue' display={['none', 'block']} m='0' width='2rem' />
|
||||
<Text flexShrink='0' display='block' zIndex='2' mx='4' color='blue'>New messages below</Text>
|
||||
<Rule borderColor='blue' flexGrow='1' m='0'/>
|
||||
@ -130,6 +130,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
return (
|
||||
<Box
|
||||
bg={highlighted ? 'washedBlue' : 'white'}
|
||||
flexShrink={0}
|
||||
width='100%'
|
||||
display='flex'
|
||||
flexWrap='wrap'
|
||||
@ -146,7 +147,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
{renderSigil
|
||||
? <MessageWithSigil {...messageProps} />
|
||||
: <MessageWithoutSigil {...messageProps} />}
|
||||
<Box fontSize={0} position='relative' width='100%' overflow='visible' style={unreadContainerStyle}>{isLastRead
|
||||
<Box flexShrink={0} fontSize={0} position='relative' width='100%' overflow='visible' style={unreadContainerStyle}>{isLastRead
|
||||
? <UnreadMarker dayBreak={dayBreak} when={msg.when} ref={unreadMarkerRef} />
|
||||
: null}</Box>
|
||||
</Box>
|
||||
@ -222,10 +223,11 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
scrollWindow={scrollWindow}
|
||||
history={history}
|
||||
api={api}
|
||||
className="fl pr3 v-top pt1"
|
||||
className="fl pr3 v-top pt1 flex-shrink-0"
|
||||
/>
|
||||
<Box flexGrow='1' display='block' className="clamp-message">
|
||||
<Box flexShrink={0} flexGrow='1' display='block' className="clamp-message">
|
||||
<Box
|
||||
flexShrink={0}
|
||||
className="hide-child"
|
||||
pt={1}
|
||||
pb={1}
|
||||
@ -235,6 +237,7 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
<Text
|
||||
fontSize={0}
|
||||
mr={3}
|
||||
flexShrink={0}
|
||||
mono={!showNickname}
|
||||
fontWeight={(showNickname) ? '500' : '400'}
|
||||
className={`mw5 db truncate pointer`}
|
||||
@ -246,9 +249,9 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
title={`~${msg.author}`}
|
||||
>{name}</Text>
|
||||
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
|
||||
<Text gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
||||
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
||||
</Box>
|
||||
<Box fontSize={fontSize ? fontSize : '14px'}><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} fontSize={fontSize} /></Box>
|
||||
<Box flexShrink={0} fontSize={fontSize ? fontSize : '14px'}><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} fontSize={fontSize} /></Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
@ -257,8 +260,8 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
|
||||
export const MessageWithoutSigil = ({ timestamp, msg, remoteContentPolicy, measure }) => (
|
||||
<>
|
||||
<Text mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
|
||||
<Box fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
|
||||
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
|
||||
<Box flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
|
||||
<MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure}/>
|
||||
</Box>
|
||||
</>
|
||||
@ -269,7 +272,7 @@ export const MessageContent = ({ content, remoteContentPolicy, measure, fontSize
|
||||
return <CodeContent content={content} />;
|
||||
} else if ('url' in content) {
|
||||
return (
|
||||
<Text fontSize={fontSize ? fontSize : '14px'} lineHeight="tall" color='black'>
|
||||
<Text flexShrink={0} fontSize={fontSize ? fontSize : '14px'} lineHeight="tall" color='black'>
|
||||
<RemoteContent
|
||||
url={content.url}
|
||||
remoteContentPolicy={remoteContentPolicy}
|
||||
@ -285,7 +288,7 @@ export const MessageContent = ({ content, remoteContentPolicy, measure, fontSize
|
||||
);
|
||||
} else if ('me' in content) {
|
||||
return (
|
||||
<Text fontStyle='italic' fontSize={fontSize ? fontSize : '14px'} lineHeight='tall' color='black'>
|
||||
<Text flexShrink={0} fontStyle='italic' fontSize={fontSize ? fontSize : '14px'} lineHeight='tall' color='black'>
|
||||
{content.me}
|
||||
</Text>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import _ from "lodash";
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Patp, Path } from "~/types/noun";
|
||||
@ -9,6 +10,7 @@ import { Association } from "~/types/metadata-update";
|
||||
import { Group } from "~/types/group-update";
|
||||
import { Envelope, IMessage } from "~/types/chat-update";
|
||||
import { LocalUpdateRemoteContentPolicy } from "~/types";
|
||||
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
||||
|
||||
import VirtualScroller from "~/views/components/VirtualScroller";
|
||||
|
||||
@ -66,7 +68,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
fetchPending: false,
|
||||
idle: true,
|
||||
initialized: false,
|
||||
lastRead: props.unreadCount ? props.mailboxSize - props.unreadCount : Infinity,
|
||||
lastRead: props.unreadCount ? props.mailboxSize - props.unreadCount : -1,
|
||||
};
|
||||
|
||||
this.dismissUnread = this.dismissUnread.bind(this);
|
||||
@ -143,7 +145,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
|
||||
if (unreadCount > prevProps.unreadCount && this.state.idle) {
|
||||
this.setState({
|
||||
lastRead: unreadCount ? mailboxSize - unreadCount : Infinity,
|
||||
lastRead: unreadCount ? mailboxSize - unreadCount : -1,
|
||||
});
|
||||
}
|
||||
|
||||
@ -159,7 +161,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.virtualList?.resetScroll();
|
||||
this.scrollToUnread();
|
||||
this.setState({
|
||||
lastRead: unreadCount ? mailboxSize - unreadCount : Infinity,
|
||||
lastRead: unreadCount ? mailboxSize - unreadCount : -1,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -254,21 +256,22 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
|
||||
const unreadMarkerRef = this.unreadMarkerRef;
|
||||
|
||||
const messages = new Map();
|
||||
const messages = new BigIntOrderedMap();
|
||||
let lastMessage = 0;
|
||||
|
||||
[...envelopes]
|
||||
.sort((a, b) => a.number - b.number)
|
||||
.forEach(message => {
|
||||
messages.set(message.number, message);
|
||||
const num = bigInt(message.number);
|
||||
messages.set(num, message);
|
||||
lastMessage = message.number;
|
||||
});
|
||||
|
||||
stationPendingMessages
|
||||
.sort((a, b) => a.when - b.when)
|
||||
.forEach((message, index) => {
|
||||
index = index + 1; // To 1-index it
|
||||
messages.set(mailboxSize + index, message);
|
||||
const idx = bigInt(index + 1); // To 1-index it
|
||||
messages.set(bigInt(mailboxSize).add(idx), message);
|
||||
lastMessage = mailboxSize + index;
|
||||
});
|
||||
|
||||
@ -299,24 +302,24 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
const msg: Envelope | IMessage = messages.get(index);
|
||||
if (!msg) return null;
|
||||
if (!this.state.initialized) {
|
||||
return <MessagePlaceholder key={index} height="64px" index={index} />;
|
||||
return <MessagePlaceholder key={index.toString()} height="64px" index={index} />;
|
||||
}
|
||||
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
|
||||
const isLastMessage: boolean = Boolean(index === lastMessage)
|
||||
const isLastRead: boolean = Boolean(!isLastMessage && index === this.state.lastRead);
|
||||
const highlighted = index === this.props.scrollTo;
|
||||
const isLastMessage: boolean = Boolean(index.eq(bigInt(lastMessage)));
|
||||
const isLastRead: boolean = Boolean(!isLastMessage && index.eq(bigInt(this.state.lastRead)));
|
||||
const highlighted = bigInt(this.props.scrollTo || -1).eq(index);
|
||||
const props = { measure, highlighted, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
||||
return (
|
||||
<ChatMessage
|
||||
key={index}
|
||||
previousMsg={messages.get(index + 1)}
|
||||
nextMsg={messages.get(index - 1)}
|
||||
key={index.toString()}
|
||||
previousMsg={messages.get(index.add(bigInt.one))}
|
||||
nextMsg={messages.get(index.subtract(bigInt.one))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
loadRows={(start, end) => {
|
||||
this.fetchMessages(start, end);
|
||||
this.fetchMessages(start.toJSNumber(), end.toJSNumber());
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
@ -83,7 +83,7 @@ export default class TextContent extends Component {
|
||||
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
||||
&& (group[0] === content.text))) { // entire message is room name?
|
||||
return (
|
||||
<Text fontSize={props.fontSize ? props.fontSize : '14px'} color='black' lineHeight="tall">
|
||||
<Text flexShrink={0} fontSize={props.fontSize ? props.fontSize : '14px'} color='black' lineHeight="tall">
|
||||
<Link
|
||||
className="bb b--black b--white-d mono"
|
||||
to={'/~landscape/join/' + group.input}>
|
||||
@ -93,7 +93,7 @@ export default class TextContent extends Component {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Text color='black' fontSize={props.fontSize ? props.fontSize : '14px'} lineHeight="tall" style={{ overflowWrap: 'break-word' }}>
|
||||
<Text flexShrink={0} color='black' fontSize={props.fontSize ? props.fontSize : '14px'} lineHeight="tall" style={{ overflowWrap: 'break-word' }}>
|
||||
<MessageMarkdown source={content.text} />
|
||||
</Text>
|
||||
);
|
||||
|
@ -72,6 +72,7 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
wrapInLink(contents) {
|
||||
return (<BaseAnchor
|
||||
href={this.props.url}
|
||||
flexShrink={0}
|
||||
style={{ color: 'inherit', textDecoration: 'none' }}
|
||||
className={`word-break-all ${(typeof contents === 'string') ? 'bb' : ''}`}
|
||||
target="_blank"
|
||||
@ -103,6 +104,7 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
if (isImage && remoteContentPolicy.imageShown) {
|
||||
return this.wrapInLink(
|
||||
<BaseImage
|
||||
flexShrink={0}
|
||||
src={url}
|
||||
style={style}
|
||||
onLoad={onLoad}
|
||||
@ -153,6 +155,7 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
height={3}
|
||||
ml={1}
|
||||
onClick={this.unfoldEmbed}
|
||||
flexShrink={0}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{this.state.unfold ? 'collapse' : 'expand'}
|
||||
@ -160,9 +163,11 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
<Box
|
||||
mb='2'
|
||||
width='100%'
|
||||
flexShrink={0}
|
||||
display={this.state.unfold ? 'block' : 'none'}
|
||||
className='embed-container'
|
||||
style={style}
|
||||
flexShrink={0}
|
||||
onLoad={onLoad}
|
||||
{...oembedProps}
|
||||
{...props}
|
||||
|
@ -1,24 +1,33 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
||||
import normalizeWheel from 'normalize-wheel';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import * as bigIntUtils from '~/logic/lib/bigInt';
|
||||
|
||||
interface RendererProps {
|
||||
index: BigInteger;
|
||||
measure: (el: any) => void;
|
||||
scrollWindow: any
|
||||
}
|
||||
|
||||
interface VirtualScrollerProps {
|
||||
origin: 'top' | 'bottom';
|
||||
loadRows(start: number, end: number): void;
|
||||
data: Map<number, any>;
|
||||
renderer(index): JSX.Element | null;
|
||||
loadRows(start: BigInteger, end: BigInteger): void;
|
||||
data: BigIntOrderedMap<BigInteger, any>;
|
||||
renderer: (props: RendererProps) => JSX.Element | null;
|
||||
onStartReached?(): void;
|
||||
onEndReached?(): void;
|
||||
size: number;
|
||||
onCalculateVisibleItems?(visibleItems: Map<number, JSX.Element>): void;
|
||||
onCalculateVisibleItems?(visibleItems: BigIntOrderedMap<BigInteger, JSX.Element>): void;
|
||||
onScroll?({ scrollTop, scrollHeight, windowHeight }): void;
|
||||
style?: any;
|
||||
}
|
||||
|
||||
interface VirtualScrollerState {
|
||||
startgap: number | undefined;
|
||||
visibleItems: Map<number, Element>;
|
||||
visibleItems: BigIntOrderedMap<BigInteger, Element>;
|
||||
endgap: number | undefined;
|
||||
totalHeight: number;
|
||||
averageHeight: number;
|
||||
@ -28,29 +37,29 @@ interface VirtualScrollerState {
|
||||
export default class VirtualScroller extends PureComponent<VirtualScrollerProps, VirtualScrollerState> {
|
||||
private scrollContainer: React.RefObject<HTMLDivElement>;
|
||||
public window: HTMLDivElement | null;
|
||||
private cache: Map<number, any>;
|
||||
private cache: BigIntOrderedMap<BigInteger, any>;
|
||||
private pendingLoad: {
|
||||
start: number;
|
||||
end: number
|
||||
start: BigInteger;
|
||||
end: BigInteger
|
||||
timeout: ReturnType<typeof setTimeout>;
|
||||
} | undefined;
|
||||
|
||||
OVERSCAN_SIZE = 100; // Minimum number of messages on either side before loadRows is called
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: VirtualScrollerProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
startgap: props.origin === 'top' ? 0 : undefined,
|
||||
visibleItems: new Map(),
|
||||
visibleItems: new BigIntOrderedMap(),
|
||||
endgap: props.origin === 'bottom' ? 0 : undefined,
|
||||
totalHeight: 0,
|
||||
averageHeight: 64,
|
||||
scrollTop: props.origin === 'top' ? 0 : Infinity
|
||||
scrollTop: props.origin === 'top' ? 0 : undefined
|
||||
};
|
||||
|
||||
this.scrollContainer = React.createRef();
|
||||
this.window = null;
|
||||
this.cache = new Map();
|
||||
this.cache = new BigIntOrderedMap();
|
||||
|
||||
this.recalculateTotalHeight = this.recalculateTotalHeight.bind(this);
|
||||
this.calculateVisibleItems = this.calculateVisibleItems.bind(this);
|
||||
@ -75,18 +84,18 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
} = this;
|
||||
}
|
||||
|
||||
scrollToData(targetIndex: number): Promise<void> {
|
||||
scrollToData(targetIndex: BigInteger): Promise<void> {
|
||||
if (!this.window) {
|
||||
return new Promise((resolve, reject) => {reject()});
|
||||
}
|
||||
const { offsetHeight } = this.window;
|
||||
let scrollTop = 0;
|
||||
let itemHeight = 0;
|
||||
new Map([...this.props.data].reverse()).forEach((datum, index) => {
|
||||
new BigIntOrderedMap([...this.props.data].reverse()).forEach((datum, index) => {
|
||||
const height = this.heightOf(index);
|
||||
if (index >= targetIndex) {
|
||||
if (index.geq(targetIndex)) {
|
||||
scrollTop += height;
|
||||
if (index === targetIndex) {
|
||||
if (index.eq(targetIndex)) {
|
||||
itemHeight = height;
|
||||
}
|
||||
}
|
||||
@ -105,20 +114,20 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
this.setState({ totalHeight, averageHeight });
|
||||
}
|
||||
|
||||
estimateIndexFromScrollTop(targetScrollTop: number): number | void {
|
||||
if (!this.window) return;
|
||||
let index = this.props.size;
|
||||
estimateIndexFromScrollTop(targetScrollTop: number): BigInteger | undefined {
|
||||
if (!this.window) return undefined;
|
||||
let index = bigInt(this.props.size);
|
||||
const { averageHeight } = this.state;
|
||||
let height = 0;
|
||||
while (height < targetScrollTop) {
|
||||
const itemHeight = this.cache.has(index) ? this.cache.get(index).height : averageHeight;
|
||||
height += itemHeight;
|
||||
index--;
|
||||
index.subtract(bigInt.one);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
heightOf(index: number): number {
|
||||
heightOf(index: BigInteger): number {
|
||||
return this.cache.has(index) ? this.cache.get(index).height : this.state.averageHeight;
|
||||
}
|
||||
|
||||
@ -126,70 +135,80 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
if (!this.window) return;
|
||||
let startgap = 0, heightShown = 0, endgap = 0;
|
||||
let startGapFilled = false;
|
||||
let visibleItems = new Map();
|
||||
let startBuffer = new Map();
|
||||
let endBuffer = new Map();
|
||||
let visibleItems = new BigIntOrderedMap<any>();
|
||||
let startBuffer = new BigIntOrderedMap<any>();
|
||||
let endBuffer = new BigIntOrderedMap<any>();
|
||||
const { scrollTop, offsetHeight: windowHeight } = this.window;
|
||||
const { averageHeight } = this.state;
|
||||
const { data, size: totalSize, onCalculateVisibleItems } = this.props;
|
||||
|
||||
const items = new Map([...data].reverse());
|
||||
|
||||
items.forEach((datum, index) => {
|
||||
//console.log([...items].map(([index]) => this.heightOf(index)));
|
||||
const list = [...data];
|
||||
console.log(list[0][0].toString());
|
||||
// console.log(list[list.length - 1][0].toString());
|
||||
[...data].forEach(([index, datum]) => {
|
||||
const height = this.heightOf(index);
|
||||
if (startgap < scrollTop && !startGapFilled) {
|
||||
console.log(index.toString());
|
||||
startBuffer.set(index, datum);
|
||||
startgap += height;
|
||||
} else if (heightShown < windowHeight) {
|
||||
startGapFilled = true;
|
||||
visibleItems.set(index, datum);
|
||||
heightShown += height;
|
||||
} else if (endBuffer.size < visibleItems.size) {
|
||||
} else if (endBuffer.size < (visibleItems.size - visibleItems.size % 5)) {
|
||||
endBuffer.set(index, data.get(index));
|
||||
} else {
|
||||
endgap += height;
|
||||
}
|
||||
});
|
||||
|
||||
// endgap += Math.abs(totalSize - data.size) * averageHeight; // Uncomment to make full height of backlog
|
||||
startBuffer = new Map([...startBuffer].reverse().slice(0, visibleItems.size));
|
||||
console.log(startgap);
|
||||
|
||||
startBuffer.forEach((datum, index) => {
|
||||
|
||||
startBuffer = new BigIntOrderedMap([...startBuffer].reverse().slice(0, (visibleItems.size - visibleItems.size % 5)));
|
||||
|
||||
|
||||
startBuffer.forEach((_datum, index) => {
|
||||
startgap -= this.heightOf(index);
|
||||
});
|
||||
|
||||
visibleItems = new Map([...visibleItems].reverse());
|
||||
endBuffer = new Map([...endBuffer].reverse());
|
||||
const firstVisibleKey = Array.from(visibleItems.keys())[0] ?? this.estimateIndexFromScrollTop(scrollTop);
|
||||
const firstNeededKey = Math.max(firstVisibleKey - this.OVERSCAN_SIZE, 0);
|
||||
if (!data.has(firstNeededKey + 1)) {
|
||||
this.loadRows(firstNeededKey, firstVisibleKey - 1);
|
||||
console.log(startBuffer.size);
|
||||
console.log(startgap);
|
||||
|
||||
const firstVisibleKey = visibleItems.peekLargest()?.[0] ?? this.estimateIndexFromScrollTop(scrollTop)!;
|
||||
const firstNeededKey = bigIntUtils.max(firstVisibleKey.subtract(bigInt(this.OVERSCAN_SIZE)), bigInt.zero)
|
||||
if (!data.has(firstNeededKey.add(bigInt.one))) {
|
||||
this.loadRows(firstNeededKey, firstVisibleKey.subtract(bigInt.one));
|
||||
}
|
||||
const lastVisibleKey = Array.from(visibleItems.keys())[visibleItems.size - 1] ?? this.estimateIndexFromScrollTop(scrollTop + windowHeight);
|
||||
const lastNeededKey = Math.min(lastVisibleKey + this.OVERSCAN_SIZE, totalSize);
|
||||
if (!data.has(lastNeededKey - 1)) {
|
||||
this.loadRows(lastVisibleKey + 1, lastNeededKey);
|
||||
const lastVisibleKey =
|
||||
visibleItems.peekSmallest()?.[0]
|
||||
?? bigInt(this.estimateIndexFromScrollTop(scrollTop + windowHeight)!);
|
||||
const lastNeededKey = bigIntUtils.min(lastVisibleKey.add(bigInt(this.OVERSCAN_SIZE)), bigInt(totalSize));
|
||||
|
||||
if (!data.has(lastNeededKey.subtract(bigInt.one))) {
|
||||
this.loadRows(lastVisibleKey.add(bigInt.one), lastNeededKey);
|
||||
}
|
||||
onCalculateVisibleItems ? onCalculateVisibleItems(visibleItems) : null;
|
||||
this.setState({
|
||||
startgap: Number(startgap.toFixed()),
|
||||
visibleItems: new Map([...endBuffer, ...visibleItems, ...startBuffer]),
|
||||
visibleItems: new BigIntOrderedMap([...startBuffer, ...visibleItems, ...endBuffer]),
|
||||
endgap: Number(endgap.toFixed()),
|
||||
});
|
||||
}
|
||||
|
||||
loadRows(start, end) {
|
||||
if (isNaN(start) || isNaN(end)) return;
|
||||
loadRows(start: BigInteger, end: BigInteger) {
|
||||
if (this.pendingLoad?.timeout) {
|
||||
clearTimeout(this.pendingLoad.timeout);
|
||||
start = Math.min(start, this.pendingLoad.start);
|
||||
end = Math.max(end, this.pendingLoad.end);
|
||||
start = bigIntUtils.min(start, this.pendingLoad.start);
|
||||
end = bigIntUtils.max(end, this.pendingLoad.end);
|
||||
}
|
||||
this.pendingLoad = {
|
||||
timeout: setTimeout(() => {
|
||||
if (!this.pendingLoad) return;
|
||||
start = Math.max(this.pendingLoad.start, 0);
|
||||
end = Math.min(Math.max(this.pendingLoad.end, 0), this.props.size);
|
||||
start = bigIntUtils.max(this.pendingLoad.start, bigInt.zero);
|
||||
end = bigIntUtils.min(bigIntUtils.max(this.pendingLoad.end, bigInt.zero), bigInt(this.props.size));
|
||||
if (start < end) {
|
||||
this.props.loadRows(start, end);
|
||||
}
|
||||
@ -204,11 +223,11 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
return new Map([
|
||||
['ArrowUp', this.state.averageHeight],
|
||||
['ArrowDown', this.state.averageHeight * -1],
|
||||
['PageUp', this.window.offsetHeight],
|
||||
['PageDown', this.window.offsetHeight * -1],
|
||||
['Home', this.window.scrollHeight],
|
||||
['End', this.window.scrollHeight * -1],
|
||||
['Space', this.window.offsetHeight * -1]
|
||||
['PageUp', this.window!.offsetHeight],
|
||||
['PageDown', this.window!.offsetHeight * -1],
|
||||
['Home', this.window!.scrollHeight],
|
||||
['End', this.window!.scrollHeight * -1],
|
||||
['Space', this.window!.offsetHeight * -1]
|
||||
]);
|
||||
}
|
||||
|
||||
@ -217,11 +236,11 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
if (map.has(event.code) && document.body.isSameNode(document.activeElement)) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
let distance = map.get(event.code);
|
||||
let distance = map.get(event.code)!;
|
||||
if (event.code === 'Space' && event.shiftKey) {
|
||||
distance = distance * -1;
|
||||
}
|
||||
this.window.scrollBy(0, distance);
|
||||
this.window!.scrollBy(0, distance);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -242,12 +261,13 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
|
||||
this.window = element;
|
||||
if (this.props.origin === 'bottom') {
|
||||
element.addEventListener('wheel', (event) => {
|
||||
/* element.addEventListener('wheel', (event) => {
|
||||
event.preventDefault();
|
||||
const normalized = normalizeWheel(event);
|
||||
element.scrollBy(0, normalized.pixelY * -1);
|
||||
return false;
|
||||
}, { passive: false });
|
||||
*/
|
||||
window.addEventListener('keydown', this.invertedKeyHandler, { passive: false });
|
||||
}
|
||||
this.resetScroll();
|
||||
@ -304,12 +324,12 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
data
|
||||
} = this.props;
|
||||
|
||||
const indexesToRender = Array.from(visibleItems.keys());
|
||||
const indexesToRender = visibleItems.keys().reverse();
|
||||
|
||||
const transform = origin === 'top' ? 'scale3d(1, 1, 1)' : 'scale3d(1, -1, 1)';
|
||||
|
||||
const render = (index) => {
|
||||
const measure = (element) => {
|
||||
const render = (index: BigInteger) => {
|
||||
const measure = (element: any) => {
|
||||
if (element) {
|
||||
this.cache.set(index, {
|
||||
height: element.offsetHeight,
|
||||
|
Loading…
Reference in New Issue
Block a user