feat(ChannelPermissions): Add permissions section in create/edit channel popup

Changes:
1. Make PermissionsView/EditPermissionsView configurable to support channel permissions config
2. Adding channel permissions support in the create/edit channel popup
3. Connect the channel permissions to backend
4. Cleaning unneeded emojiPopup
This commit is contained in:
Alex Jbanca 2024-02-06 11:31:36 +02:00 committed by Alex Jbanca
parent 56d67f5ba2
commit cf82772aed
11 changed files with 887 additions and 479 deletions

View File

@ -27,6 +27,14 @@ Item {
property int padding: Style.current.halfPadding
signal searchButtonClicked()
signal displayEditChannelPopup(string chatId,
string chatName,
string chatDescription,
string chatEmoji,
string chatColor,
string chatCategoryId,
int channelPosition,
var deleteDialog)
function addRemoveGroupMember() {
root.state = d.stateMembersSelectorContent
@ -140,7 +148,6 @@ Item {
ChatContextMenuView {
id: contextMenu
objectName: "moreOptionsContextMenu"
emojiPopup: root.emojiPopup
showDebugOptions: root.rootStore.isDebugEnabled
openHandler: function () {
if(!chatContentModule) {
@ -218,17 +225,11 @@ Item {
onDisplayProfilePopup: {
Global.openProfilePopup(publicKey)
}
onEditCommunityChannel: {
root.rootStore.editCommunityChannel(
chatId,
newName,
newDescription,
newEmoji,
newColor,
newCategory,
channelPosition // TODO change this to the signal once it is modifiable
)
onDisplayEditChannelPopup: {
root.displayEditChannelPopup(chatId, chatName, chatDescription,
chatEmoji, chatColor,
chatCategoryId, channelPosition,
contextMenu.deleteChatConfirmationDialog);
}
onAddRemoveGroupMember: {
root.addRemoveGroupMember()

View File

@ -166,6 +166,19 @@ StatusSectionLayout {
rootStore: root.rootStore
emojiPopup: root.emojiPopup
onSearchButtonClicked: root.openAppSearch()
onDisplayEditChannelPopup: {
Global.openPopup(contactColumnLoader.item.createChannelPopup, {
isEdit: true,
chatId: chatId,
channelName: chatName,
channelDescription: chatDescription,
channelEmoji: chatEmoji,
channelColor: chatColor,
categoryId: chatCategoryId,
channelPosition: channelPosition,
deleteChatConfirmationDialog: deleteDialog
});
}
}
}

View File

@ -141,7 +141,6 @@ Item {
popupMenu: ChatContextMenuView {
id: chatContextMenuView
emojiPopup: root.emojiPopup
showDebugOptions: root.store.isDebugEnabled
openHandler: function (id) {

View File

@ -58,23 +58,9 @@ QtObject {
d.editPermission(key, permissionType, holdings, channels, isPrivate)
}
// Function duplicating a permission. The new permission will be have a different id and key
function duplicatePermission(index) {
const permission = channelPermissionsModel.get(index)
if (!permission)
return
permission.id = Utils.uuid()
permission.key = Utils.uuid()
permission.holdingsListModel = d.newHoldingsModel(StatusQUtils.ModelUtils.modelToArray(permission.holdingsListModel))
permission.channelsListModel = d.newChannelsModel(StatusQUtils.ModelUtils.modelToArray(permission.channelsListModel))
channelPermissionsModel.append(permission)
}
// Function removing a permission by index
function removePermission(index) {
channelPermissionsModel.remove(index, 1);
return channelPermissionsModel.remove(index, 1);
}

View File

@ -5,9 +5,11 @@ import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.layouts 1.0
import AppLayouts.Communities.views 1.0
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import shared.popups 1.0
StackView {
@ -17,6 +19,8 @@ StackView {
required property var assetsModel
required property var collectiblesModel
required property var channelsModel
property bool showChannelSelector: true
property alias initialPage: initialItem
// id, name, image, color, owner properties expected
required property var communityDetails
@ -38,8 +42,13 @@ StackView {
pop(StackView.Immediate)
}
function pushEditView(properties) {
root.push(newPermissionView, properties, StackView.Immediate);
}
// Community Permissions possible view contents:
initialItem: SettingsPage {
id: initialItem
implicitWidth: 0
title: qsTr("Permissions")
@ -52,45 +61,51 @@ StackView {
onClicked: root.push(newPermissionView, StackView.Immediate)
}
contentItem: PermissionsView {
permissionsModel: root.permissionsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
channelsModel: root.channelsModel
communityDetails: root.communityDetails
contentItem: StatusScrollView {
contentHeight: (permissionsView.height + topPadding)
topPadding: permissionsView.topPadding
padding: 0
PermissionsView {
id: permissionsView
permissionsModel: root.permissionsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
channelsModel: root.channelsModel
communityDetails: root.communityDetails
viewWidth: root.viewWidth
viewWidth: root.viewWidth
onEditPermissionRequested: {
const item = ModelUtils.get(root.permissionsModel, index)
onEditPermissionRequested: {
const item = ModelUtils.get(root.permissionsModel, index)
const properties = {
permissionKeyToEdit: item.key,
holdingsToEditModel: item.holdingsListModel,
channelsToEditModel: item.channelsListModel,
permissionTypeToEdit: item.permissionType,
isPrivateToEditValue: item.isPrivate
const properties = {
permissionKeyToEdit: item.key,
holdingsToEditModel: item.holdingsListModel,
channelsToEditModel: item.channelsListModel,
permissionTypeToEdit: item.permissionType,
isPrivateToEditValue: item.isPrivate
}
root.pushEditView(properties);
}
root.push(newPermissionView, properties, StackView.Immediate)
}
onDuplicatePermissionRequested: {
const item = ModelUtils.get(root.permissionsModel, index)
onDuplicatePermissionRequested: {
const item = ModelUtils.get(root.permissionsModel, index)
const properties = {
holdingsToEditModel: item.holdingsListModel,
channelsToEditModel: item.channelsListModel,
permissionTypeToEdit: item.permissionType,
isPrivateToEditValue: item.isPrivate
}
const properties = {
holdingsToEditModel: item.holdingsListModel,
channelsToEditModel: item.channelsListModel,
permissionTypeToEdit: item.permissionType,
isPrivateToEditValue: item.isPrivate
root.pushEditView(properties);
}
root.push(newPermissionView, properties, StackView.Immediate)
}
onRemovePermissionRequested: {
const key = ModelUtils.get(root.permissionsModel, index, "key")
root.removePermissionRequested(key)
onRemovePermissionRequested: {
const key = ModelUtils.get(root.permissionsModel, index, "key")
root.removePermissionRequested(key)
}
}
}
}
@ -100,20 +115,33 @@ StackView {
SettingsPage {
id: newPermissionViewPage
implicitWidth: 0
title: isEditState ? qsTr("Edit permission") : qsTr("New permission")
property alias isDirty: editPermissionView.dirty
property alias isFullyFilled: editPermissionView.isFullyFilled
property alias isPrivateToEditValue: editPermissionView.isPrivate
property alias permissionTypeToEdit: editPermissionView.permissionType
property alias holdingsToEditModel: editPermissionView.selectedHoldingsModel
property alias channelsToEditModel: editPermissionView.selectedChannelsModel
property alias permissionTypeToEdit: editPermissionView.permissionType
property alias isPrivateToEditValue: editPermissionView.isPrivate
property bool holdingsRequired: editPermissionView.dirtyValues.holdingsRequired
property string permissionKeyToEdit
readonly property bool isEditState: !!permissionKeyToEdit
readonly property alias toast: settingsDirtyToastMessage
function resetChanges() {
editPermissionView.resetChanges();
}
function updatePermission() {
editPermissionView.saveChanges();
}
function createPermission() {
editPermissionView.createPermissionClicked();
}
contentItem: EditPermissionView {
id: editPermissionView
@ -123,7 +151,7 @@ StackView {
collectiblesModel: root.collectiblesModel
channelsModel: root.channelsModel
communityDetails: root.communityDetails
showChannelSelector: root.showChannelSelector
isEditState: newPermissionViewPage.isEditState
holdingsRequired: selectedHoldingsModel
? selectedHoldingsModel.count > 0 : false
@ -191,14 +219,18 @@ StackView {
dirtyValues.selectedHoldingsModel,
["key", "type", "amount"]) : []
const channels = ModelUtils.modelToArray(
dirtyValues.selectedChannelsModel, ["key"])
const channels = root.showChannelSelector ?
ModelUtils.modelToArray(
dirtyValues.selectedChannelsModel, ["key"]) :
ModelUtils.modelToArray(selectedChannelsModel, ["key"])
root.createPermissionRequested(
dirtyValues.permissionType, holdings, channels,
dirtyValues.isPrivate)
root.pop(StackView.Immediate)
if (root.showChannelSelector) {
root.pop(StackView.Immediate)
}
}
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings(isAssetType)
@ -261,7 +293,8 @@ StackView {
// delay to avoid toast blinking on entry
settingsDirtyToastMessage.active = Qt.binding(
() => editPermissionView.isEditState &&
editPermissionView.dirty)
editPermissionView.dirty &&
root.showChannelSelector)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@ StatusDropdown {
property int mode: PermissionsDropdown.Mode.Add
property int initialPermissionType: PermissionTypes.Type.None
property bool allowCommunityOptions: true
property bool enableAdminPermission: true
@ -97,13 +98,14 @@ StatusDropdown {
CustomSeparator {
Layout.fillWidth: true
Layout.preferredHeight: d.sectionHeight
visible: root.allowCommunityOptions
text: qsTr("Community")
}
CustomPermissionListItem {
permissionType: PermissionTypes.Type.Admin
enabled: root.enableAdminPermission
visible: root.allowCommunityOptions
Layout.fillWidth: true
objectName: "becomeAdmin"
@ -112,6 +114,7 @@ StatusDropdown {
CustomPermissionListItem {
permissionType: PermissionTypes.Type.Member
visible: root.allowCommunityOptions
Layout.fillWidth: true
objectName: "becomeMember"
}

View File

@ -42,6 +42,7 @@ Item {
required property CurrenciesStore currencyStore
property bool hasAddedContacts: false
property var communityData
property alias createChannelPopup: createChannelPopup
// Community transfer ownership related props:
required property bool isPendingOwnershipRequest
@ -52,6 +53,11 @@ Item {
communityData.memberRole === Constants.memberRole.admin ||
communityData.memberRole === Constants.memberRole.tokenMaster
readonly property var permissionsModel: {
root.store.prepareTokenModelForCommunity(communityData.id)
return root.store.permissionsModel
}
signal infoButtonClicked
signal manageButtonClicked
@ -297,7 +303,6 @@ Item {
chatListPopupMenu: ChatContextMenuView {
id: chatContextMenuView
emojiPopup: root.emojiPopup
showDebugOptions: root.store.isDebugEnabledfir
// TODO pass the chatModel in its entirety instead of fetching the JSOn using just the id
@ -368,17 +373,18 @@ Item {
onDisplayProfilePopup: {
Global.openProfilePopup(publicKey)
}
onEditCommunityChannel: {
communitySectionModule.editCommunityChannel(
chatId,
newName,
newDescription,
newEmoji,
newColor,
newCategory,
channelPosition // TODO change this to the signal once it is modifiable
)
onDisplayEditChannelPopup: {
Global.openPopup(createChannelPopup, {
isEdit: true,
channelName: chatName,
channelDescription: chatDescription,
channelEmoji: chatEmoji,
channelColor: chatColor,
categoryId: chatCategoryId,
chatId: chatContextMenuView.chatId,
channelPosition: channelPosition,
deleteChatConfirmationDialog: deleteChatConfirmationDialog
});
}
}
}
@ -607,11 +613,63 @@ Item {
id: createChannelPopup
CreateChannelPopup {
communitiesStore: root.communitiesStore
assetsModel: root.store.assetsModel
collectiblesModel: root.store.collectiblesModel
permissionsModel: root.store.permissionsModel
channelsModel: root.store.chatCommunitySectionModule.model
emojiPopup: root.emojiPopup
activeCommunity: root.communityData
property int channelPosition: -1
property var deleteChatConfirmationDialog
onCreateCommunityChannel: function (chName, chDescription, chEmoji, chColor,
chCategoryId) {
root.store.createCommunityChannel(chName, chDescription, chEmoji, chColor,
chCategoryId)
chatId = root.store.currentChatContentModule().chatDetails.id
}
onEditCommunityChannel: {
root.store.editCommunityChannel(chatId,
chName,
chDescription,
chEmoji,
chColor,
chCategoryId,
channelPosition);
}
onAddPermissions: function (permissions) {
for (var i = 0; i < permissions.length; i++) {
root.store.permissionsStore.createPermission(permissions[i].holdingsListModel,
permissions[i].permissionType,
permissions[i].isPrivate,
permissions[i].channelsListModel)
}
}
onRemovePermissions: function (permissions) {
for (var i = 0; i < permissions.length; i++) {
root.store.permissionsStore.removePermission(permissions[i].id)
}
}
onEditPermissions: function (permissions) {
for (var i = 0; i < permissions.length; i++) {
root.store.permissionsStore.editPermission(permissions[i].id,
permissions[i].holdingsListModel,
permissions[i].permissionType,
permissions[i].channelsListModel,
permissions[i].isPrivate)
}
}
onSetViewOnlyCanAddReaction: function (checked) {
root.store.permissionsStore.setViewOnlyCanAddReaction(chatId, checked)
}
onSetHideIfPermissionsNotMet: function (checked) {
root.store.permissionsStore.setHideIfPermissionsNotMet(chatId, checked)
}
onDeleteCommunityChannel: {
Global.openPopup(deleteChatConfirmationDialog);
close()
}
onClosed: {
destroy()

View File

@ -38,14 +38,14 @@ StatusScrollView {
readonly property alias dirtyValues: d.dirtyValues
readonly property bool isFullyFilled:
(dirtyValues.selectedHoldingsModel.count > 0 || !whoHoldsSwitch.checked) &&
readonly property bool isFullyFilled: (dirtyValues.selectedHoldingsModel.count > 0 || !whoHoldsSwitch.checked) &&
dirtyValues.permissionType !== PermissionTypes.Type.None &&
(d.isCommunityPermission || dirtyValues.selectedChannelsModel.count > 0)
(d.isCommunityPermission || !showChannelSelector || dirtyValues.selectedChannelsModel.count > 0)
property int permissionType: PermissionTypes.Type.None
property bool isPrivate: false
property bool holdingsRequired: true
property bool showChannelSelector: true
// roles: type, key, name, amount, imageSource
property var selectedHoldingsModel: ListModel {}
@ -430,6 +430,7 @@ StatusScrollView {
PermissionsDropdown {
id: permissionsDropdown
allowCommunityOptions: root.showChannelSelector
initialPermissionType: d.dirtyValues.permissionType
enableAdminPermission: root.communityDetails.owner
@ -454,7 +455,7 @@ StatusScrollView {
}
}
SequenceColumnLayout.Separator {}
SequenceColumnLayout.Separator { visible: root.showChannelSelector }
StatusItemSelector {
id: inSelector
@ -463,7 +464,7 @@ StatusScrollView {
addButton.visible: editable
itemsClickable: editable
visible: root.showChannelSelector
Layout.fillWidth: true
icon: d.isCommunityPermission ? Style.svg("communities") : Style.svg("create-category")
title: qsTr("In")
@ -566,7 +567,7 @@ StatusScrollView {
color: Theme.palette.baseColor2
}
HidePermissionPanel {
StatusIconSwitch {
Layout.topMargin: 12
Layout.fillWidth: true
Layout.leftMargin: 16
@ -574,6 +575,9 @@ StatusScrollView {
enabled: d.dirtyValues.permissionType !== PermissionTypes.Type.Admin
checked: d.dirtyValues.isPrivate
title: qsTr("Hide permission")
subTitle: qsTr("Make this permission hidden from members who dont meet its requirements")
icon: "hide"
onToggled: d.dirtyValues.isPrivate = checked
}
@ -600,7 +604,7 @@ StatusScrollView {
StatusWarningBox {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
visible: root.showChannelSelector
icon: "desktop"
text: qsTr("Any changes to community permissions will take effect after the control node receives and processes them")
borderColor: Theme.palette.baseColor1
@ -613,7 +617,7 @@ StatusScrollView {
Layout.fillWidth: true
Layout.topMargin: Style.current.bigPadding
visible: !root.isEditState
visible: !root.isEditState && root.showChannelSelector
text: qsTr("Create permission")
enabled: root.isFullyFilled
&& !root.permissionDuplicated

View File

@ -2,16 +2,27 @@ import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import SortFilterProxyModel 0.2
import shared.status 1.0
import shared.popups 1.0
import utils 1.0
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.panels 1.0
StatusScrollView {
ColumnLayout {
id: root
width: root.viewWidth
property int topPadding: count ? 16 : 0
spacing: 24
QtObject {
id: d
property int permissionIndexToRemove
}
required property var permissionsModel
required property var assetsModel
@ -22,98 +33,110 @@ StatusScrollView {
required property var communityDetails
property int viewWidth: 560 // by design
property bool viewOnlyCanAddReaction
property bool showChannelOptions: false
property bool allowIntroPanel: true
signal editPermissionRequested(int index)
signal duplicatePermissionRequested(int index)
signal removePermissionRequested(int index)
signal userRestrictionsToggled(bool checked)
readonly property alias count: repeater.count
ListModel {
id: communityItemModel
padding: 0
topPadding: count ? 16 : 0
QtObject {
id: d
property int permissionIndexToRemove
Component.onCompleted: {
append({
text: root.communityDetails.name,
imageSource: root.communityDetails.image,
color: root.communityDetails.color
})
}
}
ColumnLayout {
id: mainLayout
width: root.viewWidth
spacing: 24
IntroPanel {
Layout.fillWidth: true
ListModel {
id: communityItemModel
visible: (root.count === 0 && root.allowIntroPanel)
Component.onCompleted: {
append({
text: root.communityDetails.name,
imageSource: root.communityDetails.image,
color: root.communityDetails.color
})
}
}
image: Style.png("community/permissions2_3")
title: qsTr("Permissions")
subtitle: qsTr("You can manage your community by creating and issuing membership and access permissions")
checkersModel: [
qsTr("Give individual members access to private channels"),
qsTr("Monetise your community with subscriptions and fees"),
qsTr("Require holding a token or NFT to obtain exclusive membership rights")
]
}
IntroPanel {
Repeater {
id: repeater
model: root.permissionsModel
delegate: PermissionItem {
Layout.fillWidth: true
visible: root.count === 0
holdingsListModel: HoldingsSelectionModel {
sourceModel: model.holdingsListModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
}
image: Style.png("community/permissions2_3")
title: qsTr("Permissions")
subtitle: qsTr("You can manage your community by creating and issuing membership and access permissions")
checkersModel: [
qsTr("Give individual members access to private channels"),
qsTr("Monetise your community with subscriptions and fees"),
qsTr("Require holding a token or NFT to obtain exclusive membership rights")
]
}
permissionType: model.permissionType
permissionState: model.permissionState // TODO: Backend!
Repeater {
id: repeater
ChannelsSelectionModel {
id: channelsSelectionModel
model: root.permissionsModel
selectedChannels: model.channelsListModel ?? null
allChannels: root.channelsModel
}
delegate: PermissionItem {
Layout.fillWidth: true
channelsListModel: channelsSelectionModel.count
? channelsSelectionModel : communityItemModel
isPrivate: model.isPrivate
holdingsListModel: HoldingsSelectionModel {
sourceModel: model.holdingsListModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
}
showButtons: (model.permissionType !== PermissionTypes.Type.TokenMaster &&
model.permissionType !== PermissionTypes.Type.Owner) &&
(!!root.communityDetails && (root.communityDetails.owner ||
((root.communityDetails.admin || root.communityDetails.tokenMaster) && model.permissionType !== PermissionTypes.Type.Admin)))
permissionType: model.permissionType
permissionState: model.permissionState // TODO: Backend!
onEditClicked: root.editPermissionRequested(model.index)
onDuplicateClicked: root.duplicatePermissionRequested(model.index)
ChannelsSelectionModel {
id: channelsSelectionModel
selectedChannels: model.channelsListModel ?? null
allChannels: root.channelsModel
}
channelsListModel: channelsSelectionModel.count
? channelsSelectionModel : communityItemModel
isPrivate: model.isPrivate
showButtons: (model.permissionType !== PermissionTypes.Type.TokenMaster &&
model.permissionType !== PermissionTypes.Type.Owner) &&
(root.communityDetails.owner ||
((root.communityDetails.admin || root.communityDetails.tokenMaster) && model.permissionType !== PermissionTypes.Type.Admin))
onEditClicked: root.editPermissionRequested(model.index)
onDuplicateClicked: root.duplicatePermissionRequested(model.index)
onRemoveClicked: {
d.permissionIndexToRemove = index
declineAllDialog.open()
}
onRemoveClicked: {
d.permissionIndexToRemove = index
declineAllDialog.open()
}
}
}
StatusBaseText {
id: noPermissionsLabel
Layout.fillWidth: true
Layout.fillHeight: true
visible: (root.count === 0 && root.showChannelOptions)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: qsTr("No channel permissions")
color: Style.current.secondaryText
}
StatusIconSwitch {
Layout.fillWidth: true
padding: 0
visible: false //TODO: enable this when we have the backend support https://github.com/status-im/status-desktop/issues/13292
//visible: root.showChannelOptions
title: qsTr("Users with view only permissions can add reactions")
icon: "emojis"
checked: root.viewOnlyCanAddReaction
onToggled: {
root.userRestrictionsToggled(checked);
}
}
ConfirmationDialog {
id: declineAllDialog

View File

@ -26,10 +26,11 @@ StatusMenu {
property bool chatMuted: false
property int channelPosition: -1
property string chatCategoryId: ""
property var emojiPopup
property bool showDebugOptions: false
property alias deleteChatConfirmationDialog: deleteChatConfirmationDialogComponent
signal displayProfilePopup(string publicKey)
signal displayEditChannelPopup(string chatId)
signal requestAllHistoricMessages(string chatId)
signal unmuteChat(string chatId)
signal muteChat(string chatId, int interval)
@ -140,38 +141,7 @@ StatusMenu {
icon.name: "edit"
enabled: root.isCommunityChat && root.amIChatAdmin
onTriggered: {
Global.openPopup(editChannelPopup, {
isEdit: true,
channelName: root.chatName,
channelDescription: root.chatDescription,
channelEmoji: root.chatEmoji,
channelColor: root.chatColor,
categoryId: root.chatCategoryId
});
}
}
Component {
id: editChannelPopup
CreateChannelPopup {
anchors.centerIn: parent
isEdit: true
isDeleteable: root.isCommunityChat
emojiPopup: root.emojiPopup
onCreateCommunityChannel: {
root.createCommunityChannel(root.chatId, chName, chDescription, chEmoji, chColor);
}
onEditCommunityChannel: {
root.editCommunityChannel(root.chatId, chName, chDescription, chEmoji, chColor,
chCategoryId);
}
onDeleteCommunityChannel: {
Global.openPopup(deleteChatConfirmationDialogComponent)
close()
}
onClosed: {
destroy()
}
root.displayEditChannelPopup(root.chatId);
}
}