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:
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* 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 Grid, { GridTypeMap } from '@mui/material/Grid';
|
||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
import { GridItemProps, GridStateSnapshot, VirtuosoGrid } from 'react-virtuoso';
|
import { GridItemProps, GridStateSnapshot, VirtuosoGrid } from 'react-virtuoso';
|
||||||
@@ -73,7 +73,9 @@ type DefaultGridProps = Pick<MangaCardProps, 'mode'> & {
|
|||||||
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const HorizontalGrid = ({
|
const HorizontalGrid = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
isLoading,
|
isLoading,
|
||||||
mangas,
|
mangas,
|
||||||
inLibraryIndicator,
|
inLibraryIndicator,
|
||||||
@@ -83,8 +85,11 @@ const HorizontalGrid = ({
|
|||||||
selectedMangaIds,
|
selectedMangaIds,
|
||||||
handleSelection,
|
handleSelection,
|
||||||
mode,
|
mode,
|
||||||
}: DefaultGridProps) => (
|
}: DefaultGridProps,
|
||||||
|
ref: ForwardedRef<HTMLDivElement | null>,
|
||||||
|
) => (
|
||||||
<Grid
|
<Grid
|
||||||
|
ref={ref}
|
||||||
container
|
container
|
||||||
spacing={1}
|
spacing={1}
|
||||||
style={{
|
style={{
|
||||||
@@ -114,9 +119,12 @@ const HorizontalGrid = ({
|
|||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const VerticalGrid = ({
|
const VerticalGrid = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
isLoading,
|
isLoading,
|
||||||
mangas,
|
mangas,
|
||||||
inLibraryIndicator,
|
inLibraryIndicator,
|
||||||
@@ -131,7 +139,9 @@ const VerticalGrid = ({
|
|||||||
}: DefaultGridProps & {
|
}: DefaultGridProps & {
|
||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
loadMore: () => void;
|
loadMore: () => void;
|
||||||
}) => {
|
},
|
||||||
|
ref: ForwardedRef<HTMLDivElement | null>,
|
||||||
|
) => {
|
||||||
const location = useLocation<{ snapshot?: GridStateSnapshot }>();
|
const location = useLocation<{ snapshot?: GridStateSnapshot }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { snapshot } = location.state ?? {};
|
const { snapshot } = location.state ?? {};
|
||||||
@@ -157,6 +167,7 @@ const VerticalGrid = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Box ref={ref}>
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
useWindowScroll
|
useWindowScroll
|
||||||
overscan={window.innerHeight * 0.25}
|
overscan={window.innerHeight * 0.25}
|
||||||
@@ -180,6 +191,7 @@ const VerticalGrid = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
{/* render div to prevent UI jumping around when showing/hiding loading placeholder */
|
{/* render div to prevent UI jumping around when showing/hiding loading placeholder */
|
||||||
/* eslint-disable-next-line no-nested-ternary */}
|
/* eslint-disable-next-line no-nested-ternary */}
|
||||||
{isSelectModeActive && gridLayout === GridLayout.List ? (
|
{isSelectModeActive && gridLayout === GridLayout.List ? (
|
||||||
@@ -192,7 +204,8 @@ const VerticalGrid = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export interface IMangaGridProps extends Omit<DefaultGridProps, 'GridItemContainer'> {
|
export interface IMangaGridProps extends Omit<DefaultGridProps, 'GridItemContainer'> {
|
||||||
message?: string;
|
message?: string;
|
||||||
@@ -221,9 +234,11 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
|
|||||||
mode,
|
mode,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const gridRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [dimensions, setDimensions] = useState(document.documentElement.offsetWidth);
|
const [dimensions, setDimensions] = useState(document.documentElement.offsetWidth);
|
||||||
const [gridItemWidth] = useLocalStorage<number>('ItemWidth', 300);
|
const [gridItemWidth] = useLocalStorage<number>('ItemWidth', 300);
|
||||||
const gridRef = useRef<HTMLDivElement>(null);
|
const gridWrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const GridItemContainer = useMemo(
|
const GridItemContainer = useMemo(
|
||||||
() => GridItemContainerWithDimension(dimensions, gridItemWidth, gridLayout),
|
() => GridItemContainerWithDimension(dimensions, gridItemWidth, gridLayout),
|
||||||
[dimensions, gridItemWidth, gridLayout],
|
[dimensions, gridItemWidth, gridLayout],
|
||||||
@@ -231,7 +246,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
|
|||||||
|
|
||||||
const updateGridWidth = () => {
|
const updateGridWidth = () => {
|
||||||
const getDimensions = () => {
|
const getDimensions = () => {
|
||||||
const gridWidth = gridRef.current?.offsetWidth;
|
const gridWidth = gridWrapperRef.current?.offsetWidth;
|
||||||
|
|
||||||
if (!gridWidth) {
|
if (!gridWidth) {
|
||||||
return document.documentElement.offsetWidth;
|
return document.documentElement.offsetWidth;
|
||||||
@@ -258,6 +273,36 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
|
|||||||
return () => window.removeEventListener('resize', onResize);
|
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;
|
const hasNoItems = !isLoading && mangas.length === 0;
|
||||||
if (hasNoItems) {
|
if (hasNoItems) {
|
||||||
if (noFaces) {
|
if (noFaces) {
|
||||||
@@ -278,7 +323,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={gridRef}
|
ref={gridWrapperRef}
|
||||||
style={{
|
style={{
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
paddingBottom: '13px',
|
paddingBottom: '13px',
|
||||||
@@ -286,6 +331,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
|
|||||||
>
|
>
|
||||||
{horizontal ? (
|
{horizontal ? (
|
||||||
<HorizontalGrid
|
<HorizontalGrid
|
||||||
|
ref={gridRef}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
mangas={mangas}
|
mangas={mangas}
|
||||||
inLibraryIndicator={inLibraryIndicator}
|
inLibraryIndicator={inLibraryIndicator}
|
||||||
@@ -298,6 +344,7 @@ export const MangaGrid: React.FC<IMangaGridProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<VerticalGrid
|
<VerticalGrid
|
||||||
|
ref={gridRef}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
mangas={mangas}
|
mangas={mangas}
|
||||||
inLibraryIndicator={inLibraryIndicator}
|
inLibraryIndicator={inLibraryIndicator}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import SettingsIcon from '@mui/icons-material/Settings';
|
|||||||
import { useQueryParam, StringParam } from 'use-query-params';
|
import { useQueryParam, StringParam } from 'use-query-params';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Link from '@mui/material/Link';
|
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 FavoriteIcon from '@mui/icons-material/Favorite';
|
||||||
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||||
@@ -194,9 +194,6 @@ export function SourceMangas() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setTitle, setAction } = useContext(NavBarContext);
|
const { setTitle, setAction } = useContext(NavBarContext);
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const isLargeScreen = useMediaQuery(theme.breakpoints.up('sm'));
|
|
||||||
|
|
||||||
const { sourceId } = useParams<{ sourceId: string }>();
|
const { sourceId } = useParams<{ sourceId: string }>();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -232,7 +229,7 @@ export function SourceMangas() {
|
|||||||
contentType,
|
contentType,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
filtersToApply,
|
filtersToApply,
|
||||||
isLargeScreen ? 2 : 1,
|
1,
|
||||||
);
|
);
|
||||||
const mangas = data?.fetchSourceManga.mangas ?? [];
|
const mangas = data?.fetchSourceManga.mangas ?? [];
|
||||||
const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false;
|
const hasNextPage = data?.fetchSourceManga.hasNextPage ?? false;
|
||||||
|
|||||||
Reference in New Issue
Block a user