feat(CreateCommunityPopup): validation and ensuring min. 1 tag added

- enable the Next button, and delay the validation after it's been clicked
- visually display required fields also for logo, banner, and tags (min
1 tag is required now)
- when pasting over limit, chop the text to the maximum length, instead
of just leaving the text field empty
- do not let the popup autoclose on clicking outside or pressing Esc
- add a StoryBook page
- minor cleanups and alignments to the latest Figma designs/flows

Fixes #13966
Fixes #16479
Fixes #14902
This commit is contained in:
Lukáš Tinkl 2024-10-08 22:22:22 +02:00 committed by Lukáš Tinkl
parent 5518df3af9
commit 26dddcaff9
25 changed files with 399 additions and 102 deletions

View File

@ -0,0 +1,181 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import Storybook 1.0
import Models 1.0
import mainui 1.0
import shared.stores 1.0 as SharedStores
import AppLayouts.Communities.popups 1.0
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.stores 1.0 as CommunitiesStores
import AppLayouts.stores 1.0 as AppLayoutStores
SplitView {
id: root
Logs { id: logs }
orientation: Qt.Vertical
property var dialog
function createAndOpenDialog() {
dialog = dlgComponent.createObject(popupBg)
dialog.open()
}
Component.onCompleted: createAndOpenDialog()
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
PopupBackground {
id: popupBg
anchors.fill: parent
Button {
anchors.centerIn: parent
text: "Reopen"
onClicked: createAndOpenDialog()
}
}
Popups {
popupParent: root
sharedRootStore: SharedStores.RootStore {}
rootStore: AppLayoutStores.RootStore {}
communityTokensStore: SharedStores.CommunityTokensStore {}
isDevBuild: ctrlIsDevBuild.checked
}
Component {
id: dlgComponent
CreateCommunityPopup {
id: dialog
anchors.centerIn: parent
destroyOnClose: true
modal: false
isDiscordImport: isDiscordCheckBox.checked
isDevBuild: ctrlIsDevBuild.checked
QtObject {
id: localAppSettings
readonly property bool testEnvironment: dialog.isDevBuild
}
store: CommunitiesStores.CommunitiesStore {
readonly property string communityTags: ModelsData.communityTags
function createCommunity() {
logs.logEvent("createCommunity")
}
property string discordImportChannelName
readonly property bool discordImportInProgress: false
readonly property bool discordDataExtractionInProgress: false
readonly property int discordImportErrorsCount: 0
readonly property int discordImportWarningsCount: 0
readonly property int discordOldestMessageTimestamp: 0
property var discordFileList: ListModel {
readonly property int selectedCount: count
property bool selectedFilesValid
}
property var discordCategoriesModel: ListModel {}
property var discordChannelsModel: ListModel {
property bool hasSelectedItems
readonly property int count: 32 // hide the parsing/loading spinner
}
function setFileListItems(filePaths) {
for (const filePath of filePaths) {
discordFileList.append({"filePath": filePath, errorMessage: ""})
}
}
function removeFileListItem(path) {
for (let i = 0; i < discordFileList.count; i++) {
const item = discordFileList.get(i)
if (item.filePath === path)
discordFileList.remove(i)
}
}
function clearFileList() {
discordFileList.clear()
discordFileList.selectedFilesValid = false
}
function clearDiscordCategoriesAndChannels() {
discordCategoriesModel.clear()
discordChannelsModel.clear()
discordChannelsModel.hasSelectedItems = false
}
function requestExtractChannelsAndCategories() {
discordFileList.selectedFilesValid = true
}
function toggleOneDiscordChannel(id) {
logs.logEvent("toggleOneDiscordChannel", ["id"], arguments)
}
function resetDiscordImport() {
logs.logEvent("resetDiscordImport")
}
function requestCancelDiscordChannelImport(id) {
logs.logEvent("requestCancelDiscordChannelImport", ["id"], arguments)
}
function requestImportDiscordChannel(args, timestamp) {
logs.logEvent("requestImportDiscordChannel", ["args", "timestamp"], arguments)
}
}
}
}
}
LogsAndControlsPanel {
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
logsView.logText: logs.logText
ColumnLayout {
Switch {
id: isDiscordCheckBox
text: "Discord import"
onToggled: {
if (!!dialog && dialog.opened)
dialog.close()
}
}
Switch {
id: ctrlIsDevBuild
text: "Dev build"
onToggled: {
if (!!dialog && dialog.opened)
dialog.close()
}
}
}
}
}
// category: Popups
// https://www.figma.com/design/qHfFm7C9LwtXpfdbxssCK3/Kuba%E2%8E%9CDesktop---Communities?node-id=52741-266926&node-type=frame&t=PkDxeWSXoiZbIQXv-0
// https://www.figma.com/design/qHfFm7C9LwtXpfdbxssCK3/Kuba%E2%8E%9CDesktop---Communities?node-id=2636-359221&node-type=frame&t=PkDxeWSXoiZbIQXv-0
// https://www.figma.com/design/qHfFm7C9LwtXpfdbxssCK3/Kuba%E2%8E%9CDesktop---Communities?node-id=52741-267155&node-type=frame&t=PkDxeWSXoiZbIQXv-0

View File

@ -1,4 +1,4 @@
import QtQuick 2.14 import QtQuick 2.15
import StatusQ 0.1 import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
@ -76,7 +76,7 @@ Item {
delegate: StatusCommunityTag { delegate: StatusCommunityTag {
emoji: model.emoji emoji: model.emoji
name: model.name name: model.name
removable: root.mode === StatusCommunityTags.ShowSelectedOnly && root.active removable: root.mode === StatusCommunityTags.ShowSelectedOnly && root.active && repeater.count > 1
highlighted: root.mode === StatusCommunityTags.Highlight && model.selected highlighted: root.mode === StatusCommunityTags.Highlight && model.selected
onClicked: root.clicked(model) onClicked: root.clicked(model)

View File

@ -419,7 +419,7 @@ Item {
let utf8Length = Utils.encodeUtf8(text).length let utf8Length = Utils.encodeUtf8(text).length
if (utf8Length > root.maximumLength) { if (utf8Length > root.maximumLength) {
var cursor = cursorPosition var cursor = cursorPosition
text = previousText text = text.slice(0, maximumLength)
if (cursor > edit.length) { if (cursor > edit.length) {
cursorPosition = edit.length cursorPosition = edit.length
} else { } else {

View File

@ -1,10 +1,8 @@
import QtQuick 2.13 import QtQuick 2.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import "./"
Column { Column {
id: root id: root
@ -27,7 +25,6 @@ Column {
id: title id: title
width: parent.width width: parent.width
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.pixelSize: 15
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }

View File

@ -97,8 +97,8 @@ Item {
*/ */
property alias errorMessageCmp: errorMessage property alias errorMessageCmp: errorMessage
/*! /*!
\qmlproperty string StatusInput::label \qmlproperty int StatusInput::labelPadding
This property sets the label text. This property sets the padding of the label text.
*/ */
property int labelPadding: 8 property int labelPadding: 8
/*! /*!
@ -118,7 +118,7 @@ Item {
property int charLimit: 0 property int charLimit: 0
/*! /*!
\qmlproperty string StatusInput::charLimitLabel \qmlproperty string StatusInput::charLimitLabel
This property overrides the char default chart limit text format. This property overrides the default char limit text.
*/ */
property string charLimitLabel: "" property string charLimitLabel: ""
/*! /*!
@ -157,12 +157,12 @@ Item {
*/ */
property real maximumHeight: 44 property real maximumHeight: 44
/*! /*!
\qmlproperty list StatusBaseInput::validators \qmlproperty list<StatusValidator> StatusBaseInput::validators
This property sets the list of validators to be considered. This property sets the list of validators to be considered.
*/ */
property list<StatusValidator> validators property list<StatusValidator> validators
/*! /*!
\qmlproperty list StatusBaseInput::validators \qmlproperty list<StatusAsyncValidator> StatusBaseInput::asyncValidators
This property sets the list of async validators to be considered. This property sets the list of async validators to be considered.
*/ */
property list<StatusAsyncValidator> asyncValidators property list<StatusAsyncValidator> asyncValidators
@ -250,7 +250,7 @@ Item {
*/ */
property var errors: ({}) property var errors: ({})
/*! /*!
\qmlproperty var StatusBaseInput::errors \qmlproperty var StatusBaseInput::asyncErrors
This property holds the validation async errors. This property holds the validation async errors.
*/ */
property var asyncErrors: ({}) property var asyncErrors: ({})
@ -343,8 +343,9 @@ Item {
root.validatedValue = root.text root.validatedValue = root.text
} }
} }
/*! /*!
\qmlmethod \qmlmethod updateAsyncValidity(validatorName, value, result)
This function updates the text input async validation. This function updates the text input async validation.
*/ */
function updateAsyncValidity(validatorName, value, result) { function updateAsyncValidity(validatorName, value, result) {
@ -435,7 +436,7 @@ Item {
id: charLimitLabelItem id: charLimitLabelItem
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
height: visible ? contentHeight : 0 height: visible ? contentHeight : 0
visible: root.charLimit > 0 visible: root.charLimit > 0 || !!root.charLimitLabel
text: root.charLimitLabel ? root.charLimitLabel : "%1 / %2".arg(Utils.encodeUtf8(statusBaseInput.text).length).arg(root.charLimit) text: root.charLimitLabel ? root.charLimitLabel : "%1 / %2".arg(Utils.encodeUtf8(statusBaseInput.text).length).arg(root.charLimit)
font.pixelSize: 12 font.pixelSize: 12
color: statusBaseInput.enabled ? Theme.palette.baseColor1 : Theme.palette.directColor6 color: statusBaseInput.enabled ? Theme.palette.baseColor1 : Theme.palette.directColor6

View File

@ -29,6 +29,8 @@ StatusButton {
Down Down
} }
property bool isError
horizontalPadding: 16 horizontalPadding: 16
verticalPadding: 3 verticalPadding: 3
spacing: 4 spacing: 4
@ -37,6 +39,8 @@ StatusButton {
background: Rectangle { background: Rectangle {
radius: 8 radius: 8
color: root.bgColor color: root.bgColor
border.color: Theme.palette.dangerColor1
border.width: root.isError ? 1 : 0
} }
opacity: !root.interactive || !root.enabled ? 0.5 : 1 opacity: !root.interactive || !root.enabled ? 0.5 : 1
contentItem: RowLayout { contentItem: RowLayout {
@ -62,16 +66,11 @@ StatusButton {
elide: Text.ElideRight elide: Text.ElideRight
} }
StatusIcon { StatusIcon {
icon: "tiny/chevron-right" icon: "next"
visible: root.type === StatusPickerButton.PickerType.Next visible: root.type === StatusPickerButton.PickerType.Next
color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1 color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1
width: root.icon.width width: root.icon.width
height: root.icon.height height: root.icon.height
} }
} }
HoverHandler {
enabled: root.enabled
cursorShape: Qt.PointingHandCursor
}
} }

View File

@ -8,8 +8,8 @@ StatusValidator {
errorMessage: { errorMessage: {
minLength === 1 ? minLength === 1 ?
"Please enter a value" : qsTr("Please enter a value") :
`The value must be at least ${minLength} characters.` qsTr("The value must be at least %n character(s).", "", minLength)
} }
validate: function (value) { validate: function (value) {
@ -19,4 +19,3 @@ StatusValidator {
} }
} }
} }

View File

@ -1,4 +1,4 @@
import QtQuick 2.14 import QtQuick 2.15
QtObject { QtObject {
property string name: "" property string name: ""

View File

@ -1,6 +1,7 @@
import QtQuick 2.0 import QtQuick 2.15
import QtTest 1.0 import QtTest 1.15
import StatusQ 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1
@ -24,7 +25,7 @@ Item {
property StatusInput testControl: null property StatusInput testControl: null
name: "RegexValidationTest" name: "StatusInput-RegexValidationTest"
when: windowShown when: windowShown
@ -110,7 +111,7 @@ Item {
property StatusInput testControl: null property StatusInput testControl: null
name: "BindedValuesTest" name: "StatusInput-BindedValuesTest"
when: windowShown when: windowShown
property QtObject dataObject: QtObject { property QtObject dataObject: QtObject {
@ -167,6 +168,35 @@ Item {
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`) verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
} }
// regression test for https://github.com/status-im/status-desktop/issues/16479
function test_paste_text() {
verify(!!testControl)
const textToPaste = "1234567890abcdef" // 16 chars
ClipboardUtils.setText(textToPaste)
// clear
testControl.input.edit.clear()
testControl.charLimit = 0
keySequence(StandardKey.Paste)
// verify we can paste the full text
compare(testControl.input.edit.length, textToPaste.length)
compare(testControl.input.edit.text, textToPaste)
compare(testControl.input.dirty, true)
// clear again, and set a lower limit
testControl.input.edit.clear()
testControl.charLimit = 10
keySequence(StandardKey.Paste)
// verify we can paste (some) text and it gets truncated to the charLimit
compare(testControl.input.edit.length, testControl.charLimit)
compare(testControl.input.edit.text, textToPaste.slice(0, testControl.charLimit))
}
} }
MonitorQtOutput { MonitorQtOutput {

View File

@ -27,12 +27,16 @@ Item {
implicitHeight: layout.childrenRect.height implicitHeight: layout.childrenRect.height
function validate() {
editor.isError = !hasSelectedImage
}
ColumnLayout { ColumnLayout {
id: layout id: layout
anchors.fill: parent anchors.fill: parent
spacing: 19 spacing: 16
StatusBaseText { StatusBaseText {
text: qsTr("Community banner") text: qsTr("Community banner")
@ -67,6 +71,17 @@ Item {
additionalTextPixelSize: Theme.tertiaryTextFontSize additionalTextPixelSize: Theme.tertiaryTextFontSize
} }
} }
StatusBaseText {
Layout.fillWidth: true
Layout.topMargin: -layout.spacing/2
visible: editor.isError
text: qsTr("Upload a community banner")
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.dangerColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
} }
} }

View File

@ -19,8 +19,6 @@ ColumnLayout {
spacing: 8 spacing: 8
implicitHeight: childrenRect.height
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
text: root.title text: root.title
@ -35,6 +33,9 @@ ColumnLayout {
bgColor: root.color bgColor: root.color
contentColor: Theme.palette.white contentColor: Theme.palette.white
text: root.color.toString() text: root.color.toString()
font.weight: Font.Normal
icon.width: 24
icon.height: 24
onClicked: root.pick() onClicked: root.pick()
onTextChanged: { onTextChanged: {

View File

@ -1,4 +1,4 @@
import QtQuick 2.14 import QtQuick 2.15
import utils 1.0 import utils 1.0

View File

@ -21,6 +21,7 @@ Control {
readonly property alias isDescriptionDirty: descriptionTextInput.input.dirty readonly property alias isDescriptionDirty: descriptionTextInput.input.dirty
readonly property alias isLogoSelected: logoPicker.hasSelectedImage readonly property alias isLogoSelected: logoPicker.hasSelectedImage
readonly property alias isBannerSelected: bannerPicker.hasSelectedImage readonly property alias isBannerSelected: bannerPicker.hasSelectedImage
readonly property alias areTagsSelected: tagsPicker.hasSelectedTags
property alias name: nameInput.text property alias name: nameInput.text
property alias description: descriptionTextInput.text property alias description: descriptionTextInput.text
@ -38,6 +39,21 @@ Control {
implicitWidth: 608 implicitWidth: 608
function validate(isDevBuild = false) {
if (!nameInput.validate(true))
nameInput.input.dirty = true
if (!descriptionTextInput.validate(true))
descriptionTextInput.input.dirty = true
if (!isDevBuild) {
logoPicker.validate()
bannerPicker.validate()
tagsPicker.validate()
}
return nameInput.valid && descriptionTextInput.valid &&
(isDevBuild ? true : logoPicker.hasSelectedImage && bannerPicker.hasSelectedImage && tagsPicker.hasSelectedTags)
}
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: 16 spacing: 16
@ -45,24 +61,28 @@ Control {
id: nameInput id: nameInput
input.edit.objectName: "communityNameInput" input.edit.objectName: "communityNameInput"
Layout.fillWidth: true Layout.fillWidth: true
Component.onCompleted: nameInput.input.forceActiveFocus(Qt.MouseFocusReason) input.tabNavItem: descriptionTextInput.input.edit
Component.onCompleted: nameInput.input.forceActiveFocus()
} }
DescriptionInput { DescriptionInput {
id: descriptionTextInput id: descriptionTextInput
input.edit.objectName: "communityDescriptionInput" input.edit.objectName: "communityDescriptionInput"
input.tabNavItem: nameInput.input.edit
Layout.fillWidth: true Layout.fillWidth: true
} }
LogoPicker { LogoPicker {
id: logoPicker id: logoPicker
objectName: "communityLogoPicker" objectName: "communityLogoPicker"
onHasSelectedImageChanged: validate()
Layout.fillWidth: true Layout.fillWidth: true
} }
BannerPicker { BannerPicker {
id: bannerPicker id: bannerPicker
objectName: "communityBannerPicker" objectName: "communityBannerPicker"
onHasSelectedImageChanged: validate()
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: -8 //Closer by design Layout.topMargin: -8 //Closer by design
} }
@ -109,12 +129,13 @@ Control {
width: 640 width: 640
replaceItem: TagsPanel { replaceItem: TagsPanel {
Component.onCompleted: { Component.onCompleted: {
tags = tagsPicker.tags; tags = tagsPicker.tags
selectedTags = tagsPicker.selectedTags; selectedTags = tagsPicker.selectedTags
} }
onAccepted: { onAccepted: {
tagsPicker.selectedTags = selectedTags; tagsPicker.selectedTags = selectedTags
close(); tagsPicker.validate()
close()
} }
} }
onClosed: destroy() onClosed: destroy()

View File

@ -1,9 +1,8 @@
import QtQuick 2.14 import QtQuick 2.15
import utils 1.0 import utils 1.0
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1

View File

@ -27,6 +27,10 @@ Item {
implicitHeight: layout.childrenRect.height implicitHeight: layout.childrenRect.height
function validate() {
editor.isError = !hasSelectedImage
}
ColumnLayout { ColumnLayout {
id: layout id: layout
@ -37,7 +41,6 @@ Item {
id: label id: label
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Community logo") text: qsTr("Community logo")
font.pixelSize: 15
color: Theme.palette.directColor1 color: Theme.palette.directColor1
horizontalAlignment: Qt.AlignLeft horizontalAlignment: Qt.AlignLeft
} }
@ -59,6 +62,16 @@ Item {
visible: !editor.userSelectedImage && !root.imageData visible: !editor.userSelectedImage && !root.imageData
} }
} }
StatusBaseText {
Layout.fillWidth: true
visible: editor.isError
text: qsTr("Upload a community logo")
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.dangerColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
} }
} }

View File

@ -1,9 +1,8 @@
import QtQuick 2.14 import QtQuick 2.15
import utils 1.0 import utils 1.0
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1

View File

@ -31,7 +31,6 @@ ColumnLayout {
StatusCheckBox { StatusCheckBox {
id: archiveSupportToggle id: archiveSupportToggle
width: (parent.width-12) width: (parent.width-12)
checked: false
leftSide: false leftSide: false
padding: 0 padding: 0
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -39,11 +38,7 @@ ColumnLayout {
StatusToolTip { StatusToolTip {
text: qsTr('For this Community Setting to work, you also need to activate "Archive Protocol Enabled" in Advanced Settings') text: qsTr('For this Community Setting to work, you also need to activate "Archive Protocol Enabled" in Advanced Settings')
visible: hoverHandler.hovered visible: parent.hovered
}
HoverHandler {
id: hoverHandler
enabled: true
} }
} }
} }
@ -65,7 +60,7 @@ ColumnLayout {
ColumnLayout { ColumnLayout {
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
Layout.topMargin: 22 Layout.topMargin: 22
spacing: 0 spacing: 4
StatusCheckBox { StatusCheckBox {
id: requestToJoinToggle id: requestToJoinToggle
Layout.fillWidth: true Layout.fillWidth: true
@ -78,9 +73,8 @@ ColumnLayout {
} }
StatusBaseText { StatusBaseText {
id: warningText
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 12 Layout.rightMargin: 64
visible: requestToJoinToggle.checked visible: requestToJoinToggle.checked
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: qsTr("Warning: Only token gated communities (or token gated channels inside non-token gated community) are encrypted") text: qsTr("Warning: Only token gated communities (or token gated channels inside non-token gated community) are encrypted")

View File

@ -1,5 +1,5 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import utils 1.0 import utils 1.0
@ -15,14 +15,19 @@ ColumnLayout {
property string tags property string tags
property string selectedTags property string selectedTags
readonly property bool hasSelectedTags: localAppSettings.testEnvironment || selectedTags !== ""
signal pick() signal pick()
implicitHeight: childrenRect.height spacing: 8
spacing: 14
onSelectedTagsChanged: d.handleSelectedTags() onSelectedTagsChanged: d.handleSelectedTags()
Component.onCompleted: d.handleSelectedTags() Component.onCompleted: d.handleSelectedTags()
function validate() {
pickerButton.isError = !hasSelectedTags
}
QtObject { QtObject {
id: d id: d
@ -33,7 +38,7 @@ ColumnLayout {
d.tagsModel.clear(); d.tagsModel.clear();
for (const key of Object.keys(obj)) { for (const key of Object.keys(obj)) {
if (array.indexOf(key) != -1) { if (array.indexOf(key) !== -1) {
d.tagsModel.append({ name: key, emoji: obj[key], selected: false }); d.tagsModel.append({ name: key, emoji: obj[key], selected: false });
} }
} }
@ -42,15 +47,19 @@ ColumnLayout {
StatusBaseText { StatusBaseText {
text: qsTr("Tags") text: qsTr("Tags")
font.pixelSize: 15
color: Theme.palette.directColor1 color: Theme.palette.directColor1
} }
StatusPickerButton { StatusPickerButton {
id: pickerButton
bgColor: d.tagsModel.count === 0 ? Theme.palette.baseColor2 : "transparent" bgColor: d.tagsModel.count === 0 ? Theme.palette.baseColor2 : "transparent"
text: d.tagsModel.count === 0 ? qsTr("Choose tags describing the community") : "" text: d.tagsModel.count === 0 ? qsTr("Choose tags describing the community") : ""
onClicked: root.pick() onClicked: root.pick()
font.weight: Font.Normal
icon.width: 24
icon.height: 24
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 44
StatusCommunityTags { StatusCommunityTags {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -60,4 +69,14 @@ ColumnLayout {
contentWidth: width contentWidth: width
} }
} }
StatusBaseText {
Layout.fillWidth: true
visible: pickerButton.isError
text: qsTr("Add at least 1 tag")
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.dangerColor1
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight
}
} }

View File

@ -1,15 +1,11 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.13 import QtQuick.Controls 2.15
import QtGraphicalEffects 1.13
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 as StatusQControls import StatusQ.Controls 0.1 as StatusQControls
import shared.panels 1.0
import shared.status 1.0
import utils 1.0 import utils 1.0
Rectangle { Rectangle {
@ -61,7 +57,6 @@ Rectangle {
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 48 anchors.topMargin: 48
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: 15
color: Theme.palette.directColor1 color: Theme.palette.directColor1
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
anchors.right: parent.right anchors.right: parent.right

View File

@ -1,6 +1,6 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
@ -69,7 +69,7 @@ StatusScrollView {
// TODO: editingFinished() signal instead of this crutch // TODO: editingFinished() signal instead of this crutch
property bool locked: false property bool locked: false
implicitWidth: 256 Layout.preferredWidth: 256
validators: [ validators: [
StatusRegularExpressionValidator { StatusRegularExpressionValidator {
regularExpression: /^#(?:[0-9a-fA-F]{3}){1,2}$/ regularExpression: /^#(?:[0-9a-fA-F]{3}){1,2}$/
@ -91,22 +91,18 @@ StatusScrollView {
StatusBaseText { StatusBaseText {
text: qsTr("Preview") text: qsTr("Preview")
font.pixelSize: 15
} }
Rectangle { Rectangle {
implicitHeight: 48 Layout.fillWidth: true
Layout.preferredHeight: 44
radius: 10 radius: 10
color: root.color color: root.color
Layout.fillWidth: true
StatusBaseText { StatusBaseText {
id: preview anchors.centerIn: parent
x: 16
y: 16
text: qsTr("White text should be legible on top of this colour") text: qsTr("White text should be legible on top of this colour")
color: Theme.palette.white color: Theme.palette.white
font.pixelSize: 15
} }
} }
@ -114,7 +110,6 @@ StatusScrollView {
id: colorSelectionGrid id: colorSelectionGrid
titleText: qsTr("Standard colours") titleText: qsTr("Standard colours")
title.color: Theme.palette.directColor1 title.color: Theme.palette.directColor1
title.font.pixelSize: 15
columns: 8 columns: 8
model: Theme.palette.communityColorsArray model: Theme.palette.communityColorsArray
selectedColorIndex: -1 selectedColorIndex: -1

View File

@ -173,7 +173,9 @@ StackLayout {
Connections { Connections {
target: root target: root
onCommunityIdChanged: reset() function onCommunityIdChanged() {
reset()
}
} }
} }

View File

@ -1,6 +1,6 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
@ -23,6 +23,7 @@ StatusScrollView {
property var rightButtons: StatusButton { property var rightButtons: StatusButton {
objectName: "confirmCommunityTagsButton" objectName: "confirmCommunityTagsButton"
text: qsTr("Confirm Community Tags") text: qsTr("Confirm Community Tags")
enabled: d.countSelectedTags > 0
onClicked: { onClicked: {
var selectedTags = []; var selectedTags = [];
for (let i = 0; i < d.tagsModel.count; ++i) { for (let i = 0; i < d.tagsModel.count; ++i) {
@ -39,12 +40,12 @@ StatusScrollView {
function updateSelectedTags() { function updateSelectedTags() {
var array = selectedTags.length ? JSON.parse(selectedTags) : []; var array = selectedTags.length ? JSON.parse(selectedTags) : [];
d.cntSelectedTags = 0; d.countSelectedTags = 0;
for (let i = 0; i < d.tagsModel.count; ++i) { for (let i = 0; i < d.tagsModel.count; ++i) {
let item = d.tagsModel.get(i); let item = d.tagsModel.get(i);
if (array.indexOf(item.name) != -1) { if (array.indexOf(item.name) !== -1) {
item.selected = true; item.selected = true;
d.cntSelectedTags++; d.countSelectedTags++;
} else { } else {
item.selected = false; item.selected = false;
} }
@ -55,7 +56,7 @@ StatusScrollView {
onTagsChanged: { onTagsChanged: {
var obj = JSON.parse(tags); var obj = JSON.parse(tags);
d.cntSelectedTags = 0; d.countSelectedTags = 0;
d.tagsModel.clear(); d.tagsModel.clear();
for (const key of Object.keys(obj)) { for (const key of Object.keys(obj)) {
d.tagsModel.append({ name: key, emoji: obj[key], selected: false }); d.tagsModel.append({ name: key, emoji: obj[key], selected: false });
@ -70,7 +71,7 @@ StatusScrollView {
QtObject { QtObject {
id: d id: d
property int cntSelectedTags: 0 property int countSelectedTags: 0
property ListModel tagsModel: ListModel {} property ListModel tagsModel: ListModel {}
} }
@ -83,7 +84,6 @@ StatusScrollView {
id: tagsFilter id: tagsFilter
label: qsTr("Select tags that will fit your Community") label: qsTr("Select tags that will fit your Community")
labelPadding: Style.current.bigPadding labelPadding: Style.current.bigPadding
font.pixelSize: 15
input.asset.name: "search" input.asset.name: "search"
placeholderText: qsTr("Search tags") placeholderText: qsTr("Search tags")
Layout.fillWidth: true Layout.fillWidth: true
@ -96,9 +96,9 @@ StatusScrollView {
StatusCommunityTags { StatusCommunityTags {
filterString: tagsFilter.text filterString: tagsFilter.text
model: d.tagsModel model: d.tagsModel
enabled: d.cntSelectedTags < maxSelectedTags enabled: d.countSelectedTags < maxSelectedTags
onClicked: { onClicked: {
d.cntSelectedTags++; d.countSelectedTags++;
item.selected = true; item.selected = true;
} }
Layout.fillWidth: true Layout.fillWidth: true
@ -112,12 +112,11 @@ StatusScrollView {
RowLayout { RowLayout {
StatusBaseText { StatusBaseText {
text: qsTr("Selected tags") text: qsTr("Selected tags")
font.pixelSize: 15
Layout.fillWidth: true Layout.fillWidth: true
} }
StatusBaseText { StatusBaseText {
text: qsTr("%1 / %2").arg(d.cntSelectedTags).arg(maxSelectedTags) text: qsTr("%1 / %2").arg(d.countSelectedTags).arg(maxSelectedTags)
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
font.pixelSize: 13 font.pixelSize: 13
} }
@ -127,10 +126,21 @@ StatusScrollView {
model: d.tagsModel model: d.tagsModel
mode: StatusCommunityTags.ShowSelectedOnly mode: StatusCommunityTags.ShowSelectedOnly
onClicked: { onClicked: {
d.cntSelectedTags--; d.countSelectedTags--;
item.selected = false; item.selected = false;
} }
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: Style.current.padding
}
StatusBaseText {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.padding
text: qsTr("No tags selected yet")
color: Theme.palette.baseColor1
visible: d.countSelectedTags === 0
font.pixelSize: 13
horizontalAlignment: Qt.AlignHCenter
} }
} }
} }

View File

@ -1,6 +1,6 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3 import QtQuick.Dialogs 1.3
import utils 1.0 import utils 1.0
@ -30,6 +30,8 @@ StatusStackModal {
qsTr("Create New Community") qsTr("Create New Community")
width: 640 width: 640
closePolicy: Popup.NoAutoClose // explicit [x] click needed, or via the `close()` method
nextButton: StatusButton { nextButton: StatusButton {
objectName: "createCommunityNextBtn" objectName: "createCommunityNextBtn"
font.weight: Font.Medium font.weight: Font.Medium
@ -55,8 +57,6 @@ StatusStackModal {
if (typeof (nextAction) == "function") { if (typeof (nextAction) == "function") {
return nextAction() return nextAction()
} }
if (!root.isDiscordImport)
d.createCommunity()
} }
} }
@ -363,7 +363,12 @@ StatusStackModal {
StatusScrollView { StatusScrollView {
id: generalView id: generalView
contentWidth: availableWidth contentWidth: availableWidth
readonly property bool canGoNext: generalViewLayout.isNameValid && generalViewLayout.isDescriptionValid && (root.isDevBuild || (generalViewLayout.isLogoSelected && generalViewLayout.isBannerSelected))
readonly property var nextAction: () => {
if (generalViewLayout.validate(root.isDevBuild)) {
root.currentIndex++
}
}
padding: 0 padding: 0
clip: false clip: false
@ -389,8 +394,20 @@ StatusStackModal {
ColumnLayout { ColumnLayout {
id: introOutroMessageView id: introOutroMessageView
spacing: 11 spacing: 16
readonly property bool canGoNext: introMessageInput.valid && outroMessageInput.valid
readonly property var nextAction: () => {
if (!introMessageInput.validate(true))
introMessageInput.input.dirty = true
if (!outroMessageInput.validate(true))
outroMessageInput.input.dirty = true
if (introMessageInput.valid && outroMessageInput.valid) {
if (root.isDiscordImport)
root.currentIndex++
else
d.createCommunity()
}
}
IntroMessageInput { IntroMessageInput {
id: introMessageInput id: introMessageInput

View File

@ -35,6 +35,7 @@ Item {
property bool userSelectedImage: false property bool userSelectedImage: false
readonly property bool nothingToShow: state === d.noImageState readonly property bool nothingToShow: state === d.noImageState
property bool isError
readonly property alias cropWorkflow : imageCropWorkflow readonly property alias cropWorkflow : imageCropWorkflow
@ -119,6 +120,9 @@ Item {
icon.name: "edit_pencil" icon.name: "edit_pencil"
width: 40
height: 40
readonly property real rotationRadius: root.roundedImage ? parent.width/2 : imageCropEditor.radius readonly property real rotationRadius: root.roundedImage ? parent.width/2 : imageCropEditor.radius
transform: [ transform: [
Translate { Translate {
@ -154,10 +158,16 @@ Item {
radius: roundedImage ? Math.max(width, height)/2 : croppedPreview.radius radius: roundedImage ? Math.max(width, height)/2 : croppedPreview.radius
color: Style.current.inputBackground color: Style.current.inputBackground
border.color: Theme.palette.dangerColor1
border.width: root.isError ? 1 : 0
StatusRoundButton { StatusRoundButton {
id: addButton id: addButton
icon.name: "add" icon.name: "add"
width: 40
height: 40
readonly property real rotationRadius: root.roundedImage ? parent.width/2 : imageCropEditor.radius readonly property real rotationRadius: root.roundedImage ? parent.width/2 : imageCropEditor.radius
transform: [ transform: [

View File

@ -10,7 +10,7 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
/*! /*!
/brief Image icon and ulopad text hints for banner and logo /brief Image icon and upload text hints for banner and logo
*/ */
Control { Control {
id: root id: root