mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-14 17:41:20 +00:00
fix grid
This commit is contained in:
parent
bb50cfc9a2
commit
ae5111618f
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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])
|
||||
}
|
||||
}} />
|
||||
)}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user