diff --git a/src/hooks/useBlockedNames.tsx b/src/hooks/useBlockedNames.tsx new file mode 100644 index 0000000..f89744d --- /dev/null +++ b/src/hooks/useBlockedNames.tsx @@ -0,0 +1,42 @@ +import { useCallback, useMemo } from "react"; +import { useListStore } from "../state/lists"; +import { useCacheStore } from "../state/cache"; + +export const useBlockedNames = () => { + const filterOutItemsByNames = useListStore(state => state.filterOutItemsByNames) + const filterSearchCacheItemsByNames = useCacheStore((s)=> s.filterSearchCacheItemsByNames) + + + const addToBlockedList = useCallback(async (names: string[]) => { + const response = await qortalRequest({ + action: "ADD_LIST_ITEMS", + list_name: "blockedNames", + items: names, + }); + if (response === true) { + filterOutItemsByNames(names) + filterSearchCacheItemsByNames(names) + return true; + } else throw new Error("Unable to block names"); + }, []); + + const removeFromBlockedList = useCallback(async (names: string[]) => { + const response = await qortalRequest({ + action: "DELETE_LIST_ITEM", + list_name: "blockedNames", + items: names, + }); + if (response === true) { + return true; + } else throw new Error("Unable to remove blocked names"); + }, []); + + + return useMemo( + () => ({ + removeFromBlockedList, + addToBlockedList, + }), + [addToBlockedList, removeFromBlockedList] + ); +}; diff --git a/src/hooks/useResources.tsx b/src/hooks/useResources.tsx index 692c8f9..766056d 100644 --- a/src/hooks/useResources.tsx +++ b/src/hooks/useResources.tsx @@ -23,6 +23,7 @@ export interface Resource { } export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => { const setSearchCache = useCacheStore((s) => s.setSearchCache); + const deleteSearchCache = useCacheStore((s)=> s.deleteSearchCache) const getSearchCache = useCacheStore((s) => s.getSearchCache); const getResourceCache = useCacheStore((s) => s.getResourceCache); const setResourceCache = useCacheStore((s) => s.setResourceCache); @@ -32,7 +33,12 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => { const addList = useListStore((s) => s.addList); const setPublish = usePublishStore((state)=> state.setPublish) - const deleteList = useListStore(state => state.deleteList) + const deleteListInStore = useListStore(state => state.deleteList) + + const deleteList = useCallback((listName: string)=> { + deleteListInStore(listName) + deleteSearchCache(listName) + }, []) const requestControllers = new Map(); const getArbitraryResource = async ( diff --git a/src/index.ts b/src/index.ts index 3d028a6..45d4744 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ export { useListReturn } from './hooks/useListData'; export { useAllResourceStatus } from './hooks/useAllResourceStatus'; export { useQortBalance } from './hooks/useBalance'; export { useAuth } from './hooks/useAuth'; +export { useBlockedNames } from './hooks/useBlockedNames'; import './index.css' export { executeEvent, subscribeToEvent, unsubscribeFromEvent } from './utils/events'; export { formatBytes, formatDuration } from './utils/numbers'; diff --git a/src/state/cache.ts b/src/state/cache.ts index b2772c8..28088cb 100644 --- a/src/state/cache.ts +++ b/src/state/cache.ts @@ -71,6 +71,9 @@ interface CacheState { resourceCacheExpiryDuration: number; setSearchCacheExpiryDuration: (duration: number) => void; setResourceCacheExpiryDuration: (duration: number)=> void; + deleteSearchCache: (listName: string) => void; + filterSearchCacheItemsByNames: (names: string[]) => void; + } export const useCacheStore = create @@ -128,6 +131,12 @@ export const useCacheStore = create }, }; }), + deleteSearchCache: (listName) => + set((state) => { + const updatedSearchCache = { ...state.searchCache }; + delete updatedSearchCache[listName]; + return { searchCache: updatedSearchCache }; + }), setSearchParamsForList: (listName, searchParamsStringified) => set((state) => { const existingList = state.searchCache[listName] || {}; @@ -238,6 +247,27 @@ export const useCacheStore = create ); return { searchCache: validSearchCache }; }), + filterSearchCacheItemsByNames: (names) => + set((state) => { + const updatedSearchCache: SearchCache = {}; + + for (const [listName, list] of Object.entries(state.searchCache)) { + const updatedSearches: { [searchTerm: string]: QortalMetadata[] } = {}; + + for (const [term, items] of Object.entries(list.searches)) { + updatedSearches[term] = items.filter( + (item) => !names.includes(item.name) + ); + } + + updatedSearchCache[listName] = { + ...list, + searches: updatedSearches, + }; + } + + return { searchCache: updatedSearchCache }; + }), }), ); \ No newline at end of file diff --git a/src/state/lists.ts b/src/state/lists.ts index 258baed..7dd073a 100644 --- a/src/state/lists.ts +++ b/src/state/lists.ts @@ -1,8 +1,7 @@ -import {create} from "zustand"; +import { create } from "zustand"; import { QortalMetadata } from "../types/interfaces/resources"; import { persist } from "zustand/middleware"; - interface ListsState { [listName: string]: { name: string; @@ -15,15 +14,16 @@ interface ListStore { // CRUD Operations addList: (name: string, items: QortalMetadata[]) => void; - removeFromList: (name: string, length: number)=> void; + removeFromList: (name: string, length: number) => void; addItem: (listName: string, item: QortalMetadata) => void; - addItems: (listName: string, items: QortalMetadata[]) => void; + addItems: (listName: string, items: QortalMetadata[]) => void; updateItem: (listName: string, item: QortalMetadata) => void; deleteItem: (listName: string, itemKey: string) => void; deleteList: (listName: string) => void; // Getter function - getListByName: (listName: string) => QortalMetadata[] + getListByName: (listName: string) => QortalMetadata[]; + filterOutItemsByNames: (names: string[]) => void; } export const useListStore = create((set, get) => ({ @@ -40,7 +40,13 @@ export const useListStore = create((set, get) => ({ 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 + [name]: { + name, + items: state.lists[name].items.slice( + 0, + state.lists[name].items.length - length + ), + }, // ✅ Store items as an array }, })), @@ -50,7 +56,9 @@ export const useListStore = create((set, get) => ({ const itemKey = `${item.name}-${item.service}-${item.identifier}`; const existingItem = state.lists[listName].items.find( - (existing) => `${existing.name}-${existing.service}-${existing.identifier}` === itemKey + (existing) => + `${existing.name}-${existing.service}-${existing.identifier}` === + itemKey ); if (existingItem) return state; // Avoid duplicates @@ -65,52 +73,51 @@ export const useListStore = create((set, get) => ({ }, }; }), - addItems: (listName, items) => - set((state) => { - - if (!state.lists[listName]) { - console.warn(`List "${listName}" does not exist. Creating a new list.`); - return { - lists: { - ...state.lists, - [listName]: { name: listName, items: [...items] }, // ✅ Create new list if missing - }, - }; - } - - // ✅ Generate existing keys correctly - const existingKeys = new Set( - state.lists[listName].items.map( - (item) => `${item.name}-${item.service}-${item.identifier}` - ) - ); - - - // ✅ Ensure we correctly compare identifiers - const newItems = items.filter((item) => { - const itemKey = `${item.name}-${item.service}-${item.identifier}`; - const isDuplicate = existingKeys.has(itemKey); - - return !isDuplicate; // ✅ Only keep items that are NOT in the existing list - }); - - - if (newItems.length === 0) { - console.warn("No new items were added because they were all considered duplicates."); - return state; // ✅ Prevent unnecessary re-renders if no changes - } - - return { - lists: { - ...state.lists, - [listName]: { - ...state.lists[listName], - items: [...state.lists[listName].items, ...newItems], // ✅ Append only new items - }, - }, - }; - }), - + addItems: (listName, items) => + set((state) => { + if (!state.lists[listName]) { + console.warn(`List "${listName}" does not exist. Creating a new list.`); + return { + lists: { + ...state.lists, + [listName]: { name: listName, items: [...items] }, // ✅ Create new list if missing + }, + }; + } + + // ✅ Generate existing keys correctly + const existingKeys = new Set( + state.lists[listName].items.map( + (item) => `${item.name}-${item.service}-${item.identifier}` + ) + ); + + // ✅ Ensure we correctly compare identifiers + const newItems = items.filter((item) => { + const itemKey = `${item.name}-${item.service}-${item.identifier}`; + const isDuplicate = existingKeys.has(itemKey); + + return !isDuplicate; // ✅ Only keep items that are NOT in the existing list + }); + + if (newItems.length === 0) { + console.warn( + "No new items were added because they were all considered duplicates." + ); + return state; // ✅ Prevent unnecessary re-renders if no changes + } + + return { + lists: { + ...state.lists, + [listName]: { + ...state.lists[listName], + items: [...state.lists[listName].items, ...newItems], // ✅ Append only new items + }, + }, + }; + }), + updateItem: (listName, item) => set((state) => { if (!state.lists[listName]) return state; @@ -123,7 +130,8 @@ export const useListStore = create((set, get) => ({ [listName]: { ...state.lists[listName], items: state.lists[listName].items.map((existing) => - `${existing.name}-${existing.service}-${existing.identifier}` === itemKey + `${existing.name}-${existing.service}-${existing.identifier}` === + itemKey ? item // ✅ Update item : existing ), @@ -142,7 +150,8 @@ export const useListStore = create((set, get) => ({ [listName]: { ...state.lists[listName], items: state.lists[listName].items.filter( - (item) => `${item.name}-${item.service}-${item.identifier}` !== itemKey + (item) => + `${item.name}-${item.service}-${item.identifier}` !== itemKey ), // ✅ Remove from array }, }, @@ -160,4 +169,16 @@ export const useListStore = create((set, get) => ({ }), getListByName: (listName) => get().lists[listName]?.items || [], // ✅ Get a list by name + filterOutItemsByNames: (names) => + set((state) => { + const updatedLists: ListsState = {}; + + for (const [listName, listData] of Object.entries(state.lists)) { + updatedLists[listName] = { + ...listData, + items: listData.items.filter((item) => !names.includes(item.name)), + }; + } + return { lists: updatedLists }; + }), }));