chore(CommunityPermissions): Refactor channels handling

Community permissions model refers to channels by id instead of
taking/serving all details. UI fetches necessary metadata form
appropriate channels model.

Closes: #9588
This commit is contained in:
Michał Cieślak 2023-02-28 13:44:46 +01:00 committed by Michał
parent 2e45889a9a
commit 480c249ee0
14 changed files with 250 additions and 269 deletions

View File

@ -2,16 +2,11 @@ import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import Storybook 1.0
import Models 1.0
import AppLayouts.Chat.controls.community 1.0
ColumnLayout {
id: root
property string panelText
property int type
property string key
property string amountText
@ -31,10 +26,17 @@ ColumnLayout {
]
}
Label {
Layout.fillWidth: true
text: root.panelText
font.weight: Font.Bold
onTypeChanged: {
Qt.callLater(() => {
if (d.ensLayout) {
root.key = "*.eth"
return
}
const idx = holdingComboBox.find(root.key)
holdingComboBox.currentIndex = idx === -1 ? 0 : idx
root.key = holdingComboBox.currentText
})
}
ColumnLayout {
@ -53,6 +55,7 @@ ColumnLayout {
valueRole: "value"
onActivated: root.type = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.type)
}
}
@ -65,6 +68,7 @@ ColumnLayout {
}
ComboBox {
id: holdingComboBox
Layout.fillWidth: true
visible: !d.ensLayout
@ -100,8 +104,4 @@ ColumnLayout {
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
}

View File

@ -1,115 +0,0 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import Storybook 1.0
import Models 1.0
ColumnLayout {
id: root
property string panelText
property string name
property string icon
property string amountText
property string emoji
property bool isAmountVisible: false
property bool isImageSelectorVisible: true
property bool isEmojiSelectorVisible: false
property var iconsModel
Label {
Layout.fillWidth: true
text: root.panelText
font.weight: Font.Bold
}
RowLayout {
Rectangle {
id: image
visible: root.isImageSelectorVisible
border.color: 'lightgrey'
radius: 16
Layout.preferredHeight: 50
Layout.preferredWidth: 50
Image {
anchors.fill: parent
anchors.margins: 1
fillMode: Image.PreserveAspectFit
source: root.icon
}
MouseArea {
anchors.fill: parent
onClicked: iconSelector.open()
ImageSelectPopup {
id: iconSelector
parent: root
anchors.centerIn: parent
width: 200
height: 250
model: root.iconsModel
onSelected: {
root.icon = icon
close()
}
}
}
}
ColumnLayout {
Label {
Layout.fillWidth: true
text: "Name"
}
TextField {
background: Rectangle {
radius: 16
border.color: 'lightgrey'
}
Layout.fillWidth: true
text: root.name
onTextChanged: root.name = text
}
}
ColumnLayout {
visible: root.isAmountVisible
Label {
Layout.fillWidth: true
text: "Amount"
}
TextField {
background: Rectangle {
radius: 16
border.color: 'lightgrey'
}
Layout.fillWidth: true
text: root.amountText
onTextChanged: root.amountText = text
}
}
ColumnLayout {
visible: root.isEmojiSelectorVisible
Label {
Layout.fillWidth: true
text: "Emoji"
}
TextField {
background: Rectangle {
radius: 16
border.color: 'lightgrey'
}
Layout.fillWidth: true
text: root.emoji
onTextChanged: root.emoji = text
}
}
}
}

View File

@ -2,8 +2,6 @@ import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import Storybook 1.0
import Models 1.0
import StatusQ.Core.Utils 0.1
import AppLayouts.Chat.controls.community 1.0
@ -15,6 +13,7 @@ Flickable {
property var assetKeys: []
property var collectibleKeys: []
property var channelKeys: []
QtObject {
id: d
@ -33,38 +32,33 @@ Flickable {
ColumnLayout {
id: globalContent
spacing: 10
spacing: 20
anchors.fill: parent
Repeater {
model: root.model
Rectangle {
radius: 16
color: "whitesmoke"
GroupBox {
title: `Permission ${model.index}`
Layout.preferredHeight: content.implicitHeight + 50
Layout.fillWidth: true
ColumnLayout {
id: content
spacing: 25
spacing: 20
anchors.fill: parent
anchors.margins: 20
Label {
Layout.fillWidth: true
text: "Permission " + (model.index)
font.weight: Font.Bold
font.pixelSize: 17
}
Repeater {
model: holdingsListModel
ColumnLayout {
Repeater {
model: holdingsListModel
GroupBox {
Layout.fillWidth: true
title: `Who holds [item ${model.index}]`
CommunityPermissionsHoldingItemEditor {
Layout.fillWidth: true
panelText: "Who holds [item " + model.index + "]"
anchors.fill: parent
type: model.type
key: model.key
amountText: model.amount
@ -79,73 +73,88 @@ Flickable {
}
}
CommunityPermissionsHoldingItemEditor {
panelText: "New holding item"
type: d.newType
key: d.newKey
amountText: d.newAmount
assetKeys: root.assetKeys
collectibleKeys: root.collectibleKeys
onTypeChanged: d.newType = type
onKeyChanged: d.newKey = key
onAmountTextChanged: d.newAmount = parseFloat(amountText)
}
Button {
enabled: d.newKey && (d.newAmount || d.newType === HoldingTypes.Type.Ens)
GroupBox {
Layout.fillWidth: true
text: "Add new holding"
title: "New holding item"
onClicked: {
model.holdingsListModel.append({
type: d.newType,
key: d.newKey,
amount: d.newAmount
})
ColumnLayout {
anchors.fill: parent
CommunityPermissionsHoldingItemEditor {
Layout.fillWidth: true
type: d.newType
key: d.newKey
amountText: d.newAmount
assetKeys: root.assetKeys
collectibleKeys: root.collectibleKeys
onTypeChanged: d.newType = type
onKeyChanged: d.newKey = key
onAmountTextChanged: d.newAmount = parseFloat(amountText)
}
MenuSeparator {
Layout.fillWidth: true
}
Button {
enabled: d.newKey && (d.newAmount || d.newType === HoldingTypes.Type.Ens)
Layout.fillWidth: true
text: "Add new holding"
onClicked: {
model.holdingsListModel.append({
type: d.newType,
key: d.newKey,
amount: d.newAmount
})
}
}
}
}
ColumnLayout {
MenuSeparator {
Layout.fillWidth: true
}
Label {
text: "Channels"
}
Flow {
Layout.fillWidth: true
Repeater {
model: channelsListModel
CommunityPermissionsSettingItemEditor {
isEmojiSelectorVisible: true
id: channelsRepeater
panelText: "In [item " + model.index + "]"
name: model.text
icon: model.iconSource ? model.iconSource : ""
emoji: model.emoji ? model.emoji : ""
iconsModel: AssetsCollectiblesIconsModel {}
onNameChanged: model.name = name
onIconChanged: model.iconSource = icon
onEmojiChanged: model.emoji = emoji
}
}
}
model: root.channelKeys
CheckBox {
text: modelData
CommunityPermissionsSettingItemEditor {
Layout.fillWidth: true
panelText: "New In item"
name: d.newChannelName
icon: d.newChannelIconSource
iconsModel: AssetsCollectiblesIconsModel {}
onNameChanged: d.newChannelName = name
onIconChanged: d.newChannelIconSource = icon
}
checked: ModelUtils.contains(
channelsListModel, "itemId", modelData)
Button {
enabled: d.newChannelIconSource && d.newChannelName
Layout.fillWidth: true
text: "Add new channel"
onClicked: {
model.channelsListModel.append([{
key: d.newChannelName,
name: d.newChannelName,
iconSource: d.newChannelIconSource
}])
onToggled: {
const channels = []
const count = channelsRepeater.count
for (let i = 0; i < count; i++) {
const checked = channelsRepeater.itemAt(i).checked
if (checked) {
const itemId = root.channelKeys[i]
channels.push({ itemId })
}
}
channelsListModel.clear()
channelsListModel.append(channels)
}
}
}
}

View File

@ -46,7 +46,9 @@ SplitView {
rootStore: QtObject {
readonly property QtObject chatCommunitySectionModule: QtObject {
readonly property var model: ChannelsModel {}
readonly property var model: ChannelsModel {
id: channelsModel
}
}
readonly property QtObject mainModuleInst: QtObject {
@ -87,6 +89,13 @@ SplitView {
assetKeys: assetsModel.data.map(asset => asset.key)
collectibleKeys: collectiblesModel.data.map(collectible => collectible.key)
channelKeys: {
const array = ModelUtils.modelToArray(channelsModel,
["itemId", "isCategory"])
const channelsOnly = array.filter(channel => !channel.isCategory)
return channelsOnly.map(channel => channel.itemId)
}
}
}
}

View File

@ -1,85 +1,94 @@
import QtQuick 2.14
import QtQuick 2.15
ListModel {
ListElement {
itemId: "1"
itemId: "_welcome"
isCategory: false
categoryId: ""
name: "welcome"
emoji: ""
color: ""
icon: ""
colorId: 1
}
ListElement {
itemId: "2"
itemId: "_announcements"
isCategory: false
categoryId: ""
name: "announcements"
emoji: ""
color: ""
icon: ""
colorId: 1
}
ListElement {
itemId: "0"
itemId: ""
isCategory: true
categoryId: "1"
categoryId: "_discussion"
name: "discussion"
emoji: ""
color: ""
icon: ""
colorId: 1
}
ListElement {
itemId: "3"
itemId: "_general"
isCategory: false
categoryId: "1"
categoryId: "_discussion"
name: "general"
emoji: "👋"
color: ""
icon: ""
colorId: 1
}
ListElement {
itemId: "4"
itemId: "_help"
isCategory: false
categoryId: "1"
categoryId: "_discussion"
name: "help"
emoji: "⚽"
color: ""
icon: ""
colorId: 1
}
ListElement {
itemId: "0"
itemId: ""
isCategory: true
categoryId: "2"
categoryId: "_support"
name: "support"
emoji: ""
color: ""
icon: ""
colorId: 1
}
ListElement {
itemId: "5"
itemId: "_faq"
isCategory: false
categoryId: "2"
categoryId: "_support"
name: "faq"
emoji: ""
color: ""
icon: ""
colorId: 5
}
ListElement {
itemId: "6"
itemId: "_report-scam"
isCategory: false
categoryId: "2"
categoryId: "_support"
name: "report-scam"
emoji: ""
color: ""
icon: ""
colorId: 4
}
ListElement {
itemId: "0"
itemId: ""
isCategory: true
categoryId: "3"
categoryId: "_faq"
name: "faq"
emoji: ""
color: ""
icon: ""
colorId: 5
}
}

View File

@ -188,27 +188,15 @@ QtObject {
function createChannelsModel1() {
return [
{
key: "general",
text: "#general",
color: "lightgreen",
emoji: "👋"
itemId: "_welcome"
},
{
key: "faq",
text: "#faq",
color: "lightblue",
emoji: "⚽"
itemId: "_general"
}
]
}
function createChannelsModel2() {
return [
{
key: "socks",
iconSource: ModelsData.icons.socks,
text: "Socks"
}
]
return []
}
}

View File

@ -12,6 +12,15 @@ QtObject {
return Internal.ModelUtils.get(model, index)
}
function getByKey(model, keyRole, keyValue, role = "") {
const idx = indexOf(model, keyRole, keyValue)
if (idx === -1)
return null
return get(model, idx, role)
}
function modelToArray(model, roles) {
if (!model)
return []
@ -46,6 +55,10 @@ QtObject {
return -1
}
function contains(model, role, key) {
return indexOf(model, role, key) !== -1
}
function checkItemsEquality(itemA, itemB, roles) {
return roles.every((role) => itemA[role] === itemB[role])
}

View File

@ -201,7 +201,7 @@ SettingsPageLayout {
const channels = ModelUtils.modelToArray(
dirtyValues.channelsModel,
["itemId", "text", "emoji", "color"])
["itemId"])
root.store.createPermission(holdings,
dirtyValues.permissionType,
@ -221,7 +221,7 @@ SettingsPageLayout {
const channels = ModelUtils.modelToArray(
dirtyValues.channelsModel,
["itemId", "text", "emoji", "color"])
["itemId"])
root.store.editPermission(
d.permissionKeyToEdit,

View File

@ -18,7 +18,6 @@ QtObject {
property string holdings: qsTr("1 ETH")
property string permissions: qsTr("View and Post")
property string channels: qsTr("#general")
}
property var assetsModel: chatCommunitySectionModule.tokenList

View File

@ -0,0 +1,73 @@
import QtQml 2.15
import SortFilterProxyModel 0.2
import StatusQ.Core.Utils 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
SortFilterProxyModel {
property var channelsModel
readonly property QtObject _d: QtObject {
id: d
readonly property ModelChangeTracker tracker: ModelChangeTracker {
model: channelsModel
onRevisionChanged: {
const metadata = new Map()
const count = channelsModel.rowCount()
for (let i = 0; i < count; i++) {
const item = ModelUtils.get(channelsModel, i)
const text = "#" + item.name
const imageSource = item.icon
const emoji = item.emoji
const color = !!item.color ? item.color
: Theme.palette.userCustomizationColors[item.colorId]
metadata.set(item.itemId, { text, imageSource, emoji, color })
}
d.metadata = metadata
}
onModelChanged: revisionChanged()
}
property var metadata: new Map()
function get(itemId, key) {
const item = metadata.get(itemId)
return !!item ? item[key] : ""
}
}
proxyRoles: [
ExpressionRole {
name: "text"
expression: d.get(model.itemId, name)
},
ExpressionRole {
name: "imageSource"
expression: d.get(model.itemId, name)
},
ExpressionRole {
name: "emoji"
expression: d.get(model.itemId, name)
},
ExpressionRole {
name: "color"
expression: d.get(model.itemId, name)
},
ExpressionRole {
name: "operator"
// Direct call for singleton enum is not handled properly by SortFilterProxyModel.
readonly property int none: OperatorsUtils.Operators.None
expression: none
}
]
}

View File

@ -45,7 +45,7 @@ StatusScrollView {
// roles: type, key, name, amount, imageSource
property var holdingsModel: ListModel {}
// roles: itemId, text, emoji, color
// roles: itemId, text, icon, emoji, color, colorId
property var channelsModel: ListModel {}
property alias duplicationWarningVisible: duplicationPanel.visible
@ -72,7 +72,7 @@ StatusScrollView {
modelA: root.dirtyValues.channelsModel
modelB: root.channelsModel
roles: ["itemId", "text", "emoji", "color"]
roles: ["itemId"]
mode: ModelsComparator.CompareMode.Set
}
@ -95,7 +95,7 @@ StatusScrollView {
} else {
inSelector.itemsModel = 0
inSelector.wholeCommunitySelected = false
inSelector.itemsModel = d.dirtyValues.channelsModel
inSelector.itemsModel = channelsSelectionModel
}
}
@ -141,12 +141,11 @@ StatusScrollView {
d.dirtyValues.channelsModel.clear()
d.dirtyValues.channelsModel.append(
ModelUtils.modelToArray(root.channelsModel,
["itemId", "text", "emoji", "color", "operator"]))
ModelUtils.modelToArray(root.channelsModel, ["itemId"]))
if (root.channelsModel && (root.channelsModel.count || d.dirtyValues.permissionType === PermissionTypes.Type.None)) {
inSelector.wholeCommunitySelected = false
inSelector.itemsModel = d.dirtyValues.channelsModel
inSelector.itemsModel = channelsSelectionModel
} else {
inSelector.wholeCommunitySelected = true
inSelector.itemsModel = inModelCommunity
@ -439,6 +438,14 @@ StatusScrollView {
}
}
ChannelsSelectionModel {
id: channelsSelectionModel
sourceModel: d.dirtyValues.channelsModel
channelsModel: root.rootStore.chatCommunitySectionModule.model
}
InDropdown {
id: inDropdown
@ -457,15 +464,11 @@ StatusScrollView {
channels.forEach(channel => {
d.dirtyValues.channelsModel.append({
itemId: channel.itemId,
text: "#" + channel.name,
emoji: channel.emoji,
color: channel.color,
operator: OperatorsUtils.Operators.None
itemId: channel.itemId
})
})
inSelector.itemsModel = d.dirtyValues.channelsModel
inSelector.itemsModel = channelsSelectionModel
close()
}

View File

@ -66,21 +66,15 @@ StatusScrollView {
permissionType: model.permissionType
SortFilterProxyModel {
id: proxiedChannelsModel
ChannelsSelectionModel {
id: channelsSelectionModel
sourceModel: model.channelsListModel
proxyRoles: [
ExpressionRole {
name: "imageSource"
expression: model.iconSource
}
]
channelsModel: rootStore.chatCommunitySectionModule.model
}
channelsListModel: proxiedChannelsModel.count
? proxiedChannelsModel : communityItemModel
channelsListModel: channelsSelectionModel.count
? channelsSelectionModel : communityItemModel
isPrivate: model.isPrivate
onEditClicked: root.editPermissionRequested(model.index)

View File

@ -12,7 +12,6 @@ SortFilterProxyModel {
property var assetsModel
property var collectiblesModel
readonly property ModelChangeTracker _assetsChanges: ModelChangeTracker {
model: assetsModel
}
@ -49,7 +48,7 @@ SortFilterProxyModel {
expression: {
_assetsChanges.revision
_collectiblesChanges.revision
getText(model.type, model.key, model.amount)
return getText(model.type, model.key, model.amount)
}
},
ExpressionRole {
@ -68,7 +67,7 @@ SortFilterProxyModel {
expression: {
_assetsChanges.revision
_collectiblesChanges.revision
getIcon(model.type, model.key)
return getIcon(model.type, model.key)
}
},
ExpressionRole {

View File

@ -1,7 +1,7 @@
ChannelsSelectionModel 1.0 ChannelsSelectionModel.qml
CommunityNewCollectibleView 1.0 CommunityNewCollectibleView.qml
CommunityNewPermissionView 1.0 CommunityNewPermissionView.qml
CommunityPermissionsView 1.0 CommunityPermissionsView.qml
CommunityWelcomePermissionsView 1.0 CommunityWelcomePermissionsView.qml
CommunityWelcomeSettingsView 1.0 CommunityWelcomeSettingsView.qml
HoldingsSelectionModel 1.0 HoldingsSelectionModel.qml
JoinCommunityView 1.0 JoinCommunityView.qml
CommunityWelcomeSettingsView 1.0 CommunityWelcomeSettingsView.qml
CommunityNewCollectibleView 1.0 CommunityNewCollectibleView.qml