Feature/automatically load more pages until scrollbar is visible (#667)

* Rename "grifRed" to "gridWrapperRef"

* Automatically fetch more pages until scrollbar is visible
This commit is contained in:
schroda
2024-03-23 23:34:33 +01:00
committed by GitHub
parent 83635faf95
commit 66d6c95dfd
2 changed files with 168 additions and 124 deletions

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import React, { ForwardedRef, forwardRef, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import Grid, { GridTypeMap } from '@mui/material/Grid';
import { Box, Typography } from '@mui/material';
import { GridItemProps, GridStateSnapshot, VirtuosoGrid } from 'react-virtuoso';
@@ -73,7 +73,9 @@ type DefaultGridProps = Pick<MangaCardProps, 'mode'> & {
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
};
const HorizontalGrid = ({
const HorizontalGrid = forwardRef(
(
{
isLoading,
mangas,
inLibraryIndicator,
@@ -83,8 +85,11 @@ const HorizontalGrid = ({
selectedMangaIds,
handleSelection,
mode,
}: DefaultGridProps) => (
}: DefaultGridProps,
ref: ForwardedRef<HTMLDivElement | null>,
) => (
<Grid
ref={ref}
container
spacing={1}
style={{
@@ -114,9 +119,12 @@ const HorizontalGrid = ({
))
)}
</Grid>
),
);
const VerticalGrid = ({
const VerticalGrid = forwardRef(
(
{
isLoading,
mangas,
inLibraryIndicator,
@@ -128,10 +136,12 @@ const VerticalGrid = ({
selectedMangaIds,
handleSelection,
mode,
}: DefaultGridProps & {
}: DefaultGridProps & {
hasNextPage: boolean;
loadMore: () => void;
}) => {
},
ref: ForwardedRef<HTMLDivElement | null>,
) => {
const location = useLocation<{ snapshot?: GridStateSnapshot }>();
const navigate = useNavigate();
const { snapshot } = location.state ?? {};
@@ -157,6 +167,7 @@ const VerticalGrid = ({
return (
<>
<Box ref={ref}>
<VirtuosoGrid
useWindowScroll
overscan={window.innerHeight * 0.25}
@@ -180,6 +191,7 @@ const VerticalGrid = ({
)
}
/>
</Box>
{/* render div to prevent UI jumping around when showing/hiding loading placeholder */
/* eslint-disable-next-line no-nested-ternary */}
{isSelectModeActive && gridLayout === GridLayout.List ? (
@@ -192,7 +204,8 @@ const VerticalGrid = ({
) : null}
</>
);
};
},
);
export interface IMangaGridProps extends Omit<DefaultGridProps, 'GridItemContainer'> {
message?: string;
@@ -221,9 +234,11 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
mode,
} = props;
const gridRef = useRef<HTMLDivElement>(null);
const [dimensions, setDimensions] = useState(document.documentElement.offsetWidth);
const [gridItemWidth] = useLocalStorage<number>('ItemWidth', 300);
const gridRef = useRef<HTMLDivElement>(null);
const gridWrapperRef = useRef<HTMLDivElement>(null);
const GridItemContainer = useMemo(
() => GridItemContainerWithDimension(dimensions, gridItemWidth, gridLayout),
[dimensions, gridItemWidth, gridLayout],
@@ -231,7 +246,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
const updateGridWidth = () => {
const getDimensions = () => {
const gridWidth = gridRef.current?.offsetWidth;
const gridWidth = gridWrapperRef.current?.offsetWidth;
if (!gridWidth) {
return document.documentElement.offsetWidth;
@@ -258,6 +273,36 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
return () => window.removeEventListener('resize', onResize);
}, []);
useEffect(() => {
if (!gridRef.current) {
return () => {};
}
if (gridRef.current.offsetHeight > document.documentElement.clientHeight) {
return () => {};
}
const resizeObserver = new ResizeObserver(() => {
const gridHeight = gridRef.current!.offsetHeight;
const isScrollbarVisible = gridHeight > document.documentElement.clientHeight;
if (!gridHeight) {
return;
}
if (isScrollbarVisible) {
resizeObserver.disconnect();
return;
}
loadMore();
resizeObserver.disconnect();
});
resizeObserver.observe(gridRef.current);
return () => resizeObserver.disconnect();
}, [loadMore]);
const hasNoItems = !isLoading && mangas.length === 0;
if (hasNoItems) {
if (noFaces) {
@@ -278,7 +323,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
return (
<div
ref={gridRef}
ref={gridWrapperRef}
style={{
overflow: 'hidden',
paddingBottom: '13px',
@@ -286,6 +331,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
>
{horizontal ? (
<HorizontalGrid
ref={gridRef}
isLoading={isLoading}
mangas={mangas}
inLibraryIndicator={inLibraryIndicator}
@@ -298,6 +344,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
/>
) : (
<VerticalGrid
ref={gridRef}
isLoading={isLoading}
mangas={mangas}
inLibraryIndicator={inLibraryIndicator}

View File

@@ -13,7 +13,7 @@ import SettingsIcon from '@mui/icons-material/Settings';
import { useQueryParam, StringParam } from 'use-query-params';
import { useTranslation } from 'react-i18next';
import Link from '@mui/material/Link';
import { Box, Button, styled, useTheme, useMediaQuery, Tooltip } from '@mui/material';
import { Box, Button, styled, Tooltip } from '@mui/material';
import FavoriteIcon from '@mui/icons-material/Favorite';
import NewReleasesIcon from '@mui/icons-material/NewReleases';
import FilterListIcon from '@mui/icons-material/FilterList';
@@ -194,9 +194,6 @@ export function SourceMangas() {
const { t } = useTranslation();
const { setTitle, setAction } = useContext(NavBarContext);
const theme = useTheme();
const isLargeScreen = useMediaQuery(theme.breakpoints.up('sm'));
const { sourceId } = useParams<{ sourceId: string }>();
const navigate = useNavigate();
@@ -232,7 +229,7 @@ export function SourceMangas() {
contentType,
searchTerm,
filtersToApply,
isLargeScreen ? 2 : 1,
1,
);
const mangas = data?.fetchSourceManga.mangas ?? [];
const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false;