mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-13 20:10:07 +02:00
Add filter functionality for infinite album list
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Card } from '@mantine/core';
|
||||
import { Card, Skeleton } from '@mantine/core';
|
||||
import { motion } from 'framer-motion';
|
||||
import styled from 'styled-components';
|
||||
import { CardRow } from 'renderer/types';
|
||||
@@ -10,7 +10,6 @@ const CardWrapper = styled(motion.div)<{
|
||||
itemHeight: number;
|
||||
itemWidth: number;
|
||||
}>`
|
||||
display: flex;
|
||||
flex: ${({ itemWidth }) => `0 0 ${itemWidth}px`};
|
||||
width: ${({ itemWidth }) => `${itemWidth}px`};
|
||||
height: ${({ itemHeight }) => `${itemHeight}px`};
|
||||
@@ -46,9 +45,17 @@ const ImageSection = styled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const Image = styled(motion.div)<{ height: number; src: string }>`
|
||||
interface ImageProps {
|
||||
height: number;
|
||||
src: string;
|
||||
}
|
||||
|
||||
const Image = styled(motion.div).attrs((props: ImageProps) => ({
|
||||
style: {
|
||||
background: `url(${props.src})`,
|
||||
},
|
||||
}))<ImageProps>`
|
||||
height: ${({ height }) => `${height}px`};
|
||||
background: ${({ src }) => `url(${src})`};
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border: 0;
|
||||
@@ -82,9 +89,9 @@ export const GridCard = ({ data, index, style, isScrolling }: any) => {
|
||||
itemGap,
|
||||
itemCount,
|
||||
cardControls,
|
||||
handlePlayQueueAdd,
|
||||
cardRows,
|
||||
itemData,
|
||||
handlePlayQueueAdd,
|
||||
} = data;
|
||||
|
||||
const startIndex = index * columnCount;
|
||||
@@ -98,45 +105,33 @@ export const GridCard = ({ data, index, style, isScrolling }: any) => {
|
||||
itemGap={itemGap}
|
||||
itemHeight={itemHeight}
|
||||
itemWidth={itemWidth}
|
||||
tabIndex={0}
|
||||
>
|
||||
<StyledCard>
|
||||
<ImageSection>
|
||||
<Image
|
||||
animate={{
|
||||
opacity: 1,
|
||||
}}
|
||||
height={itemWidth}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
}}
|
||||
src={itemData[i]?.image}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: 'anticipate',
|
||||
}}
|
||||
>
|
||||
{!isScrolling && (
|
||||
<Skeleton visible={!itemData[i]}>
|
||||
<StyledCard>
|
||||
<ImageSection>
|
||||
<Image height={itemWidth} src={itemData[i]?.image}>
|
||||
<ControlsContainer>
|
||||
<GridCardControls
|
||||
cardControls={cardControls}
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
itemData={itemData[i]}
|
||||
/>
|
||||
{!isScrolling && (
|
||||
<GridCardControls
|
||||
cardControls={cardControls}
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
itemData={itemData[i]}
|
||||
/>
|
||||
)}
|
||||
</ControlsContainer>
|
||||
)}
|
||||
</Image>
|
||||
</ImageSection>
|
||||
<DetailSection>
|
||||
{cardRows.map((row: CardRow) => (
|
||||
<Row key={`row-${row.prop}`}>
|
||||
<Text overflow="hidden" weight={500}>
|
||||
{itemData[i] && itemData[i][row.prop]}
|
||||
</Text>
|
||||
</Row>
|
||||
))}
|
||||
</DetailSection>
|
||||
</StyledCard>
|
||||
</Image>
|
||||
</ImageSection>
|
||||
<DetailSection>
|
||||
{cardRows.map((row: CardRow) => (
|
||||
<Row key={`row-${row.prop}`}>
|
||||
<Text overflow="hidden" weight={500}>
|
||||
{itemData[i] && itemData[i][row.prop]}
|
||||
</Text>
|
||||
</Row>
|
||||
))}
|
||||
</DetailSection>
|
||||
</StyledCard>
|
||||
</Skeleton>
|
||||
</CardWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ export const VirtualGridWrapper = ({
|
||||
itemCount,
|
||||
columnCount,
|
||||
rowCount,
|
||||
itemData,
|
||||
...rest
|
||||
}: Omit<FixedSizeListProps, 'ref' | 'itemSize' | 'children'> & {
|
||||
cardControls: any;
|
||||
cardRows: CardRow[];
|
||||
columnCount: number;
|
||||
itemData: any[];
|
||||
itemGap: number;
|
||||
itemHeight: number;
|
||||
itemWidth: number;
|
||||
@@ -27,39 +29,37 @@ export const VirtualGridWrapper = ({
|
||||
}) => {
|
||||
const { handlePlayQueueAdd } = usePlayQueueHandler();
|
||||
|
||||
const itemData = useMemo(
|
||||
const memo = useMemo(
|
||||
() => ({
|
||||
cardControls,
|
||||
cardRows,
|
||||
columnCount,
|
||||
handlePlayQueueAdd,
|
||||
itemCount,
|
||||
itemData: rest.itemData,
|
||||
itemData,
|
||||
itemGap,
|
||||
itemHeight,
|
||||
itemWidth,
|
||||
}),
|
||||
[
|
||||
cardRows,
|
||||
cardControls,
|
||||
cardRows,
|
||||
columnCount,
|
||||
handlePlayQueueAdd,
|
||||
itemCount,
|
||||
rest.itemData,
|
||||
itemData,
|
||||
itemGap,
|
||||
itemHeight,
|
||||
itemWidth,
|
||||
handlePlayQueueAdd,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<FixedSizeList
|
||||
style={{ scrollBehavior: 'smooth' }}
|
||||
{...rest}
|
||||
ref={refInstance}
|
||||
initialScrollOffset={0}
|
||||
{...rest}
|
||||
itemCount={rowCount}
|
||||
itemData={itemData}
|
||||
itemData={memo}
|
||||
itemSize={itemHeight + itemGap}
|
||||
overscanCount={10}
|
||||
>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { forwardRef, Ref, useState } from 'react';
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeListProps } from 'react-window';
|
||||
import InfiniteLoader from 'react-window-infinite-loader';
|
||||
import { CardRow } from 'renderer/types';
|
||||
import { VirtualGridWrapper } from './VirtualGridWrapper';
|
||||
|
||||
interface VirtualGridProps
|
||||
extends Omit<
|
||||
FixedSizeListProps,
|
||||
'children' | 'itemSize' | 'height' | 'width'
|
||||
> {
|
||||
extends Omit<FixedSizeListProps, 'children' | 'itemSize'> {
|
||||
cardControls: any;
|
||||
cardRows: CardRow[];
|
||||
itemGap?: number;
|
||||
@@ -20,119 +16,113 @@ interface VirtualGridProps
|
||||
queryParams?: Record<string, any>;
|
||||
}
|
||||
|
||||
export const VirtualInfiniteGrid = forwardRef(
|
||||
(
|
||||
{
|
||||
itemCount,
|
||||
itemGap,
|
||||
itemSize,
|
||||
cardControls,
|
||||
cardRows,
|
||||
minimumBatchSize,
|
||||
query,
|
||||
queryParams,
|
||||
}: VirtualGridProps,
|
||||
ref: Ref<InfiniteLoader>
|
||||
) => {
|
||||
const [itemData, setItemData] = useState<any[]>([]);
|
||||
export const VirtualInfiniteGrid = ({
|
||||
itemCount,
|
||||
itemGap,
|
||||
itemSize,
|
||||
cardControls,
|
||||
cardRows,
|
||||
minimumBatchSize,
|
||||
query,
|
||||
queryParams,
|
||||
height,
|
||||
width,
|
||||
}: VirtualGridProps) => {
|
||||
const [itemData, setItemData] = useState<any[]>([]);
|
||||
const listRef = useRef<any>(null);
|
||||
const loader = useRef<InfiniteLoader>(null);
|
||||
|
||||
const isItemLoaded = (index: number, columnCount: number) => {
|
||||
const itemIndex = index * columnCount;
|
||||
|
||||
return (
|
||||
itemIndex < itemData.length * columnCount &&
|
||||
itemData[itemIndex] !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
const loadMoreItems = async (
|
||||
startIndex: number,
|
||||
stopIndex: number,
|
||||
limit: number,
|
||||
columnCount: number
|
||||
) => {
|
||||
const currentPage = Math.ceil(startIndex / minimumBatchSize!);
|
||||
|
||||
const t = await query({
|
||||
limit,
|
||||
page: currentPage,
|
||||
...queryParams,
|
||||
});
|
||||
|
||||
// Need to multiply by columnCount due to the grid layout
|
||||
const start = startIndex * columnCount;
|
||||
const end = (stopIndex + 1) * columnCount;
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
const newData: any[] = [...itemData];
|
||||
|
||||
let itemIndex = 0;
|
||||
for (let rowIndex = start; rowIndex < end; rowIndex += 1) {
|
||||
newData[rowIndex] = t?.data[itemIndex];
|
||||
itemIndex += 1;
|
||||
}
|
||||
|
||||
setItemData(newData);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const debouncedLoadMoreItems = debounce(loadMoreItems, 300);
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => {
|
||||
const itemHeight = itemSize! + cardRows.length * 25;
|
||||
|
||||
const columnCount = Math.floor(
|
||||
(Number(width) - itemGap! + 3) / (itemSize! + itemGap! + 2)
|
||||
);
|
||||
|
||||
const rowCount = Math.ceil(itemCount / columnCount);
|
||||
|
||||
const pageItemLimit = columnCount * minimumBatchSize!;
|
||||
|
||||
return (
|
||||
<InfiniteLoader
|
||||
ref={ref}
|
||||
isItemLoaded={(index) => isItemLoaded(index, columnCount)}
|
||||
itemCount={itemCount || 0}
|
||||
loadMoreItems={(startIndex, stopIndex) =>
|
||||
debouncedLoadMoreItems(
|
||||
startIndex,
|
||||
stopIndex,
|
||||
pageItemLimit,
|
||||
columnCount
|
||||
)
|
||||
}
|
||||
minimumBatchSize={minimumBatchSize}
|
||||
threshold={10}
|
||||
>
|
||||
{({ onItemsRendered, ref: infiniteLoaderRef }) => (
|
||||
<VirtualGridWrapper
|
||||
useIsScrolling
|
||||
cardControls={cardControls}
|
||||
cardRows={cardRows}
|
||||
columnCount={columnCount}
|
||||
height={height}
|
||||
itemCount={itemCount || 0}
|
||||
itemData={itemData}
|
||||
itemGap={itemGap!}
|
||||
itemHeight={itemHeight!}
|
||||
itemWidth={itemSize}
|
||||
refInstance={infiniteLoaderRef}
|
||||
rowCount={rowCount}
|
||||
width={width}
|
||||
onItemsRendered={onItemsRendered}
|
||||
/>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
const { itemHeight, rowCount, columnCount } = useMemo(() => {
|
||||
const itemsPerRow = Math.floor(
|
||||
(Number(width) - itemGap! + 3) / (itemSize! + itemGap! + 2)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
columnCount: itemsPerRow,
|
||||
itemHeight: itemSize! + cardRows.length * 25,
|
||||
rowCount: Math.ceil(itemCount / itemsPerRow),
|
||||
};
|
||||
}, [cardRows.length, itemCount, itemGap, itemSize, width]);
|
||||
|
||||
const isItemLoaded = (index: number) => {
|
||||
const itemIndex = index * columnCount;
|
||||
|
||||
return itemData[itemIndex] !== undefined;
|
||||
};
|
||||
|
||||
const loadMoreItems = async (startIndex: number, stopIndex: number) => {
|
||||
// Fixes a caching bug(?) when switching between filters and the itemCount increases
|
||||
if (startIndex === 1) return;
|
||||
|
||||
// Need to multiply by columnCount due to the grid layout
|
||||
const start = startIndex * columnCount;
|
||||
const end = stopIndex * columnCount + columnCount;
|
||||
|
||||
const t = await query({
|
||||
limit: end - start,
|
||||
skip: start,
|
||||
...queryParams,
|
||||
});
|
||||
|
||||
const newData: any[] = [...itemData];
|
||||
|
||||
let itemIndex = 0;
|
||||
for (let rowIndex = start; rowIndex < end; rowIndex += 1) {
|
||||
newData[rowIndex] = t.data[itemIndex];
|
||||
itemIndex += 1;
|
||||
}
|
||||
|
||||
setItemData(newData);
|
||||
};
|
||||
|
||||
const debouncedLoadMoreItems = debounce(loadMoreItems, 300);
|
||||
|
||||
useEffect(() => {
|
||||
if (loader.current) {
|
||||
listRef.current.scrollTo(0);
|
||||
loader.current.resetloadMoreItemsCache(true);
|
||||
setItemData(() => []);
|
||||
|
||||
loadMoreItems(0, minimumBatchSize! * 2);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minimumBatchSize, queryParams, setItemData]);
|
||||
|
||||
return (
|
||||
<InfiniteLoader
|
||||
ref={loader}
|
||||
isItemLoaded={(index) => isItemLoaded(index)}
|
||||
itemCount={itemCount || 0}
|
||||
loadMoreItems={(startIndex, stopIndex) =>
|
||||
debouncedLoadMoreItems(startIndex, stopIndex)
|
||||
}
|
||||
minimumBatchSize={minimumBatchSize}
|
||||
threshold={30}
|
||||
>
|
||||
{({ onItemsRendered, ref: infiniteLoaderRef }) => (
|
||||
<VirtualGridWrapper
|
||||
useIsScrolling
|
||||
cardControls={cardControls}
|
||||
cardRows={cardRows}
|
||||
columnCount={columnCount}
|
||||
height={height}
|
||||
itemCount={itemCount || 0}
|
||||
itemData={itemData}
|
||||
itemGap={itemGap!}
|
||||
itemHeight={itemHeight!}
|
||||
itemWidth={itemSize}
|
||||
refInstance={(list) => {
|
||||
infiniteLoaderRef(list);
|
||||
listRef.current = list;
|
||||
}}
|
||||
rowCount={rowCount}
|
||||
width={width}
|
||||
onItemsRendered={onItemsRendered}
|
||||
/>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
);
|
||||
};
|
||||
|
||||
VirtualInfiniteGrid.defaultProps = {
|
||||
itemGap: 10,
|
||||
|
||||
Reference in New Issue
Block a user