perf(admin): speed up admin tabs significantly (#16363)

Iterates #16043

* fix(admin): improve admin panel loading by putting sections in loaders

* fix(admin): speed up members tab by using nim model and real search

* fix(admin): speed up airdrop panel

* fix(admin): mint panel and airdrop panel interactions and previous btn

* fix(admin): speed up overview page

* fix(admin): speed up permissions page
This commit is contained in:
Jonathan Rainville 2024-09-27 13:28:27 -04:00 committed by GitHub
parent c09de56678
commit 1cad66bb2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 397 additions and 348 deletions

View File

@ -236,6 +236,9 @@ method joinCommunityOrEditSharedAddresses*(self: AccessInterface) {.base.} =
method prepareTokenModelForCommunity*(self: AccessInterface, communityId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method prepareTokenModelForCommunityChat*(self: AccessInterface, communityId: string, chatId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getCommunityPublicKeyFromPrivateKey*(self: AccessInterface, communityPrivateKey: string): string {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -886,6 +886,25 @@ method prepareTokenModelForCommunity*(self: Module, communityId: string) =
self.view.spectatedCommunityPermissionModel.setItems(tokenPermissionsItems)
self.checkPermissions(communityId, @[])
method prepareTokenModelForCommunityChat*(self: Module, communityId: string, chatId: string) =
let community = self.controller.getCommunityById(communityId)
var tokenPermissionsItems: seq[TokenPermissionItem] = @[]
for id, tokenPermission in community.tokenPermissions:
var containsChat = false
for id in tokenPermission.chatIds:
if id == chatId:
containsChat = true
break
if not containsChat:
continue
let chats = community.getCommunityChats(tokenPermission.chatIds)
let tokenPermissionItem = buildTokenPermissionItem(tokenPermission, chats)
tokenPermissionsItems.add(tokenPermissionItem)
self.view.spectatedCommunityPermissionModel.setItems(tokenPermissionsItems)
self.checkPermissions(communityId, @[])
proc applyPermissionResponse*(self: Module, communityId: string, permissions: Table[string, CheckPermissionsResultDto]) =
let community = self.controller.getCommunityById(communityId)
for id, criteriaResult in permissions:

View File

@ -347,6 +347,9 @@ QtObject:
proc prepareTokenModelForCommunity(self: View, communityId: string) {.slot.} =
self.delegate.prepareTokenModelForCommunity(communityId)
proc prepareTokenModelForCommunityChat(self: View, communityId: string, chatId: string) {.slot.} =
self.delegate.prepareTokenModelForCommunityChat(communityId, chatId)
proc signProfileKeypairAndAllNonKeycardKeypairs*(self: View) {.slot.} =
self.delegate.signProfileKeypairAndAllNonKeycardKeypairs()

View File

@ -132,6 +132,10 @@ QtObject {
root.communitiesModuleInst.prepareTokenModelForCommunity(publicKey)
}
function prepareTokenModelForCommunityChat(publicKey, chatId) {
root.communitiesModuleInst.prepareTokenModelForCommunityChat(publicKey, chatId)
}
readonly property bool allChannelsAreHiddenBecauseNotPermitted: root.chatCommunitySectionModule.allChannelsAreHiddenBecauseNotPermitted &&
!root.chatCommunitySectionModule.requiresTokenPermissionToJoin

View File

@ -252,7 +252,7 @@ QtObject {
filters: [
FastExpressionFilter {
function filterPredicate(id, permissionType) {
return !PermissionTypes.isCommunityPermission(permissionType) && root.permissionsModel.belongsToChat(id, root.channelId)
return !PermissionTypes.isCommunityPermission(permissionType)
}
expression: {
return filterPredicate(model.id, model.permissionType)

View File

@ -3,6 +3,7 @@ import QtQuick.Controls 2.15
import StatusQ.Controls 0.1
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.layouts 1.0
import AppLayouts.Communities.views 1.0
import AppLayouts.Communities.helpers 1.0

View File

@ -5,6 +5,7 @@ import QtQuick.Controls 2.14
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
@ -70,23 +71,31 @@ Item {
sourceModel: root.model
proxyRoles: FastExpressionRole {
function displayNameProxy(localNickname, ensName, displayName, aliasName) {
return ProfileUtils.displayName(localNickname, ensName, displayName, aliasName);
}
name: "preferredDisplayName"
expectedRoles: ["localNickname", "displayName", "ensName", "alias"]
expression: displayNameProxy(model.localNickname, model.ensName, model.displayName, model.alias);
}
sorters : [
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
]
filters: AnyOf {
SearchFilter {
roleName: "localNickname"
searchPhrase: memberSearch.text
}
SearchFilter {
roleName: "displayName"
searchPhrase: memberSearch.text
}
SearchFilter {
roleName: "ensName"
searchPhrase: memberSearch.text
}
SearchFilter {
roleName: "alias"
searchPhrase: memberSearch.text
}
}
}
spacing: 0
@ -288,8 +297,6 @@ Item {
readonly property string title: model.preferredDisplayName
width: membersList.width
visible: memberSearch.text === "" || title.toLowerCase().includes(memberSearch.text.toLowerCase())
height: visible ? implicitHeight : 0
color: "transparent"
pubKey: model.isEnsVerified ? "" : Utils.getElidedCompressedPk(model.pubKey)

View File

@ -258,6 +258,14 @@ StackLayout {
contentItem: Loader {
id: editSettingsPanelLoader
active: false
onVisibleChanged: {
if (visible) {
active = true
}
}
function reloadContent() {
active = false
active = true

View File

@ -99,11 +99,7 @@ StackView {
onClicked: root.push(newPermissionView, StackView.Immediate)
}
contentItem: StatusScrollView {
contentHeight: (permissionsView.height + topPadding)
topPadding: permissionsView.topPadding
padding: 0
PermissionsView {
contentItem: PermissionsView {
id: permissionsView
permissionsModel: root.permissionsModel
assetsModel: root.assetsModel
@ -147,7 +143,6 @@ StackView {
}
}
}
}
Component {
id: newPermissionView

View File

@ -545,7 +545,7 @@ Item {
collectiblesModel: root.store.collectiblesModel
ensCommunityPermissionsEnabled: root.store.ensCommunityPermissionsEnabled
permissionsModel: {
root.store.prepareTokenModelForCommunity(communityData.id)
root.store.prepareTokenModelForCommunityChat(communityData.id, chatId)
return root.store.permissionsModel
}
channelsModel: root.store.chatCommunitySectionModule.model

View File

@ -4,6 +4,7 @@ import QtQuick.Dialogs 1.3
import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
@ -74,14 +75,19 @@ StatusSectionLayout {
signal backToCommunityClicked
backButtonName: stackLayout.children[d.currentIndex].previousPageName || ""
backButtonName: {
if (!stackLayout.children[d.currentIndex].item || !stackLayout.children[d.currentIndex].item.previousPageName) {
return ""
}
return stackLayout.children[d.currentIndex].item.previousPageName
}
//navigate to a specific section and subsection
function goTo(section: int, subSection: int) {
d.goTo(section, subSection)
}
onBackButtonClicked: stackLayout.children[d.currentIndex].navigateBack()
onBackButtonClicked: stackLayout.children[d.currentIndex].item.navigateBack()
leftPanel: Item {
anchors.fill: parent
@ -167,12 +173,20 @@ StatusSectionLayout {
currentIndex: d.currentIndex
OverviewSettingsPanel {
onCurrentIndexChanged: {
children[currentIndex].active = true
}
// OVERVIEW
Loader {
active: true
readonly property int sectionKey: Constants.CommunitySettingsSections.Overview
readonly property string sectionName: qsTr("Overview")
readonly property string sectionIcon: "show"
readonly property bool sectionEnabled: true
sourceComponent: OverviewSettingsPanel {
isOwner: root.isOwner
isAdmin: root.isAdmin
isTokenMaster: root.isTokenMasterOwner
@ -263,13 +277,18 @@ StatusSectionLayout {
root.chatCommunitySectionModule.setCommunityShard(shardIndex)
}
}
}
// MEMBERS
Loader {
active: false
MembersSettingsPanel {
readonly property int sectionKey: Constants.CommunitySettingsSections.Members
readonly property string sectionName: qsTr("Members")
readonly property string sectionIcon: "group-chat"
readonly property bool sectionEnabled: true
sourceComponent: MembersSettingsPanel {
rootStore: root.rootStore
membersModel: root.community.members
bannedMembersModel: root.community.bannedMembers
@ -289,13 +308,17 @@ StatusSectionLayout {
Global.openCommunityMemberMessagesPopupRequested(root.rootStore, root.chatCommunitySectionModule, pubKey, displayName)
}
}
}
PermissionsSettingsPanel {
// PERMISISONS
Loader {
active: false
readonly property int sectionKey: Constants.CommunitySettingsSections.Permissions
readonly property string sectionName: qsTr("Permissions")
readonly property string sectionIcon: "objects"
readonly property bool sectionEnabled: true
sourceComponent: PermissionsSettingsPanel {
readonly property PermissionsStore permissionsStore:
rootStore.permissionsStore
@ -328,17 +351,21 @@ StatusSectionLayout {
onNavigateToMintTokenSettings: {
root.goTo(Constants.CommunitySettingsSections.MintTokens)
mintPanel.openNewTokenForm(isAssetType)
mintPanelLoader.item.openNewTokenForm(isAssetType)
}
}
}
MintTokensSettingsPanel {
id: mintPanel
// TOKEN
Loader {
id: mintPanelLoader
active: false
readonly property int sectionKey: Constants.CommunitySettingsSections.MintTokens
readonly property string sectionName: qsTr("Tokens")
readonly property string sectionIcon: "token"
readonly property bool sectionEnabled: true
sourceComponent: MintTokensSettingsPanel {
enabledChainIds: root.enabledChainIds
readonly property CommunityTokensStore communityTokensStore:
@ -417,24 +444,30 @@ StatusSectionLayout {
root.goTo(Constants.CommunitySettingsSections.Airdrops)
// Force a token selection to be airdroped with given amount
airdropPanel.selectToken(tokenKey, amount, type)
airdropPanelLoader.item.selectToken(tokenKey, amount, type)
// Set given addresses as recipients
airdropPanel.addAddresses(addresses)
airdropPanelLoader.item.addAddresses(addresses)
}
onKickUserRequested: root.rootStore.removeUserFromCommunity(contactId)
onBanUserRequested: root.rootStore.banUserFromCommunity(contactId)
}
}
AirdropsSettingsPanel {
id: airdropPanel
// AIRDROPS
Loader {
id: airdropPanelLoader
active: false
readonly property int sectionKey: Constants.CommunitySettingsSections.Airdrops
readonly property string sectionName: qsTr("Airdrops")
readonly property string sectionIcon: "airdrop"
readonly property bool sectionEnabled: true
sourceComponent: AirdropsSettingsPanel {
id: airdropsSettingsPanel
communityDetails: d.communityDetails
// Profile type
@ -449,90 +482,82 @@ StatusSectionLayout {
readonly property CommunityTokensStore communityTokensStore:
rootStore.communityTokensStore
readonly property var communityTokens: root.community.communityTokens
readonly property RolesRenamingModel renamedTokensBySymbolModel: RolesRenamingModel {
sourceModel: root.community.communityTokens || null
mapping: [
RoleRename {
from: "symbol"
to: "key"
},
RoleRename {
from: "image"
to: "iconSource"
}
]
}
Loader {
id: assetsModelLoader
active: airdropPanel.communityTokens
sourceComponent: SortFilterProxyModel {
sourceModel: airdropPanel.communityTokens
assetsModel: SortFilterProxyModel {
sourceModel: renamedTokensBySymbolModel
filters: ValueFilter {
roleName: "tokenType"
value: Constants.TokenType.ERC20
}
proxyRoles: [
ExpressionRole {
ConstantRole {
name: "category"
// Singleton cannot be used directly in the expression
readonly property int category: TokenCategories.Category.Own
expression: category
value: TokenCategories.Category.Own
},
ExpressionRole {
name: "iconSource"
expression: model.image
},
ExpressionRole {
name: "key"
expression: model.symbol
},
ExpressionRole {
ConstantRole {
name: "communityId"
expression: ""
value: ""
}
]
}
}
Loader {
id: collectiblesModelLoader
active: airdropPanel.communityTokens
sourceComponent: SortFilterProxyModel {
sourceModel: airdropPanel.communityTokens
collectiblesModel: SortFilterProxyModel {
sourceModel: renamedTokensBySymbolModel
filters: [
ValueFilter {
roleName: "tokenType"
value: Constants.TokenType.ERC721
},
ExpressionFilter {
function getPrivileges(privilegesLevel) {
return privilegesLevel === Constants.TokenPrivilegesLevel.Community ||
(root.isOwner && privilegesLevel === Constants.TokenPrivilegesLevel.TMaster)
AnyOf {
ValueFilter {
roleName: "privilegesLevel"
value: Constants.TokenPrivilegesLevel.Community
}
ValueFilter {
roleName: "privilegesLevel"
value: Constants.TokenPrivilegesLevel.TMaster
enabled: root.isOwner
}
expression: { return getPrivileges(model.privilegesLevel) }
}
]
proxyRoles: [
ExpressionRole {
ConstantRole {
name: "category"
// Singleton cannot be used directly in the epression
readonly property int category: TokenCategories.Category.Own
expression: category
value: TokenCategories.Category.Own
},
ExpressionRole {
name: "iconSource"
expression: model.image
},
ExpressionRole {
name: "key"
expression: model.symbol
},
ExpressionRole {
ConstantRole {
name: "communityId"
expression: ""
value: ""
}
]
}
Connections {
target: root.rootStore.communityTokensStore
function onAirdropStateChanged(communityId, tokenName, chainName, status, url) {
if (root.community.id !== communityId) {
return
}
if (status == Constants.ContractTransactionStatus.InProgress) {
airdropsSettingsPanel.navigateBack()
}
}
}
assetsModel: assetsModelLoader.item
collectiblesModel: collectiblesModelLoader.item
membersModel: community.members
enabledChainIds: root.enabledChainIds
onEnableNetwork: root.enableNetwork(chainId)
@ -544,12 +569,13 @@ StatusSectionLayout {
onNavigateToMintTokenSettings: {
root.goTo(Constants.CommunitySettingsSections.MintTokens)
mintPanel.openNewTokenForm(isAssetType)
mintPanelLoader.item.openNewTokenForm(isAssetType)
}
onRegisterAirdropFeeSubscriber: d.feesBroker.registerAirdropFeesSubscriber(feeSubscriber)
}
}
}
QtObject {
id: d
@ -723,7 +749,6 @@ StatusSectionLayout {
case Constants.ContractTransactionStatus.InProgress:
title = qsTr("Airdrop on %1 in progress...").arg(chainName)
loading = true
airdropPanel.navigateBack()
break
case Constants.ContractTransactionStatus.Completed:
title = qsTr("Airdrop on %1 in complete").arg(chainName)

View File

@ -43,7 +43,7 @@ ColumnLayout {
signal removePermissionRequested(int index)
signal userRestrictionsToggled(bool checked)
readonly property alias count: repeater.count
readonly property alias count: listView.count
Connections {
target: root.communityDetails
@ -58,7 +58,7 @@ ColumnLayout {
}
}
function resetCommunityItemModel(){
function resetCommunityItemModel() {
communityItemModel.clear()
communityItemModel.append({
text: root.communityDetails.name,
@ -87,14 +87,18 @@ ColumnLayout {
]
}
Repeater {
id: repeater
StatusListView {
id: listView
reuseItems: true
model: root.permissionsModel
spacing: 24
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: contentHeight
Layout.topMargin: root.topPadding
delegate: PermissionItem {
Layout.fillWidth: true
width: root.viewWidth
holdingsListModel: HoldingsSelectionModel {
sourceModel: model.holdingsListModel
@ -155,6 +159,7 @@ ColumnLayout {
root.userRestrictionsToggled(checked);
}
}
ConfirmationDialog {
id: declineAllDialog

View File

@ -91,13 +91,6 @@ Item {
}
}
]
proxyRoles: ExpressionRole {
function displayNameProxy(nickname, ensName, displayName, aliasName) {
return ProfileUtils.displayName(nickname, ensName, displayName, aliasName)
}
name: "preferredDisplayName"
expression: displayNameProxy(model.localNickname, model.ensName, model.displayName, model.alias)
}
sorters: [
StringSorter {

View File

@ -21,20 +21,6 @@ QtObject {
//TODO: backend implementation
}
// TODO: Replace with proper backend implementation
// This is per chat, not per community
readonly property bool viewAndPostCriteriaMet: {
if (selectedChannelPermissionsModel.count == 0)
return true
for (var i = 0; i < selectedChannelPermissionsModel.count; i++) {
var permissionItem = selectedChannelPermissionsModel.get(i);
if (permissionItem && permissionItem.tokenCriteriaMet)
return true
}
return false
}
readonly property var selectedChannelPermissionsModel: SortFilterProxyModel {
id: selectedChannelPermissionsModel
sourceModel: root.permissionsModel