From 50e9ea8e7d1ccc978d6181051d177c8de10df37a Mon Sep 17 00:00:00 2001 From: John Thomson Date: Thu, 6 Jan 2022 17:12:23 -0600 Subject: [PATCH] Lazy loading of collections --- .../collectionsTab/BookButton.tsx | 7 ++-- .../collectionsTab/BooksOfCollection.tsx | 32 +++++++++++++++++-- .../collectionsTab/CollectionsTabPane.tsx | 10 ++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/BloomBrowserUI/collectionsTab/BookButton.tsx b/src/BloomBrowserUI/collectionsTab/BookButton.tsx index bd4d93a0352e..95bc447c4d7a 100644 --- a/src/BloomBrowserUI/collectionsTab/BookButton.tsx +++ b/src/BloomBrowserUI/collectionsTab/BookButton.tsx @@ -27,6 +27,8 @@ import { MenuItemSpec } from "./BooksOfCollection"; +export const bookButtonHeight = 120; + export const BookButton: React.FunctionComponent<{ book: IBookInfo; collection: ICollection; @@ -190,7 +192,6 @@ export const BookButton: React.FunctionComponent<{ props.book.title ); - const buttonHeight = 120; const renameHeight = 40; const downSize = 14; // size of down-arrow icon @@ -270,7 +271,7 @@ export const BookButton: React.FunctionComponent<{ (teamCollectionStatus?.who ? " checkedOut" : "") } css={css` - height: ${buttonHeight}px; + height: ${bookButtonHeight}px; width: 90px; border: none; overflow: hidden; @@ -372,7 +373,7 @@ export const BookButton: React.FunctionComponent<{ height: ${renameHeight}px; margin-left: 1px; border: 1px solid ${kBloomLightBlue}; - top: ${buttonHeight - renameHeight - 6}px; + top: ${bookButtonHeight - renameHeight - 6}px; padding-top: 4px; position: absolute; font-size: 12px; diff --git a/src/BloomBrowserUI/collectionsTab/BooksOfCollection.tsx b/src/BloomBrowserUI/collectionsTab/BooksOfCollection.tsx index 67646725bd53..9b466b4f5152 100644 --- a/src/BloomBrowserUI/collectionsTab/BooksOfCollection.tsx +++ b/src/BloomBrowserUI/collectionsTab/BooksOfCollection.tsx @@ -5,7 +5,7 @@ import { BloomApi } from "../utils/bloomApi"; import Menu from "@material-ui/core/Menu"; import MenuItem from "@material-ui/core/MenuItem"; import NestedMenuItem from "material-ui-nested-menu-item"; -import { BookButton } from "./BookButton"; +import { BookButton, bookButtonHeight } from "./BookButton"; import { useMonitorBookSelection } from "../app/selectedBook"; import { element } from "prop-types"; import { useL10n } from "../react_components/l10nHooks"; @@ -13,6 +13,7 @@ import { useState } from "react"; import { useSubscribeToWebSocketForEvent } from "../utils/WebSocketManager"; import { Divider } from "@material-ui/core"; import { BookSelectionManager, useIsSelected } from "./bookSelectionManager"; +import LazyLoad from "react-lazyload"; export interface IBookInfo { id: string; @@ -33,6 +34,11 @@ export const BooksOfCollection: React.FunctionComponent<{ collectionId: string; isEditableCollection: boolean; manager: BookSelectionManager; + // If supplied, the collection will be wrapped in a LazyLoad so that most of its rendering + // isn't done until it is visible on screen. This requires that we can identify the containing + // element which actually scrolls the BooksOfCollection into and out of view. This prop is + // required to be a selector which will select that exact element. + lazyContainer?: string; }> = props => { if (!props.collectionId) { window.alert("null collectionId"); @@ -139,7 +145,13 @@ export const BooksOfCollection: React.FunctionComponent<{ props.collectionId ); - return ( + // This is an approximation. 5 buttons per line is about what we get in a default + // layout on a fairly typical screen. We'd get a better approximation if we used + // the width of a button and knew the width of the container. But I think this is good + // enough. Worst case, we expand a bit more than we need. + const collectionHeight = bookButtonHeight * Math.ceil(books.length / 5); + + const content = (
); + // There's no point in lazily loading an empty list of books. But more importantly, on early renders + // before we actually retrieve the list of books, books is always an empty array. If we render a + // LazyLoad at that point, it will have height zero, and then all of them fit on the page, and the + // LazyLoad code determines that they are all visible and expands all of them, and we don't get any + // laziness at all. + return props.lazyContainer && books.length > 0 ? ( + + {content} + + ) : ( + content + ); }; export interface MenuItemSpec { diff --git a/src/BloomBrowserUI/collectionsTab/CollectionsTabPane.tsx b/src/BloomBrowserUI/collectionsTab/CollectionsTabPane.tsx index 739ebf6616a7..4587f2382623 100644 --- a/src/BloomBrowserUI/collectionsTab/CollectionsTabPane.tsx +++ b/src/BloomBrowserUI/collectionsTab/CollectionsTabPane.tsx @@ -47,6 +47,16 @@ export const CollectionsTabPane: React.FunctionComponent<{}> = () => { collectionId={c.id} isEditableCollection={false} manager={manager} + // We need this selector to identify the element that scrolls the collections... + // the one that actually has overflow=auto. This is an element nested inside the + // SplitPane, and (since the documentation of react-collapse-pane is broken) I can't + // find any way to put an explicit id on that element. Nor is there a different + // class automatically applied to the second one. So all I can see to do is + // to get it by finding an element with these two classes that follows another one + // that has them. This is of course using knowledge of the implementation of SplitPane + // which we have no business using, and may break with the next version of SplitPane. + // But I can't find a better option. + lazyContainer=".Pane.horizontal ~ .Pane.horizontal" />
);