Prevent nodes out of viewport triggering render (#296)

* Prevent nodes out of viewport triggering render

* Update frontend/src/common/SortedCollection.ts

Co-authored-by: David <dvdplm@gmail.com>

* Tweak the comment on `setFocus`, move it closer to `ref` and `hasChangedSince`

* Switch `SortedCollection.ref()` to a getter

Co-authored-by: David <dvdplm@gmail.com>
This commit is contained in:
Maciej Hirsz
2020-11-02 19:33:51 +01:00
committed by GitHub
parent 98cd3cfa12
commit 54039faa3b
3 changed files with 53 additions and 15 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ export class Connection {
public handleMessages = (messages: FeedMessage.Message[]) => {
const { nodes, chains, sortBy, selectedColumns } = this.state;
const ref = nodes.ref();
const { ref } = nodes;
const updateState: UpdateBound = (state) => {
this.state = this.update(state);
+43 -8
View File
@@ -97,11 +97,27 @@ export namespace SortedCollection {
export type StateRef = Opaque<number, 'SortedCollection.StateRef'>;
}
interface Focus {
start: number;
end: number;
}
export class SortedCollection<Item extends { id: number }> {
// Comparator function used to sort the collection
private compare: Compare<Item>;
// Mapping item `id` to the `Item`, this uses array as a structure with
// the assumption that `id`s provided are increments from `0`, and that
// vacant `id`s will be re-used in the future.
private map = Array<Maybe<Item>>();
// Actual sorted list of `Item`s.
private list = Array<Item>();
// Internal tracker for changes, this number increments whenever the
// order of the **focused** elements in the collection changes
private changeRef = 0;
// Marks the range of indicies that are focused for tracking.
// **Note:** `start` is inclusive, while `end` is exclusive (much like
// `Array.slice()`).
private focus: Focus = { start: 0, end: 0 };
constructor(compare: Compare<Item>) {
this.compare = compare;
@@ -114,10 +130,6 @@ export class SortedCollection<Item extends { id: number }> {
this.changeRef += 1;
}
public ref(): SortedCollection.StateRef {
return this.changeRef as SortedCollection.StateRef;
}
public add(item: Item) {
if (this.map.length <= item.id) {
// Grow map if item.id would be out of scope
@@ -131,9 +143,11 @@ export class SortedCollection<Item extends { id: number }> {
this.map[item.id] = item;
sortedInsert(item, this.list, this.compare);
const index = sortedInsert(item, this.list, this.compare);
this.changeRef += 1;
if (index < this.focus.end) {
this.changeRef += 1;
}
}
public remove(id: number) {
@@ -147,7 +161,9 @@ export class SortedCollection<Item extends { id: number }> {
this.list.splice(index, 1);
this.map[id] = null;
this.changeRef += 1;
if (index < this.focus.end) {
this.changeRef += 1;
}
}
public get(id: number): Maybe<Item> {
@@ -184,7 +200,13 @@ export class SortedCollection<Item extends { id: number }> {
const newIndex = sortedInsert(item, this.list, this.compare);
if (newIndex !== index) {
this.changeRef += 1;
const outOfFocus =
(index < this.focus.start && newIndex < this.focus.start) ||
(index >= this.focus.end && newIndex >= this.focus.end);
if (!outOfFocus) {
this.changeRef += 1;
}
}
}
@@ -207,6 +229,7 @@ export class SortedCollection<Item extends { id: number }> {
public mutEachAndSort(mutator: (item: Item) => void) {
this.list.forEach(mutator);
this.list.sort(this.compare);
this.changeRef += 1;
}
public clear() {
@@ -216,6 +239,18 @@ export class SortedCollection<Item extends { id: number }> {
this.changeRef += 1;
}
// Set a new `Focus`. Any changes to the order of items within the `Focus`
// will increment `changeRef`.
public setFocus(start: number, end: number) {
this.focus = { start, end };
}
// Get the reference to current ordering state of focused items.
public get ref(): SortedCollection.StateRef {
return this.changeRef as SortedCollection.StateRef;
}
// Check if order of focused items has changed since obtaining a `ref`.
public hasChangedSince(ref: SortedCollection.StateRef): boolean {
return this.changeRef > ref;
}
+9 -6
View File
@@ -52,11 +52,11 @@ export class List extends React.Component<List.Props, {}> {
}
public render() {
const { selectedColumns } = this.props.appState;
const { pins, sortBy } = this.props;
const { filter } = this.state;
const { pins, sortBy, appState } = this.props;
const { selectedColumns } = appState;
const { filter, listStart, listEnd } = this.state;
let nodes = this.props.appState.nodes.sorted();
let nodes = appState.nodes.sorted();
if (filter != null) {
nodes = nodes.filter(filter);
@@ -73,10 +73,13 @@ export class List extends React.Component<List.Props, {}> {
</React.Fragment>
);
}
// With filter present, we can no longer guarantee that focus corresponds
// to rendering view, so we put the whole list in focus
appState.nodes.setFocus(0, nodes.length);
} else {
appState.nodes.setFocus(listStart, listEnd);
}
const { listStart, listEnd } = this.state;
const height = TH_HEIGHT + nodes.length * TR_HEIGHT;
const transform = `translateY(${listStart * TR_HEIGHT}px)`;