status-desktop/ui/app/AppLayouts/Communities/popups/CreateChannelPopup.qml

976 lines
44 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import AppLayouts.Communities.views 1.0
import AppLayouts.Communities.panels 1.0
import AppLayouts.Communities.models 1.0
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.stores 1.0 as CommunitiesStores
StatusStackModal {
id: root
property CommunitiesStores.CommunitiesStore communitiesStore
property bool isDiscordImport // creating new or importing from discord?
property bool isEdit: false
property bool isDeleteable: false
property bool viewOnlyCanAddReaction
property bool hideIfPermissionsNotMet: false
property bool ensCommunityPermissionsEnabled: false
property string communityId: ""
property string chatId: "_newChannel"
property string categoryId: ""
property string channelName: ""
property string channelDescription: ""
property string channelEmoji: ""
property string channelColor: d.communityDetails.color !== "" ? d.communityDetails.color : ""
property bool emojiPopupOpened: false
property var emojiPopup: null
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, bool viewOnlyCanAddReaction, bool hideIfPermissionsNotMet)
signal editCommunityChannel(string chName, string chDescription, string chEmoji, string chColor, string chCategoryId, bool viewOnlyCanAddReaction, bool hideIfPermissionsNotMet)
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 setHideIfPermissionsNotMet(bool checked)
width: 640
leftPadding: 0
rightPadding: 0
currentIndex: d.currentPage
closePolicy: d.dirty && !root.isDiscordImport ? Popup.NoAutoClose : (Popup.CloseOnEscape | Popup.CloseOnPressOutside)
closeHandler: d.closeRequested
enum CurrentPage {
ChannelDetails, //0
ColorPicker, //1
ChannelPermissions, //2
DiscordImportUploadFile, //3
DiscordImportUploadStart //4
}
QtObject {
id: d
readonly property bool dirty: d.channelEditModel.dirtyPermissions ||
d.viewOnlyCanAddReaction !== root.viewOnlyCanAddReaction ||
d.hideIfPermissionsNotMet !== root.hideIfPermissionsNotMet ||
nameInput.input.text !== root.channelName ||
descriptionTextArea.text !== root.channelDescription ||
!Qt.colorEqual(colorPanel.color, root.channelColor) ||
nameInput.input.asset.emoji !== root.channelEmoji
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(colorPanel.color.toString().toUpperCase(), communityColorValidator) === ""
}
function openEmojiPopup(leftSide = false) {
root.emojiPopupOpened = true;
root.emojiPopup.open();
root.emojiPopup.emojiSize = StatusQUtils.Emoji.size.verySmall;
root.emojiPopup.x = leftSide ? root.x + Theme.padding : (root.x + (root.width - root.emojiPopup.width - Theme.padding));
root.emojiPopup.y = root.y + root.header.height + root.topPadding + nameInput.height + Theme.smallPadding;
}
function _getChannelConfig() {
return {
communityId: root.communityId,
discordChannelId: root.communitiesStore.discordImportChannelId,
categoryId: root.categoryId,
name: StatusQUtils.Utils.filterXSS(nameInput.input.text),
description: StatusQUtils.Utils.filterXSS(descriptionTextArea.text),
color: colorPanel.color.toString().toUpperCase(),
emoji: StatusQUtils.Emoji.deparse(nameInput.input.asset.emoji),
options: {
// TODO
}
}
}
function requestImportDiscordChannel() {
const error = root.communitiesStore.requestImportDiscordChannel(_getChannelConfig(), datePicker.selectedDate.valueOf()/1000)
if (error) {
creatingError.text = error.error
creatingError.open()
}
}
function saveAndClose() {
if (!d.isFormValid()) {
scrollView.scrollBackUp()
return
}
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,
colorPanel.color.toString().toUpperCase(),
root.categoryId,
d.viewOnlyCanAddReaction,
d.hideIfPermissionsNotMet)
} else {
root.editCommunityChannel(StatusQUtils.Utils.filterXSS(nameInput.input.text),
StatusQUtils.Utils.filterXSS(descriptionTextArea.text),
emoji,
colorPanel.color.toString().toUpperCase(),
root.categoryId,
d.viewOnlyCanAddReaction,
d.hideIfPermissionsNotMet)
}
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.hideIfPermissionsNotMet !== d.hideIfPermissionsNotMet) {
root.setHideIfPermissionsNotMet(d.hideIfPermissionsNotMet);
}
// TODO Open the channel once we have designs for it
root.close()
}
function closeRequested() {
if (d.dirty && !root.isDiscordImport)
closeConfirmation.open()
else
root.close()
}
}
StatusConfirmationDialog {
id: closeConfirmation
title: qsTr("Save changes to #%1 channel?").arg(root.channelName || nameInput.input.text)
body: qsTr("You have made changes to #%1 channel. If you close this dialog without saving these changes will be lost?").arg(root.channelName || nameInput.input.text)
acceptButtonText: qsTr("Save changes")
rejectButtonText: qsTr("Close without saving")
onAccepted: {
d.saveAndClose()
}
onRejected: {
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"
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"
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 {
text: qsTr("Clear all")
type: StatusBaseButton.Type.Danger
visible: typeof currentItem.isFileListView !== "undefined" && currentItem.isFileListView
enabled: !fileListView.fileListModelEmpty && !root.communitiesStore.discordDataExtractionInProgress
onClicked: root.communitiesStore.clearFileList()
}
readonly property StatusButton deleteChannelButton: StatusButton {
objectName: "deleteCommunityChannelBtn"
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: {
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
}
Layout.minimumWidth: implicitWidth
}
leftButtons: [ backButton ]
rightButtons: [clearFilesButton, deleteChannelButton, nextButton, finishButton]
onAboutToShow: {
if (root.isDiscordImport) {
if (!root.communitiesStore.discordImportInProgress) {
root.communitiesStore.clearFileList()
root.communitiesStore.clearDiscordCategoriesAndChannels()
}
for (let i = 0; i < discordPages.length; i++) {
stackItems.push(discordPages[i])
}
}
nameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason)
if (isEdit) {
nameInput.text = root.channelName
descriptionTextArea.text = root.channelDescription
if (root.channelEmoji) {
nameInput.input.asset.emoji = root.channelEmoji
}
} else {
nameInput.input.asset.isLetterIdenticon = true;
}
updateRightButtons()
}
readonly property list<Item> discordPages: [
Item {
id: fileListViewItem
readonly property bool isFileListView: true
readonly property var fileListModel: root.communitiesStore.discordFileList
readonly property bool fileListModelEmpty: !fileListModel.count
readonly property bool canGoNext: fileListModel.selectedCount
|| (fileListModel.selectedCount && fileListModel.selectedFilesValid)
readonly property string nextButtonText: fileListModel.selectedCount && fileListModel.selectedFilesValid ?
qsTr("Proceed with (%1/%2) files").arg(fileListModel.selectedCount).arg(fileListModel.count) :
fileListModel.selectedCount && fileListModel.selectedCount === fileListModel.count ? qsTr("Validate %n file(s)", "", fileListModel.selectedCount)
: fileListModel.selectedCount ? qsTr("Validate (%1/%2) files").arg(fileListModel.selectedCount).arg(fileListModel.count)
: qsTr("Start channel import")
readonly property var nextAction: function () {
if (!fileListViewItem.fileListModel.selectedFilesValid)
return root.communitiesStore.requestExtractChannelsAndCategories()
d.currentPage = CreateChannelPopup.CurrentPage.DiscordImportUploadStart;
}
ColumnLayout {
id: fileListView
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 24
RowLayout {
Layout.fillWidth: true
spacing: 12
StatusBaseText {
Layout.fillWidth: true
maximumLineCount: 2
wrapMode: Text.Wrap
elide: Text.ElideRight
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: fileListViewItem.fileListModelEmpty && !issuePill.visible
font.pixelSize: 12
color: Theme.palette.baseColor1
text: qsTr("(JSON file format only)")
}
IssuePill {
id: issuePill
type: root.communitiesStore.discordImportErrorsCount ? IssuePill.Type.Error : IssuePill.Type.Warning
count: root.communitiesStore.discordImportErrorsCount || root.communitiesStore.discordImportWarningsCount || 0
visible: !!count && !fileListViewItem.fileListModelEmpty
}
StatusButton {
Layout.alignment: Qt.AlignRight
text: qsTr("Browse files")
type: StatusBaseButton.Type.Primary
onClicked: fileDialog.open()
enabled: !root.communitiesStore.discordDataExtractionInProgress
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.palette.baseColor4
ColumnLayout {
visible: fileListViewItem.fileListModelEmpty
anchors.top: parent.top
anchors.topMargin: 60
anchors.horizontalCenter: parent.horizontalCenter
spacing: 8
StatusRoundIcon {
Layout.alignment: Qt.AlignHCenter
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)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
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)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
Component {
id: floatingDivComp
Rectangle {
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: ListView.view ? ListView.view.width : 0
height: 4
color: Theme.palette.directColor8
}
}
StatusListView {
visible: !fileListViewItem.fileListModelEmpty
enabled: !root.communitiesStore.discordDataExtractionInProgress
anchors.fill: parent
leftMargin: 8
rightMargin: 8
model: fileListViewItem.fileListModel
header: !atYBeginning ? floatingDivComp : null
headerPositioning: ListView.OverlayHeader
footer: !atYEnd ? floatingDivComp : null
footerPositioning: ListView.OverlayHeader
delegate: ColumnLayout {
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
RowLayout {
spacing: 20
Layout.fillWidth: true
Layout.topMargin: 8
StatusBaseText {
Layout.fillWidth: true
text: model.filePath
font.pixelSize: 13
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 2
}
StatusFlatRoundButton {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
type: StatusFlatRoundButton.Type.Secondary
icon.name: "close"
icon.color: Theme.palette.directColor1
icon.width: 24
icon.height: 24
onClicked: root.communitiesStore.removeFileListItem(model.filePath)
}
}
StatusBaseText {
Layout.fillWidth: true
text: "%1 %2".arg("⚠").arg(model.errorMessage)
visible: model.errorMessage
font.pixelSize: 13
font.weight: Font.Medium
elide: Text.ElideMiddle
color: Theme.palette.dangerColor1
verticalAlignment: Qt.AlignTop
}
}
}
}
}
FileDialog {
id: fileDialog
title: qsTr("Choose files to import")
selectMultiple: true
nameFilters: [qsTr("JSON files (%1)").arg("*.json *.JSON")]
onAccepted: {
if (fileDialog.fileUrls.length > 0) {
const files = []
for (let i = 0; i < fileDialog.fileUrls.length; i++)
files.push(decodeURI(fileDialog.fileUrls[i].toString()))
root.communitiesStore.setFileListItems(files)
}
}
}
},
Item {
readonly property bool canGoNext: root.communitiesStore.discordChannelsModel.hasSelectedItems
readonly property var nextAction: function () {
d.requestImportDiscordChannel()
// replace ourselves with the progress dialog, no way back
root.leftButtons[0].visible = false
root.backgroundColor = Theme.palette.baseColor4
root.replace(progressComponent)
}
ColumnLayout {
id: categoriesAndChannelsView
spacing: 24
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
Component {
id: progressComponent
DiscordImportProgressContents {
width: root.availableWidth
store: root.communitiesStore
importingSingleChannel: true
onClose: root.close()
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: !root.communitiesStore.discordChannelsModel.count
Loader {
anchors.centerIn: parent
active: parent.visible
sourceComponent: StatusLoadingIndicator {
width: 50
height: 50
}
}
}
ColumnLayout {
spacing: 12
visible: root.communitiesStore.discordChannelsModel.count
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Select the chat history you would like to import into #%1...").arg(StatusQUtils.Utils.filterXSS(nameInput.input.text))
wrapMode: Text.WordWrap
}
RowLayout {
spacing: 20
Layout.fillWidth: true
StatusRadioButton {
text: qsTr("Import all history")
checked: true
}
StatusRadioButton {
id: startDateRadio
text: qsTr("Start date")
}
StatusDatePicker {
id: datePicker
Layout.fillWidth: true
selectedDate: new Date(root.communitiesStore.discordOldestMessageTimestamp * 1000)
enabled: startDateRadio.checked
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.palette.baseColor4
StatusListView {
anchors.fill: parent
anchors.margins: 16
model: root.communitiesStore.discordCategoriesModel
delegate: ColumnLayout {
width: ListView.view.width
spacing: 8
StatusBaseText {
readonly property string categoryId: model.id
id: categoryCheckbox
text: model.name
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.leftMargin: 24
Repeater {
Layout.fillWidth: true
model: root.communitiesStore.discordChannelsModel
delegate: StatusRadioButton {
width: parent.width
text: model.name
checked: model.selected
visible: model.categoryId === categoryCheckbox.categoryId
onToggled: root.communitiesStore.toggleOneDiscordChannel(model.id)
Component.onCompleted: {
if (model.selected) {
root.communitiesStore.toggleOneDiscordChannel(model.id)
}
}
}
}
}
}
}
}
}
}
}
]
Connections {
enabled: root.opened && root.emojiPopupOpened
target: emojiPopup
function onEmojiSelected(emojiText: string, atCursor: bool) {
nameInput.input.asset.isLetterIdenticon = false;
nameInput.input.asset.emoji = emojiText
}
function onClosed() {
root.emojiPopupOpened = false
}
}
stackItems: [
StatusScrollView {
id: scrollView
readonly property bool canGoNext: d.isFormValid() && (root.isDiscordImport ? true : d.dirty)
property ScrollBar vScrollBar: ScrollBar.vertical
contentWidth: availableWidth
padding: 0
function scrollBackUp() {
vScrollBar.setPosition(0)
}
ColumnLayout {
id: content
width: scrollView.availableWidth
spacing: Theme.padding
StatusInput {
id: nameInput
Layout.fillWidth: true
Layout.leftMargin: Theme.padding
Layout.rightMargin: Theme.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)
input.cursorPosition = cursorPosition
if (root.channelEmoji === "") {
input.letterIconName = text
}
}
input.asset.color: colorPanel.color
input.rightComponent: StatusRoundButton {
objectName: "StatusChannelPopup_emojiButton"
implicitWidth: 32
implicitHeight: 32
icon.width: 20
icon.height: 20
icon.name: "smiley"
onClicked: d.openEmojiPopup()
}
onIconClicked: {
d.openEmojiPopup(true);
}
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(nameInput.errors, qsTr("channel name"))
},
StatusRegularExpressionValidator {
regularExpression: Constants.regularExpressions.alphanumericalExpanded
errorMessage: Constants.errorMessages.alphanumericalExpandedRegExp
}
]
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 82
Layout.leftMargin: Theme.padding
Layout.rightMargin: Theme.padding
StatusBaseText {
width: parent.width
anchors.top: parent.top
anchors.topMargin: Theme.halfPadding
text: qsTr("Channel colour")
}
StatusPickerButton {
id: colorSelectorButton
property string validationError: ""
width: parent.width
height: 44
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)
}
}
}
}
StatusInput {
id: descriptionTextArea
Layout.fillWidth: true
Layout.topMargin: Theme.halfPadding
Layout.leftMargin: Theme.padding
Layout.rightMargin: Theme.padding
input.edit.objectName: "createOrEditCommunityChannelDescriptionInput"
input.verticalAlignment: TextEdit.AlignTop
label: qsTr("Description")
charLimit: 140
placeholderText: qsTr("Describe the channel")
input.multiline: true
minimumHeight: 108
maximumHeight: 108
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: Utils.getErrorMessage(descriptionTextArea.errors, qsTr("channel description"))
},
StatusRegularExpressionValidator {
regularExpression: Constants.regularExpressions.alphanumericalExpanded
errorMessage: Constants.errorMessages.alphanumericalExpandedRegExp
}
]
}
Separator {
Layout.fillWidth: true
visible: viewOnlyCanAddReactionCheckbox.visible
}
StatusCheckBox {
objectName: "hideChannelCheckbox"
id: viewOnlyCanAddReactionCheckbox
Layout.fillWidth: true
Layout.preferredHeight: 48
Layout.leftMargin: Theme.padding
Layout.rightMargin: Theme.padding
leftSide: false
text: qsTr("Hide channel from members who don't have permissions to view the channel")
checked: d.hideIfPermissionsNotMet
onToggled: {
d.hideIfPermissionsNotMet = checked;
}
}
Separator {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 56
Layout.leftMargin: Theme.padding
Layout.rightMargin: Theme.padding
StatusBaseText {
text: qsTr("Permissions")
}
Item { Layout.fillWidth: true }
StatusButton {
objectName: "addPermissionButton"
text: qsTr("Add permission")
enabled: !!nameInput.text
property ListModel channelToAddPermission: ListModel { }
onClicked: {
channelToAddPermission.clear();
channelToAddPermission.append({"key": root.chatId, "name": nameInput.text});
const properties = {
channelsToEditModel: channelToAddPermission,
header: null,
topPadding: -root.subHeaderPadding - 8,
leftPadding: 0,
viewWidth: scrollView.availableWidth - 32
};
editPermissionView.pushEditView(properties);
d.currentPage = CreateChannelPopup.CurrentPage.ChannelPermissions;
}
}
}
PermissionsView {
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
Layout.leftMargin: Theme.padding
Layout.rightMargin: Theme.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) {
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.channelColor && root.channelColor != Theme.palette.primaryColor1
color: 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
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
permissionsModel: d.channelEditModel.channelPermissionsModel
channelsModel: d.channelEditModel.liveChannelsModel
communityDetails: d.communityDetails
showChannelSelector: false
ensCommunityPermissionsEnabled: root.ensCommunityPermissionsEnabled
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.isSaveEnabled ? currentItem.isSaveEnabled : false
readonly property bool deleteButtonEnabled: editPermissionView.canGoNext
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;
}
}
]
MessageDialog {
id: creatingError
title: qsTr("Error creating the channel")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
}