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.Controls 2.14
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.14
import Storybook 1.0
import Models 1.0
import AppLayouts.Chat.controls.community 1.0 import AppLayouts.Chat.controls.community 1.0
ColumnLayout { ColumnLayout {
id: root id: root
property string panelText
property int type property int type
property string key property string key
property string amountText property string amountText
@ -31,10 +26,17 @@ ColumnLayout {
] ]
} }
Label { onTypeChanged: {
Layout.fillWidth: true Qt.callLater(() => {
text: root.panelText if (d.ensLayout) {
font.weight: Font.Bold root.key = "*.eth"
return
}
const idx = holdingComboBox.find(root.key)
holdingComboBox.currentIndex = idx === -1 ? 0 : idx
root.key = holdingComboBox.currentText
})
} }
ColumnLayout { ColumnLayout {
@ -53,6 +55,7 @@ ColumnLayout {
valueRole: "value" valueRole: "value"
onActivated: root.type = currentValue onActivated: root.type = currentValue
Component.onCompleted: currentIndex = indexOfValue(root.type) Component.onCompleted: currentIndex = indexOfValue(root.type)
} }
} }
@ -65,6 +68,7 @@ ColumnLayout {
} }
ComboBox { ComboBox {
id: holdingComboBox
Layout.fillWidth: true Layout.fillWidth: true
visible: !d.ensLayout 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.Controls 2.14
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.14
import Storybook 1.0
import Models 1.0
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
import AppLayouts.Chat.controls.community 1.0 import AppLayouts.Chat.controls.community 1.0
@ -15,6 +13,7 @@ Flickable {
property var assetKeys: [] property var assetKeys: []
property var collectibleKeys: [] property var collectibleKeys: []
property var channelKeys: []
QtObject { QtObject {
id: d id: d
@ -33,38 +32,33 @@ Flickable {
ColumnLayout { ColumnLayout {
id: globalContent id: globalContent
spacing: 10 spacing: 20
anchors.fill: parent anchors.fill: parent
Repeater { Repeater {
model: root.model model: root.model
Rectangle { GroupBox {
radius: 16 title: `Permission ${model.index}`
color: "whitesmoke"
Layout.preferredHeight: content.implicitHeight + 50 Layout.preferredHeight: content.implicitHeight + 50
Layout.fillWidth: true Layout.fillWidth: true
ColumnLayout { ColumnLayout {
id: content id: content
spacing: 25 spacing: 20
anchors.fill: parent anchors.fill: parent
anchors.margins: 20
Label { Repeater {
Layout.fillWidth: true model: holdingsListModel
text: "Permission " + (model.index)
font.weight: Font.Bold
font.pixelSize: 17
}
ColumnLayout { GroupBox {
Repeater { Layout.fillWidth: true
model: holdingsListModel title: `Who holds [item ${model.index}]`
CommunityPermissionsHoldingItemEditor { CommunityPermissionsHoldingItemEditor {
Layout.fillWidth: true anchors.fill: parent
panelText: "Who holds [item " + model.index + "]"
type: model.type type: model.type
key: model.key key: model.key
amountText: model.amount amountText: model.amount
@ -79,73 +73,88 @@ Flickable {
} }
} }
CommunityPermissionsHoldingItemEditor { GroupBox {
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)
Layout.fillWidth: true Layout.fillWidth: true
text: "Add new holding" title: "New holding item"
onClicked: { ColumnLayout {
model.holdingsListModel.append({ anchors.fill: parent
type: d.newType,
key: d.newKey, CommunityPermissionsHoldingItemEditor {
amount: d.newAmount 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 { Repeater {
model: channelsListModel id: channelsRepeater
CommunityPermissionsSettingItemEditor {
isEmojiSelectorVisible: true
panelText: "In [item " + model.index + "]" model: root.channelKeys
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
}
}
}
CheckBox {
text: modelData
CommunityPermissionsSettingItemEditor { checked: ModelUtils.contains(
Layout.fillWidth: true channelsListModel, "itemId", modelData)
panelText: "New In item"
name: d.newChannelName
icon: d.newChannelIconSource
iconsModel: AssetsCollectiblesIconsModel {}
onNameChanged: d.newChannelName = name
onIconChanged: d.newChannelIconSource = icon
}
Button { onToggled: {
enabled: d.newChannelIconSource && d.newChannelName const channels = []
Layout.fillWidth: true const count = channelsRepeater.count
text: "Add new channel"
onClicked: { for (let i = 0; i < count; i++) {
model.channelsListModel.append([{ const checked = channelsRepeater.itemAt(i).checked
key: d.newChannelName,
name: d.newChannelName, if (checked) {
iconSource: d.newChannelIconSource const itemId = root.channelKeys[i]
}]) channels.push({ itemId })
}
}
channelsListModel.clear()
channelsListModel.append(channels)
}
}
} }
} }

View File

@ -46,7 +46,9 @@ SplitView {
rootStore: QtObject { rootStore: QtObject {
readonly property QtObject chatCommunitySectionModule: QtObject { readonly property QtObject chatCommunitySectionModule: QtObject {
readonly property var model: ChannelsModel {} readonly property var model: ChannelsModel {
id: channelsModel
}
} }
readonly property QtObject mainModuleInst: QtObject { readonly property QtObject mainModuleInst: QtObject {
@ -87,6 +89,13 @@ SplitView {
assetKeys: assetsModel.data.map(asset => asset.key) assetKeys: assetsModel.data.map(asset => asset.key)
collectibleKeys: collectiblesModel.data.map(collectible => collectible.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 { ListModel {
ListElement { ListElement {
itemId: "1" itemId: "_welcome"
isCategory: false isCategory: false
categoryId: "" categoryId: ""
name: "welcome" name: "welcome"
emoji: "" emoji: ""
color: "" color: ""
icon: ""
colorId: 1 colorId: 1
} }
ListElement { ListElement {
itemId: "2" itemId: "_announcements"
isCategory: false isCategory: false
categoryId: "" categoryId: ""
name: "announcements" name: "announcements"
emoji: "" emoji: ""
color: "" color: ""
icon: ""
colorId: 1 colorId: 1
} }
ListElement { ListElement {
itemId: "0" itemId: ""
isCategory: true isCategory: true
categoryId: "1" categoryId: "_discussion"
name: "discussion" name: "discussion"
emoji: "" emoji: ""
color: "" color: ""
icon: ""
colorId: 1 colorId: 1
} }
ListElement { ListElement {
itemId: "3" itemId: "_general"
isCategory: false isCategory: false
categoryId: "1" categoryId: "_discussion"
name: "general" name: "general"
emoji: "👋" emoji: "👋"
color: "" color: ""
icon: ""
colorId: 1 colorId: 1
} }
ListElement { ListElement {
itemId: "4" itemId: "_help"
isCategory: false isCategory: false
categoryId: "1" categoryId: "_discussion"
name: "help" name: "help"
emoji: "⚽" emoji: "⚽"
color: "" color: ""
icon: ""
colorId: 1 colorId: 1
} }
ListElement { ListElement {
itemId: "0" itemId: ""
isCategory: true isCategory: true
categoryId: "2" categoryId: "_support"
name: "support" name: "support"
emoji: "" emoji: ""
color: "" color: ""
icon: ""
colorId: 1 colorId: 1
} }
ListElement { ListElement {
itemId: "5" itemId: "_faq"
isCategory: false isCategory: false
categoryId: "2" categoryId: "_support"
name: "faq" name: "faq"
emoji: "" emoji: ""
color: "" color: ""
icon: ""
colorId: 5 colorId: 5
} }
ListElement { ListElement {
itemId: "6" itemId: "_report-scam"
isCategory: false isCategory: false
categoryId: "2" categoryId: "_support"
name: "report-scam" name: "report-scam"
emoji: "" emoji: ""
color: "" color: ""
icon: ""
colorId: 4 colorId: 4
} }
ListElement { ListElement {
itemId: "0" itemId: ""
isCategory: true isCategory: true
categoryId: "3" categoryId: "_faq"
name: "faq" name: "faq"
emoji: "" emoji: ""
color: "" color: ""
icon: ""
colorId: 5 colorId: 5
} }
} }

View File

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

View File

@ -12,6 +12,15 @@ QtObject {
return Internal.ModelUtils.get(model, index) 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) { function modelToArray(model, roles) {
if (!model) if (!model)
return [] return []
@ -46,6 +55,10 @@ QtObject {
return -1 return -1
} }
function contains(model, role, key) {
return indexOf(model, role, key) !== -1
}
function checkItemsEquality(itemA, itemB, roles) { function checkItemsEquality(itemA, itemB, roles) {
return roles.every((role) => itemA[role] === itemB[role]) return roles.every((role) => itemA[role] === itemB[role])
} }

View File

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

View File

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

View File

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

View File

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

View File

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