From 3f8dfee3cd3f5c002c4d206a189fd3d730d0c94d Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Thu, 19 Sep 2024 16:32:38 -0400 Subject: [PATCH] refactor(community_tokens): only fetch holders when going to the page (#16308) * refactor(community_tokens): only fetch holders when going to the page Fixes #16307 Instead of fetching community token holders each time members change, we fetch when the page for the token is opened. It shows a small loading text then the resulting holders. If the list is already available (fetched previously, we show it directly). There is still the timer to refresh the list if you stay on the page. * add loading property to storybook --- .../main/communities/tokens/controller.nim | 6 - .../main/communities/tokens/io_interface.nim | 6 - .../communities/tokens/models/token_item.nim | 5 +- .../communities/tokens/models/token_model.nim | 169 +++++++++++------- .../main/communities/tokens/module.nim | 6 - .../modules/main/communities/tokens/view.nim | 6 - src/app/modules/main/controller.nim | 14 +- src/app/modules/main/io_interface.nim | 9 + src/app/modules/main/module.nim | 14 ++ src/app/modules/main/view.nim | 6 + .../modules/shared_models/section_item.nim | 3 + .../service/community_tokens/service.nim | 37 ++-- storybook/pages/CommunityTokenViewPage.qml | 11 ++ .../Communities/helpers/TokenObject.qml | 3 + .../panels/MintTokensSettingsPanel.qml | 1 + .../views/CommunitySettingsView.qml | 2 +- .../Communities/views/CommunityTokenView.qml | 13 +- .../shared/stores/CommunityTokensStore.qml | 7 +- 18 files changed, 199 insertions(+), 119 deletions(-) diff --git a/src/app/modules/main/communities/tokens/controller.nim b/src/app/modules/main/communities/tokens/controller.nim index 0924796926..1d66af99e1 100644 --- a/src/app/modules/main/communities/tokens/controller.nim +++ b/src/app/modules/main/communities/tokens/controller.nim @@ -178,9 +178,3 @@ proc declineOwnership*(self: Controller, communityId: string) = proc asyncGetOwnerTokenOwnerAddress*(self: Controller, chainId: int, contractAddress: string) = self.communityTokensService.asyncGetOwnerTokenOwnerAddress(chainId, contractAddress) - -proc startTokenHoldersManagement*(self: Controller, chainId: int, contractAddress: string) = - self.communityTokensService.startTokenHoldersManagement(chainId, contractAddress) - -proc stopTokenHoldersManagement*(self: Controller) = - self.communityTokensService.stopTokenHoldersManagement() \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/io_interface.nim b/src/app/modules/main/communities/tokens/io_interface.nim index 61848f4ceb..47926ca61a 100644 --- a/src/app/modules/main/communities/tokens/io_interface.nim +++ b/src/app/modules/main/communities/tokens/io_interface.nim @@ -119,9 +119,3 @@ method onOwnerTokenOwnerAddress*(self: AccessInterface, chainId: int, contractAd method asyncGetOwnerTokenDetails*(self: AccessInterface, communityId: string) {.base.} = raise newException(ValueError, "No implementation available") - -method startTokenHoldersManagement*(self: AccessInterface, chainId: int, contractAddress: string) {.base.} = - raise newException(ValueError, "No implementation available") - -method stopTokenHoldersManagement*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/models/token_item.nim b/src/app/modules/main/communities/tokens/models/token_item.nim index 4db2fd9d04..493933ce02 100644 --- a/src/app/modules/main/communities/tokens/models/token_item.nim +++ b/src/app/modules/main/communities/tokens/models/token_item.nim @@ -20,6 +20,7 @@ type burnState*: ContractTransactionStatus remoteDestructedAddresses*: seq[string] tokenOwnersModel*: token_owners_model.TokenOwnersModel + tokenHoldersLoading*: bool proc initTokenItem*( tokenDto: CommunityTokenDto, @@ -29,7 +30,7 @@ proc initTokenItem*( burnState: ContractTransactionStatus, remoteDestructedAddresses: seq[string], remainingSupply: Uint256, - destructedAmount: Uint256 + destructedAmount: Uint256, ): TokenItem = result.tokenDto = tokenDto if network != nil: @@ -45,6 +46,7 @@ proc initTokenItem*( # TODO: provide number of messages here result = initTokenOwnersItem(owner.contactId, owner.name, owner.imageSource, 0, owner.collectibleOwner, remoteDestructedAddresses) )) + result.tokenHoldersLoading = false proc `$`*(self: TokenItem): string = result = fmt"""TokenItem( @@ -55,6 +57,7 @@ proc `$`*(self: TokenItem): string = destructedAmount: {self.destructedAmount}, burnState: {self.burnState}, tokenOwnersModel: {self.tokenOwnersModel}, + tokenHoldersLoading: {self.tokenHoldersLoading}, remoteDestructedAddresses: {self.remoteDestructedAddresses} ]""" diff --git a/src/app/modules/main/communities/tokens/models/token_model.nim b/src/app/modules/main/communities/tokens/models/token_model.nim index 06365c26eb..5e29440033 100644 --- a/src/app/modules/main/communities/tokens/models/token_model.nim +++ b/src/app/modules/main/communities/tokens/models/token_model.nim @@ -26,6 +26,7 @@ type ChainName ChainIcon TokenOwnersModel + TokenHoldersLoading AccountName AccountAddress RemainingSupply @@ -50,76 +51,105 @@ QtObject: new(result, delete) result.setup - proc updateDeployState*(self: TokenModel, chainId: int, contractAddress: string, deployState: DeployState) = + proc getItemIndex(self: TokenModel, chainId: int, contractAddress: string): int = for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): - self.items[i].tokenDto.deployState = deployState - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[ModelRole.DeployState.int]) - return + if self.items[i].tokenDto.address == contractAddress and self.items[i].tokenDto.chainId == chainId: + return i + return -1 + + proc updateDeployState*(self: TokenModel, chainId: int, contractAddress: string, deployState: DeployState) = + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1 and self.items[itemIdx].tokenDto.deployState == deployState: + return + + self.items[itemIdx].tokenDto.deployState = deployState + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.DeployState.int]) proc updateAddress*(self: TokenModel, chainId: int, oldContractAddress: string, newContractAddress: string) = - for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == oldContractAddress) and (self.items[i].tokenDto.chainId == chainId)): - self.items[i].tokenDto.address = newContractAddress - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[ModelRole.TokenAddress.int]) - return + let itemIdx = self.getItemIndex(chainId, oldContractAddress) + if itemIdx == -1 and self.items[itemIdx].tokenDto.address == newContractAddress: + return + + self.items[itemIdx].tokenDto.address = newContractAddress + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.TokenAddress.int]) proc updateBurnState*(self: TokenModel, chainId: int, contractAddress: string, burnState: ContractTransactionStatus) = - for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): - self.items[i].burnState = burnState - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[ModelRole.BurnState.int]) - return + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1 and self.items[itemIdx].burnState == burnState: + return + + self.items[itemIdx].burnState = burnState + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.BurnState.int]) proc updateRemoteDestructedAddresses*(self: TokenModel, chainId: int, contractAddress: string, remoteDestructedAddresses: seq[string]) = - for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): - self.items[i].remoteDestructedAddresses = remoteDestructedAddresses - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[ModelRole.RemotelyDestructState.int]) - self.items[i].tokenOwnersModel.updateRemoteDestructState(remoteDestructedAddresses) - self.dataChanged(index, index, @[ModelRole.TokenOwnersModel.int]) - return + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1 and self.items[itemIdx].remoteDestructedAddresses == remoteDestructedAddresses: + return + + self.items[itemIdx].remoteDestructedAddresses = remoteDestructedAddresses + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.RemotelyDestructState.int]) + self.items[itemIdx].tokenOwnersModel.updateRemoteDestructState(remoteDestructedAddresses) + self.dataChanged(index, index, @[ModelRole.TokenOwnersModel.int]) proc updateSupply*(self: TokenModel, chainId: int, contractAddress: string, supply: Uint256, destructedAmount: Uint256) = - for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): - if self.items[i].tokenDto.supply != supply or self.items[i].destructedAmount != destructedAmount: - self.items[i].tokenDto.supply = supply - self.items[i].destructedAmount = destructedAmount - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[ModelRole.Supply.int]) - return + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1: + return + + if self.items[itemIdx].tokenDto.supply != supply or self.items[itemIdx].destructedAmount != destructedAmount: + self.items[itemIdx].tokenDto.supply = supply + self.items[itemIdx].destructedAmount = destructedAmount + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.Supply.int]) proc updateRemainingSupply*(self: TokenModel, chainId: int, contractAddress: string, remainingSupply: Uint256) = - for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): - if self.items[i].remainingSupply != remainingSupply: - self.items[i].remainingSupply = remainingSupply - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[ModelRole.RemainingSupply.int]) - return + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1 and self.items[itemIdx].remainingSupply == remainingSupply: + return + + self.items[itemIdx].remainingSupply = remainingSupply + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.RemainingSupply.int]) + + proc hasTokenHolders*(self: TokenModel, chainId: int, contractAddress: string): bool = + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1: + return false + return self.items[itemIdx].tokenOwnersModel.count > 0 proc setCommunityTokenOwners*(self: TokenModel, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) = - for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): - self.items[i].tokenOwnersModel.setItems(owners.map(proc(owner: CommunityCollectibleOwner): TokenOwnersItem = - # TODO: provide number of messages here - result = initTokenOwnersItem(owner.contactId, owner.name, owner.imageSource, 0, owner.collectibleOwner, self.items[i].remoteDestructedAddresses) - )) - let index = self.createIndex(i, 0, nil) - defer: index.delete - self.dataChanged(index, index, @[ModelRole.TokenOwnersModel.int]) - return + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1: + return + + self.items[itemIdx].tokenHoldersLoading = false + self.items[itemIdx].tokenOwnersModel.setItems(owners.map(proc(owner: CommunityCollectibleOwner): TokenOwnersItem = + # TODO: provide number of messages here + result = initTokenOwnersItem(owner.contactId, owner.name, owner.imageSource, 0, owner.collectibleOwner, self.items[itemIdx].remoteDestructedAddresses) + )) + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.TokenOwnersModel.int, ModelRole.TokenHoldersLoading.int]) + + proc setCommunityTokenHoldersLoading*(self: TokenModel, chainId: int, contractAddress: string, value: bool) = + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1 and self.items[itemIdx].tokenHoldersLoading == value: + return + + self.items[itemIdx].tokenHoldersLoading = value + let index = self.createIndex(itemIdx, 0, nil) + defer: index.delete + self.dataChanged(index, index, @[ModelRole.TokenHoldersLoading.int]) proc countChanged(self: TokenModel) {.signal.} @@ -129,7 +159,6 @@ QtObject: self.endResetModel() self.countChanged() - proc getOwnerToken*(self: TokenModel): TokenItem = for i in 0 ..< self.items.len: if(self.items[i].tokenDto.privilegesLevel == PrivilegesLevel.Owner): @@ -144,17 +173,18 @@ QtObject: self.endInsertRows() self.countChanged() - proc removeItemByChainIdAndAddress*(self: TokenModel, chainId: int, address: string) = - for i in 0 ..< self.items.len: - if((self.items[i].tokenDto.address == address) and (self.items[i].tokenDto.chainId == chainId)): - let parentModelIndex = newQModelIndex() - defer: parentModelIndex.delete + proc removeItemByChainIdAndAddress*(self: TokenModel, chainId: int, contractAddress: string) = + let itemIdx = self.getItemIndex(chainId, contractAddress) + if itemIdx == -1: + return - self.beginRemoveRows(parentModelIndex, i, i) - self.items.delete(i) - self.endRemoveRows() - self.countChanged() - return + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + self.beginRemoveRows(parentModelIndex, itemIdx, itemIdx) + self.items.delete(itemIdx) + self.endRemoveRows() + self.countChanged() proc getCount*(self: TokenModel): int {.slot.} = self.items.len @@ -184,6 +214,7 @@ QtObject: ModelRole.ChainName.int:"chainName", ModelRole.ChainIcon.int:"chainIcon", ModelRole.TokenOwnersModel.int:"tokenOwnersModel", + ModelRole.TokenHoldersLoading.int:"tokenHoldersLoading", ModelRole.AccountName.int:"accountName", ModelRole.AccountAddress.int:"accountAddress", ModelRole.RemainingSupply.int:"remainingSupply", @@ -235,6 +266,8 @@ QtObject: result = newQVariant(item.chainIcon) of ModelRole.TokenOwnersModel: result = newQVariant(item.tokenOwnersModel) + of ModelRole.TokenHoldersLoading: + result = newQVariant(item.tokenHoldersLoading) of ModelRole.AccountName: result = newQVariant(item.accountName) of ModelRole.AccountAddress: diff --git a/src/app/modules/main/communities/tokens/module.nim b/src/app/modules/main/communities/tokens/module.nim index 9f731b0fe9..f005a01b5b 100644 --- a/src/app/modules/main/communities/tokens/module.nim +++ b/src/app/modules/main/communities/tokens/module.nim @@ -385,9 +385,3 @@ method onOwnerTokenOwnerAddress*(self: Module, chainId: int, contractAddress: st "contractAddress": contractAddress } self.view.setOwnerTokenDetails($jsonObj) - -method startTokenHoldersManagement*(self: Module, chainId: int, contractAddress: string) = - self.controller.startTokenHoldersManagement(chainId, contractAddress) - -method stopTokenHoldersManagement*(self: Module) = - self.controller.stopTokenHoldersManagement() \ No newline at end of file diff --git a/src/app/modules/main/communities/tokens/view.nim b/src/app/modules/main/communities/tokens/view.nim index c19d7fa9e0..9ffb55402e 100644 --- a/src/app/modules/main/communities/tokens/view.nim +++ b/src/app/modules/main/communities/tokens/view.nim @@ -145,9 +145,3 @@ QtObject: QtProperty[string] ownerTokenDetails: read = getOwnerTokenDetails notify = ownerTokenDetailsChanged - - proc startTokenHoldersManagement*(self: View, chainId: int, contractAddress: string) {.slot.} = - self.communityTokensModule.startTokenHoldersManagement(chainId, contractAddress) - - proc stopTokenHoldersManagement*(self: View) {.slot.} = - self.communityTokensModule.stopTokenHoldersManagement() \ No newline at end of file diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 70e812e567..3f32ea0201 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -392,6 +392,10 @@ proc init*(self: Controller) = let args = CommunityTokenOwnersArgs(e) self.delegate.onCommunityTokenOwnersFetched(args.communityId, args.chainId, args.contractAddress, args.owners) + self.events.on(SIGNAL_COMMUNITY_TOKEN_OWNERS_LOADING_FAILED) do(e: Args): + let args = CommunityTokenOwnersArgs(e) + self.delegate.errorLoadingTokenHolders(args.communityId, args.chainId, args.contractAddress, args.error) + self.events.on(SIGNAL_ACCEPT_REQUEST_TO_JOIN_LOADING) do(e: Args): var args = CommunityMemberArgs(e) self.delegate.onAcceptRequestToJoinLoading(args.communityId, args.pubKey) @@ -412,10 +416,6 @@ proc init*(self: Controller) = let args = CommunityMemberStatusUpdatedArgs(e) self.delegate.onMembershipStateUpdated(args.communityId, args.memberPubkey, args.state) - self.events.on(SIGNAL_COMMUNITY_MEMBERS_CHANGED) do(e: Args): - let args = CommunityMembersArgs(e) - self.communityTokensService.fetchCommunityTokenOwners(args.communityId) - self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED) do(e: Args): let args = SharedKeycarModuleFlowTerminatedArgs(e) if args.uniqueIdentifier == UNIQUE_MAIN_MODULE_KEYCARD_SYNC_IDENTIFIER: @@ -597,3 +597,9 @@ proc asyncGetRevealedAccountsForAllMembers*(self: Controller, communityId: strin proc asyncReevaluateCommunityMembersPermissions*(self: Controller, communityId: string) = self.communityService.asyncReevaluateCommunityMembersPermissions(communityId) + +proc startTokenHoldersManagement*(self: Controller, chainId: int, contractAddress: string) = + self.communityTokensService.startTokenHoldersManagement(chainId, contractAddress) + +proc stopTokenHoldersManagement*(self: Controller) = + self.communityTokensService.stopTokenHoldersManagement() diff --git a/src/app/modules/main/io_interface.nim b/src/app/modules/main/io_interface.nim index 582e6c903c..e36cd2c807 100644 --- a/src/app/modules/main/io_interface.nim +++ b/src/app/modules/main/io_interface.nim @@ -337,6 +337,9 @@ method onOwnerTokensDeploymentStarted*(self: AccessInterface, ownerToken: Commun method onCommunityTokenOwnersFetched*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) {.base.} = raise newException(ValueError, "No implementation available") +method errorLoadingTokenHolders*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, error: string) {.base.} = + raise newException(ValueError, "No implementation available") + method onCommunityTokenDeployStateChanged*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, deployState: DeployState) {.base.} = raise newException(ValueError, "No implementation available") @@ -421,6 +424,12 @@ method openSectionChatAndMessage*(self: AccessInterface, sectionId: string, chat method updateRequestToJoinState*(self: AccessInterface, sectionId: string, requestToJoinState: RequestToJoinState) {.base.} = raise newException(ValueError, "No implementation available") +method startTokenHoldersManagement*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method stopTokenHoldersManagement*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + # This way (using concepts) is used only for the modules managed by AppController type DelegateInterface* = concept c diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 0c6d14c369..0b233609b8 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -1213,6 +1213,20 @@ method onCommunityTokenRemoved*[T](self: Module[T], communityId: string, chainId if item.id != "": item.removeCommunityToken(chainId, address) +method startTokenHoldersManagement*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string) = + self.controller.startTokenHoldersManagement(chainId, contractAddress) + let item = self.view.model().getItemById(communityId) + if item.id != "" and not item.communityTokens.hasTokenHolders(chainId, contractAddress): + item.setCommunityTokenHoldersLoading(chainId, contractAddress, value = true) + +method stopTokenHoldersManagement*[T](self: Module[T]) = + self.controller.stopTokenHoldersManagement() + +method errorLoadingTokenHolders*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string, error: string) = + let item = self.view.model().getItemById(communityId) + if item.id != "": + item.setCommunityTokenHoldersLoading(chainId, contractAddress, value = false) + method onCommunityTokenOwnersFetched*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) = let item = self.view.model().getItemById(communityId) if item.id != "": diff --git a/src/app/modules/main/view.nim b/src/app/modules/main/view.nim index 9712e5ffe8..50e407d6b0 100644 --- a/src/app/modules/main/view.nim +++ b/src/app/modules/main/view.nim @@ -375,3 +375,9 @@ QtObject: proc emitCommunityMemberStatusEphemeralNotification*(self:View, communityName: string, memberName: string, membershipState: int) = self.communityMemberStatusEphemeralNotification(communityName, memberName, membershipState) + + proc startTokenHoldersManagement*(self: View, communityId: string, chainId: int, contractAddress: string) {.slot.} = + self.delegate.startTokenHoldersManagement(communityId, chainId, contractAddress) + + proc stopTokenHoldersManagement*(self: View) {.slot.} = + self.delegate.stopTokenHoldersManagement() diff --git a/src/app/modules/shared_models/section_item.nim b/src/app/modules/shared_models/section_item.nim index c023754424..0bf1ec6d4e 100644 --- a/src/app/modules/shared_models/section_item.nim +++ b/src/app/modules/shared_models/section_item.nim @@ -373,6 +373,9 @@ proc updateRemoteDestructedAddresses*(self: SectionItem, chainId: int, contractA proc setCommunityTokenOwners*(self: SectionItem, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) {.inline.} = self.communityTokensModel.setCommunityTokenOwners(chainId, contractAddress, owners) +proc setCommunityTokenHoldersLoading*(self: SectionItem, chainId: int, contractAddress: string, value: bool) {.inline.} = + self.communityTokensModel.setCommunityTokenHoldersLoading(chainId, contractAddress, value) + proc communityTokens*(self: SectionItem): community_tokens_model.TokenModel {.inline.} = self.communityTokensModel diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim index 9eb845b947..42162376b5 100644 --- a/src/app_service/service/community_tokens/service.nim +++ b/src/app_service/service/community_tokens/service.nim @@ -156,6 +156,7 @@ type contractAddress*: string chainId*: int owners*: seq[CommunityCollectibleOwner] + error*: string type CommunityTokensDetailsArgs* = ref object of Args @@ -259,6 +260,7 @@ const SIGNAL_COMPUTE_SELF_DESTRUCT_FEE* = "communityTokens-computeSelfDestructFe const SIGNAL_COMPUTE_BURN_FEE* = "communityTokens-computeBurnFee" const SIGNAL_COMPUTE_AIRDROP_FEE* = "communityTokens-computeAirdropFee" const SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED* = "communityTokens-communityTokenOwnersFetched" +const SIGNAL_COMMUNITY_TOKEN_OWNERS_LOADING_FAILED* = "communityTokens-communityTokenOwnersLoadingFailed" const SIGNAL_REMOTE_DESTRUCT_STATUS* = "communityTokens-communityTokenRemoteDestructStatus" const SIGNAL_BURN_STATUS* = "communityTokens-communityTokenBurnStatus" const SIGNAL_BURN_ACTION_RECEIVED* = "communityTokens-communityTokenBurnActionReceived" @@ -1290,17 +1292,28 @@ QtObject: proc onCommunityTokenOwnersFetched*(self:Service, response: string) {.slot.} = let responseJson = response.parseJson() - if responseJson{"error"}.kind != JNull and responseJson{"error"}.getStr != "": - let errorMessage = responseJson["error"].getStr - error "Can't fetch community token owners", chainId=responseJson{"chainId"}, contractAddress=responseJson{"contractAddress"}, errorMsg=errorMessage - return let chainId = responseJson{"chainId"}.getInt let contractAddress = responseJson{"contractAddress"}.getStr let communityId = responseJson{"communityId"}.getStr - let communityTokenOwners = toCommunityCollectibleOwners(responseJson{"result"}) - self.tokenOwnersCache[(chainId, contractAddress)] = communityTokenOwners - let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: communityTokenOwners) - self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data) + + try: + if responseJson{"error"}.kind != JNull and responseJson{"error"}.getStr != "": + raise newException(ValueError, responseJson["error"].getStr) + + let communityTokenOwners = toCommunityCollectibleOwners(responseJson{"result"}) + self.tokenOwnersCache[(chainId, contractAddress)] = communityTokenOwners + let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: communityTokenOwners) + self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data) + except Exception as e: + error "Can't fetch community token owners", chainId=responseJson{"chainId"}, contractAddress=responseJson{"contractAddress"}, errorMsg=e.msg + + let data = CommunityTokenOwnersArgs( + chainId: chainId, + contractAddress: contractAddress, + communityId: communityId, + error: e.msg, + ) + self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_LOADING_FAILED, data) # restart token holders timer self.restartTokenHoldersTimer(chainId, contractAddress) @@ -1313,14 +1326,6 @@ QtObject: let community = self.communityService.getCommunityById(communityId) return community.isPrivilegedUser() - # used when community members changed - proc fetchCommunityTokenOwners*(self: Service, communityId: string) = - if not self.iAmCommunityPrivilegedUser(communityId): - return - let tokens = self.getCommunityTokens(communityId) - for token in tokens: - self.fetchCommunityOwners(token) - proc getOwnerToken*(self: Service, communityId: string): CommunityTokenDto = let communityTokens = self.getCommunityTokens(communityId) for token in communityTokens: diff --git a/storybook/pages/CommunityTokenViewPage.qml b/storybook/pages/CommunityTokenViewPage.qml index 7cfe999701..5452ebad8c 100644 --- a/storybook/pages/CommunityTokenViewPage.qml +++ b/storybook/pages/CommunityTokenViewPage.qml @@ -41,6 +41,7 @@ SplitView { chainName: networksGroup.checkedButton.text chainIcon: networksGroup.checkedButton.chainIcon accountName: "helloworld" + tokenHoldersLoading: loadingTokenHolders.checked // collectible-specific properties remotelyDestructState: remotelyDestructStateBox.checked @@ -141,6 +142,16 @@ SplitView { } } + GroupBox { + Layout.fillWidth: true + + CheckBox { + id: loadingTokenHolders + text: "Loading Token Holders" + checked: false + } + } + GroupBox { Layout.fillWidth: true diff --git a/ui/app/AppLayouts/Communities/helpers/TokenObject.qml b/ui/app/AppLayouts/Communities/helpers/TokenObject.qml index 748a3431a2..aa520aae5c 100644 --- a/ui/app/AppLayouts/Communities/helpers/TokenObject.qml +++ b/ui/app/AppLayouts/Communities/helpers/TokenObject.qml @@ -51,4 +51,7 @@ QtObject { // Asset-specific properties: property int decimals: 2 + + // Loading indicators + property bool tokenHoldersLoading: false } diff --git a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml index 62e01461d9..530799155a 100644 --- a/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/MintTokensSettingsPanel.qml @@ -1005,6 +1005,7 @@ StackView { token.accountAddress: model.accountAddress token.multiplierIndex: model.multiplierIndex token.tokenAddress: model.tokenAddress + token.tokenHoldersLoading: model.tokenHoldersLoading } onCountChanged: { diff --git a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml index 525bb13849..45d3b27f66 100644 --- a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml @@ -375,7 +375,7 @@ StatusSectionLayout { onRegisterBurnTokenFeesSubscriber: d.feesBroker.registerBurnFeesSubscriber(feeSubscriber) - onStartTokenHoldersManagement: communityTokensStore.startTokenHoldersManagement(chainId, address) + onStartTokenHoldersManagement: communityTokensStore.startTokenHoldersManagement(root.community.id, chainId, address) onStopTokenHoldersManagement: communityTokensStore.stopTokenHoldersManagement() diff --git a/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml b/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml index 22f75e1c61..761ecf168c 100644 --- a/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunityTokenView.qml @@ -96,6 +96,7 @@ StatusScrollView { id: d readonly property int iconSize: 20 + property bool loadingTokenHolders: false readonly property var renamedTokenOwnersModel: RolesRenamingModel { sourceModel: root.tokenOwnersModel @@ -172,7 +173,7 @@ StatusScrollView { wrapMode: Text.Wrap font.pixelSize: Style.current.primaryTextFontSize color: Theme.palette.baseColor1 - text: qsTr("Review token details before minting it as they can’t be edited later") + text: qsTr("Review token details before minting it as they can't be edited later") } } @@ -228,7 +229,15 @@ StatusScrollView { id: tokenHolderLoader visible: !root.preview && root.deploymentCompleted - sourceComponent: isOwnerTokenItem ? tokenHolderContact : sortableTokenHolderPanel + sourceComponent: isOwnerTokenItem ? tokenHolderContact : (token.tokenHoldersLoading ? tokenHoldersLoadingComponent : sortableTokenHolderPanel) + } + + Component { + id: tokenHoldersLoadingComponent + + StatusBaseText { + text: qsTr("Loading token holders...") + } } Component { diff --git a/ui/imports/shared/stores/CommunityTokensStore.qml b/ui/imports/shared/stores/CommunityTokensStore.qml index 5aba9bf15c..eac1a210e2 100644 --- a/ui/imports/shared/stores/CommunityTokensStore.qml +++ b/ui/imports/shared/stores/CommunityTokensStore.qml @@ -7,6 +7,7 @@ QtObject { id: root property var communityTokensModuleInst: communityTokensModule ?? null + property var mainModuleInst: mainModule ?? null // Network selection properties: property var flatNetworks: networksModule.flatNetworks @@ -231,11 +232,11 @@ QtObject { communityTokensModuleInst.asyncGetOwnerTokenDetails(communityId) } - function startTokenHoldersManagement(chainId, contractAddress) { - communityTokensModuleInst.startTokenHoldersManagement(chainId, contractAddress) + function startTokenHoldersManagement(communityId, chainId, contractAddress) { + mainModuleInst.startTokenHoldersManagement(communityId, chainId, contractAddress) } function stopTokenHoldersManagement() { - communityTokensModuleInst.stopTokenHoldersManagement() + mainModuleInst.stopTokenHoldersManagement() } }