Skip to content

Commit

Permalink
feat: adds collection data in CollectionsGalleryView (#2391)
Browse files Browse the repository at this point in the history
* feat: adds collection data in CollectionsGalleryView

* enhancement: getCollectionFromNft

* chore: remove unnnecessary console errors

* fix: not visible collections
  • Loading branch information
jeeanribeiro authored Apr 29, 2024
1 parent a5681bc commit 9711f05
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,57 +1,57 @@
<script lang="ts">
import { Button, IconName, Pill, Text } from '@bloomwalletio/ui'
import { CollectiblesListMenu, EmptyListPlaceholder } from '@components'
import { Filter } from '@components/filter'
import { localize } from '@core/i18n'
import { PopupId, openPopup } from '@desktop/auxiliary/popup'
import features from '@features/features'
import { SearchInput } from '@ui'
import { writable } from 'svelte/store'
import { CollectiblesTabs } from '../components'
import { collectionsSearchTerm, selectedAccountCollections } from '@core/nfts/stores'
import { Collections, isVisibleCollection } from '@core/nfts'
function onReceiveClick(): void {
openPopup({
id: PopupId.ReceiveAddress,
})
}
// MOCKS
const collections: { name: string }[] = []
let collectionSearchTerm = ''
const collectionFilter = writable(undefined)
const ownedCollections = collections
let queriedCollections: typeof collections = []
$: collectionSearchTerm,
$collectionFilter,
(queriedCollections = ownedCollections
.filter((collection) => collection)
.sort((collection1, collection2) =>
collection1.name.toLowerCase().localeCompare(collection2.name.toLowerCase())
))
let queriedCollections: Collections = {}
$: $collectionsSearchTerm,
(queriedCollections = Object.fromEntries(
Object.entries($selectedAccountCollections)
.filter(([, collection]) => isVisibleCollection(collection))
.sort(([, collection1], [, collection2]) =>
collection1?.name.toLowerCase().localeCompare(collection2?.name.toLowerCase())
)
))
$: hasCollections = Object.keys($selectedAccountCollections).length > 0
</script>

<collections-gallery-view class="flex flex-col w-full h-full gap-4">
<header class="flex flex-row items-center justify-between">
<div class="flex flex-row text-left gap-2 items-center flex-1">
<Text type="h6">{localize('views.collectibles.collectionsGallery.title')}</Text>
<Pill color="neutral">
<Text textColor="secondary">{String(queriedCollections.length ?? '')}</Text>
<Text textColor="secondary">{String(Object.keys($selectedAccountCollections).length ?? '')}</Text>
</Pill>
</div>
<CollectiblesTabs />
<div class="flex justify-end items-center gap-5 h-10 shrink-0 flex-1">
{#if collections.length}
<SearchInput bind:value={collectionSearchTerm} />
<Filter filterStore={collectionFilter} />
{#if hasCollections}
<SearchInput bind:value={$collectionsSearchTerm} />
{/if}
{#if features.collectibles.erc721.enabled}
<CollectiblesListMenu />
{/if}
</div>
</header>
{#if collections.length}
{#if queriedCollections.length}
{#if hasCollections}
{#if Object.keys(queriedCollections).length > 0}
<!-- <CollectionsGallery collections={queriedCollections} /> -->
{#each Object.keys(queriedCollections) as collection}
<Text>{queriedCollections[collection].name}</Text>
{/each}
{:else}
<div class="w-full h-full flex flex-col items-center justify-center">
<EmptyListPlaceholder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NftStandard } from '../enums'
import { Nft } from './nft.interface'

export interface Collection {
standard: NftStandard
name: string
type: string
uri: string
nfts: Nft[]
}
1 change: 1 addition & 0 deletions packages/shared/src/lib/core/nfts/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './collection.interface'
export * from './download-metadata.interface'
export * from './erc721-contract-metadata.interface'
export * from './nft-filter.interface'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { EvmNetworkId, NetworkId } from '@core/network/types'
import { Address } from '@iota/sdk/out/types'
import { MimeType, NftStandard } from '../enums'
import { IDownloadMetadata, IErc721ContractMetadata, IErc721TokenMetadata, IIrc27Metadata } from '../interfaces'

Expand All @@ -10,7 +9,7 @@ export interface IIrc27Nft extends IBaseNft {
nftAddress: string
rawMetadata: string
metadata?: IIrc27Metadata
issuer?: Address
issuer?: { type: number; aliasId?: string; nftId?: string }
timelockTime?: number
expirationTime?: number
latestOutputId: string
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/lib/core/nfts/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './all-account-nfts.store'
export * from './downloading-nft.store'
export * from './nft-download-queue.store'
export * from './nft-filter.store'
export * from './selected-account-collections.store'
export * from './persisted-nfts.store'
export * from './selected-account-nfts.store'
export * from './selected-collectibles-tabs.store'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { derived, get, Readable, Writable, writable } from 'svelte/store'
import { selectedAccountNfts } from './selected-account-nfts.store'
import { NftStandard } from '../enums'
import { Nft } from '../interfaces'
import { getCollectionFromNft } from '../utils'
import { Collections } from '../types'

export const collectionsStore: Writable<Collections> = writable({})

async function updateCollections(nfts: Nft[]): Promise<void> {
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<Collections> = derived(
collectionsStore,
($collectionsStore) => $collectionsStore
)

export const collectionsSearchTerm: Writable<string> = writable('')
3 changes: 3 additions & 0 deletions packages/shared/src/lib/core/nfts/types/collections.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Collection } from '../interfaces'

export type Collections = { [key: string]: Collection }
1 change: 1 addition & 0 deletions packages/shared/src/lib/core/nfts/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './collections.type'
export * from './nft-download-options.type'
export * from './persisted-nft.type'
42 changes: 42 additions & 0 deletions packages/shared/src/lib/core/nfts/utils/getCollectionFromNft.ts
Original file line number Diff line number Diff line change
@@ -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<Collection | undefined> {
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)
}
}
10 changes: 6 additions & 4 deletions packages/shared/src/lib/core/nfts/utils/index.ts
Original file line number Diff line number Diff line change
@@ -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'
20 changes: 20 additions & 0 deletions packages/shared/src/lib/core/nfts/utils/isVisibleCollection.ts
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 9711f05

Please sign in to comment.