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,7 +61,12 @@ StackView {
onClicked: root.push(newPermissionView, StackView.Immediate)
}
contentItem: PermissionsView {
contentItem: StatusScrollView {
contentHeight: (permissionsView.height + topPadding)
topPadding: permissionsView.topPadding
padding: 0
PermissionsView {
id: permissionsView
permissionsModel: root.permissionsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
@ -72,7 +86,7 @@ StackView {
isPrivateToEditValue: item.isPrivate
}
root.push(newPermissionView, properties, StackView.Immediate)
root.pushEditView(properties);
}
onDuplicatePermissionRequested: {
@ -85,7 +99,7 @@ StackView {
isPrivateToEditValue: item.isPrivate
}
root.push(newPermissionView, properties, StackView.Immediate)
root.pushEditView(properties);
}
onRemovePermissionRequested: {
@ -94,26 +108,40 @@ StackView {
}
}
}
}
Component {
id: newPermissionView
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,15 +219,19 @@ 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)
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)
}
}
}

View File

@ -2,11 +2,14 @@ import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtQuick.Dialogs 1.3
import QtQml 2.15
import QtQml.Models 2.15
import utils 1.0
import shared.panels 1.0
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
@ -15,8 +18,10 @@ import StatusQ.Controls.Validators 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.views 1.0
import AppLayouts.Communities.panels 1.0
import AppLayouts.Communities.models 1.0
import AppLayouts.Communities.controls 1.0
StatusStackModal {
id: root
@ -26,9 +31,12 @@ StatusStackModal {
property bool isDiscordImport // creating new or importing from discord?
property bool isEdit: false
property bool isDeleteable: false
property bool viewOnlyCanAddReaction
property bool hideIfPermissionsNotMet
property string communityId: ""
property string chatId: ""
property string chatId: "_newChannel"
property string categoryId: ""
property string channelName: ""
property string channelDescription: ""
@ -39,20 +47,90 @@ StatusStackModal {
readonly property int communityColorValidator: Utils.Validate.NoEmpty
| Utils.Validate.TextHexColor
property var activeCommunity
required property var assetsModel
required property var collectiblesModel
required property var permissionsModel
required property var channelsModel
readonly property int maxChannelNameLength: 24
readonly property int maxChannelDescLength: 140
// channel signals
signal createCommunityChannel(string chName, string chDescription, string chEmoji, string chColor, string chCategoryId)
signal editCommunityChannel(string chName, string chDescription, string chEmoji, string chColor, string chCategoryId)
signal deleteCommunityChannel()
// Permissions signals:
// permissions arg is a list of objects with the following properties:
// - key: string
// - id: string
// - permissionType: string
// - holdings: list of objects with the following properties:
// - key: string
// - type: string
// - amount: string
// - channels: list of objects with the following properties:
// - key: string
// - isPrivate: bool
signal addPermissions(var permissions)
signal removePermissions(var permissions)
signal editPermissions(var permissions)
signal setViewOnlyCanAddReaction(bool checked)
signal setHideIfPermissionsNotMet(bool checked)
width: 640
leftPadding: 0
rightPadding: 0
currentIndex: d.currentPage
enum CurrentPage {
ChannelDetails, //0
ColorPicker, //1
ChannelPermissions, //2
DiscordImportUploadFile, //3
DiscordImportUploadStart //4
}
QtObject {
id: d
property int currentPage: CreateChannelPopup.CurrentPage.ChannelDetails
readonly property QtObject communityDetails: QtObject {
readonly property string id: root.activeCommunity.id
readonly property string name: root.activeCommunity.name
readonly property string image: root.activeCommunity.image
readonly property string color: root.activeCommunity.color
readonly property bool owner: root.activeCommunity.memberRole === Constants.memberRole.owner
readonly property bool admin: root.activeCommunity.memberRole === Constants.memberRole.admin
readonly property bool tokenMaster: root.activeCommunity.memberRole === Constants.memberRole.tokenMaster
}
readonly property ChannelPermissionsModelEditor channelEditModel: ChannelPermissionsModelEditor {
channelId: root.chatId
name: nameInput.input.text
emoji: nameInput.input.asset.emoji
color: colorPanel.color.toString().toUpperCase()
channelsModel: root.channelsModel
permissionsModel: root.permissionsModel
newChannelMode: !root.isEdit
property Connections rootConnection: Connections {
target: root
function onClosed() {
d.channelEditModel.reset()
}
}
}
property bool viewOnlyCanAddReaction: root.viewOnlyCanAddReaction
property bool hideIfPermissionsNotMet: root.hideIfPermissionsNotMet
property bool colorPickerOpened: false
function isFormValid() {
return nameInput.valid && descriptionTextArea.valid &&
Utils.validateAndReturnError(colorDialog.color.toString().toUpperCase(), communityColorValidator) === ""
Utils.validateAndReturnError(colorPanel.color.toString().toUpperCase(), communityColorValidator) === ""
}
function openEmojiPopup(leftSide = false) {
@ -70,7 +148,7 @@ StatusStackModal {
categoryId: root.categoryId,
name: StatusQUtils.Utils.filterXSS(nameInput.input.text),
description: StatusQUtils.Utils.filterXSS(descriptionTextArea.text),
color: colorDialog.color.toString().toUpperCase(),
color: colorPanel.color.toString().toUpperCase(),
emoji: StatusQUtils.Emoji.deparse(nameInput.input.asset.emoji),
options: {
// TODO
@ -85,66 +163,99 @@ StatusStackModal {
creatingError.open()
}
}
}
stackTitle: isDiscordImport ? qsTr("New Channel With Imported Chat History") :
isEdit ? qsTr("Edit #%1").arg(root.channelName)
: qsTr("New channel")
nextButton: StatusButton {
objectName: "createChannelNextBtn"
font.weight: Font.Medium
text: typeof currentItem.nextButtonText !== "undefined" ? currentItem.nextButtonText : qsTr("Import chat history")
enabled: typeof(currentItem.canGoNext) == "undefined" || currentItem.canGoNext
loading: root.communitiesStore.discordDataExtractionInProgress
onClicked: {
const nextAction = currentItem.nextAction
if (typeof(nextAction) == "function") {
return nextAction()
}
root.currentIndex++
}
}
finishButton: StatusButton {
objectName: "createOrEditCommunityChannelBtn"
font.weight: Font.Medium
text: isDiscordImport ? qsTr("Import chat history") : isEdit ? qsTr("Save changes") : qsTr("Create channel")
enabled: typeof(currentItem.canGoNext) == "undefined" || currentItem.canGoNext
onClicked: {
let nextAction = currentItem.nextAction
if (typeof (nextAction) == "function") {
return nextAction()
}
if (!root.isDiscordImport) {
if (!d.isFormValid()) {
scrollView.scrollBackUp()
return
}
function saveAndClose() {
let emoji = StatusQUtils.Emoji.deparse(nameInput.input.asset.emoji)
if (!isEdit) {
root.createCommunityChannel(StatusQUtils.Utils.filterXSS(nameInput.input.text),
StatusQUtils.Utils.filterXSS(descriptionTextArea.text),
emoji,
colorDialog.color.toString().toUpperCase(),
colorPanel.color.toString().toUpperCase(),
root.categoryId)
} else {
root.editCommunityChannel(StatusQUtils.Utils.filterXSS(nameInput.input.text),
StatusQUtils.Utils.filterXSS(descriptionTextArea.text),
emoji,
colorDialog.color.toString().toUpperCase(),
colorPanel.color.toString().toUpperCase(),
root.categoryId)
}
if (d.channelEditModel.dirtyPermissions) {
var newPermissions = d.channelEditModel.getAddedPermissions();
if (newPermissions.length > 0) {
root.addPermissions(newPermissions);
}
var editedPermissions = d.channelEditModel.getEditedPermissions();
if (editedPermissions.length > 0) {
root.editPermissions(editedPermissions);
}
var removedPermissions = d.channelEditModel.getRemovedPermissions();
if (removedPermissions.length > 0) {
root.removePermissions(removedPermissions);
}
}
if (root.viewOnlyCanAddReaction !== d.viewOnlyCanAddReaction) {
root.setViewOnlyCanAddReaction(d.viewOnlyCanAddReaction);
}
if (root.hideIfPermissionsNotMet !== d.hideIfPermissionsNotMet) {
root.setHideIfPermissionsNotMet(d.hideIfPermissionsNotMet);
}
// TODO Open the channel once we have designs for it
root.close()
}
}
stackTitle: isDiscordImport ? qsTr("New Channel With Imported Chat History") :
!!currentItem.stackTitleText ? currentItem.stackTitleText :
(isEdit ? qsTr("Edit #%1").arg(root.channelName) : qsTr("New channel"))
nextButton: StatusButton {
objectName: "createOrEditCommunityChannelBtn"
font.weight: Font.Medium
height: 44
visible: !d.colorPickerOpened
enabled: typeof(currentItem.canGoNext) == "undefined" || currentItem.canGoNext
text: !!currentItem.nextButtonText ? currentItem.nextButtonText :
d.colorPickerOpened ? qsTr("Set channel color") : (
isDiscordImport ? qsTr("Import chat history") :
isEdit ? qsTr("Save changes") : qsTr("Create channel"))
loading: root.communitiesStore.discordDataExtractionInProgress
onClicked: {
let nextAction = currentItem.nextAction
if (typeof (nextAction) == "function") {
return nextAction()
}
}
}
finishButton: StatusButton {
objectName: "createChannelNextBtn"
font.weight: Font.Medium
height: 44
text: (typeof currentItem.nextButtonText !== "undefined") ? currentItem.nextButtonText :
qsTr("Import chat history")
enabled: typeof(currentItem.canGoNext) == "undefined" || currentItem.canGoNext
onClicked: {
const nextAction = currentItem.nextAction
if (typeof(nextAction) == "function") {
return nextAction()
}
}
}
//TODO
onCurrentIndexChanged: {
d.colorPickerOpened = false;
}
readonly property StatusButton clearFilesButton: StatusButton {
font.weight: Font.Medium
text: qsTr("Clear all")
height: 44
type: StatusBaseButton.Type.Danger
visible: typeof currentItem.isFileListView !== "undefined" && currentItem.isFileListView
enabled: !fileListView.fileListModelEmpty && !root.communitiesStore.discordDataExtractionInProgress
@ -153,12 +264,32 @@ StatusStackModal {
readonly property StatusButton deleteChannelButton: StatusButton {
objectName: "deleteCommunityChannelBtn"
visible: isEdit && isDeleteable && !isDiscordImport && typeof(replaceItem) === "undefined"
text: qsTr("Delete channel")
height: 44
visible: isEdit && isDeleteable && !isDiscordImport && (d.currentPage === CreateChannelPopup.CurrentPage.ChannelDetails) ||
!!currentItem.deleteButtonText
text: (d.currentPage === CreateChannelPopup.CurrentPage.ChannelPermissions) ? currentItem.deleteButtonText : qsTr("Delete channel")
enabled: (d.currentPage === CreateChannelPopup.CurrentPage.ChannelPermissions) ? currentItem.deleteButtonEnabled : true
type: StatusBaseButton.Type.Danger
onClicked: root.deleteCommunityChannel()
onClicked: {
const nextAction = currentItem.nextDeleteAction
if (typeof(nextAction) == "function") {
return nextAction()
} else {
root.deleteCommunityChannel();
}
}
}
property Item backButton: StatusBackButton {
visible: d.currentPage !== CreateChannelPopup.CurrentPage.ChannelDetails
onClicked: {
d.currentPage = (d.currentPage === CreateChannelPopup.CurrentPage.DiscordImportUploadStart) ?
CreateChannelPopup.CurrentPage.DiscordImportUploadFile : CreateChannelPopup.CurrentPage.ChannelDetails
}
}
leftButtons: [ backButton ]
rightButtons: [clearFilesButton, deleteChannelButton, nextButton, finishButton]
onAboutToShow: {
@ -179,7 +310,7 @@ StatusStackModal {
if (root.channelEmoji) {
nameInput.input.asset.emoji = root.channelEmoji
}
colorDialog.color = root.channelColor
colorPanel.color = root.channelColor
} else {
nameInput.input.asset.isLetterIdenticon = true;
}
@ -188,10 +319,8 @@ StatusStackModal {
}
readonly property list<Item> discordPages: [
ColumnLayout {
id: fileListView
spacing: 24
Item {
id: fileListViewItem
readonly property bool isFileListView: true
readonly property var fileListModel: root.communitiesStore.discordFileList
@ -205,11 +334,17 @@ StatusStackModal {
: fileListModel.selectedCount ? qsTr("Validate (%1/%2) files").arg(fileListModel.selectedCount).arg(fileListModel.count)
: qsTr("Start channel import")
readonly property var nextAction: function () {
if (!fileListView.fileListModel.selectedFilesValid)
if (!fileListViewItem.fileListModel.selectedFilesValid)
return root.communitiesStore.requestExtractChannelsAndCategories()
root.currentIndex++
d.currentPage = CreateChannelPopup.CurrentPage.DiscordImportUploadStart;
}
ColumnLayout {
id: fileListView
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 24
RowLayout {
Layout.fillWidth: true
@ -219,12 +354,12 @@ StatusStackModal {
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Text.ElideRight
text: fileListView.fileListModelEmpty ? qsTr("Select Discord channel JSON files to import") :
text: fileListViewItem.fileListModelEmpty ? qsTr("Select Discord channel JSON files to import") :
root.communitiesStore.discordImportErrorsCount ? qsTr("Some of your community files cannot be used") :
qsTr("Uncheck any files you would like to exclude from the import")
}
StatusBaseText {
visible: fileListView.fileListModelEmpty && !issuePill.visible
visible: fileListViewItem.fileListModelEmpty && !issuePill.visible
font.pixelSize: 12
color: Theme.palette.baseColor1
text: qsTr("(JSON file format only)")
@ -233,7 +368,7 @@ StatusStackModal {
id: issuePill
type: root.communitiesStore.discordImportErrorsCount ? IssuePill.Type.Error : IssuePill.Type.Warning
count: root.communitiesStore.discordImportErrorsCount || root.communitiesStore.discordImportWarningsCount || 0
visible: !!count && !fileListView.fileListModelEmpty
visible: !!count && !fileListViewItem.fileListModelEmpty
}
StatusButton {
Layout.alignment: Qt.AlignRight
@ -250,7 +385,7 @@ StatusStackModal {
color: Theme.palette.baseColor4
ColumnLayout {
visible: fileListView.fileListModelEmpty
visible: fileListViewItem.fileListModelEmpty
anchors.top: parent.top
anchors.topMargin: 60
anchors.horizontalCenter: parent.horizontalCenter
@ -261,32 +396,29 @@ StatusStackModal {
asset.name: "info"
}
StatusBaseText {
id: infoText1
Layout.topMargin: 8
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Qt.AlignHCenter
text: qsTr("Export the Discord channels chat history data using %1").arg("<a href='https://github.com/Tyrrrz/DiscordChatExporter/releases/tag/2.40.4'>DiscordChatExporter</a>")
onLinkActivated: Global.openLink(link)
HoverHandler {
id: handler1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: handler1.hovered && parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
StatusBaseText {
id: infoText2
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Qt.AlignHCenter
text: qsTr("Refer to this <a href='https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs/Readme.md'>documentation</a> if you have any queries")
onLinkActivated: Global.openLink(link)
HoverHandler {
id: handler2
}
onHoveredLinkChanged: print(hoveredLink)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: handler2.hovered && parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
@ -302,12 +434,12 @@ StatusStackModal {
}
StatusListView {
visible: !fileListView.fileListModelEmpty
visible: !fileListViewItem.fileListModelEmpty
enabled: !root.communitiesStore.discordDataExtractionInProgress
anchors.fill: parent
leftMargin: 8
rightMargin: 8
model: fileListView.fileListModel
model: fileListViewItem.fileListModel
header: !atYBeginning ? floatingDivComp : null
headerPositioning: ListView.OverlayHeader
footer: !atYEnd ? floatingDivComp : null
@ -352,7 +484,7 @@ StatusStackModal {
}
}
}
}
FileDialog {
id: fileDialog
title: qsTr("Choose files to import")
@ -368,11 +500,7 @@ StatusStackModal {
}
}
},
ColumnLayout {
id: categoriesAndChannelsView
spacing: 24
Item {
readonly property bool canGoNext: root.communitiesStore.discordChannelsModel.hasSelectedItems
readonly property var nextAction: function () {
d.requestImportDiscordChannel()
@ -382,6 +510,13 @@ StatusStackModal {
root.replace(progressComponent)
}
ColumnLayout {
id: categoriesAndChannelsView
spacing: 24
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
Component {
id: progressComponent
DiscordImportProgressContents {
@ -480,6 +615,7 @@ StatusStackModal {
}
}
}
}
]
Connections {
@ -512,18 +648,17 @@ StatusStackModal {
ColumnLayout {
id: content
width: scrollView.availableWidth
spacing: 0
spacing: Style.current.padding
StatusInput {
id: nameInput
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
input.edit.objectName: "createOrEditCommunityChannelNameInput"
label: qsTr("Channel name")
charLimit: root.maxChannelNameLength
placeholderText: qsTr("# Name the channel")
input.onTextChanged: {
const cursorPosition = input.cursorPosition
input.text = Utils.convertSpacesToDashes(input.text)
@ -532,7 +667,7 @@ StatusStackModal {
input.letterIconName = text
}
}
input.asset.color: colorDialog.color.toString()
input.asset.color: colorPanel.color.toString()
input.rightComponent: StatusRoundButton {
objectName: "StatusChannelPopup_emojiButton"
implicitWidth: 32
@ -559,48 +694,48 @@ StatusStackModal {
}
Item {
Layout.preferredHeight: 16
Layout.fillWidth: true
Layout.preferredHeight: 82
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
StatusBaseText {
width: parent.width
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
text: qsTr("Channel colour")
}
StatusPickerButton {
id: colorSelectorButton
ColorPicker {
id: colorDialog
Layout.fillWidth: true
title: qsTr("Channel colour")
color: root.isEdit && root.channelColor ? root.channelColor : Theme.palette.primaryColor1
onPick: root.replace(colorPanel)
Component {
id: colorPanel
ColorPanel {
title: qsTr("Channel colour")
buttonText: qsTr("Select Colour")
Component.onCompleted: color = colorDialog.color
onAccepted: {
colorDialog.color = color
root.replaceItem = undefined
property string validationError: ""
width: parent.width
anchors.bottom: parent.bottom
bgColor: colorPanel.colorSelected ? colorPanel.color : Theme.palette.baseColor2
contentColor: colorPanel.colorSelected ? Theme.palette.white : Theme.palette.baseColor1
text: colorPanel.colorSelected ? colorPanel.color.toString().toUpperCase() : qsTr("Pick a colour")
onClicked: { d.currentPage = CreateChannelPopup.CurrentPage.ColorPicker; d.colorPickerOpened = true; }
onTextChanged: {
if (colorPanel.colorSelected) {
validationError = Utils.validateAndReturnError(text, communityColorValidator)
}
}
}
}
Item {
Layout.preferredHeight: 16
Layout.fillWidth: true
}
StatusInput {
id: descriptionTextArea
Layout.fillWidth: true
Layout.topMargin: Style.current.halfPadding
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
input.edit.objectName: "createOrEditCommunityChannelDescriptionInput"
input.verticalAlignment: TextEdit.AlignTop
label: qsTr("Description")
charLimit: 140
placeholderText: qsTr("Describe the channel")
input.multiline: true
minimumHeight: 88
maximumHeight: 88
minimumHeight: 108
maximumHeight: 108
validators: [
StatusMinLengthValidator {
minLength: 1
@ -612,6 +747,189 @@ StatusStackModal {
}
]
}
Separator {
Layout.fillWidth: true
visible: viewOnlyCanAddReactionCheckbox.visible
}
StatusCheckBox {
id: viewOnlyCanAddReactionCheckbox
Layout.fillWidth: true
Layout.preferredHeight: 48
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
leftSide: false
text: qsTr("Hide channel from members who don't have permissions to view the channel")
visible: false //TODO: Enable connect to the backend when it's ready https://github.com/status-im/status-desktop/issues/13291
checked: d.hideIfPermissionsNotMet
onToggled: {
d.hideIfPermissionsNotMet = checked;
}
}
Separator {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 56
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
StatusBaseText {
text: qsTr("Permissions")
}
Item { Layout.fillWidth: true }
StatusButton {
text: qsTr("Add permission")
enabled: !!nameInput.text
property ListModel channelToAddPermission: ListModel { }
onClicked: {
channelToAddPermission.clear();
channelToAddPermission.append({"key": root.chatId, "name": nameInput.text});
const propertiess = {
channelsToEditModel: channelToAddPermission,
header: null,
topPadding: -root.subHeaderPadding - 8,
leftPadding: 0,
rightPadding: 16,
viewWidth: scrollView.availableWidth - 32
};
editPermissionView.pushEditView(propertiess);
d.currentPage = CreateChannelPopup.CurrentPage.ChannelPermissions;
}
}
}
PermissionsView {
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
viewWidth: (scrollView.availableWidth - 32)
permissionsModel: d.channelEditModel.channelPermissionsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
viewOnlyCanAddReaction: root.viewOnlyCanAddReaction
channelsModel: d.channelEditModel.liveChannelsModel
communityDetails: d.communityDetails
showChannelOptions: true
allowIntroPanel: false
onRemovePermissionRequested: {
console.assert(d.channelEditModel.removePermission(index))
}
onDuplicatePermissionRequested: {
const item = StatusQUtils.ModelUtils.get(d.channelEditModel.channelPermissionsModel, index);
const properties = {
holdingsToEditModel: item.holdingsListModel,
channelsToEditModel: item.channelsListModel,
permissionTypeToEdit: item.permissionType,
isPrivateToEditValue: item.isPrivate,
header: null,
topPadding: -root.subHeaderPadding - 8,
leftPadding: 0,
rightPadding: 16,
viewWidth: scrollView.availableWidth - 32
}
editPermissionView.pushEditView(properties);
editPermissionView.currentItem.resetChanges()
d.currentPage = CreateChannelPopup.CurrentPage.ChannelPermissions;
}
onEditPermissionRequested: {
const item = d.channelEditModel.channelPermissionsModel.get(index);
const requireHoldings = (item.holdingsListModel.count ?? item.holdingsListModel.rowCount()) > 0;
const properties = {
permissionKeyToEdit: item.key,
holdingsToEditModel: item.holdingsListModel,
channelsToEditModel: item.channelsListModel,
permissionTypeToEdit: item.permissionType,
isPrivateToEditValue: item.isPrivate,
header: null,
topPadding: -root.subHeaderPadding - 8,
leftPadding: 0,
rightPadding: 16,
viewWidth: scrollView.availableWidth - 32
}
editPermissionView.pushEditView(properties);
editPermissionView.currentItem.resetChanges()
d.currentPage = CreateChannelPopup.CurrentPage.ChannelPermissions;
}
onUserRestrictionsToggled: {
d.viewOnlyCanAddReaction = checked;
}
}
}
readonly property var nextAction: function () {
if (!root.isDiscordImport) {
if (!d.isFormValid()) {
scrollView.scrollBackUp()
return
}
d.saveAndClose()
} else {
d.currentPage = CreateChannelPopup.CurrentPage.DiscordImportUploadFile;
}
}
},
ColorPanel {
id: colorPanel
readonly property string stackTitleText: qsTr("Channel Colour")
readonly property string nextButtonText: qsTr("Select Channel Colour")
padding: 0
leftPadding: 16
rightPadding: 16
height: Math.min(parent.height, 624)
property bool colorSelected: root.isEdit && root.channelColor
color: root.isEdit && root.channelColor ? root.channelColor : Theme.palette.primaryColor1
onAccepted: {
colorSelected = true; d.colorPickerOpened = false; d.currentPage = CreateChannelPopup.CurrentPage.ChannelDetails;
}
readonly property var nextAction: function () {
accepted();
}
},
PermissionsSettingsPanel {
id: editPermissionView
leftPadding: 16
rightPadding: 16
initialPage.header: null
initialPage.topPadding: 0
initialPage.leftPadding: 0
viewWidth: scrollView.availableWidth - 32
readonly property string nextButtonText: !!currentItem.permissionKeyToEdit ?
qsTr("Update permission") : qsTr("Create permission")
readonly property string stackTitleText: !!currentItem.permissionKeyToEdit ?
qsTr("Edit #%1 permission").arg(nameInput.text) : qsTr("New #%1 permission").arg(nameInput.text)
readonly property string deleteButtonText: !!currentItem.permissionKeyToEdit ?
qsTr("Revert changes") : ""
readonly property bool canGoNext: !!currentItem && currentItem.isDirty && currentItem.isFullyFilled ? currentItem.isDirty && currentItem.isFullyFilled : false
readonly property bool deleteButtonEnabled: editPermissionView.canGoNext
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
permissionsModel: d.channelEditModel.channelPermissionsModel
channelsModel: d.channelEditModel.liveChannelsModel
communityDetails: d.communityDetails
showChannelSelector: false
readonly property var nextDeleteAction: function () {
if (!!currentItem.permissionKeyToEdit) {
currentItem.resetChanges();
}
}
readonly property var nextAction: function () {
if (!!currentItem.permissionKeyToEdit) {
currentItem.updatePermission();
} else {
currentItem.createPermission();
}
}
onCreatePermissionRequested: {
d.channelEditModel.appendPermission(holdings, channels, permissionType, isPrivate)
d.currentPage = CreateChannelPopup.CurrentPage.ChannelDetails;
}
onUpdatePermissionRequested: {
d.channelEditModel.editPermission(key, permissionType, holdings, channels, isPrivate)
d.currentPage = CreateChannelPopup.CurrentPage.ChannelDetails;
}
}
]

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,27 +33,16 @@ 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
padding: 0
topPadding: count ? 16 : 0
QtObject {
id: d
property int permissionIndexToRemove
}
ColumnLayout {
id: mainLayout
width: root.viewWidth
spacing: 24
ListModel {
id: communityItemModel
@ -58,7 +58,7 @@ StatusScrollView {
IntroPanel {
Layout.fillWidth: true
visible: root.count === 0
visible: (root.count === 0 && root.allowIntroPanel)
image: Style.png("community/permissions2_3")
title: qsTr("Permissions")
@ -100,8 +100,8 @@ StatusScrollView {
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))
(!!root.communityDetails && (root.communityDetails.owner ||
((root.communityDetails.admin || root.communityDetails.tokenMaster) && model.permissionType !== PermissionTypes.Type.Admin)))
onEditClicked: root.editPermissionRequested(model.index)
onDuplicateClicked: root.duplicatePermissionRequested(model.index)
@ -112,8 +112,31 @@ StatusScrollView {
}
}
}
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);
}
}