diff --git a/src/components/ResourceList/ResourceListDisplay.tsx b/src/components/ResourceList/ResourceListDisplay.tsx index 0e671a5..f0a9839 100644 --- a/src/components/ResourceList/ResourceListDisplay.tsx +++ b/src/components/ResourceList/ResourceListDisplay.tsx @@ -90,11 +90,11 @@ export const MemorizedComponent = ({ disableScrollTracker }: PropsResourceListDisplay) => { const { fetchResources } = useResources(); - const { getTemporaryResources, filterOutDeletedResources } = useCacheStore(); + const { filterOutDeletedResources } = useCacheStore(); const memoizedParams = useMemo(() => JSON.stringify(search), [search]); const addList = useListStore().addList const removeFromList = useListStore().removeFromList - + const temporaryResources = useCacheStore().getTemporaryResources(listName) const addItems = useListStore().addItems const list = useListStore().getListByName(listName) const [isLoading, setIsLoading] = useState(list?.length > 0 ? false : true); @@ -151,10 +151,9 @@ export const MemorizedComponent = ({ setResourceCacheExpiryDuration(resourceCacheDuration) } }, []) - const listToDisplay = useMemo(()=> { - return filterOutDeletedResources([...getTemporaryResources(listName), ...list]) - }, [list, listName, filterOutDeletedResources, getTemporaryResources]) + return filterOutDeletedResources([...temporaryResources, ...list]) + }, [list, listName, filterOutDeletedResources, temporaryResources]) diff --git a/src/hooks/useResources.tsx b/src/hooks/useResources.tsx index 6768bba..7c31029 100644 --- a/src/hooks/useResources.tsx +++ b/src/hooks/useResources.tsx @@ -1,10 +1,9 @@ import React, { useCallback } from "react"; import { - QortalMetadata, QortalSearchParams, } from "../types/interfaces/resources"; -import { useCacheStore } from "../state/cache"; +import { ListItem, useCacheStore } from "../state/cache"; import { RequestQueueWithPromise } from "../utils/queue"; import { base64ToUint8Array, uint8ArrayToObject } from "../utils/base64"; @@ -13,7 +12,6 @@ export const requestQueueProductPublishesBackup = new RequestQueueWithPromise( 5 ); - interface TemporaryResource { qortalMetadata: QortalMetadata; data: any; @@ -25,11 +23,10 @@ export const useResources = () => { getResourceCache, setResourceCache, addTemporaryResource, - markResourceAsDeleted + markResourceAsDeleted, } = useCacheStore(); const requestControllers = new Map(); - const getArbitraryResource = async ( url: string, key: string @@ -43,7 +40,7 @@ export const useResources = () => { try { const res = await fetch(url, { signal: controller.signal }); - if(!res?.ok) throw new Error('Error in downloading') + if (!res?.ok) throw new Error("Error in downloading"); return await res.text(); } catch (error: any) { if (error?.name === "AbortError") { @@ -65,22 +62,46 @@ export const useResources = () => { requestControllers.clear(); }; - const fetchIndividualPublish = useCallback( - async (item: QortalMetadata) => { + const fetchIndividualPublishJson = useCallback( + async ( + item: QortalMetadata, + includeMetadata?: boolean + ): Promise => { try { const key = `${item?.service}-${item?.name}-${item?.identifier}`; const cachedProduct = getResourceCache( `${item?.service}-${item?.name}-${item?.identifier}` ); - if (cachedProduct) return; + + if (cachedProduct) return cachedProduct; setResourceCache( `${item?.service}-${item?.name}-${item?.identifier}`, null ); let hasFailedToDownload = false; let res: string | undefined = undefined; + let metadata try { + if (includeMetadata) { + const url = `/arbitrary/resources/search?mode=ALL&service=${item?.service}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${item?.name}&exactmatchnames=true&offset=0&identifier=${item?.identifier}`; + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + if (!response?.ok) return false; + const resMetadata = await response.json(); + if (resMetadata?.length === 0) { + setResourceCache( + `${item?.service}-${item?.name}-${item?.identifier}`, + false + ); + return false; + } + metadata = resMetadata[0]; + } res = await requestQueueProductPublishes.enqueue( (): Promise => { return getArbitraryResource( @@ -93,7 +114,6 @@ export const useResources = () => { hasFailedToDownload = true; } - if (res === "canceled") return false; if (hasFailedToDownload) { @@ -125,7 +145,7 @@ export const useResources = () => { const toObject = uint8ArrayToObject(toUint); const fullDataObject = { data: { ...toObject }, - qortalMetadata: item, + qortalMetadata: includeMetadata ? metadata : item, }; setResourceCache( `${item?.service}-${item?.name}-${item?.identifier}`, @@ -143,10 +163,10 @@ export const useResources = () => { const fetchDataFromResults = useCallback( (responseData: QortalMetadata[]): void => { for (const item of responseData) { - fetchIndividualPublish(item); + fetchIndividualPublishJson(item, false); } }, - [fetchIndividualPublish] + [fetchIndividualPublishJson] ); const fetchResources = useCallback( @@ -158,18 +178,18 @@ export const useResources = () => { if (cancelRequests) { cancelAllRequests(); } - + const cacheKey = generateCacheKey(params); const searchCache = getSearchCache(listName, cacheKey); if (searchCache) { return searchCache; } - + let responseData: QortalMetadata[] = []; let filteredResults: QortalMetadata[] = []; let lastCreated = params.before || null; const targetLimit = params.limit ?? 20; // Use `params.limit` if provided, else default to 20 - + while (filteredResults.length < targetLimit) { const response = await qortalRequest({ action: "SEARCH_QDN_RESOURCES", @@ -178,36 +198,35 @@ export const useResources = () => { limit: targetLimit - filteredResults.length, // Adjust limit dynamically before: lastCreated, }); - + if (!response || response.length === 0) { break; // No more data available } - + responseData = response; - const validResults = responseData.filter(item => item.size !== 32); + const validResults = responseData.filter((item) => item.size !== 32); filteredResults = [...filteredResults, ...validResults]; - + if (filteredResults.length >= targetLimit) { filteredResults = filteredResults.slice(0, targetLimit); break; } - + lastCreated = responseData[responseData.length - 1]?.created; if (!lastCreated) break; } - + setSearchCache(listName, cacheKey, filteredResults); fetchDataFromResults(filteredResults); - + return filteredResults; }, [getSearchCache, setSearchCache, fetchDataFromResults] ); - - const addNewResources = useCallback( (listName: string, resources: TemporaryResource[]) => { + addTemporaryResource( listName, resources.map((item) => item.qortalMetadata) @@ -215,42 +234,38 @@ export const useResources = () => { resources.forEach((temporaryResource) => { setResourceCache( `${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`, - temporaryResource.data + temporaryResource ); }); }, [] ); - const updateNewResources = useCallback( - (resources: TemporaryResource[]) => { - - resources.forEach((temporaryResource) => { - setResourceCache( - `${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`, - temporaryResource.data - ); - }); - }, - [] - ); - - const deleteProduct = useCallback(async (qortalMetadata: QortalMetadata)=> { - if(!qortalMetadata?.service || !qortalMetadata?.identifier) throw new Error('Missing fields') - await qortalRequest({ - action: "PUBLISH_QDN_RESOURCE", - service: qortalMetadata.service, - identifier: qortalMetadata.identifier, - base64: 'RA==', - }); - markResourceAsDeleted(qortalMetadata) - return true - }, []) + const updateNewResources = useCallback((resources: TemporaryResource[]) => { + resources.forEach((temporaryResource) => { + setResourceCache( + `${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`, + temporaryResource + ); + }); + }, []); + const deleteProduct = useCallback(async (qortalMetadata: QortalMetadata) => { + if (!qortalMetadata?.service || !qortalMetadata?.identifier) + throw new Error("Missing fields"); + await qortalRequest({ + action: "PUBLISH_QDN_RESOURCE", + service: qortalMetadata.service, + identifier: qortalMetadata.identifier, + base64: "RA==", + }); + markResourceAsDeleted(qortalMetadata); + return true; + }, []); return { fetchResources, - fetchIndividualPublish, + fetchIndividualPublishJson, addNewResources, updateNewResources, deleteProduct, diff --git a/src/state/cache.ts b/src/state/cache.ts index b257881..c14f92c 100644 --- a/src/state/cache.ts +++ b/src/state/cache.ts @@ -145,6 +145,7 @@ export const useCacheStore = create addTemporaryResource: (listName, newResources, customExpiry) => set((state) => { + const expiry = Date.now() + (customExpiry || 5 * 60 * 1000); const existingResources = state.searchCache[listName]?.temporaryNewResources || []; diff --git a/src/types/interfaces/resources.ts b/src/types/interfaces/resources.ts index e97a7b4..c0ffa3f 100644 --- a/src/types/interfaces/resources.ts +++ b/src/types/interfaces/resources.ts @@ -60,6 +60,14 @@ export interface QortalMetadata { name: string identifier: string service: Service + metadata?: { + title?: string + category?: number + categoryName?: string + tags?: string[] + description?: string + } + updated?: number } export interface QortalSearchParams { @@ -72,6 +80,7 @@ export interface QortalMetadata { title?: string; description?: string; prefix?: boolean; + includemetadata?: boolean; exactMatchNames?: boolean; minLevel?: number; nameListFilter?: string;