2022-06-28 15:03:10 +00:00
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
2023-05-19 16:07:50 +00:00
import shared . views . chat 1.0
2022-06-28 15:03:10 +00:00
import shared . controls . chat 1.0
2023-08-07 13:35:14 +00:00
import shared . controls 1.0
2022-06-28 15:03:10 +00:00
2023-06-23 06:17:04 +00:00
import AppLayouts . Communities . layouts 1.0
2022-06-28 15:03:10 +00:00
Item {
id: root
property string placeholderText
property var model
2023-06-20 07:14:25 +00:00
property var rootStore
2023-08-21 19:07:40 +00:00
property int memberRole: Constants . memberRole . none
readonly property bool isOwner: memberRole === Constants . memberRole . owner
readonly property bool isTokenMaster: memberRole === Constants . memberRole . tokenMaster
2022-06-28 15:03:10 +00:00
signal kickUserClicked ( string id , string name )
signal banUserClicked ( string id , string name )
signal unbanUserClicked ( string id )
2022-08-04 07:49:41 +00:00
signal acceptRequestToJoin ( string id )
signal declineRequestToJoin ( string id )
2022-06-28 15:03:10 +00:00
enum TabType {
AllMembers ,
2022-08-04 07:49:41 +00:00
BannedMembers ,
PendingRequests ,
DeclinedRequests
2022-06-28 15:03:10 +00:00
}
2023-06-26 11:48:45 +00:00
property int panelType: MembersTabPanel . TabType . AllMembers
2022-06-28 15:03:10 +00:00
ColumnLayout {
anchors.fill: parent
2022-08-08 15:56:57 +00:00
spacing: 30
2022-06-28 15:03:10 +00:00
2023-08-17 08:55:28 +00:00
SearchBox {
2022-06-28 15:03:10 +00:00
id: memberSearch
2022-09-22 22:18:15 +00:00
Layout.preferredWidth: 400
2022-08-08 15:56:57 +00:00
Layout.leftMargin: 12
2022-06-28 15:03:10 +00:00
placeholderText: root . placeholderText
2023-04-19 16:48:57 +00:00
enabled: ! ! model && model . count > 0
2022-06-28 15:03:10 +00:00
}
2023-08-17 08:55:28 +00:00
StatusListView {
2022-06-28 15:03:10 +00:00
id: membersList
2022-10-25 19:08:15 +00:00
objectName: "CommunityMembersTabPanel_MembersListViews"
2022-06-28 15:03:10 +00:00
Layout.fillWidth: true
Layout.fillHeight: true
model: root . model
2023-08-17 08:55:28 +00:00
spacing: 0
2022-06-28 15:03:10 +00:00
delegate: StatusMemberListItem {
id: memberItem
2023-08-17 09:01:43 +00:00
// 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
2023-08-17 08:55:28 +00:00
readonly property bool itsMe: model . pubKey . toLowerCase ( ) === Global . userProfile . pubKey . toLowerCase ( )
2022-08-04 07:49:41 +00:00
readonly property bool isHovered: memberItem . sensor . containsMouse
2023-08-21 19:07:40 +00:00
readonly property bool canBeBanned: {
if ( memberItem . itsMe ) {
return false
}
switch ( model . memberRole ) {
// Owner can't be banned
case Constants.memberRole.owner: return false
// TokenMaster can only be banned by owner
case Constants.memberRole.tokenMaster: return root . isOwner
// Admin can only be banned by owner and tokenMaster
case Constants.memberRole.admin: return root . isOwner || root . isTokenMaster
// All normal members can be banned by all privileged users
default: return true
}
}
2023-08-17 09:01:43 +00:00
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
2022-06-28 15:03:10 +00:00
statusListItemComponentsSlot.spacing: 16
2022-08-04 07:49:41 +00:00
statusListItemTitleArea.anchors.rightMargin: 0
statusListItemSubTitle.elide: Text . ElideRight
rightPadding: 75
2022-08-08 15:56:57 +00:00
leftPadding: 12
2022-06-28 15:03:10 +00:00
components: [
2023-08-08 08:41:59 +00:00
DisabledTooltipButton {
id: kickButton
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-17 09:01:43 +00:00
visible: kickButtonVisible || kickPendingButtonVisible
interactive: kickButtonVisible
2023-08-08 08:41:59 +00:00
tooltipText: qsTr ( "Waiting for owner node to come online" )
buttonComponent: StatusButton {
objectName: "MemberListItem_KickButton"
2023-08-17 09:01:43 +00:00
text: kickButtonVisible ? qsTr ( "Kick" ) : qsTr ( "Kick pending" )
2023-08-08 08:41:59 +00:00
type: StatusBaseButton . Type . Danger
size: StatusBaseButton . Size . Small
2023-08-17 08:55:28 +00:00
onClicked: root . kickUserClicked ( model . pubKey , memberItem . title )
2023-08-08 08:41:59 +00:00
enabled: kickButton . interactive
}
2022-06-28 15:03:10 +00:00
} ,
2023-08-08 08:41:59 +00:00
DisabledTooltipButton {
id: banButton
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-08 08:41:59 +00:00
//using opacity instead of visible to avoid the acceptButton jumping around
2023-08-17 09:01:43 +00:00
opacity: banButtonVisible || banPendingButtonVisible
visible: ! ! banButton . opacity || kickButton . visible
enabled: ! ! opacity
interactive: banButtonVisible
tooltipText: qsTr ( "Waiting for owner node to come online" )
2023-08-08 08:41:59 +00:00
buttonComponent: StatusButton {
2023-08-17 09:01:43 +00:00
text: banButtonVisible ? qsTr ( "Ban" ) : qsTr ( "Ban pending" )
2023-08-08 08:41:59 +00:00
type: StatusBaseButton . Type . Danger
size: StatusBaseButton . Size . Small
2023-08-17 08:55:28 +00:00
onClicked: root . banUserClicked ( model . pubKey , memberItem . title )
2023-08-08 08:41:59 +00:00
enabled: banButton . interactive
}
2022-06-28 15:03:10 +00:00
} ,
StatusButton {
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-17 09:01:43 +00:00
visible: unbanButtonVisible
2022-06-28 15:03:10 +00:00
text: qsTr ( "Unban" )
onClicked: root . unbanUserClicked ( model . pubKey )
2022-08-04 07:49:41 +00:00
} ,
2023-08-07 13:35:14 +00:00
DisabledTooltipButton {
id: acceptButton
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-17 09:01:43 +00:00
visible: acceptButtonVisible || acceptPendingButtonVisible
2023-08-07 13:35:14 +00:00
tooltipText: qsTr ( "Waiting for owner node to come online" )
2023-08-17 09:01:43 +00:00
interactive: acceptButtonVisible
2023-08-07 13:35:14 +00:00
buttonComponent: StatusButton {
2023-08-17 09:01:43 +00:00
text: acceptButtonVisible ? qsTr ( "Accept" ) : qsTr ( "Accept Pending" )
2023-08-07 13:35:14 +00:00
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 )
}
2023-03-21 11:21:23 +00:00
} ,
2023-08-07 13:35:14 +00:00
DisabledTooltipButton {
id: rejectButton
2023-08-17 08:55:28 +00:00
anchors.verticalCenter: parent . verticalCenter
2023-08-07 13:35:14 +00:00
//using opacity instead of visible to avoid the acceptButton jumping around
2023-08-17 09:01:43 +00:00
opacity: rejectButtonVisible || rejectPendingButtonVisible
enabled: ! ! opacity
visible: ! ! rejectButton . opacity || acceptButton . visible
2023-08-07 13:35:14 +00:00
tooltipText: qsTr ( "Waiting for owner node to come online" )
2023-08-17 09:01:43 +00:00
interactive: rejectButtonVisible
2023-08-07 13:35:14 +00:00
buttonComponent: StatusButton {
2023-08-17 09:01:43 +00:00
text: rejectPendingButtonVisible ? qsTr ( "Reject pending" ) : qsTr ( "Reject" )
2023-08-07 13:35:14 +00:00
type: StatusBaseButton . Type . Danger
icon.name: "close-circle"
icon.color: enabled ? Style.current.danger : disabledTextColor
enabled: rejectButton . interactive
onClicked: root . declineRequestToJoin ( model . requestToJoinId )
}
2022-06-28 15:03:10 +00:00
}
]
width: membersList . width
visible: memberSearch . text === "" || title . toLowerCase ( ) . includes ( memberSearch . text . toLowerCase ( ) )
height: visible ? implicitHeight : 0
color: "transparent"
2023-04-19 16:48:57 +00:00
pubKey: model . isEnsVerified ? "" : Utils . getElidedCompressedPk ( model . pubKey )
2022-06-28 15:03:10 +00:00
nickName: model . localNickname
2022-12-05 09:56:44 +00:00
userName: ProfileUtils . displayName ( "" , model . ensName , model . displayName , model . alias )
2022-06-28 15:03:10 +00:00
status: model . onlineStatus
2022-09-06 15:06:33 +00:00
asset.color: Utils . colorForColorId ( model . colorId )
2022-08-11 11:55:08 +00:00
asset.name: model . icon
2022-09-09 12:51:10 +00:00
asset.isImage: ! ! model . icon
asset.isLetterIdenticon: ! model . icon
2022-08-11 11:55:08 +00:00
asset.width: 40
asset.height: 40
2023-01-10 11:29:24 +00:00
ringSettings.ringSpecModel: model . colorHash
2023-06-26 11:48:45 +00:00
statusListItemIcon.badge.visible: ( root . panelType === MembersTabPanel . TabType . AllMembers )
2022-06-28 15:03:10 +00:00
2023-04-06 14:23:19 +00:00
onClicked: {
if ( mouse . button === Qt . RightButton ) {
2023-05-19 16:07:50 +00:00
Global . openMenu ( memberContextMenuComponent , this , {
selectedUserPublicKey: model . pubKey ,
2023-08-17 08:55:28 +00:00
selectedUserDisplayName: memberItem . title ,
2023-05-19 16:07:50 +00:00
selectedUserIcon: asset . name ,
} )
2023-04-06 14:23:19 +00:00
} else {
2023-05-19 16:07:50 +00:00
Global . openProfilePopup ( model . pubKey )
2023-04-06 14:23:19 +00:00
}
}
2022-06-28 15:03:10 +00:00
}
}
}
2023-05-19 16:07:50 +00:00
Component {
id: memberContextMenuComponent
ProfileContextMenu {
id: memberContextMenuView
store: root . rootStore
2023-08-17 08:55:28 +00:00
myPublicKey: Global . userProfile . pubKey
2023-05-19 16:07:50 +00:00
onOpenProfileClicked: {
Global . openProfilePopup ( publicKey , null )
}
onCreateOneToOneChat: {
Global . changeAppSectionBySectionType ( Constants . appSection . chat )
root . rootStore . chatCommunitySectionModule . createOneToOneChat ( communityId , chatId , ensName )
}
onClosed: {
destroy ( )
}
}
}
2022-06-28 15:03:10 +00:00
}