import QtQuick 2.14 import QtQuick.Layouts 1.14 import QtQuick.Controls 2.14 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 import StatusQ.Components 0.1 import StatusQ.Popups 0.1 import utils 1.0 import shared.views.chat 1.0 import shared.controls.chat 1.0 import shared.controls 1.0 import AppLayouts.Communities.layouts 1.0 Item { id: root property string placeholderText property var model property var rootStore signal kickUserClicked(string id, string name) signal banUserClicked(string id, string name) signal unbanUserClicked(string id) signal acceptRequestToJoin(string id) signal declineRequestToJoin(string id) enum TabType { AllMembers, BannedMembers, PendingRequests, DeclinedRequests } property int panelType: MembersTabPanel.TabType.AllMembers ColumnLayout { anchors.fill: parent spacing: 30 SearchBox { id: memberSearch Layout.preferredWidth: 400 Layout.leftMargin: 12 placeholderText: root.placeholderText enabled: !!model && model.count > 0 } StatusListView { id: membersList objectName: "CommunityMembersTabPanel_MembersListViews" Layout.fillWidth: true Layout.fillHeight: true model: root.model spacing: 0 delegate: StatusMemberListItem { id: memberItem // Buttons visibility conditions: // 1. Tab based buttons - only visible when the tab is selected // a. All members tab // - Kick; - Kick pending // - Ban; - Ban pending // b. Pending requests tab // - Accept; - Accept pending // - Reject; - Reject pending // c. Rejected members tab // - Accept; - Accept pending // d. Banned members tab // - Unban // 2. Pending states - buttons in pending states are always visible in their specific tab. Other buttons are disabled if the request is in pending state // - Accept button is visible when the user is hovered or when the request is in accepted pending state. This condition can be overriden by the ctaAllowed property // - Reject button is visible when the user is hovered or when the request is in rejected pending state. This condition can be overriden by the ctaAllowed property // - Kick and ban buttons are visible when the user is hovered or when the request is in kick or ban pending state. This condition can be overriden by the ctaAllowed property // 3. Other conditions - buttons are visible when the user is hovered and is not himself or other privileged user /// Helpers /// // Tab based buttons readonly property bool tabIsShowingKickBanButtons: root.panelType === MembersTabPanel.TabType.AllMembers readonly property bool tabIsShowingUnbanButton: root.panelType === MembersTabPanel.TabType.BannedMembers readonly property bool tabIsShowingRejectButton: root.panelType === MembersTabPanel.TabType.PendingRequests readonly property bool tabIsShowingAcceptButton: root.panelType === MembersTabPanel.TabType.PendingRequests || root.panelType === MembersTabPanel.TabType.DeclinedRequests // Request states readonly property bool isPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.Pending readonly property bool isAccepted: model.membershipRequestState === Constants.CommunityMembershipRequestState.Accepted readonly property bool isRejected: model.membershipRequestState === Constants.CommunityMembershipRequestState.Rejected readonly property bool isRejectedPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.RejectedPending readonly property bool isAcceptedPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.AcceptedPending readonly property bool isBanPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedPending readonly property bool isKickPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.KickedPending readonly property bool isBanned: model.membershipRequestState === Constants.CommunityMembershipRequestState.Banned readonly property bool isKicked: model.membershipRequestState === Constants.CommunityMembershipRequestState.Kicked // TODO: Connect to backend when available // The admin that initited the pending state can change the state. Actions are not visible for other admins readonly property bool ctaAllowed: !isRejectedPending && !isAcceptedPending && !isBanPending && !isKickPending readonly property bool itsMe: model.pubKey.toLowerCase() === Global.userProfile.pubKey.toLowerCase() readonly property bool isHovered: memberItem.sensor.containsMouse readonly property bool canBeBanned: !memberItem.itsMe && (model.memberRole !== Constants.memberRole.owner && model.memberRole !== Constants.memberRole.admin) readonly property bool showOnHover: isHovered && ctaAllowed /// Button visibility /// readonly property bool acceptButtonVisible: tabIsShowingAcceptButton && (isPending || isRejected || isRejectedPending) && showOnHover readonly property bool rejectButtonVisible: tabIsShowingRejectButton && (isPending || isAcceptedPending) && showOnHover readonly property bool acceptPendingButtonVisible: tabIsShowingAcceptButton && isAcceptedPending readonly property bool rejectPendingButtonVisible: tabIsShowingRejectButton && isRejectedPending readonly property bool kickButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned readonly property bool banButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned readonly property bool kickPendingButtonVisible: tabIsShowingKickBanButtons && isKickPending readonly property bool banPendingButtonVisible: tabIsShowingKickBanButtons && isBanPending readonly property bool unbanButtonVisible: tabIsShowingUnbanButton && isBanned && showOnHover statusListItemComponentsSlot.spacing: 16 statusListItemTitleArea.anchors.rightMargin: 0 statusListItemSubTitle.elide: Text.ElideRight rightPadding: 75 leftPadding: 12 components: [ DisabledTooltipButton { id: kickButton anchors.verticalCenter: parent.verticalCenter visible: kickButtonVisible || kickPendingButtonVisible interactive: kickButtonVisible tooltipText: qsTr("Waiting for owner node to come online") buttonComponent: StatusButton { objectName: "MemberListItem_KickButton" text: kickButtonVisible ? qsTr("Kick") : qsTr("Kick pending") type: StatusBaseButton.Type.Danger size: StatusBaseButton.Size.Small onClicked: root.kickUserClicked(model.pubKey, memberItem.title) enabled: kickButton.interactive } }, DisabledTooltipButton { id: banButton anchors.verticalCenter: parent.verticalCenter //using opacity instead of visible to avoid the acceptButton jumping around opacity: banButtonVisible || banPendingButtonVisible visible: !!banButton.opacity || kickButton.visible enabled: !!opacity interactive: banButtonVisible tooltipText: qsTr("Waiting for owner node to come online") buttonComponent: StatusButton { text: banButtonVisible ? qsTr("Ban") : qsTr("Ban pending") type: StatusBaseButton.Type.Danger size: StatusBaseButton.Size.Small onClicked: root.banUserClicked(model.pubKey, memberItem.title) enabled: banButton.interactive } }, StatusButton { anchors.verticalCenter: parent.verticalCenter visible: unbanButtonVisible text: qsTr("Unban") onClicked: root.unbanUserClicked(model.pubKey) }, DisabledTooltipButton { id: acceptButton anchors.verticalCenter: parent.verticalCenter visible: acceptButtonVisible || acceptPendingButtonVisible tooltipText: qsTr("Waiting for owner node to come online") interactive: acceptButtonVisible buttonComponent: StatusButton { text: acceptButtonVisible ? qsTr("Accept") : qsTr("Accept Pending") icon.name: "checkmark-circle" icon.color: enabled ? Theme.palette.successColor1 : disabledTextColor normalColor: Theme.palette.successColor2 hoverColor: Theme.palette.successColor3 textColor: Theme.palette.successColor1 loading: model.requestToJoinLoading enabled: acceptButton.interactive onClicked: root.acceptRequestToJoin(model.requestToJoinId) } }, DisabledTooltipButton { id: rejectButton anchors.verticalCenter: parent.verticalCenter //using opacity instead of visible to avoid the acceptButton jumping around opacity: rejectButtonVisible || rejectPendingButtonVisible enabled: !!opacity visible: !!rejectButton.opacity || acceptButton.visible tooltipText: qsTr("Waiting for owner node to come online") interactive: rejectButtonVisible buttonComponent: StatusButton { text: rejectPendingButtonVisible ? qsTr("Reject pending") : qsTr("Reject") type: StatusBaseButton.Type.Danger icon.name: "close-circle" icon.color: enabled ? Style.current.danger : disabledTextColor enabled: rejectButton.interactive onClicked: root.declineRequestToJoin(model.requestToJoinId) } } ] 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) nickName: model.localNickname userName: ProfileUtils.displayName("", model.ensName, model.displayName, model.alias) status: model.onlineStatus asset.color: Utils.colorForColorId(model.colorId) asset.name: model.icon asset.isImage: !!model.icon asset.isLetterIdenticon: !model.icon asset.width: 40 asset.height: 40 ringSettings.ringSpecModel: model.colorHash statusListItemIcon.badge.visible: (root.panelType === MembersTabPanel.TabType.AllMembers) onClicked: { if(mouse.button === Qt.RightButton) { Global.openMenu(memberContextMenuComponent, this, { selectedUserPublicKey: model.pubKey, selectedUserDisplayName: memberItem.title, selectedUserIcon: asset.name, }) } else { Global.openProfilePopup(model.pubKey) } } } } } Component { id: memberContextMenuComponent ProfileContextMenu { id: memberContextMenuView store: root.rootStore myPublicKey: Global.userProfile.pubKey onOpenProfileClicked: { Global.openProfilePopup(publicKey, null) } onCreateOneToOneChat: { Global.changeAppSectionBySectionType(Constants.appSection.chat) root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName) } onClosed: { destroy() } } } }