2022-05-26 15:46:02 +00:00
import QtQuick 2.14
2022-08-18 14:25:06 +00:00
import QtQuick . Controls 2.14
2022-05-26 15:46:02 +00:00
import QtQuick . Layouts 1.14
2020-12-11 20:29:46 +00:00
import QtQuick . Dialogs 1.3
2021-09-28 15:04:06 +00:00
import utils 1.0
2021-10-27 21:27:49 +00:00
import shared . panels 1.0
import shared . popups 1.0
2020-12-11 20:29:46 +00:00
2021-07-16 10:11:03 +00:00
import StatusQ . Core 0.1
import StatusQ . Core . Theme 0.1
2022-07-05 10:12:27 +00:00
import StatusQ . Core . Utils 0.1 as StatusQUtils
2021-07-16 10:11:03 +00:00
import StatusQ . Components 0.1
import StatusQ . Controls 0.1
2021-08-17 10:49:13 +00:00
import StatusQ . Controls . Validators 0.1
2021-07-16 10:11:03 +00:00
import StatusQ . Popups 0.1
2022-06-21 08:57:09 +00:00
import "../../Chat/controls/community"
2022-05-26 15:46:02 +00:00
2022-06-16 09:41:21 +00:00
import "../controls"
import "../panels"
StatusStackModal {
2022-05-26 15:46:02 +00:00
id: root
2020-12-17 13:24:33 +00:00
2021-10-22 20:49:47 +00:00
property var store
2022-08-18 14:25:06 +00:00
property bool isDiscordImport // creating new or importing from discord?
2020-12-11 20:29:46 +00:00
2022-08-18 14:25:06 +00:00
stackTitle: isDiscordImport ? qsTr ( "Import a community from Discord into Status" ) :
qsTr ( "Create New Community" )
2022-06-28 10:55:33 +00:00
width: 640
nextButton: StatusButton {
2022-07-18 20:56:33 +00:00
objectName: "createCommunityNextBtn"
2022-08-18 15:29:55 +00:00
font.weight: Font . Medium
text: typeof currentItem . nextButtonText !== "undefined" ? currentItem.nextButtonText : qsTr ( "Next" )
enabled: typeof ( currentItem . canGoNext ) == "undefined" || currentItem . canGoNext
2022-08-18 15:59:12 +00:00
loading: root . store . discordDataExtractionInProgress
2022-08-18 15:29:55 +00:00
onClicked: {
let nextAction = currentItem . nextAction
if ( typeof ( nextAction ) == "function" ) {
return nextAction ( )
}
root . currentIndex ++
}
2022-06-28 10:55:33 +00:00
}
2021-07-16 10:11:03 +00:00
2022-06-16 09:41:21 +00:00
finishButton: StatusButton {
2022-07-18 20:56:33 +00:00
objectName: "createCommunityFinalBtn"
2022-08-18 15:29:55 +00:00
font.weight: Font . Medium
text: root . isDiscordImport ? qsTr ( "Start Discord import" ) : qsTr ( "Create Community" )
enabled: typeof ( currentItem . canGoNext ) == "undefined" || currentItem . canGoNext
onClicked: {
let nextAction = currentItem . nextAction
if ( typeof ( nextAction ) == "function" ) {
return nextAction ( )
}
if ( ! root . isDiscordImport )
d . createCommunity ( )
}
2022-06-16 09:41:21 +00:00
}
2020-12-11 20:29:46 +00:00
2022-08-18 15:59:12 +00:00
readonly property var clearFilesButton: StatusButton {
font.weight: Font . Medium
text: qsTr ( "Clear all" )
type: StatusBaseButton . Type . Danger
visible: root . currentItem . objectName === "discordFileListView" // no better way to address the current item in the stack :/
enabled: ! fileListView . fileListModelEmpty && ! root . store . discordDataExtractionInProgress
onClicked: root . store . clearFileList ( )
}
rightButtons: [ clearFilesButton , nextButton , finishButton ]
onAboutToShow: {
nameInput . input . edit . forceActiveFocus ( )
if ( root . isDiscordImport ) {
2022-10-20 07:50:35 +00:00
if ( ! root . store . discordImportInProgress ) {
root . store . clearFileList ( )
root . store . clearDiscordCategoriesAndChannels ( )
}
2022-08-18 15:59:12 +00:00
for ( let i = 0 ; i < discordPages . length ; i ++ ) {
stackItems . push ( discordPages [ i ] )
}
}
}
readonly property list < Item > discordPages: [
ColumnLayout {
id: fileListView
objectName: "discordFileListView" // !!! DON'T CHANGE, clearFilesButton depends on this
spacing: 24
readonly property var fileListModel: root . store . 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 ? qsTr ( "Validate (%1/%2) files" ) . arg ( fileListModel . selectedCount ) . arg ( fileListModel . count )
: qsTr ( "Import files" )
readonly property var nextAction: function ( ) {
if ( ! fileListView . fileListModel . selectedFilesValid ) {
return root . store . requestExtractChannelsAndCategories ( )
}
root . currentIndex ++
}
RowLayout {
Layout.fillWidth: true
spacing: 12
StatusBaseText {
font.pixelSize: 15
text: fileListView . fileListModelEmpty ? qsTr ( "Select Discord JSON files to import" ) :
root . store . 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
font.pixelSize: 12
color: Theme . palette . baseColor1
text: qsTr ( "(JSON file format only)" )
}
IssuePill {
type: root . store . discordImportErrorsCount ? IssuePill.Type.Error : IssuePill . Type . Warning
2022-09-15 07:31:38 +00:00
count: {
if ( root . store . discordImportErrorsCount > 0 ) {
return root . store . discordImportErrorsCount
}
if ( root . store . discordImportWarningsCount > 0 ) {
return root . store . discordImportWarningsCount
}
return 0
}
visible: ! ! count
2022-08-18 15:59:12 +00:00
}
Item { Layout.fillWidth: true }
StatusButton {
text: qsTr ( "Browse files" )
type: StatusBaseButton . Type . Primary
onClicked: fileDialog . open ( )
enabled: ! root . store . discordDataExtractionInProgress
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme . palette . baseColor4
ColumnLayout {
visible: fileListView . fileListModelEmpty
anchors.top: parent . top
anchors.topMargin: 60
anchors.horizontalCenter: parent . horizontalCenter
spacing: 8
StatusRoundIcon {
Layout.alignment: Qt . AlignHCenter
2022-08-11 11:55:08 +00:00
asset.name: "info"
2022-08-18 15:59:12 +00:00
}
StatusBaseText {
Layout.topMargin: 8
Layout.alignment: Qt . AlignHCenter
horizontalAlignment: Qt . AlignHCenter
linkColor: hoveredLink ? Qt . lighter ( Theme . palette . primaryColor1 ) : Theme . palette . primaryColor1
text: qsTr ( "Export your Discord JSON data using %1" )
. arg ( "<a href='https://github.com/Tyrrrz/DiscordChatExporter'>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
}
}
StatusBaseText {
Layout.alignment: Qt . AlignHCenter
horizontalAlignment: Qt . AlignHCenter
linkColor: hoveredLink ? Qt . lighter ( Theme . palette . primaryColor1 ) : Theme . palette . primaryColor1
text: qsTr ( "Refer to this <a href='https://github.com/Tyrrrz/DiscordChatExporter/wiki'>wiki</a> if you have any queries" )
onLinkActivated: Global . openLink ( link )
HoverHandler {
id: handler2
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt . NoButton
cursorShape: handler2 . hovered && parent . hoveredLink ? Qt.PointingHandCursor : Qt . ArrowCursor
}
}
}
StatusListView {
visible: ! fileListView . fileListModelEmpty
enabled: ! root . store . discordDataExtractionInProgress
anchors.fill: parent
anchors.margins: 16
model: fileListView . fileListModel
delegate: ColumnLayout {
width: ListView . view . width
2022-11-08 14:07:14 +00:00
RowLayout {
spacing: 20
2022-08-18 15:59:12 +00:00
Layout.fillWidth: true
2022-11-08 14:07:14 +00:00
Layout.topMargin: 8
StatusBaseText {
Layout.fillWidth: true
text: model . filePath
font.pixelSize: 13
elide: Text . ElideRight
wrapMode: Text . WordWrap
maximumLineCount: 2
}
StatusFlatRoundButton {
id: removeButton
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 . store . removeFileListItem ( model . filePath )
}
2022-08-18 15:59:12 +00:00
}
2022-11-08 14:07:14 +00:00
2022-08-18 15:59:12 +00:00
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" ) ]
onAccepted: {
if ( fileDialog . fileUrls . length > 0 ) {
let files = [ ]
for ( let i = 0 ; i < fileDialog . fileUrls . length ; i ++ )
files . push ( decodeURI ( fileDialog . fileUrls [ i ] . toString ( ) ) )
root . store . setFileListItems ( files )
}
}
}
} ,
ColumnLayout {
id: categoriesAndChannelsView
spacing: 24
readonly property bool canGoNext: root . store . discordChannelsModel . hasSelectedItems
readonly property var nextAction: function ( ) {
2022-09-15 07:31:38 +00:00
d . requestImportDiscordCommunity ( )
// replace ourselves with the progress dialog, no way back
root . leftButtons [ 0 ] . visible = false
root . backgroundColor = Theme . palette . baseColor4
root . replace ( progressComponent )
}
Component {
id: progressComponent
DiscordImportProgressContents {
width: root . availableWidth
store: root . store
onClose: root . close ( )
}
2022-08-18 15:59:12 +00:00
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: ! root . store . discordChannelsModel . count
Loader {
anchors.centerIn: parent
active: parent . visible
sourceComponent: StatusLoadingIndicator {
width: 50
height: 50
}
}
}
ColumnLayout {
spacing: 12
visible: root . store . discordChannelsModel . count
StatusBaseText {
Layout.fillWidth: true
text: qsTr ( "Please select the categories and channels you would like to import" )
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 . store . 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 . store . discordCategoriesModel
delegate: ColumnLayout {
width: ListView . view . width
spacing: 8
StatusCheckBox {
readonly property string categoryId: model . id
id: categoryCheckbox
checked: model . selected
text: model . name
onToggled: root . store . toggleDiscordCategory ( categoryId , checked )
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.leftMargin: 24
Repeater {
Layout.fillWidth: true
model: root . store . discordChannelsModel
delegate: StatusCheckBox {
width: parent . width
text: model . name
checked: model . selected
visible: model . categoryId === categoryCheckbox . categoryId
onToggled: root . store . toggleDiscordChannel ( model . id , checked )
}
}
}
}
}
}
}
}
]
2022-08-03 00:07:27 +00:00
2022-06-16 09:41:21 +00:00
stackItems: [
2022-07-13 12:29:38 +00:00
StatusScrollView {
2022-05-26 15:46:02 +00:00
id: generalView
2020-12-11 20:29:46 +00:00
2022-08-18 15:29:55 +00:00
readonly property bool canGoNext: nameInput . valid && descriptionTextInput . valid
2022-09-27 08:51:53 +00:00
clip: false
2022-05-26 15:46:02 +00:00
ColumnLayout {
id: generalViewLayout
2022-07-14 11:03:36 +00:00
width: generalView . availableWidth
2022-08-03 00:07:27 +00:00
spacing: 16
2021-07-16 10:11:03 +00:00
2022-05-26 15:46:02 +00:00
CommunityNameInput {
id: nameInput
2022-07-18 20:56:33 +00:00
input.edit.objectName: "createCommunityNameInput"
2022-05-26 15:46:02 +00:00
Layout.fillWidth: true
2022-08-03 00:07:27 +00:00
input.tabNavItem: descriptionTextInput . input . edit
2022-05-18 12:21:03 +00:00
}
2021-07-16 10:11:03 +00:00
2022-05-26 15:46:02 +00:00
CommunityDescriptionInput {
id: descriptionTextInput
2022-07-18 20:56:33 +00:00
input.edit.objectName: "createCommunityDescriptionInput"
2022-05-26 15:46:02 +00:00
Layout.fillWidth: true
2022-08-03 00:07:27 +00:00
input.tabNavItem: nameInput . input . edit
2021-07-16 10:11:03 +00:00
}
2022-05-26 15:46:02 +00:00
CommunityLogoPicker {
id: logoPicker
Layout.fillWidth: true
2021-03-01 19:43:50 +00:00
}
2021-02-10 20:37:17 +00:00
2022-08-17 12:44:53 +00:00
CommunityBannerPicker {
id: bannerPicker
Layout.fillWidth: true
}
2022-05-26 15:46:02 +00:00
CommunityColorPicker {
id: colorPicker
2022-06-16 09:41:21 +00:00
onPick: root . replace ( colorPanel )
2022-05-26 15:46:02 +00:00
Layout.fillWidth: true
2022-06-16 09:41:21 +00:00
Component {
id: colorPanel
CommunityColorPanel {
Component.onCompleted: color = colorPicker . color
onAccepted: {
colorPicker . color = color ;
root . replace ( null ) ;
}
}
}
2022-05-26 15:46:02 +00:00
}
2020-12-11 20:29:46 +00:00
2022-06-09 14:59:54 +00:00
CommunityTagsPicker {
id: communityTagsPicker
tags: root . store . communityTags
2022-06-16 09:41:21 +00:00
onPick: root . replace ( tagsPanel )
2022-06-09 14:59:54 +00:00
Layout.fillWidth: true
2022-06-16 09:41:21 +00:00
Component {
id: tagsPanel
CommunityTagsPanel {
Component.onCompleted: {
tags = communityTagsPicker . tags ;
selectedTags = communityTagsPicker . selectedTags ;
}
onAccepted: {
communityTagsPicker . selectedTags = selectedTags ;
root . replace ( null ) ;
}
}
}
2022-06-09 14:59:54 +00:00
}
2022-05-26 15:46:02 +00:00
StatusModalDivider {
Layout.fillWidth: true
2020-12-11 20:29:46 +00:00
}
2022-05-26 15:46:02 +00:00
CommunityOptions {
id: options
archiveSupportOptionVisible: root . store . isCommunityHistoryArchiveSupportEnabled
archiveSupportEnabled: archiveSupportOptionVisible
2022-04-08 10:46:38 +00:00
}
2022-05-10 16:13:36 +00:00
2022-05-26 15:46:02 +00:00
Item {
Layout.fillHeight: true
2022-05-10 16:13:36 +00:00
}
}
2022-06-16 09:41:21 +00:00
} ,
2022-08-18 15:29:55 +00:00
2022-05-26 15:46:02 +00:00
ColumnLayout {
id: introOutroMessageView
2022-07-29 08:00:37 +00:00
spacing: 11
2022-08-18 15:29:55 +00:00
readonly property bool canGoNext: introMessageInput . valid && outroMessageInput . valid
2022-05-26 15:46:02 +00:00
CommunityIntroMessageInput {
id: introMessageInput
2022-07-18 20:56:33 +00:00
input.edit.objectName: "createCommunityIntroMessageInput"
2022-08-18 15:29:55 +00:00
input.tabNavItem: outroMessageInput . input . edit
2020-12-17 13:24:33 +00:00
2022-05-26 15:46:02 +00:00
Layout.fillWidth: true
Layout.fillHeight: true
2020-12-11 20:29:46 +00:00
2022-07-29 08:00:37 +00:00
minimumHeight: height
maximumHeight: ( height - Style . current . xlPadding )
2022-05-26 15:46:02 +00:00
}
CommunityOutroMessageInput {
id: outroMessageInput
2022-07-18 20:56:33 +00:00
input.edit.objectName: "createCommunityOutroMessageInput"
2022-08-18 15:29:55 +00:00
input.tabNavItem: introMessageInput . input . edit
2022-05-26 15:46:02 +00:00
Layout.fillWidth: true
2021-05-24 03:09:49 +00:00
}
2020-12-11 20:29:46 +00:00
}
2022-06-16 09:41:21 +00:00
]
2022-05-26 15:46:02 +00:00
QtObject {
id: d
2022-09-15 07:31:38 +00:00
function _getCommunityConfig ( ) {
return {
name: StatusQUtils . Utils . filterXSS ( nameInput . input . text ) ,
description: StatusQUtils . Utils . filterXSS ( descriptionTextInput . input . text ) ,
introMessage: StatusQUtils . Utils . filterXSS ( introMessageInput . input . text ) ,
outroMessage: StatusQUtils . Utils . filterXSS ( outroMessageInput . input . text ) ,
color: colorPicker . color . toString ( ) . toUpperCase ( ) ,
tags: communityTagsPicker . selectedTags ,
image: {
src: logoPicker . source ,
AX: logoPicker . cropRect . x ,
AY: logoPicker . cropRect . y ,
BX: logoPicker . cropRect . x + logoPicker . cropRect . width ,
BY: logoPicker . cropRect . y + logoPicker . cropRect . height ,
} ,
options: {
historyArchiveSupportEnabled: options . archiveSupportEnabled ,
checkedMembership: options . requestToJoinEnabled ? Constants.communityChatOnRequestAccess : Constants . communityChatPublicAccess ,
2022-10-07 16:33:23 +00:00
pinMessagesAllowedForMembers: options . pinMessagesEnabled ,
encrypted: options . requestToJoinEnabled && options . encrypted // Only communities with memberships can be encrypted
2022-09-15 07:31:38 +00:00
} ,
bannerJsonStr: JSON . stringify ( { imagePath: String ( bannerPicker . source ) . replace ( "file://" , "" ) , cropRect: bannerPicker . cropRect } )
}
}
2022-05-26 15:46:02 +00:00
function createCommunity ( ) {
2022-09-15 07:31:38 +00:00
const error = root . store . createCommunity ( _getCommunityConfig ( ) )
2022-05-26 15:46:02 +00:00
if ( error ) {
errorDialog . text = error . error
errorDialog . open ( )
}
root . close ( )
}
2022-09-15 07:31:38 +00:00
function requestImportDiscordCommunity ( ) {
const error = root . store . requestImportDiscordCommunity ( _getCommunityConfig ( ) , datePicker . selectedDate . valueOf ( ) / 1000 )
if ( error ) {
errorDialog . text = error . error
errorDialog . open ( )
}
}
2022-05-26 15:46:02 +00:00
}
2021-07-16 10:11:03 +00:00
MessageDialog {
2022-05-26 15:46:02 +00:00
id: errorDialog
title: qsTr ( "Error creating the community" )
2021-07-16 10:11:03 +00:00
icon: StandardIcon . Critical
standardButtons: StandardButton . Ok
}
2020-12-11 20:29:46 +00:00
}