diff --git a/packages/desktop/views/dashboard/collectibles/views/CollectionsGalleryView.svelte b/packages/desktop/views/dashboard/collectibles/views/CollectionsGalleryView.svelte index e95024b661..28f84f689d 100644 --- a/packages/desktop/views/dashboard/collectibles/views/CollectionsGalleryView.svelte +++ b/packages/desktop/views/dashboard/collectibles/views/CollectionsGalleryView.svelte @@ -1,13 +1,13 @@ @@ -35,23 +33,25 @@
{localize('views.collectibles.collectionsGallery.title')} - {String(queriedCollections.length ?? '')} + {String(Object.keys($selectedAccountCollections).length ?? '')}
- {#if collections.length} - - + {#if hasCollections} + {/if} {#if features.collectibles.erc721.enabled} {/if}
- {#if collections.length} - {#if queriedCollections.length} + {#if hasCollections} + {#if Object.keys(queriedCollections).length > 0} + {#each Object.keys(queriedCollections) as collection} + {queriedCollections[collection].name} + {/each} {:else}
= writable({}) + +async function updateCollections(nfts: Nft[]): Promise { + const existingCollections = get(collectionsStore) + + if (nfts.length === 0) { + if (Object.keys(existingCollections).length > 0) { + collectionsStore.set({}) + } + return + } + + const collectionsUpdate = { ...existingCollections } + + await Promise.all( + nfts.map(async (nft) => { + if (nft.standard !== NftStandard.Irc27 || !nft.issuer) { + return + } + + const issuerId = nft.issuer.aliasId ?? nft.issuer.nftId + if (!issuerId) { + return + } + + if (!collectionsUpdate[issuerId]) { + const collection = await getCollectionFromNft(nft) + if (collection) { + collectionsUpdate[issuerId] = { ...collection, nfts: [nft] } + } + } else { + const existingNfts = collectionsUpdate[issuerId].nfts + if (!existingNfts.find((existingNft) => existingNft.id === nft.id)) { + collectionsUpdate[issuerId].nfts.push(nft) + } + } + }) + ) + collectionsStore.set(collectionsUpdate) +} + +selectedAccountNfts.subscribe((nfts) => { + void updateCollections(nfts) +}) + +export const selectedAccountCollections: Readable = derived( + collectionsStore, + ($collectionsStore) => $collectionsStore +) + +export const collectionsSearchTerm: Writable = writable('') diff --git a/packages/shared/src/lib/core/nfts/types/collections.type.ts b/packages/shared/src/lib/core/nfts/types/collections.type.ts new file mode 100644 index 0000000000..32dae6f7df --- /dev/null +++ b/packages/shared/src/lib/core/nfts/types/collections.type.ts @@ -0,0 +1,3 @@ +import { Collection } from '../interfaces' + +export type Collections = { [key: string]: Collection } diff --git a/packages/shared/src/lib/core/nfts/types/index.ts b/packages/shared/src/lib/core/nfts/types/index.ts index 3d34a70382..77e3cbecb6 100644 --- a/packages/shared/src/lib/core/nfts/types/index.ts +++ b/packages/shared/src/lib/core/nfts/types/index.ts @@ -1,2 +1,3 @@ +export * from './collections.type' export * from './nft-download-options.type' export * from './persisted-nft.type' diff --git a/packages/shared/src/lib/core/nfts/utils/getCollectionFromNft.ts b/packages/shared/src/lib/core/nfts/utils/getCollectionFromNft.ts new file mode 100644 index 0000000000..232f4d632e --- /dev/null +++ b/packages/shared/src/lib/core/nfts/utils/getCollectionFromNft.ts @@ -0,0 +1,42 @@ +import { NftStandard } from '../enums' +import { Collection, Nft } from '../interfaces' +import { Converter } from '@core/utils' +import { getClient } from '@core/profile-manager' +import type { AliasOutput, MetadataFeature, NftOutput } from '@iota/sdk' +import { FeatureType } from '@iota/sdk/out/types' + +export async function getCollectionFromNft(nft: Nft): Promise { + if (nft.standard !== NftStandard.Irc27) { + return + } + + const { aliasId = '', nftId = '' } = nft.issuer ?? {} + if (!aliasId && !nftId) { + return + } + + try { + const client = await getClient() + const outputId = aliasId ? await client.aliasOutputId(aliasId) : await client.nftOutputId(nftId) + if (!outputId) { + return + } + + const outputResponse = await client.getOutput(outputId) + const output = outputResponse.output as AliasOutput | NftOutput + + const metadataFeature = output.immutableFeatures?.find( + (feature) => feature.type === FeatureType.Metadata + ) as MetadataFeature + + if (!metadataFeature?.data) { + return + } + + const { standard, name, type, uri } = JSON.parse(Converter.hexToUtf8(metadataFeature.data)) + + return { standard, name, type, uri, nfts: [] } + } catch (error) { + console.error('Error retrieving collection from NFT:', error) + } +} diff --git a/packages/shared/src/lib/core/nfts/utils/index.ts b/packages/shared/src/lib/core/nfts/utils/index.ts index 93e479f6d5..f8305f6b34 100644 --- a/packages/shared/src/lib/core/nfts/utils/index.ts +++ b/packages/shared/src/lib/core/nfts/utils/index.ts @@ -1,17 +1,19 @@ export * from './buildNftFromPersistedErc721Nft' +export * from './buildPersistedErc721Nft' export * from './checkIfNftShouldBeDownloaded' +export * from './fetchWithTimeout' +export * from './getCollectionFromNft' +export * from './getFetchableNftUrls' export * from './getFilePathForNft' export * from './getNftsFromNftIds' export * from './getOwnerOfErc721Nft' -export * from './buildPersistedErc721Nft' -export * from './getFetchableNftUrls' export * from './getPrimaryNftUrl' export * from './getSpendableStatusFromUnspentNftOutput' -export * from './fetchWithTimeout' export * from './isIrc27Nft' -export * from './isNftOwnedByAnyAccount' export * from './isNftLocked' +export * from './isNftOwnedByAnyAccount' export * from './isScamIrc27Nft' export * from './isValidNftUri' +export * from './isVisibleCollection' export * from './isVisibleNft' export * from './parseNftMetadata' diff --git a/packages/shared/src/lib/core/nfts/utils/isVisibleCollection.ts b/packages/shared/src/lib/core/nfts/utils/isVisibleCollection.ts new file mode 100644 index 0000000000..6c367e4cbd --- /dev/null +++ b/packages/shared/src/lib/core/nfts/utils/isVisibleCollection.ts @@ -0,0 +1,20 @@ +import { get } from 'svelte/store' +import { collectionsSearchTerm } from '../stores' +import { Collection } from '../interfaces' + +export function isVisibleCollection(collection: Collection): boolean { + const searchTerm = get(collectionsSearchTerm) + + if (!isVisibleWithSearchTerm(collection, searchTerm)) { + return false + } + + return true +} + +function isVisibleWithSearchTerm(collection: Collection, searchTerm: string): boolean { + if (searchTerm) { + return collection.name.toLowerCase().includes(searchTerm.toLowerCase()) + } + return true +}