This commit is contained in:
PhilReact 2025-03-14 02:03:23 +02:00
parent bb50cfc9a2
commit ae5111618f
5 changed files with 89 additions and 48 deletions

View File

@ -1,6 +1,6 @@
{
"name": "qapp-core",
"version": "1.0.0",
"version": "1.0.2",
"description": "Qortal's core React library with global state, UI components, and utilities",
"main": "dist/index.js",
"module": "dist/index.mjs",

View File

@ -5,54 +5,33 @@ interface DynamicGridProps {
itemWidth?: number; // Minimum width per item
gap?: number; // Spacing between grid items
children: ReactNode
minItemWidth?: number
}
const DynamicGrid: React.FC<DynamicGridProps> = ({
items,
itemWidth = 300, // Default min item width
gap = 10, // Default gap between items
children
minItemWidth = 200, // Minimum width per item
gap = 10, // Space between items
children,
}) => {
const gridRef = useRef<HTMLDivElement>(null);
const [columns, setColumns] = useState(1);
useEffect(() => {
const updateColumns = () => {
if (gridRef.current) {
const containerWidth = gridRef.current.offsetWidth;
const newColumns = Math.floor(containerWidth / (itemWidth + gap));
setColumns(newColumns > 0 ? newColumns : 1); // Ensure at least 1 column
}
};
updateColumns(); // Initial column calculation
const resizeObserver = new ResizeObserver(updateColumns);
if (gridRef.current) {
resizeObserver.observe(gridRef.current);
}
return () => resizeObserver.disconnect(); // Cleanup observer
}, [itemWidth, gap]);
return (
<div style={{ display: "flex", flexDirection: 'column', alignItems: "center", width: "100%" }}>
{/* ✅ Centers the grid inside the parent */}
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: "100%" }}>
<div
ref={gridRef}
style={{
display: "grid",
gridTemplateColumns: `repeat(${columns}, minmax(${itemWidth}px, 1fr))`, // ✅ Dynamically calculated columns
gridTemplateColumns: `repeat(auto-fill, minmax(${minItemWidth}px, 1fr))`, // ✅ Expands to fit width
gap: `${gap}px`,
width: "100%", // ✅ Ensures grid fills available space
maxWidth: "1200px", // ✅ Optional max width to prevent excessive stretching
margin: "auto", // ✅ Centers the grid horizontally
gridAutoFlow: "row", // ✅ Ensures rows behave correctly
width: "100%",
maxWidth: "100%", // ✅ Prevents overflow
margin: "auto",
overflow: "hidden", // ✅ Prevents horizontal scrollbars
gridAutoFlow: "row dense", // ✅ Fills space efficiently
}}
>
{items.map((component, index) => (
<div key={index} style={{ width: "100%", display: "flex", justifyContent: "center" }}>
{component} {/* ✅ Render user-provided component */}
<div key={index} style={{ width: "100%", display: "flex", justifyContent: "center", maxWidth: '400px' }}>
{component} {/* ✅ Renders user-provided component */}
</div>
))}
</div>
@ -61,4 +40,7 @@ const DynamicGrid: React.FC<DynamicGridProps> = ({
);
};
export default DynamicGrid;

View File

@ -27,6 +27,9 @@ interface ResourceListStyles {
disabledVirutalizationStyles?: {
parentContainer?: CSSProperties;
};
horizontalStyles?: {
minItemWidth?: number
}
}
interface DefaultLoaderParams {
@ -76,11 +79,15 @@ export const ResourceListDisplay = ({
listName
}: PropsResourceListDisplay) => {
const { fetchResources } = useResources();
const { getTemporaryResources } = useCacheStore();
const [isLoading, setIsLoading] = useState(false);
const memoizedParams = useMemo(() => JSON.stringify(params), [params]);
const addList = useListStore().addList
const addItems = useListStore().addItems
const list = useListStore().getListByName(listName)
const listToDisplay = useMemo(()=> {
return [...getTemporaryResources(listName), ...list]
}, [list, listName])
const getResourceList = useCallback(async () => {
try {
@ -99,7 +106,8 @@ export const ResourceListDisplay = ({
try {
// setIsLoading(true);
const parsedParams = {...(JSON.parse(memoizedParams))};
parsedParams.offset = (parsedParams?.offset || 0) + list.length
parsedParams.before = list.length === 0 ? null : list[list.length - 1]?.created
parsedParams.offset = null
const res = await fetchResources(parsedParams, listName); // Awaiting the async function
addItems(listName, res || [])
} catch (error) {
@ -107,7 +115,7 @@ export const ResourceListDisplay = ({
} finally {
setIsLoading(false);
}
}, [memoizedParams, listName, list?.length]);
}, [memoizedParams, listName, list]);
useEffect(() => {
getResourceList();
@ -130,7 +138,7 @@ export const ResourceListDisplay = ({
noResultsMessage={
defaultLoaderParams?.listNoResultsText || "No results available"
}
resultsLength={list?.length}
resultsLength={listToDisplay?.length}
isLoading={isLoading}
loadingMessage={
defaultLoaderParams?.listLoadingText || "Retrieving list. Please wait."
@ -147,7 +155,7 @@ export const ResourceListDisplay = ({
>
<div style={{ display: "flex", flexGrow: 1 }}>
{!disableVirtualization && (
<VirtualizedList list={list} onSeenLastItem={(item)=> {
<VirtualizedList list={listToDisplay} onSeenLastItem={(item)=> {
getResourceMoreList()
if(onSeenLastItem){
onSeenLastItem(item)
@ -172,8 +180,9 @@ export const ResourceListDisplay = ({
{disableVirtualization && direction === "HORIZONTAL" && (
<>
<DynamicGrid
minItemWidth={styles?.horizontalStyles?.minItemWidth}
gap={styles?.gap}
items={list?.map((item, index) => {
items={listToDisplay?.map((item, index) => {
return (
<React.Fragment
key={`${item?.name}-${item?.service}-${item?.identifier}`}
@ -190,12 +199,12 @@ export const ResourceListDisplay = ({
})}
>
{!isLoading && list?.length > 0 && (
{!isLoading && listToDisplay?.length > 0 && (
<LazyLoad onLoadMore={()=> {
getResourceMoreList()
if(onSeenLastItem){
onSeenLastItem(list[list?.length - 1])
onSeenLastItem(listToDisplay[listToDisplay?.length - 1])
}
}} />
)}
@ -205,7 +214,7 @@ export const ResourceListDisplay = ({
)}
{disableVirtualization && direction === "VERTICAL" && (
<div style={disabledVirutalizationStyles}>
{list?.map((item, index) => {
{listToDisplay?.map((item, index) => {
return (
<React.Fragment
key={`${item?.name}-${item?.service}-${item?.identifier}`}
@ -222,11 +231,11 @@ export const ResourceListDisplay = ({
</React.Fragment>
);
})}
{!isLoading && list?.length > 0 && (
{!isLoading && listToDisplay?.length > 0 && (
<LazyLoad onLoadMore={()=> {
getResourceMoreList()
if(onSeenLastItem){
onSeenLastItem(list[list?.length - 1])
onSeenLastItem(listToDisplay[listToDisplay?.length - 1])
}
}} />
)}

View File

@ -7,8 +7,12 @@ import { base64ToUint8Array, uint8ArrayToObject } from '../utils/base64';
export const requestQueueProductPublishes = new RequestQueueWithPromise(20);
export const requestQueueProductPublishesBackup = new RequestQueueWithPromise(5);
interface TemporaryResource {
qortalMetadata: QortalMetadata
data: any
}
export const useResources = () => {
const { setSearchCache, getSearchCache, getResourceCache, setResourceCache } = useCacheStore();
const { setSearchCache, getSearchCache, getResourceCache, setResourceCache, addTemporaryResource, getTemporaryResources } = useCacheStore();
const requestControllers = new Map<string, AbortController>();
const getArbitraryResource = async (url: string, key: string): Promise<string> => {
@ -135,9 +139,16 @@ export const useResources = () => {
},
[getSearchCache, setSearchCache, fetchDataFromResults]
);
const addNewResources = useCallback((listName:string, temporaryResources: TemporaryResource[])=> {
addTemporaryResource(listName, temporaryResources.map((item)=> item.qortalMetadata))
}, [])
return {
fetchResources,
fetchIndividualPublish
fetchIndividualPublish,
addNewResources
}
}

View File

@ -7,6 +7,7 @@ interface SearchCache {
searches: {
[searchTerm: string]: QortalMetadata[]; // List of products for each search term
};
temporaryNewResources: QortalMetadata[],
expiry: number; // Expiry timestamp for the whole list
};
}
@ -53,6 +54,8 @@ interface CacheState {
getSearchCache: (listName: string, searchTerm: string) => QortalMetadata[] | null;
clearExpiredCache: () => void;
getResourceCache: (id: string, ignoreExpire?: boolean) => ListItem | false | null;
addTemporaryResource: (listName: string, newResources: QortalMetadata[], customExpiry?: number)=> void;
getTemporaryResources:(listName: string)=> QortalMetadata[]
}
export const useCacheStore = create<CacheState>((set, get) => ({
@ -91,6 +94,7 @@ export const useCacheStore = create<CacheState>((set, get) => ({
...(state.searchCache[listName]?.searches || {}), // Preserve existing searches
[searchTerm]: data, // Store new search term results
},
temporaryNewResources: state.searchCache[listName]?.temporaryNewResources || [], // Preserve existing temp resources
expiry, // Expiry for the entire list
},
},
@ -106,7 +110,42 @@ export const useCacheStore = create<CacheState>((set, get) => ({
}
return null; // Cache expired or doesn't exist
},
addTemporaryResource: (listName, newResources, customExpiry) =>
set((state) => {
const expiry = Date.now() + (customExpiry || 5 * 60 * 1000); // Reset expiry
const existingResources = state.searchCache[listName]?.temporaryNewResources || [];
// Merge and remove duplicates, keeping the latest by `created` timestamp
const uniqueResourcesMap = new Map<string, QortalMetadata>();
[...existingResources, ...newResources].forEach((item) => {
const key = `${item.service}-${item.name}-${item.identifier}`;
const existingItem = uniqueResourcesMap.get(key);
if (!existingItem || item.created > existingItem.created) {
uniqueResourcesMap.set(key, item);
}
});
return {
searchCache: {
...state.searchCache,
[listName]: {
...state.searchCache[listName],
temporaryNewResources: Array.from(uniqueResourcesMap.values()), // Store unique items
expiry, // Reset expiry
},
},
};
}),
getTemporaryResources: (listName: string) => {
const cache = get().searchCache[listName];
if (cache && cache.expiry > Date.now()) {
return cache.temporaryNewResources || [];
}
return []; // Return empty array if expired or doesn't exist
},
// Clear expired caches
clearExpiredCache: () =>
set((state) => {