Skip to content
This repository has been archived by the owner on Feb 19, 2025. It is now read-only.

Commit

Permalink
Added virtual list
Browse files Browse the repository at this point in the history
  • Loading branch information
S0ulDrag0n committed May 24, 2023
1 parent 5a82cc9 commit b5ee2d0
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 13 deletions.
84 changes: 77 additions & 7 deletions src/components/PageExtractionModal/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,110 @@
import React, { createElement } from "react";

interface ListItemInputProps {
ref: any;
item: any;
render: any;
isVisible: boolean;
parentAddEventListener: any;
parentRemoveEventListener: any;
}

interface ListItemState {
renderTarget: any;
shouldRenderItem: boolean;
isVisible: boolean;
}

class ListItem extends React.Component<ListItemInputProps, ListItemState> {
private static MAX_SIZE = 0;
private _containerRef: React.RefObject<HTMLDivElement>;
private _measurementRef: React.RefObject<HTMLDivElement>;
private _resizeObserver: ResizeObserver;
private _height = 0;
private _scrollHandle: any;
constructor(props: ListItemInputProps) {
super(props);
this._containerRef = React.createRef();
this._measurementRef = React.createRef();
this._resizeObserver = new ResizeObserver(() => {
const rect = this._measurementRef.current?.getBoundingClientRect();
if (!rect || rect.height === 0 || (this._height && rect.height < this._height)) {
return;
}
this._height = rect.height;
if (this._height > ListItem.MAX_SIZE) {
ListItem.MAX_SIZE = this._height;
}
});
this.props.parentAddEventListener("scroll", this.onParentScroll);
const renderTarget = this.props.render(this.props.item);
const isPromise = renderTarget instanceof Promise;
if (isPromise) {
renderTarget.then((result: any) => this.setState({ renderTarget: result, shouldRenderItem: true }));
}
this.state = {
renderTarget: isPromise ? undefined : renderTarget,
shouldRenderItem: !isPromise
shouldRenderItem: !isPromise,
isVisible: true
};
}
componentDidMount(): void {
// @ts-ignore
this._resizeObserver.observe(this._measurementRef.current);
}
componentWillUnmount(): void {
if (this._measurementRef.current) {
this._resizeObserver.unobserve(this._measurementRef.current);
}
this.props.parentRemoveEventListener("scroll", this.onParentScroll);
}
onParentScroll = (parentRect: any, _scrollTop: number, padding: number) => {
clearTimeout(this._scrollHandle);
this._scrollHandle = setTimeout(() => {
const rect = this._containerRef.current?.getBoundingClientRect();
if (this.doRectanglesIntersect(parentRect, rect, padding)) {
this.setState({ isVisible: true });
} else {
this.setState({ isVisible: false });
}
}, 100);
};
doRectanglesIntersect = (rect1: any, rect2: any, padding = 13): boolean => {
const itemPadding = ListItem.MAX_SIZE * padding;
const rect1Top = rect1.y - itemPadding;
const rect1Bottom = rect1.y + rect1.height + itemPadding;
const rect2Top = rect2.y;
const rect2Bottom = rect2.y + rect2.height;

const verticalIntersection = rect1Top < rect2Bottom && rect1Bottom > rect2Top;

return verticalIntersection;
};
render(): JSX.Element {
if (!this.props.isVisible || !this.state.shouldRenderItem) {
if (!this.state.shouldRenderItem) {
return <></>;
}
return (
<div
ref={this.props.ref}
style={{ display: "flex", alignItems: "center", justifyContent: "center", margin: "0.5em 0px" }}
ref={this._containerRef}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "0.5em 0px",
// DEBUGGING ONLY
// backgroundColor: this.state.isVisible ? "green" : "red"
}}
>
{this.state.renderTarget}
<div ref={this._measurementRef}>
{this.state.isVisible ? (
this.state.renderTarget
) : (
<div
style={{
height: `${ListItem.MAX_SIZE < this._height ? this._height : ListItem.MAX_SIZE}px`
}}
></div>
)}
</div>
</div>
);
}
Expand Down
55 changes: 50 additions & 5 deletions src/components/PageExtractionModal/VirtualList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,59 @@ import ListItem from "./ListItem";

interface VirtualListInputProps {
items: any[];
numItems: number;
padding: number;
render: any;
height: string | number;
}

class VirtualList extends React.Component<VirtualListInputProps> {
private scrollContainerRef: React.RefObject<HTMLDivElement>;
private _scrollContainerRef: React.RefObject<HTMLDivElement>;
private _eventListeners: Record<string, any[]>;
constructor(props: VirtualListInputProps) {
super(props);
this.scrollContainerRef = React.createRef();
this._scrollContainerRef = React.createRef();
this._eventListeners = {};
}
componentDidMount(): void {
this.trigger("mount", this._scrollContainerRef.current?.getBoundingClientRect());
}
addEventListener = (event: string, handler: any): void => {
if (!this._eventListeners[event]) {
this._eventListeners[event] = [];
}
if (handler) {
this._eventListeners[event].push(handler);
}
};
removeEventListener = (event: string, handler: any): void => {
if (!handler || !this._eventListeners[event]) {
return;
}
this._eventListeners[event] = this._eventListeners[event].reduce((acc, existingHandler) => {
if (existingHandler !== handler) {
acc.push(existingHandler);
}
return acc;
}, []);
};
trigger = (event: string, ...parameters: any[]): void => {
if (!this._eventListeners[event]) {
return;
}
this._eventListeners[event].forEach(handler => handler(...parameters));
};
onScroll = (): void => {
this.trigger(
"scroll",
this._scrollContainerRef.current?.getBoundingClientRect(),
this._scrollContainerRef.current?.scrollTop,
this.props.padding
);
};
render(): JSX.Element {
return (
<div
ref={this.scrollContainerRef}
ref={this._scrollContainerRef}
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
Expand All @@ -26,9 +64,16 @@ class VirtualList extends React.Component<VirtualListInputProps> {
overflow: "auto",
height: this.props.height
}}
onScroll={this.onScroll}
>
{this.props.items.map((item, i) => (
<ListItem key={i} item={item} render={this.props.render} isVisible ref={undefined} />
<ListItem
key={i}
item={item}
render={this.props.render}
parentAddEventListener={this.addEventListener}
parentRemoveEventListener={this.removeEventListener}
/>
))}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/PageExtractionModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ class PageExtractionModal extends React.Component<PageExtractionModalInputProps,
</div>
<VirtualList
height="400px"
numItems={9}
padding={13}
render={this.loadThumbnail}
items={this.state.pageCount}
/>
Expand Down

0 comments on commit b5ee2d0

Please sign in to comment.