mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-19 03:11:20 +00:00
added pagination for lists
This commit is contained in:
parent
e5eff9827a
commit
f613054b9a
@ -1,22 +1,28 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import { CircularProgress } from '@mui/material';
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
|
||||
interface Props {
|
||||
onLoadMore: () => void
|
||||
}
|
||||
|
||||
const LazyLoad: React.FC<Props> = ({ onLoadMore }) => {
|
||||
const hasTriggeredRef = useRef(false); // Prevents multiple auto-triggers
|
||||
|
||||
const [ref, inView] = useInView({
|
||||
threshold: 0.7
|
||||
})
|
||||
threshold: 0.7,
|
||||
triggerOnce: false, // Allows multiple triggers, but we control when
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
onLoadMore()
|
||||
if (inView && !hasTriggeredRef.current) {
|
||||
hasTriggeredRef.current = true; // Set flag so it doesn’t trigger again immediately
|
||||
onLoadMore();
|
||||
setTimeout(() => {
|
||||
hasTriggeredRef.current = false; // Reset trigger after a short delay
|
||||
}, 1000);
|
||||
}
|
||||
}, [inView])
|
||||
}, [inView]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -24,11 +30,11 @@ const LazyLoad: React.FC<Props> = ({ onLoadMore }) => {
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
minHeight: '25px'
|
||||
height: '50px',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
><CircularProgress /></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LazyLoad
|
||||
export default LazyLoad;
|
||||
|
@ -1,26 +1,37 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export const useScrollTracker = (listName: string, hasList: boolean, disableScrollTracker?: boolean) => {
|
||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
||||
const [hasMount, setHasMount] = useState(false);
|
||||
const scrollPositionRef = useRef(0); // Store the last known scroll position
|
||||
|
||||
export const useScrollTracker = (listName: string) => {
|
||||
useEffect(() => {
|
||||
if (!listName) return;
|
||||
|
||||
if(disableScrollTracker) return
|
||||
if (!listName || !hasList) return;
|
||||
|
||||
const SCROLL_KEY = `scroll-position-${listName}`;
|
||||
|
||||
// 🔹 Restore saved scroll position for the given list
|
||||
// 🔹 Restore scroll when the component mounts
|
||||
const savedPosition = sessionStorage.getItem(SCROLL_KEY);
|
||||
if (savedPosition) {
|
||||
window.scrollTo(0, parseInt(savedPosition, 10));
|
||||
setTimeout(() => {
|
||||
setHasMount(true);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// 🔹 Capture scroll position before unmount
|
||||
const handleScroll = () => {
|
||||
sessionStorage.setItem(SCROLL_KEY, window.scrollY.toString());
|
||||
scrollPositionRef.current = window.scrollY; // Store the last known scroll position
|
||||
};
|
||||
|
||||
// 🔹 Save scroll position on scroll
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
sessionStorage.setItem(SCROLL_KEY, scrollPositionRef.current.toString());
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [listName]); // ✅ Only runs when listName changes
|
||||
};
|
||||
}, [listName, hasList, disableScrollTracker]);
|
||||
|
||||
return { elementRef, hasMount };
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ interface DynamicGridProps {
|
||||
gap?: number; // Spacing between grid items
|
||||
children: ReactNode
|
||||
minItemWidth?: number
|
||||
setColumnsPerRow: (columns: number)=> void;
|
||||
}
|
||||
|
||||
const DynamicGrid: React.FC<DynamicGridProps> = ({
|
||||
@ -13,12 +14,32 @@ const DynamicGrid: React.FC<DynamicGridProps> = ({
|
||||
minItemWidth = 200, // Minimum width per item
|
||||
gap = 10, // Space between items
|
||||
children,
|
||||
|
||||
setColumnsPerRow
|
||||
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const itemContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const updateColumns = () => {
|
||||
if (containerRef.current && itemContainerRef.current) {
|
||||
const containerWidth = containerRef.current.clientWidth;
|
||||
const itemWidth = itemContainerRef.current.clientWidth
|
||||
const calculatedColumns = Math.floor(containerWidth / itemWidth);
|
||||
setColumnsPerRow(calculatedColumns);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateColumns(); // Run on mount
|
||||
window.addEventListener("resize", updateColumns);
|
||||
return () => window.removeEventListener("resize", updateColumns);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: "100%" }}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(auto-fill, minmax(${minItemWidth}px, 1fr))`, // ✅ Expands to fit width
|
||||
@ -31,7 +52,7 @@ const DynamicGrid: React.FC<DynamicGridProps> = ({
|
||||
}}
|
||||
>
|
||||
{items.map((component, index) => (
|
||||
<div key={index} style={{ width: "100%", display: "flex", justifyContent: "center", maxWidth: '400px' }}>
|
||||
<div ref={index === 0 ? itemContainerRef : null} key={index} style={{ width: "100%", display: "flex", justifyContent: "center", maxWidth: '400px' }}>
|
||||
{component} {/* ✅ Renders user-provided component */}
|
||||
</div>
|
||||
))}
|
||||
|
@ -1,21 +1,24 @@
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import React, { useMemo, useRef, useState } from "react";
|
||||
import DynamicGrid from "./DynamicGrid";
|
||||
import LazyLoad from "../../common/LazyLoad";
|
||||
import { ListItem, useCacheStore } from "../../state/cache";
|
||||
import { ListItem } from "../../state/cache";
|
||||
import { QortalMetadata } from "../../types/interfaces/resources";
|
||||
import { ListItemWrapper } from "./ResourceListDisplay";
|
||||
import { DefaultLoaderParams, ListItemWrapper } from "./ResourceListDisplay";
|
||||
|
||||
interface HorizontalPaginatedListProps {
|
||||
items: QortalMetadata[];
|
||||
listItem: (item: ListItem, index: number) => React.ReactNode;
|
||||
loaderItem?: (status: "LOADING" | "ERROR") => React.ReactNode;
|
||||
onLoadMore: () => void;
|
||||
maxItems?: number;
|
||||
onLoadMore: (limit: number) => void;
|
||||
onLoadLess: (limit: number)=> void;
|
||||
minItemWidth?: number;
|
||||
gap?: number;
|
||||
isLoading?: boolean;
|
||||
onSeenLastItem?: (listItem: ListItem) => void;
|
||||
|
||||
isLoadingMore: boolean;
|
||||
limit: number,
|
||||
disablePagination?: boolean
|
||||
defaultLoaderParams?: DefaultLoaderParams;
|
||||
}
|
||||
|
||||
export const HorizontalPaginatedList = ({
|
||||
@ -23,84 +26,78 @@ export const HorizontalPaginatedList = ({
|
||||
listItem,
|
||||
loaderItem,
|
||||
onLoadMore,
|
||||
maxItems = 60,
|
||||
onLoadLess,
|
||||
minItemWidth,
|
||||
gap,
|
||||
isLoading,
|
||||
onSeenLastItem,
|
||||
|
||||
isLoadingMore,
|
||||
limit,
|
||||
disablePagination,
|
||||
defaultLoaderParams
|
||||
}: HorizontalPaginatedListProps) => {
|
||||
const [displayedItems, setDisplayedItems] = useState(items);
|
||||
|
||||
const lastItemRef= useRef<any>(null)
|
||||
const lastItemRef2= useRef<any>(null)
|
||||
const [columnsPerRow, setColumnsPerRow] = useState<null | number>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayedItems(items);
|
||||
}, [items]);
|
||||
const displayedLimit = useMemo(()=> {
|
||||
if(disablePagination) return limit || 20
|
||||
return Math.floor((limit || 20) / (columnsPerRow || 3)) * (columnsPerRow || 3);
|
||||
}, [columnsPerRow, disablePagination])
|
||||
|
||||
const preserveScroll = useCallback((updateFunction: () => void) => {
|
||||
const previousScrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
|
||||
const previousScrollWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
|
||||
|
||||
updateFunction(); // Perform the update (fetch new data, remove old)
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const newScrollWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
|
||||
document.documentElement.scrollLeft = document.body.scrollLeft =
|
||||
previousScrollLeft - (previousScrollWidth - newScrollWidth);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (displayedItems.length > maxItems) {
|
||||
preserveScroll(() => {
|
||||
const excess = displayedItems.length - maxItems;
|
||||
setDisplayedItems((prev) => prev.slice(excess)); // Trim from the start
|
||||
});
|
||||
}
|
||||
}, [displayedItems, maxItems, preserveScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
|
||||
const clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
|
||||
const scrollWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
|
||||
|
||||
if (scrollLeft + clientWidth >= scrollWidth - 10 && !isLoading) {
|
||||
onLoadMore();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, [onLoadMore, isLoading]);
|
||||
const displayedItems = disablePagination ? items : items?.length < (displayedLimit * 3) ? items?.slice(0, displayedLimit * 3) : items.slice(- (displayedLimit * 3))
|
||||
|
||||
return (
|
||||
<div style={{ overflow: "auto", width: "100%", display: "flex", flexGrow: 1 }}>
|
||||
<div style={{ overflowX: "hidden", width: "100%", display: "flex", flexGrow: 1, flexDirection: 'column' }}>
|
||||
{!disablePagination && items?.length > (displayedLimit * 3) && (
|
||||
<LazyLoad
|
||||
onLoadMore={async () => {
|
||||
|
||||
await onLoadLess(displayedLimit);
|
||||
lastItemRef2.current.scrollIntoView({ behavior: "auto", block: "start" });
|
||||
setTimeout(() => {
|
||||
window.scrollBy({ top: -50, behavior: "instant" }); // 'smooth' if needed
|
||||
}, 0);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DynamicGrid
|
||||
setColumnsPerRow={setColumnsPerRow}
|
||||
minItemWidth={minItemWidth}
|
||||
gap={gap}
|
||||
items={displayedItems.map((item, index) => (
|
||||
items={displayedItems?.map((item, index, list) => (
|
||||
<React.Fragment key={`${item?.name}-${item?.service}-${item?.identifier}`}>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
}} ref={index === displayedLimit ? lastItemRef2 : index === list.length -displayedLimit - 1 ? lastItemRef : null}>
|
||||
<ListItemWrapper
|
||||
defaultLoaderParams={defaultLoaderParams}
|
||||
item={item}
|
||||
index={index}
|
||||
render={listItem}
|
||||
renderListItemLoader={loaderItem}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
>
|
||||
{!isLoading && displayedItems.length > 0 && (
|
||||
|
||||
<LazyLoad
|
||||
onLoadMore={() => {
|
||||
onLoadMore();
|
||||
if (onSeenLastItem) {
|
||||
// onSeenLastItem(displayedItems[displayedItems.length - 1]);
|
||||
}
|
||||
onLoadMore={async () => {
|
||||
await onLoadMore(displayedLimit);
|
||||
lastItemRef.current.scrollIntoView({ behavior: "auto", block: "end" });
|
||||
setTimeout(() => {
|
||||
window.scrollBy({ top: 50, behavior: "instant" }); // 'smooth' if needed
|
||||
}, 0);
|
||||
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
</DynamicGrid>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2,28 +2,25 @@ import React, {
|
||||
CSSProperties,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
useTransition,
|
||||
} from "react";
|
||||
import {
|
||||
QortalMetadata,
|
||||
QortalSearchParams,
|
||||
} from "../../types/interfaces/resources";
|
||||
import { useResources } from "../../hooks/useResources";
|
||||
import { MessageWrapper, VirtualizedList } from "../../common/VirtualizedList";
|
||||
import { VirtualizedList } from "../../common/VirtualizedList";
|
||||
import { ListLoader } from "../../common/ListLoader";
|
||||
import { ListItem, useCacheStore } from "../../state/cache";
|
||||
import { ResourceLoader } from "./ResourceLoader";
|
||||
import { ItemCardWrapper } from "./ItemCardWrapper";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import DynamicGrid from "./DynamicGrid";
|
||||
import LazyLoad from "../../common/LazyLoad";
|
||||
import { useListStore } from "../../state/lists";
|
||||
import { useScrollTracker } from "../../common/useScrollTracker";
|
||||
import { HorizontalPaginatedList } from "./HorizontalPaginationList";
|
||||
import { VerticalPaginatedList } from "./VerticalPaginationList";
|
||||
type Direction = "VERTICAL" | "HORIZONTAL";
|
||||
|
||||
interface ResourceListStyles {
|
||||
@ -37,7 +34,7 @@ interface ResourceListStyles {
|
||||
}
|
||||
}
|
||||
|
||||
interface DefaultLoaderParams {
|
||||
export interface DefaultLoaderParams {
|
||||
listLoadingText?: string;
|
||||
listNoResultsText?: string;
|
||||
listItemLoadingText?: string;
|
||||
@ -57,6 +54,8 @@ interface BaseProps {
|
||||
children?: React.ReactNode;
|
||||
searchCacheDuration?: number
|
||||
resourceCacheDuration?: number
|
||||
disablePagination?: boolean
|
||||
disableScrollTracker?: boolean
|
||||
}
|
||||
|
||||
// ✅ Restrict `direction` only when `disableVirtualization = false`
|
||||
@ -86,17 +85,22 @@ export const MemorizedComponent = ({
|
||||
onSeenLastItem,
|
||||
listName,
|
||||
searchCacheDuration,
|
||||
resourceCacheDuration
|
||||
resourceCacheDuration,
|
||||
disablePagination,
|
||||
disableScrollTracker
|
||||
}: PropsResourceListDisplay) => {
|
||||
const { fetchResources } = useResources();
|
||||
const { getTemporaryResources, filterOutDeletedResources } = useCacheStore();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const memoizedParams = useMemo(() => JSON.stringify(search), [search]);
|
||||
const addList = useListStore().addList
|
||||
const removeFromList = useListStore().removeFromList
|
||||
|
||||
const addItems = useListStore().addItems
|
||||
const getListByName = useListStore().getListByName
|
||||
const list = useListStore().getListByName(listName)
|
||||
const [isLoading, setIsLoading] = useState(list?.length > 0 ? false : true);
|
||||
|
||||
const isListExpired = useCacheStore().isListExpired(listName)
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
||||
const initialized = useRef(false)
|
||||
|
||||
const getResourceList = useCallback(async () => {
|
||||
@ -124,13 +128,16 @@ export const MemorizedComponent = ({
|
||||
useEffect(() => {
|
||||
if(initialized.current) return
|
||||
initialized.current = true
|
||||
if(!isListExpired) return
|
||||
if(!isListExpired) {
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
sessionStorage.removeItem(`scroll-position-${listName}`);
|
||||
getResourceList();
|
||||
}, [getResourceList, isListExpired]); // Runs when dependencies change
|
||||
|
||||
useScrollTracker(listName);
|
||||
const {elementRef} = useScrollTracker(listName, list?.length > 0, disableScrollTracker);
|
||||
|
||||
const setSearchCacheExpiryDuration = useCacheStore().setSearchCacheExpiryDuration
|
||||
const setResourceCacheExpiryDuration = useCacheStore().setResourceCacheExpiryDuration
|
||||
@ -152,18 +159,24 @@ export const MemorizedComponent = ({
|
||||
|
||||
|
||||
|
||||
const getResourceMoreList = useCallback(async () => {
|
||||
const getResourceMoreList = useCallback(async (displayLimit?: number) => {
|
||||
try {
|
||||
// setIsLoading(true);
|
||||
setIsLoadingMore(true)
|
||||
const parsedParams = {...(JSON.parse(memoizedParams))};
|
||||
parsedParams.before = list.length === 0 ? null : list[list.length - 1]?.created
|
||||
parsedParams.offset = null
|
||||
if(displayLimit){
|
||||
parsedParams.limit = displayLimit
|
||||
}
|
||||
const responseData = await fetchResources(parsedParams, listName); // Awaiting the async function
|
||||
addItems(listName, responseData || [])
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch resources:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setTimeout(() => {
|
||||
setIsLoadingMore(false);
|
||||
|
||||
}, 1000);
|
||||
}
|
||||
}, [memoizedParams, listName, list]);
|
||||
|
||||
@ -191,6 +204,10 @@ export const MemorizedComponent = ({
|
||||
}, [listName]);
|
||||
|
||||
return (
|
||||
<div ref={elementRef} style={{
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}>
|
||||
<ListLoader
|
||||
noResultsMessage={
|
||||
defaultLoaderParams?.listNoResultsText || "No results available"
|
||||
@ -204,6 +221,7 @@ export const MemorizedComponent = ({
|
||||
loaderHeight={styles?.listLoadingHeight}
|
||||
>
|
||||
<div
|
||||
|
||||
style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
@ -236,72 +254,24 @@ export const MemorizedComponent = ({
|
||||
)}
|
||||
{disableVirtualization && direction === "HORIZONTAL" && (
|
||||
<>
|
||||
<DynamicGrid
|
||||
minItemWidth={styles?.horizontalStyles?.minItemWidth}
|
||||
gap={styles?.gap}
|
||||
items={listToDisplay?.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment
|
||||
key={`${item?.name}-${item?.service}-${item?.identifier}`}
|
||||
>
|
||||
<ListItemWrapper
|
||||
defaultLoaderParams={defaultLoaderParams}
|
||||
item={item}
|
||||
index={index}
|
||||
render={listItem}
|
||||
renderListItemLoader={loaderItem}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
>
|
||||
|
||||
{!isLoading && listToDisplay?.length > 0 && (
|
||||
<LazyLoad onLoadMore={()=> {
|
||||
getResourceMoreList()
|
||||
if(onSeenLastItem){
|
||||
|
||||
onSeenLastItem(listToDisplay[listToDisplay?.length - 1])
|
||||
}
|
||||
}} />
|
||||
)}
|
||||
</DynamicGrid>
|
||||
<HorizontalPaginatedList defaultLoaderParams={defaultLoaderParams} disablePagination={disablePagination} limit={search?.limit || 20} onLoadLess={(displayLimit)=> {
|
||||
removeFromList(listName, displayLimit)
|
||||
}} isLoadingMore={isLoadingMore} items={listToDisplay} listItem={listItem} onLoadMore={(displayLimit)=> getResourceMoreList(displayLimit)} gap={styles?.gap} isLoading={isLoading} minItemWidth={styles?.horizontalStyles?.minItemWidth} loaderItem={loaderItem} />
|
||||
</>
|
||||
|
||||
)}
|
||||
{disableVirtualization && direction === "VERTICAL" && (
|
||||
<div style={disabledVirutalizationStyles}>
|
||||
{listToDisplay?.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment
|
||||
key={`${item?.name}-${item?.service}-${item?.identifier}`}
|
||||
>
|
||||
|
||||
<ListItemWrapper
|
||||
defaultLoaderParams={defaultLoaderParams}
|
||||
item={item}
|
||||
index={index}
|
||||
render={listItem}
|
||||
renderListItemLoader={loaderItem}
|
||||
/>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{!isLoading && listToDisplay?.length > 0 && (
|
||||
<LazyLoad onLoadMore={()=> {
|
||||
getResourceMoreList()
|
||||
if(onSeenLastItem){
|
||||
onSeenLastItem(listToDisplay[listToDisplay?.length - 1])
|
||||
}
|
||||
}} />
|
||||
)}
|
||||
|
||||
<VerticalPaginatedList disablePagination={disablePagination} limit={search?.limit || 20} onLoadLess={(displayLimit)=> {
|
||||
|
||||
removeFromList(listName, displayLimit)
|
||||
}} defaultLoaderParams={defaultLoaderParams} isLoadingMore={isLoadingMore} items={listToDisplay} listItem={listItem} onLoadMore={(displayLimit)=> getResourceMoreList(displayLimit)} gap={styles?.gap} isLoading={isLoading} minItemWidth={styles?.horizontalStyles?.minItemWidth} loaderItem={loaderItem} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ListLoader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
97
src/components/ResourceList/VerticalPaginationList.tsx
Normal file
97
src/components/ResourceList/VerticalPaginationList.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import DynamicGrid from "./DynamicGrid";
|
||||
import LazyLoad from "../../common/LazyLoad";
|
||||
import { ListItem } from "../../state/cache";
|
||||
import { QortalMetadata } from "../../types/interfaces/resources";
|
||||
import { DefaultLoaderParams, ListItemWrapper } from "./ResourceListDisplay";
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
interface VerticalPaginatedListProps {
|
||||
items: QortalMetadata[];
|
||||
listItem: (item: ListItem, index: number) => React.ReactNode;
|
||||
loaderItem?: (status: "LOADING" | "ERROR") => React.ReactNode;
|
||||
onLoadMore: (limit: number) => void;
|
||||
onLoadLess: (limit: number)=> void;
|
||||
minItemWidth?: number;
|
||||
gap?: number;
|
||||
isLoading?: boolean;
|
||||
onSeenLastItem?: (listItem: ListItem) => void;
|
||||
isLoadingMore: boolean;
|
||||
limit: number,
|
||||
disablePagination?: boolean
|
||||
defaultLoaderParams?: DefaultLoaderParams;
|
||||
}
|
||||
|
||||
export const VerticalPaginatedList = ({
|
||||
items,
|
||||
listItem,
|
||||
loaderItem,
|
||||
onLoadMore,
|
||||
onLoadLess,
|
||||
minItemWidth,
|
||||
gap,
|
||||
isLoading,
|
||||
onSeenLastItem,
|
||||
isLoadingMore,
|
||||
limit,
|
||||
disablePagination,
|
||||
defaultLoaderParams
|
||||
}: VerticalPaginatedListProps) => {
|
||||
|
||||
const lastItemRef= useRef<any>(null)
|
||||
const lastItemRef2= useRef<any>(null)
|
||||
|
||||
const displayedLimit = limit || 20
|
||||
|
||||
const displayedItems = disablePagination ? items : items.slice(- (displayedLimit * 3))
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disablePagination && items?.length > (displayedLimit * 3) && (
|
||||
<LazyLoad
|
||||
onLoadMore={async () => {
|
||||
|
||||
await onLoadLess(displayedLimit);
|
||||
lastItemRef2.current.scrollIntoView({ behavior: "auto", block: "start" });
|
||||
setTimeout(() => {
|
||||
window.scrollBy({ top: -50, behavior: "instant" }); // 'smooth' if needed
|
||||
}, 0);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{displayedItems?.map((item, index, list) => {
|
||||
return (
|
||||
<React.Fragment
|
||||
key={`${item?.name}-${item?.service}-${item?.identifier}`}
|
||||
>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
}} ref={index === displayedLimit ? lastItemRef2 : index === list.length -displayedLimit - 1 ? lastItemRef : null}>
|
||||
<ListItemWrapper
|
||||
defaultLoaderParams={defaultLoaderParams}
|
||||
item={item}
|
||||
index={index}
|
||||
render={listItem}
|
||||
renderListItemLoader={loaderItem}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
<LazyLoad
|
||||
onLoadMore={async () => {
|
||||
await onLoadMore(displayedLimit);
|
||||
lastItemRef.current.scrollIntoView({ behavior: "auto", block: "end" });
|
||||
setTimeout(() => {
|
||||
window.scrollBy({ top: 50, behavior: "instant" }); // 'smooth' if needed
|
||||
}, 0);
|
||||
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -29,6 +29,7 @@ export const useResources = () => {
|
||||
} = useCacheStore();
|
||||
const requestControllers = new Map<string, AbortController>();
|
||||
|
||||
|
||||
const getArbitraryResource = async (
|
||||
url: string,
|
||||
key: string
|
||||
@ -42,6 +43,7 @@ export const useResources = () => {
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { signal: controller.signal });
|
||||
if(!res?.ok) throw new Error('Error in downloading')
|
||||
return await res.text();
|
||||
} catch (error: any) {
|
||||
if (error?.name === "AbortError") {
|
||||
@ -90,6 +92,8 @@ export const useResources = () => {
|
||||
} catch (error) {
|
||||
hasFailedToDownload = true;
|
||||
}
|
||||
|
||||
|
||||
if (res === "canceled") return false;
|
||||
|
||||
if (hasFailedToDownload) {
|
||||
|
@ -15,6 +15,7 @@ interface ListStore {
|
||||
|
||||
// CRUD Operations
|
||||
addList: (name: string, items: QortalMetadata[]) => void;
|
||||
removeFromList: (name: string, length: number)=> void;
|
||||
addItem: (listName: string, item: QortalMetadata) => void;
|
||||
addItems: (listName: string, items: QortalMetadata[]) => void;
|
||||
updateItem: (listName: string, item: QortalMetadata) => void;
|
||||
@ -35,6 +36,13 @@ export const useListStore = create<ListStore>((set, get) => ({
|
||||
[name]: { name, items }, // ✅ Store items as an array
|
||||
},
|
||||
})),
|
||||
removeFromList: (name, length) =>
|
||||
set((state) => ({
|
||||
lists: {
|
||||
...state.lists,
|
||||
[name]: { name, items: state.lists[name].items.slice(0, state.lists[name].items.length - length) }, // ✅ Store items as an array
|
||||
},
|
||||
})),
|
||||
|
||||
addItem: (listName, item) =>
|
||||
set((state) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user