feat(@desktop/wallet): implement nested collectibles model

Part of #12072
This commit is contained in:
Dario Gabriel Lipicar 2023-09-04 06:24:14 -03:00 committed by dlipicar
parent 61f3d903ce
commit bad497cc90
14 changed files with 423 additions and 34 deletions

View File

@ -42,7 +42,11 @@ proc newModule*(
result.view = newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = accountsc.newController(result, walletAccountService)
result.collectiblesController = collectiblesc.newController(int32(backend_collectibles.CollectiblesRequestID.ProfileShowcase), events)
result.collectiblesController = collectiblesc.newController(
requestId = int32(backend_collectibles.CollectiblesRequestID.ProfileShowcase),
autofetch = false,
events = events
)
result.moduleLoaded = false
## Forward declarations
@ -58,7 +62,7 @@ method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
method getCollectiblesModel*(self: Module): QVariant =
return self.collectiblesController.getModel()
return self.collectiblesController.getModelAsVariant()
method convertWalletAccountDtoToKeyPairAccountItem(self: Module, account: WalletAccountDto): KeyPairAccountItem =
result = newKeyPairAccountItem(

View File

@ -122,7 +122,11 @@ proc newModule*(
result.transactionService = transactionService
result.activityController = activityc.newController(int32(ActivityID.History), currencyService, tokenService, events)
result.tmpActivityController = activityc.newController(int32(ActivityID.Temporary), currencyService, tokenService, events)
result.collectiblesController = collectiblesc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), events)
result.collectiblesController = collectiblesc.newController(
requestId = int32(backend_collectibles.CollectiblesRequestID.WalletAccount),
autofetch = false,
events = events
)
result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events)
result.filter = initFilter(result.controller)

View File

@ -1,6 +1,8 @@
import stint
import ../../../shared_models/currency_amount
import app_service/service/transaction/dto
import app/modules/shared_models/collectibles_model as collectibles
import app/modules/shared_models/collectibles_nested_model as nested_collectibles
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -57,3 +59,9 @@ method setSelectedReceiveAccountIndex*(self: AccessInterface, index: int) =
method filterChanged*(self: AccessInterface, addresses: seq[string], chainIds: seq[int]) =
raise newException(ValueError, "No implementation available")
method getCollectiblesModel*(self: AccessInterface): collectibles.Model =
raise newException(ValueError, "No implementation available")
method getNestedCollectiblesModel*(self: AccessInterface): nested_collectibles.Model =
raise newException(ValueError, "No implementation available")

View File

@ -13,6 +13,11 @@ import app/modules/shared/wallet_utils
import app_service/service/transaction/dto
import app/modules/shared_models/currency_amount
import app/modules/shared_modules/collectibles/controller as collectiblesc
import app/modules/shared_models/collectibles_model as collectibles
import app/modules/shared_models/collectibles_nested_model as nested_collectibles
import backend/collectibles as backend_collectibles
export io_interface
const cancelledRequest* = "cancelled"
@ -31,7 +36,10 @@ type
delegate: delegate_interface.AccessInterface
events: EventEmitter
view: View
controller: Controller
controller: controller.Controller
# Get the list of owned collectibles by the currently selected account
collectiblesController: collectiblesc.Controller
nestedCollectiblesModel: nested_collectibles.Model
moduleLoaded: bool
tmpSendTransactionDetails: TmpSendTransactionDetails
senderCurrentAccountIndex: int
@ -52,8 +60,15 @@ proc newModule*(
result = Module()
result.delegate = delegate
result.events = events
result.view = newView(result)
result.controller = controller.newController(result, events, walletAccountService, networkService, currencyService, transactionService)
result.collectiblesController = collectiblesc.newController(
requestId = int32(backend_collectibles.CollectiblesRequestID.WalletSend),
autofetch = true,
events = events
)
result.nestedCollectiblesModel = nested_collectibles.newModel(result.collectiblesController.getModel())
result.view = newView(result)
result.moduleLoaded = false
result.senderCurrentAccountIndex = 0
result.receiveCurrentAccountIndex = 0
@ -61,6 +76,8 @@ proc newModule*(
method delete*(self: Module) =
self.view.delete
self.controller.delete
self.nestedCollectiblesModel.delete
self.collectiblesController.delete
method convertSendToNetworkToNetworkItem(self: Module, network: SendToNetwork): NetworkItem =
result = initNetworkItem(
@ -297,8 +314,21 @@ method filterChanged*(self: Module, addresses: seq[string], chainIds: seq[int])
self.view.switchSenderAccountByAddress(addresses[0])
self.view.switchReceiveAccountByAddress(addresses[0])
proc updateCollectiblesFilter*(self: Module) =
let addresses = @[self.view.getSenderAddressByIndex(self.senderCurrentAccountIndex)]
let chainIds = self.controller.getChainIds()
self.collectiblesController.globalFilterChanged(addresses, chainIds)
method setSelectedSenderAccountIndex*(self: Module, index: int) =
self.senderCurrentAccountIndex = index
self.updateCollectiblesFilter()
method setSelectedReceiveAccountIndex*(self: Module, index: int) =
self.receiveCurrentAccountIndex = index
method getCollectiblesModel*(self: Module): collectibles.Model =
return self.collectiblesController.getModel()
method getNestedCollectiblesModel*(self: Module): nested_collectibles.Model =
return self.nestedCollectiblesModel

View File

@ -2,6 +2,8 @@ import NimQml, sequtils, strutils, stint, sugar
import ./io_interface, ./accounts_model, ./account_item, ./network_model, ./network_item, ./suggested_route_item, ./transaction_routes
import app/modules/shared_models/token_model
import app/modules/shared_models/collectibles_model as collectibles
import app/modules/shared_models/collectibles_nested_model as nested_collectibles
QtObject:
type
@ -10,6 +12,9 @@ QtObject:
accounts: AccountsModel
# this one doesn't include watch accounts and its what the user switches when using the sendModal
senderAccounts: AccountsModel
# list of collectibles owned by the selected sender account
collectiblesModel: collectibles.Model
nestedCollectiblesModel: nested_collectibles.Model
# for send modal
selectedSenderAccount: AccountItem
fromNetworksModel: NetworkModel
@ -43,6 +48,8 @@ QtObject:
result.fromNetworksModel = newNetworkModel()
result.toNetworksModel = newNetworkModel()
result.transactionRoutes = newTransactionRoutes()
result.collectiblesModel = delegate.getCollectiblesModel()
result.nestedCollectiblesModel = delegate.getNestedCollectiblesModel()
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -61,6 +68,20 @@ QtObject:
read = getSenderAccounts
notify = senderAccountsChanged
proc collectiblesModelChanged*(self: View) {.signal.}
proc getCollectiblesModel(self: View): QVariant {.slot.} =
return newQVariant(self.collectiblesModel)
QtProperty[QVariant] collectiblesModel:
read = getCollectiblesModel
notify = collectiblesModelChanged
proc nestedCollectiblesModelChanged*(self: View) {.signal.}
proc getNestedCollectiblesModel(self: View): QVariant {.slot.} =
return newQVariant(self.nestedCollectiblesModel)
QtProperty[QVariant] nestedCollectiblesModel:
read = getNestedCollectiblesModel
notify = nestedCollectiblesModelChanged
proc selectedSenderAccountChanged*(self: View) {.signal.}
proc getSelectedSenderAccount(self: View): QVariant {.slot.} =
return newQVariant(self.selectedSenderAccount)
@ -72,6 +93,9 @@ QtObject:
read = getSelectedSenderAccount
notify = selectedSenderAccountChanged
proc getSenderAddressByIndex*(self: View, index: int): string {.slot.} =
return self.senderAccounts.getItemByIndex(index).address()
proc selectedReceiveAccountChanged*(self: View) {.signal.}
proc getSelectedReceiveAccount(self: View): QVariant {.slot.} =
return newQVariant(self.selectedReceiveAccount)

View File

@ -11,6 +11,8 @@ type
imageUrl: string
backgroundColor: string
collectionName: string
collectionSlug: string
collectionImageUrl: string
isLoading: bool
isPinned: bool
@ -24,6 +26,8 @@ proc initItem*(
imageUrl: string,
backgroundColor: string,
collectionName: string,
collectionSlug: string,
collectionImageUrl: string,
isPinned: bool
): Item =
result.chainId = chainId
@ -35,11 +39,13 @@ proc initItem*(
result.imageUrl = imageUrl
result.backgroundColor = if (backgroundColor == ""): "transparent" else: ("#" & backgroundColor)
result.collectionName = collectionName
result.collectionSlug = collectionSlug
result.collectionImageUrl = collectionImageUrl
result.isLoading = false
result.isPinned = isPinned
proc initItem*: Item =
result = initItem(0, "", u256(0), "", "", "", "", "transparent", "Collectibles", false)
result = initItem(0, "", u256(0), "", "", "", "", "transparent", "Collectibles", "", "", false)
proc initLoadingItem*: Item =
result = initItem()
@ -56,6 +62,8 @@ proc `$`*(self: Item): string =
imageUrl: {self.imageUrl},
backgroundColor: {self.backgroundColor},
collectionName: {self.collectionName},
collectionSlug: {self.collectionSlug},
collectionImageUrl: {self.collectionImageUrl},
isLoading: {self.isLoading},
isPinned: {self.isPinned},
]"""
@ -88,9 +96,19 @@ proc getImageUrl*(self: Item): string =
proc getBackgroundColor*(self: Item): string =
return self.backgroundColor
# Unique ID to identify collection, generated by us
proc getCollectionId*(self: Item): string =
return fmt"{self.getChainId}+{self.getContractAddress}"
proc getCollectionName*(self: Item): string =
return self.collectionName
proc getCollectionSlug*(self: Item): string =
return self.collectionSlug
proc getCollectionImageUrl*(self: Item): string =
return self.collectionImageUrl
proc getIsLoading*(self: Item): bool =
return self.isLoading

View File

@ -14,7 +14,9 @@ type
MediaUrl
MediaType
BackgroundColor
CollectionUid
CollectionName
CollectionSlug
IsLoading
IsPinned
@ -145,7 +147,9 @@ QtObject:
CollectibleRole.MediaType.int:"mediaType",
CollectibleRole.ImageUrl.int:"imageUrl",
CollectibleRole.BackgroundColor.int:"backgroundColor",
CollectibleRole.CollectionUid.int:"collectionUid",
CollectibleRole.CollectionName.int:"collectionName",
CollectibleRole.CollectionSlug.int:"collectionSlug",
CollectibleRole.IsLoading.int:"isLoading",
CollectibleRole.IsPinned.int:"isPinned",
}.toTable
@ -180,8 +184,12 @@ QtObject:
result = newQVariant(item.getImageUrl())
of CollectibleRole.BackgroundColor:
result = newQVariant(item.getBackgroundColor())
of CollectibleRole.CollectionUid:
result = newQVariant(item.getCollectionId())
of CollectibleRole.CollectionName:
result = newQVariant(item.getCollectionName())
of CollectibleRole.CollectionSlug:
result = newQVariant(item.getCollectionSlug())
of CollectibleRole.IsLoading:
result = newQVariant(false)
of CollectibleRole.IsPinned:
@ -195,6 +203,26 @@ QtObject:
error "Invalid role for loading item"
result = newQVariant()
proc rowData(self: Model, index: int, column: string): string {.slot.} =
if (index >= self.items.len):
return
let item = self.items[index]
case column:
of "uid": result = item.getId()
of "chainId": result = $item.getChainId()
of "contractAddress": result = item.getContractAddress()
of "tokenId": result = item.getTokenId().toString()
of "name": result = item.getName()
of "mediaUrl": result = item.getMediaUrl()
of "mediaType": result = item.getMediaType()
of "imageUrl": result = item.getImageUrl()
of "backgroundColor": result = item.getBackgroundColor()
of "collectionUid": result = item.getCollectionId()
of "collectionName": result = item.getCollectionName()
of "collectionSlug": result = item.getCollectionSlug()
of "isLoading": result = $false
of "isPinned": result = $item.getIsPinned()
proc appendCollectibleItems(self: Model, newItems: seq[Item]) =
if len(newItems) == 0:
return
@ -271,6 +299,9 @@ QtObject:
else:
self.removeLoadingItems()
proc getItems*(self: Model): seq[Item] =
return self.items
proc setItems*(self: Model, newItems: seq[Item], offset: int, hasMore: bool) =
if offset == 0:
self.removeCollectibleItems()

View File

@ -0,0 +1,60 @@
import strformat, stint
type
Item* = object
id: string # Collectible ID if isCollection=false, Collection Slug otherwise
chainId: int
name: string
iconUrl: string
collectionId: string
collectionName: string
isCollection: bool
proc initItem*(
id: string,
chainId: int,
name: string,
iconUrl: string,
collectionId: string,
collectionName: string,
isCollection: bool
): Item =
result.id = id
result.chainId = chainId
result.name = name
result.iconUrl = iconUrl
result.collectionId = collectionId
result.collectionName = collectionName
result.isCollection = isCollection
proc `$`*(self: Item): string =
result = fmt"""CollectiblesNestedEntry(
id: {self.id},
chainId: {self.chainId},
name: {self.name},
iconUrl: {self.iconUrl},
collectionId: {self.collectionId},
collectionName: {self.collectionName},
isCollection: {self.isCollection},
]"""
proc getId*(self: Item): string =
return self.id
proc getChainId*(self: Item): int =
return self.chainId
proc getName*(self: Item): string =
return self.name
proc getIconUrl*(self: Item): string =
return self.iconUrl
proc getCollectionId*(self: Item): string =
return self.collectionId
proc getCollectionName*(self: Item): string =
return self.collectionName
proc getIsCollection*(self: Item): bool =
return self.isCollection

View File

@ -0,0 +1,166 @@
import NimQml, Tables, strutils, strformat, sequtils, logging
import ./collectibles_model as flat_model
import ./collectibles_item as flat_item
import ./collectibles_nested_item as nested_item
import ./collectibles_nested_utils
type
CollectiblesNestedRole {.pure.} = enum
Uid = UserRole + 1,
ChainId
Name
IconUrl
CollectionUid
CollectionName
IsCollection
QtObject:
type
Model* = ref object of QAbstractListModel
flatModel: flat_model.Model
items: seq[nested_item.Item]
currentCollectionUid: string
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(flatModel: flat_model.Model): Model =
new(result, delete)
result.flatModel = flatModel
result.items = @[]
result.currentCollectionUid = ""
result.setup
signalConnect(result.flatModel, "countChanged()", result, "refreshItems()")
# Forward declaration
proc refreshItems*(self: Model)
proc `$`*(self: Model): string =
result = fmt"""CollectiblesNestedModel(
flatModel: {self.flatModel},
currentCollectionUid: {self.currentCollectionUid},
]"""
proc countChanged(self: Model) {.signal.}
proc getCount*(self: Model): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
proc getCurrentCollectionUid*(self: Model): string {.slot.} =
result = self.currentCollectionUid
proc currentCollectionUidChanged(self: Model) {.signal.}
proc setCurrentCollectionUid(self: Model, currentCollectionUid: string) {.slot.} =
self.currentCollectionUid = currentCollectionUid
self.currentCollectionUidChanged()
self.refreshItems()
QtProperty[string] currentCollectionUid:
read = getCurrentCollectionUid
write = setCurrentCollectionUid
notify = currentCollectionUidChanged
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
CollectiblesNestedRole.Uid.int:"uid",
CollectiblesNestedRole.ChainId.int:"chainId",
CollectiblesNestedRole.Name.int:"name",
CollectiblesNestedRole.IconUrl.int:"iconUrl",
CollectiblesNestedRole.CollectionUid.int:"collectionUid",
CollectiblesNestedRole.CollectionName.int:"collectionName",
CollectiblesNestedRole.IsCollection.int:"isCollection",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.getCount()):
return
let item = self.items[index.row]
let enumRole = role.CollectiblesNestedRole
case enumRole:
of CollectiblesNestedRole.Uid:
result = newQVariant(item.getId())
of CollectiblesNestedRole.ChainId:
result = newQVariant(item.getChainId())
of CollectiblesNestedRole.Name:
result = newQVariant(item.getName())
of CollectiblesNestedRole.IconUrl:
result = newQVariant(item.getIconUrl())
of CollectiblesNestedRole.CollectionUid:
result = newQVariant(item.getCollectionId())
of CollectiblesNestedRole.CollectionName:
result = newQVariant(item.getCollectionName())
of CollectiblesNestedRole.IsCollection:
result = newQVariant(item.getIsCollection())
proc rowData(self: Model, index: int, column: string): string {.slot.} =
if (index >= self.items.len):
return
let item = self.items[index]
case column:
of "uid": result = item.getId()
of "chainId": result = $item.getChainId()
of "name": result = item.getName()
of "iconUrl": result = item.getIconUrl()
of "collectionUid": result = item.getCollectionId()
of "collectionName": result = item.getCollectionName()
of "isCollection": result = $item.getIsCollection()
proc getCollectiblesPerCollectionId(items: seq[flat_item.Item]): Table[string, seq[flat_item.Item]] =
var collectiblesPerCollection = initTable[string, seq[flat_item.Item]]()
for item in items:
let collectionId = item.getCollectionId()
if not collectiblesPerCollection.hasKey(collectionId):
collectiblesPerCollection[collectionId] = @[]
collectiblesPerCollection[collectionId].add(item)
return collectiblesPerCollection
proc refreshItems*(self: Model) {.slot.} =
self.beginResetModel()
self.items = @[]
var collectiblesPerCollection = getCollectiblesPerCollectionId(self.flatModel.getItems())
for collectionId, collectionCollectibles in collectiblesPerCollection.pairs:
if self.currentCollectionUid == "":
# No collection selected
# If the collection contains more than 1 collectible, we add a single collection item
# Otherwise, we add the collectible
if collectionCollectibles.len > 1:
let collectionItem = collectibleToCollectionNestedItem(collectionCollectibles[0])
self.items.add(collectionItem)
else:
for collectible in collectionCollectibles:
let collectibleItem = collectibleToCollectibleNestedItem(collectible)
self.items.add(collectibleItem)
else:
if self.currentCollectionUid == collectionId:
for collectible in collectionCollectibles:
let collectibleItem = collectibleToCollectibleNestedItem(collectible)
self.items.add(collectibleItem)
# No need to keep looking
break
self.endResetModel()
self.countChanged()
proc resetModel*(self: Model) =
self.beginResetModel()
self.items = @[]
self.endResetModel()
self.countChanged()

View File

@ -0,0 +1,25 @@
import sequtils, sugar, times
import collectibles_item as flat_item
import collectibles_nested_item as nested_item
proc collectibleToCollectibleNestedItem*(flatItem: flat_item.Item): nested_item.Item =
return nested_item.initItem(
flatItem.getId(),
flatItem.getChainId(),
flatItem.getName(),
flatItem.getImageUrl(),
flatItem.getCollectionId(),
flatItem.getCollectionName(),
false
)
proc collectibleToCollectionNestedItem*(flatItem: flat_item.Item): nested_item.Item =
return nested_item.initItem(
flatItem.getCollectionId(),
flatItem.getChainId(),
flatItem.getCollectionName(),
flatItem.getCollectionImageUrl(),
flatItem.getCollectionId(),
flatItem.getCollectionName(),
true
)

View File

@ -19,5 +19,7 @@ proc collectibleToItem*(c: backend.CollectibleHeader, isPinned: bool = false) :
c.imageUrl,
c.backgroundColor,
c.collectionName,
c.collectionSlug,
c.collectionImageUrl,
isPinned
)

View File

@ -23,6 +23,7 @@ QtObject:
chainIds: seq[int]
requestId: int32
autofetch: bool
proc setup(self: Controller) =
self.QObject.setup
@ -30,33 +31,15 @@ QtObject:
proc delete*(self: Controller) =
self.QObject.delete
proc getModel*(self: Controller): QVariant {.slot.} =
proc getModel*(self: Controller): Model =
return self.model
proc getModelAsVariant*(self: Controller): QVariant {.slot.} =
return newQVariant(self.model)
QtProperty[QVariant] model:
read = getModel
read = getModelAsVariant
proc processFilterOwnedCollectiblesResponse(self: Controller, response: JsonNode) =
defer: self.model.setIsFetching(false)
let res = fromJson(response, backend_collectibles.FilterOwnedCollectiblesResponse)
let isError = res.errorCode != ErrorCodeSuccess
defer: self.model.setIsError(isError)
if isError:
error "error fetching collectibles entries: ", res.errorCode
return
try:
let items = res.collectibles.map(header => collectibleToItem(header))
self.model.setItems(items, res.offset, res.hasMore)
except Exception as e:
error "Error converting activity entries: ", e.msg
proc resetModel*(self: Controller) {.slot.} =
self.model.setItems(@[], 0, true)
self.fetchFromStart = true
proc loadMoreItems(self: Controller) {.slot.} =
if self.model.getIsFetching():
@ -77,6 +60,33 @@ QtObject:
self.fetchFromStart = true
error "error fetching collectibles entries: ", response.error
proc processFilterOwnedCollectiblesResponse(self: Controller, response: JsonNode) =
defer: self.model.setIsFetching(false)
let res = fromJson(response, backend_collectibles.FilterOwnedCollectiblesResponse)
let isError = res.errorCode != ErrorCodeSuccess
defer: self.model.setIsError(isError)
if isError:
error "error fetching collectibles entries: ", res.errorCode
return
try:
let items = res.collectibles.map(header => collectibleToItem(header))
self.model.setItems(items, res.offset, res.hasMore)
except Exception as e:
error "Error converting activity entries: ", e.msg
if self.autofetch and res.hasMore:
self.loadMoreItems()
proc resetModel*(self: Controller) {.slot.} =
self.model.setItems(@[], 0, true)
self.fetchFromStart = true
if self.autofetch:
self.loadMoreItems()
proc setupEventHandlers(self: Controller) =
self.eventsHandler.onOwnedCollectiblesFilteringDone(proc (jsonObj: JsonNode) =
self.processFilterOwnedCollectiblesResponse(jsonObj)
@ -87,14 +97,15 @@ QtObject:
)
self.eventsHandler.onCollectiblesOwnershipUpdateFinished(proc () =
self.resetModel()
self.model.setIsUpdating(false)
self.resetModel()
)
proc newController*(requestId: int32, events: EventEmitter): Controller =
proc newController*(requestId: int32, autofetch: bool, events: EventEmitter): Controller =
new(result, delete)
result.requestId = requestId
result.autofetch = autofetch
result.model = newModel()
result.fetchFromStart = true

View File

@ -12,6 +12,7 @@ type
CollectiblesRequestID* = enum
WalletAccount
ProfileShowcase
WalletSend
# Declared in services/wallet/collectibles/service.go
const eventCollectiblesOwnershipUpdateStarted*: string = "wallet-collectibles-ownership-update-started"
@ -40,7 +41,6 @@ type
collectibles*: seq[CollectibleDetails]
errorCode*: ErrorCode
# Responses
proc fromJson*(e: JsonNode, T: typedesc[FilterOwnedCollectiblesResponse]): FilterOwnedCollectiblesResponse {.inline.} =
var collectibles: seq[CollectibleHeader]

View File

@ -54,6 +54,8 @@ type
animationMediaType*: string
backgroundColor*: string
collectionName*: string
collectionSlug*: string
collectionImageUrl*: string
# Mirrors services/wallet/collectibles/types.go CollectibleDetails
CollectibleDetails* = ref object of RootObj
@ -250,7 +252,9 @@ proc `$`*(self: CollectibleHeader): string =
animationUrl:{self.animationUrl},
animationMediaType:{self.animationMediaType},
backgroundColor:{self.backgroundColor},
collectionName:{self.collectionName}
collectionName:{self.collectionName},
collectionSlug:{self.collectionSlug},
collectionImageUrl:{self.collectionImageUrl}
)"""
proc fromJson*(t: JsonNode, T: typedesc[CollectibleHeader]): CollectibleHeader {.inline.} =
@ -262,6 +266,8 @@ proc fromJson*(t: JsonNode, T: typedesc[CollectibleHeader]): CollectibleHeader {
result.animationMediaType = t["animation_media_type"].getStr()
result.backgroundColor = t["background_color"].getStr()
result.collectionName = t["collection_name"].getStr()
result.collectionSlug = t["collection_slug"].getStr()
result.collectionImageUrl = t["collection_image_url"].getStr()
# CollectibleDetails
proc `$`*(self: CollectibleDetails): string =
@ -276,7 +282,7 @@ proc `$`*(self: CollectibleDetails): string =
backgroundColor:{self.backgroundColor},
collectionName:{self.collectionName},
collectionSlug:{self.collectionSlug},
collectionImageUrl:{self.collectionImageUrl},
collectionImageUrl:{self.collectionImageUrl}
)"""
proc fromJson*(t: JsonNode, T: typedesc[CollectibleDetails]): CollectibleDetails {.inline.} =