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

339 lines
9.3 KiB
QML

import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Popups 0.1
import AppLayouts.Communities.controls 1.0
import shared.controls 1.0
import SortFilterProxyModel 0.2
StatusDropdown {
id: root
width: 289
padding: 8
// force keeping within the bounds of the enclosing window
margins: 0
property bool allowChoosingEntireCommunity: false
property bool showAddChannelButton: false
property string communityName
property string communityImage
property string communityColor
property var model
property int acceptMode: InDropdown.AcceptMode.Add
enum AcceptMode {
Add, Update
}
signal addChannelClicked
signal communitySelected
signal channelsSelected(var channels)
function setSelectedChannels(channels) {
d.selectedChannels.clear()
channels.forEach(c => d.selectedChannels.add(c))
d.selectedChannelsChanged()
}
onAboutToHide: {
searcher.text = ""
listView.positionViewAtBeginning()
}
onAboutToShow: listView.Layout.preferredHeight = Math.min(
listView.implicitHeight, 420)
// only channels (no entries representing categories), sorted according to
// category position and position
SortFilterProxyModel {
id: onlyChannelsModel
sourceModel: root.model
filters: ValueFilter {
roleName: "isCategory"
value: false
}
sorters: [
RoleSorter {
roleName: "categoryPosition"
priority: 2 // Higher number -> higher priority
},
RoleSorter {
roleName: "position"
priority: 1
}
]
}
// only items representing categories
SortFilterProxyModel {
id: categoriesModel
sourceModel: root.model
filters: ValueFilter {
roleName: "isCategory"
value: true
}
}
// categories, name role renamed to categoryName
RolesRenamingModel {
id: categoriesModelRenamed
sourceModel: categoriesModel
mapping: RoleRename {
from: "name"
to: "categoryName"
}
}
// categories joined to channels model in order to provide channelName,
// in order to be used in section.property.
LeftJoinModel {
id: joined
leftModel: onlyChannelsModel
rightModel: categoriesModelRenamed
joinRole: "categoryId"
rolesToJoin: "categoryName"
}
// final filtering based on user's input in search bar
SortFilterProxyModel {
id: filtered
sourceModel: joined
filters: RegExpFilter {
roleName: "name"
pattern: `*${searcher.text}*`
caseSensitivity : Qt.CaseInsensitive
syntax: RegExpFilter.Wildcard
}
}
QtObject {
id: d
readonly property int defaultVMargin: 9
readonly property int maxHeightCountNo: 5
readonly property int itemStandardHeight: 44
readonly property var selectedChannels: new Set()
function resolveEmoji(emoji) {
return !!emoji ? emoji : ""
}
function resolveColor(color, colorId) {
return !!color ? color : Theme.palette.userCustomizationColors[colorId]
}
}
contentItem: ColumnLayout {
spacing: 0
SearchBox {
id: searcher
Layout.fillWidth: true
topPadding: 0
bottomPadding: 0
minimumHeight: 36
maximumHeight: 36
}
StatusListItem {
Layout.fillWidth: true
Layout.topMargin: d.defaultVMargin
Layout.preferredHeight: d.itemStandardHeight
visible: root.allowChoosingEntireCommunity
title: root.communityName
subTitle: qsTr("Community")
asset.name: root.communityImage
asset.color: root.communityColor
asset.isImage: true
asset.width: 32
asset.height: 32
asset.isLetterIdenticon: !asset.name
asset.charactersLen: 2
asset.letterSize: 15
leftPadding: 8
rightPadding: 6
statusListItemTitleArea.anchors.leftMargin: 8
statusListItemTitle.font.pixelSize: 13
statusListItemTitle.font.weight: Font.Medium
statusListItemSubTitle.font.pixelSize: 12
components: [
StatusRadioButton {
id: radioButton
size: StatusRadioButton.Size.Small
rightPadding: 0
}
]
// using MouseArea instead of build-in 'clicked' signal to avoid
// intercepting event by the StatusRadioButton
MouseArea {
anchors.fill: parent
onClicked: {
radioButton.toggle()
radioButton.toggled()
}
cursorShape: Qt.PointingHandCursor
}
}
StatusMenuSeparator {
Layout.fillWidth: true
Layout.topMargin: 4 - implicitHeight / 2
Layout.bottomMargin: 4 - implicitHeight / 2
visible: root.allowChoosingEntireCommunity
}
StatusListView {
id: listView
model: filtered
Layout.fillWidth: true
Layout.minimumHeight: Math.min(d.maxHeightCountNo * d.itemStandardHeight,
contentHeight)
Layout.maximumHeight: Layout.minimumHeight
Layout.bottomMargin: d.defaultVMargin
Layout.topMargin: !root.allowChoosingEntireCommunity
&& !root.allowChoosingEntireCommunity ? d.defaultVMargin : 0
Component {
id: addChannelButtonComponent
StatusIconTextButton {
height: 36
leftPadding: 8
spacing: 8
statusIcon: "add"
icon.width: 16
icon.height: 16
iconRotation: 0
text: qsTr("Add channel")
onClicked: root.addChannelClicked()
}
}
header: root.showAddChannelButton ? addChannelButtonComponent : null
section.delegate: CategoryListItem {
title: section
width: ListView.view.width
MouseArea {
anchors.fill: parent
onClicked: {
const categoryId = ModelUtils.getByKey(
categoriesModel, "name", section, "categoryId")
const allKeys = ModelUtils.modelToArray(
filtered, ["itemId", "categoryId"])
const inCategoryKeys = allKeys.filter(
e => e.categoryId === categoryId)
const allSelected = inCategoryKeys.every(
e => d.selectedChannels.has(e.itemId))
if (allSelected)
inCategoryKeys.forEach(
e => d.selectedChannels.delete(e.itemId))
else
inCategoryKeys.forEach(
e => d.selectedChannels.add(e.itemId))
d.selectedChannelsChanged()
}
}
}
section.property: "categoryName"
delegate: CommunityListItem {
id: communitySubItem
width: ListView.view.width
visible: show
title: "#" + model.name
asset.name: model.icon ?? ""
asset.emoji: d.resolveEmoji(model.emoji)
asset.color: d.resolveColor(model.color, model.colorId)
checked: d.selectedChannels.has(model.itemId)
checkBox.onToggled: {
if (checked)
d.selectedChannels.add(model.itemId)
else
d.selectedChannels.delete(model.itemId)
d.selectedChannelsChanged()
}
}
}
StatusButton {
Layout.fillWidth: true
enabled: root.acceptMode === InDropdown.AcceptMode.Update
|| d.selectedChannels.size > 0
text: {
if (root.acceptMode === InDropdown.AcceptMode.Update)
return qsTr("Update")
if (radioButton.checked)
return qsTr("Add community")
if (d.selectedChannels.size === 0)
return qsTr("Add")
return qsTr("Add %n channel(s)", "", d.selectedChannels.size)
}
onClicked: {
if (radioButton.checked) {
root.communitySelected()
return
}
root.channelsSelected(Array.from(d.selectedChannels.values()))
}
}
}
}