Timeline: Add alternate height measurement cache

This commit is contained in:
Evan Hahn 2022-02-11 16:28:28 -06:00 committed by GitHub
parent 0174687542
commit 48137a498c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 61 deletions

View File

@ -23,52 +23,6 @@ index d9716a0..e7a9f9f 100644
});
}
}
diff --git a/node_modules/react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.js b/node_modules/react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.js
index 262776b..156cf0f 100644
--- a/node_modules/react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.js
+++ b/node_modules/react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.js
@@ -65,6 +65,7 @@ var CellMeasurerCache = function () {
minWidth = params.minWidth;
+ this._highWaterMark = 0;
this._hasFixedHeight = fixedHeight === true;
this._hasFixedWidth = fixedWidth === true;
this._minHeight = minHeight || 0;
@@ -101,6 +102,24 @@ var CellMeasurerCache = function () {
this._updateCachedColumnAndRowSizes(rowIndex, columnIndex);
}
+ }, {
+ key: 'clearPlus',
+ value: function clearPlus(rowIndex) {
+ var columnIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
+
+ if (this._highWaterMark <= rowIndex) {
+ this.clear(rowIndex, columnIndex);
+ return;
+ }
+
+ for (let i = rowIndex, max = this._highWaterMark; i <= max; i += 1) {
+ var key = this._keyMapper(i, columnIndex);
+ delete this._cellHeightCache[key];
+ delete this._cellWidthCache[key];
+ }
+
+ this._highWaterMark = Math.max(0, rowIndex - 1);
+ }
}, {
key: 'clearAll',
value: function clearAll() {
@@ -168,6 +187,8 @@ var CellMeasurerCache = function () {
this._rowCount = rowIndex + 1;
}
+ this._highWaterMark = Math.max(this._highWaterMark, rowIndex);
+
// Size is cached per cell so we don't have to re-measure if cells are re-ordered.
this._cellHeightCache[key] = height;
this._cellWidthCache[key] = width;
diff --git a/node_modules/react-virtualized/dist/commonjs/Grid/Grid.js b/node_modules/react-virtualized/dist/commonjs/Grid/Grid.js
index e1b959a..09c16c5 100644
--- a/node_modules/react-virtualized/dist/commonjs/Grid/Grid.js

View File

@ -7,12 +7,7 @@ import type { ReactChild, ReactNode, RefObject } from 'react';
import React from 'react';
import { createSelector } from 'reselect';
import type { Grid, ListRowProps } from 'react-virtualized';
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List,
} from 'react-virtualized';
import { AutoSizer, CellMeasurer, List } from 'react-virtualized';
import Measure from 'react-measure';
import * as log from '../../logging/log';
@ -42,6 +37,7 @@ import type { GroupNameCollisionsWithIdsByTitle } from '../../util/groupMemberNa
import { hasUnacknowledgedCollisions } from '../../util/groupMemberNameCollisions';
import { TimelineFloatingHeader } from './TimelineFloatingHeader';
import {
RowHeightCache,
fromItemIndexToRow,
fromRowToItemIndex,
getEphemeralRows,
@ -52,6 +48,7 @@ import {
getWidthBreakpoint,
} from '../../util/timelineUtil';
const ESTIMATED_ROW_HEIGHT = 64;
const AT_BOTTOM_THRESHOLD = 15;
const NEAR_BOTTOM_THRESHOLD = 15;
const AT_TOP_THRESHOLD = 10;
@ -301,10 +298,7 @@ const getActions = createSelector(
);
export class Timeline extends React.PureComponent<PropsType, StateType> {
private cellSizeCache = new CellMeasurerCache({
defaultHeight: 64,
fixedWidth: true,
});
private cellSizeCache = new RowHeightCache(ESTIMATED_ROW_HEIGHT);
private mostRecentWidth = 0;
@ -442,9 +436,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
this.offsetFromBottom = undefined;
this.resizeFlag = false;
if (isNumber(row) && row > 0) {
// This is a private interface we want to use.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.cellSizeCache as any).clearPlus(row, 0);
this.cellSizeCache.clearPlus(row);
} else {
this.cellSizeCache.clearAll();
}
@ -881,6 +873,11 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
);
};
private getRowHeightFromCache = ({
index,
}: Readonly<{ index: number }>): number =>
this.cellSizeCache.getHeight(index);
private scrollToBottom = (setFocus?: boolean): void => {
const { selectMessage, id, items } = this.props;
@ -1342,7 +1339,11 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
return (
<List
deferredMeasurementCache={this.cellSizeCache}
// React Virtualized has an incorrect type for this prop. Until [a fix][0]
// is merged, we have to do this cast.
// [0]: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/58705
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deferredMeasurementCache={this.cellSizeCache as any}
height={height}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onScroll={this.onScroll as any}
@ -1350,7 +1351,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
onRowsRendered={this.onRowsRendered}
ref={this.listRef}
rowCount={rowCount}
rowHeight={this.cellSizeCache.rowHeight}
rowHeight={this.getRowHeightFromCache}
rowRenderer={this.rowRenderer}
scrollToAlignment="start"
scrollToIndex={scrollToIndex}

View File

@ -1,10 +1,72 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { CellMeasurerCacheInterface } from 'react-virtualized/dist/es/CellMeasurer';
import { isNumber } from 'lodash';
import type { PropsType } from '../components/conversation/Timeline';
import { WidthBreakpoint } from '../components/_util';
export class RowHeightCache implements CellMeasurerCacheInterface {
private readonly cache = new Map<number, number>();
private highestRowIndexSeen = 0;
constructor(private readonly estimatedRowHeight: number) {}
hasFixedWidth(): boolean {
return true;
}
getWidth(): number {
// If the cache has a fixed width, we can just return a fixed value. See [the
// React Virtualized source code][0] for an example.
// [0]: https://github.com/bvaughn/react-virtualized/blob/abe0530a512639c042e74009fbf647abdb52d661/source/CellMeasurer/CellMeasurerCache.js#L6
return 100;
}
hasFixedHeight(): boolean {
return false;
}
getHeight(rowIndex: number): number {
return this.cache.get(rowIndex) ?? this.estimatedRowHeight;
}
has(rowIndex: number): boolean {
return this.cache.has(rowIndex);
}
set(
rowIndex: number,
_columnIndex: number,
_width: number,
height: number
): void {
this.cache.set(rowIndex, height);
this.highestRowIndexSeen = Math.max(this.highestRowIndexSeen, rowIndex);
}
clearPlus(rowIndex: number): void {
if (rowIndex <= 0) {
this.clearAll();
} else {
for (let i = rowIndex; i <= this.highestRowIndexSeen; i += 1) {
this.cache.delete(i);
}
this.highestRowIndexSeen = Math.min(
this.highestRowIndexSeen,
rowIndex - 1
);
}
}
clearAll(): void {
this.cache.clear();
this.highestRowIndexSeen = 0;
}
}
export function fromItemIndexToRow(
itemIndex: number,
{