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 StatusStackModal { id: root property var 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 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 + Style.current.padding : (root.x + (root.width - root.emojiPopup.width - Style.current.padding)); root.emojiPopup.y = root.y + root.header.height + root.topPadding + nameInput.height + Style.current.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 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 channel’s chat history data using %1").arg("DiscordChatExporter") 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 documentation 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: 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) 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: 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 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: 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: 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: 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") 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 { 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: 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) { 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 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 } }