636 lines
22 KiB
QML
636 lines
22 KiB
QML
import QtQuick 2.15
|
||
import QtQuick.Layouts 1.15
|
||
import QtQml 2.15
|
||
|
||
import StatusQ 0.1
|
||
import StatusQ.Core 0.1
|
||
import StatusQ.Core.Theme 0.1
|
||
import StatusQ.Components 0.1
|
||
import StatusQ.Controls 0.1
|
||
import StatusQ.Core.Utils 0.1
|
||
|
||
import utils 1.0
|
||
import shared.panels 1.0
|
||
|
||
import AppLayouts.Communities.controls 1.0
|
||
import AppLayouts.Communities.helpers 1.0
|
||
import AppLayouts.Communities.panels 1.0
|
||
import AppLayouts.Communities.popups 1.0
|
||
|
||
StatusScrollView {
|
||
id: root
|
||
|
||
required property var assetsModel
|
||
required property var collectiblesModel
|
||
required property var channelsModel
|
||
|
||
// id, name, image, color, owner properties expected
|
||
required property var communityDetails
|
||
|
||
readonly property bool saveEnabled: root.isFullyFilled
|
||
&& !root.permissionDuplicated
|
||
&& (isEditState ? !root.permissionTypeLimitExceeded : !root.permissionTypeLimitReached)
|
||
|
||
property int viewWidth: 560 // by design
|
||
property bool isEditState: false
|
||
|
||
readonly property bool dirty:
|
||
root.holdingsRequired !== d.dirtyValues.holdingsRequired ||
|
||
(d.dirtyValues.holdingsRequired && !holdingsModelComparator.equal) ||
|
||
!channelsModelComparator.equal ||
|
||
root.isPrivate !== d.dirtyValues.isPrivate ||
|
||
root.permissionType !== d.dirtyValues.permissionType
|
||
|
||
readonly property alias dirtyValues: d.dirtyValues
|
||
|
||
readonly property bool isFullyFilled: (dirtyValues.selectedHoldingsModel.count > 0 || !whoHoldsSwitch.checked) &&
|
||
dirtyValues.permissionType !== PermissionTypes.Type.None &&
|
||
(d.isCommunityPermission || !showChannelSelector || dirtyValues.selectedChannelsModel.count > 0)
|
||
|
||
property int permissionType: PermissionTypes.Type.None
|
||
property bool isPrivate: false
|
||
property bool holdingsRequired: true
|
||
property bool showChannelSelector: true
|
||
|
||
// roles: type, key, name, amount, imageSource
|
||
property var selectedHoldingsModel: ListModel {}
|
||
|
||
// roles: itemId, text, icon, emoji, color, colorId
|
||
property var selectedChannelsModel: ListModel {}
|
||
|
||
property bool permissionDuplicated: false
|
||
property bool permissionTypeLimitReached: false
|
||
property bool permissionTypeLimitExceeded
|
||
|
||
signal createPermissionClicked
|
||
signal navigateToMintTokenSettings(bool isAssetType)
|
||
|
||
function resetChanges() {
|
||
d.loadInitValues()
|
||
}
|
||
|
||
ModelsComparator {
|
||
id: holdingsModelComparator
|
||
|
||
modelA: root.dirtyValues.selectedHoldingsModel
|
||
modelB: root.selectedHoldingsModel
|
||
|
||
roles: ["key", "amount"]
|
||
mode: ModelsComparator.CompareMode.Set
|
||
}
|
||
|
||
ModelsComparator {
|
||
id: channelsModelComparator
|
||
|
||
modelA: root.dirtyValues.selectedChannelsModel
|
||
modelB: root.selectedChannelsModel
|
||
|
||
roles: ["key"]
|
||
mode: ModelsComparator.CompareMode.Set
|
||
}
|
||
|
||
QtObject {
|
||
id: d
|
||
|
||
readonly property int maxHoldingsItems: 5
|
||
|
||
readonly property int dropdownHorizontalOffset: 4
|
||
readonly property int dropdownVerticalOffset: 1
|
||
|
||
readonly property bool isCommunityPermission:
|
||
PermissionTypes.isCommunityPermission(dirtyValues.permissionType)
|
||
|
||
onIsCommunityPermissionChanged: {
|
||
if (isCommunityPermission) {
|
||
d.dirtyValues.selectedChannelsModel.clear()
|
||
inSelector.wholeCommunitySelected = true
|
||
inSelector.model = inModelCommunity
|
||
} else {
|
||
inSelector.model = 0
|
||
inSelector.wholeCommunitySelected = false
|
||
inSelector.model = channelsSelectionModel
|
||
}
|
||
}
|
||
|
||
readonly property QtObject dirtyValues: QtObject {
|
||
readonly property ListModel selectedHoldingsModel: ListModel {}
|
||
readonly property ListModel selectedChannelsModel: ListModel {}
|
||
|
||
property int permissionType: PermissionTypes.Type.None
|
||
property bool isPrivate: false
|
||
property bool holdingsRequired: true
|
||
|
||
Binding on isPrivate {
|
||
value: d.dirtyValues.permissionType === PermissionTypes.Type.Admin
|
||
}
|
||
|
||
function getHoldingIndex(key) {
|
||
return ModelUtils.indexOf(selectedHoldingsModel, "key", key)
|
||
}
|
||
|
||
function getTokenKeysAndAmounts() {
|
||
return ModelUtils.modelToArray(selectedHoldingsModel, ["type", "key", "amount"])
|
||
.filter(item => item.type !== Constants.TokenType.ENS)
|
||
.map(item => ({ key: item.key, amount: item.amount }))
|
||
}
|
||
|
||
function getEnsNames() {
|
||
return ModelUtils.modelToArray(selectedHoldingsModel, ["type", "name"])
|
||
.filter(item => item.type === Constants.TokenType.ENS)
|
||
.map(item => item.name)
|
||
}
|
||
}
|
||
|
||
function loadInitValues() {
|
||
// Holdings:
|
||
d.dirtyValues.selectedHoldingsModel.clear()
|
||
d.dirtyValues.selectedHoldingsModel.append(
|
||
ModelUtils.modelToArray(root.selectedHoldingsModel,
|
||
["type", "key", "amount"]))
|
||
|
||
// Permissions:
|
||
d.dirtyValues.permissionType = root.permissionType
|
||
|
||
// Channels
|
||
d.dirtyValues.selectedChannelsModel.clear()
|
||
d.dirtyValues.selectedChannelsModel.append(
|
||
ModelUtils.modelToArray(root.selectedChannelsModel, ["key"]))
|
||
|
||
if (root.selectedChannelsModel &&
|
||
(root.selectedChannelsModel.rowCount()
|
||
|| d.dirtyValues.permissionType === PermissionTypes.Type.None)) {
|
||
inSelector.wholeCommunitySelected = false
|
||
inSelector.model = channelsSelectionModel
|
||
} else {
|
||
inSelector.wholeCommunitySelected = true
|
||
inSelector.model = inModelCommunity
|
||
}
|
||
|
||
// Is private permission
|
||
d.dirtyValues.isPrivate = root.isPrivate
|
||
|
||
// Are holdings required
|
||
d.dirtyValues.holdingsRequired = root.holdingsRequired
|
||
}
|
||
}
|
||
|
||
onPermissionTypeChanged: Qt.callLater(() => d.loadInitValues())
|
||
contentWidth: mainLayout.width
|
||
contentHeight: mainLayout.height
|
||
|
||
SequenceColumnLayout {
|
||
id: mainLayout
|
||
|
||
width: root.viewWidth
|
||
title: qsTr("Anyone")
|
||
|
||
StatusItemSelector {
|
||
id: tokensSelector
|
||
|
||
property int editedIndex: -1
|
||
|
||
Layout.fillWidth: true
|
||
icon: Style.svg("contact_verified")
|
||
title: qsTr("Who holds")
|
||
placeholderText: qsTr("Example: 10 SNT")
|
||
tagLeftPadding: 2
|
||
asset.height: 28
|
||
asset.width: asset.height
|
||
addButton.visible: count < d.maxHoldingsItems &&
|
||
whoHoldsSwitch.checked
|
||
|
||
model: HoldingsSelectionModel {
|
||
sourceModel: d.dirtyValues.selectedHoldingsModel
|
||
|
||
assetsModel: root.assetsModel
|
||
collectiblesModel: root.collectiblesModel
|
||
}
|
||
|
||
label.enabled: whoHoldsSwitch.checked
|
||
placeholderItem.visible: count === 0 && whoHoldsSwitch.checked
|
||
|
||
Binding on model {
|
||
when: !whoHoldsSwitch.checked
|
||
value: 0
|
||
restoreMode: Binding.RestoreBindingOrValue
|
||
}
|
||
|
||
Binding on bottomPadding {
|
||
when: !whoHoldsSwitch.checked
|
||
value: 0
|
||
restoreMode: Binding.RestoreBindingOrValue
|
||
}
|
||
|
||
children: StatusSwitch {
|
||
id: whoHoldsSwitch
|
||
|
||
padding: 0
|
||
anchors.right: parent.right
|
||
anchors.top: parent.top
|
||
anchors.rightMargin: 12
|
||
anchors.topMargin: 10
|
||
|
||
checked: d.dirtyValues.holdingsRequired
|
||
onToggled: d.dirtyValues.holdingsRequired = checked
|
||
}
|
||
|
||
HoldingsDropdown {
|
||
id: dropdown
|
||
|
||
communityId: root.communityDetails.id
|
||
|
||
assetsModel: root.assetsModel
|
||
collectiblesModel: root.collectiblesModel
|
||
showTokenAmount: false
|
||
|
||
function addItem(type, item, amount) {
|
||
const key = item.key
|
||
|
||
d.dirtyValues.selectedHoldingsModel.append(
|
||
{ type, key, amount })
|
||
}
|
||
|
||
function prepareUpdateIndex(key) {
|
||
const itemIndex = tokensSelector.editedIndex
|
||
const existingIndex = d.dirtyValues.getHoldingIndex(key)
|
||
|
||
if (itemIndex !== -1 && existingIndex !== -1 && itemIndex !== existingIndex) {
|
||
const previousKey = d.dirtyValues.selectedHoldingsModel.get(itemIndex).key
|
||
d.dirtyValues.selectedHoldingsModel.remove(existingIndex)
|
||
return d.dirtyValues.getHoldingIndex(previousKey)
|
||
}
|
||
|
||
if (itemIndex === -1) {
|
||
return existingIndex
|
||
}
|
||
|
||
return itemIndex
|
||
}
|
||
|
||
onOpened: {
|
||
usedTokens = d.dirtyValues.getTokenKeysAndAmounts()
|
||
usedEnsNames = d.dirtyValues.getEnsNames().filter(item => item !== ensDomainName)
|
||
}
|
||
|
||
onAddAsset: {
|
||
const modelItem = PermissionsHelpers.getTokenByKey(
|
||
root.assetsModel, key)
|
||
|
||
addItem(Constants.TokenType.ERC20, modelItem, AmountsArithmetic.fromNumber(amount, modelItem.decimals).toFixed())
|
||
dropdown.close()
|
||
}
|
||
|
||
onAddCollectible: {
|
||
const modelItem = PermissionsHelpers.getTokenByKey(
|
||
root.collectiblesModel, key)
|
||
|
||
addItem(Constants.TokenType.ERC721, modelItem, String(amount))
|
||
dropdown.close()
|
||
}
|
||
|
||
onAddEns: {
|
||
d.dirtyValues.selectedHoldingsModel.append(
|
||
{ type: Constants.TokenType.ENS, key: domain, amount: "1" })
|
||
dropdown.close()
|
||
}
|
||
|
||
onUpdateAsset: {
|
||
const itemIndex = prepareUpdateIndex(key)
|
||
const modelItem = PermissionsHelpers.getTokenByKey(root.assetsModel, key)
|
||
|
||
d.dirtyValues.selectedHoldingsModel.set(
|
||
itemIndex, { type: Constants.TokenType.ERC20, key, amount: AmountsArithmetic.fromNumber(amount, modelItem.decimals).toFixed() })
|
||
dropdown.close()
|
||
}
|
||
|
||
onUpdateCollectible: {
|
||
const itemIndex = prepareUpdateIndex(key)
|
||
const modelItem = PermissionsHelpers.getTokenByKey(
|
||
root.collectiblesModel, key)
|
||
|
||
d.dirtyValues.selectedHoldingsModel.set(
|
||
itemIndex,
|
||
{ type: Constants.TokenType.ERC721, key, amount: String(amount) })
|
||
dropdown.close()
|
||
}
|
||
|
||
onUpdateEns: {
|
||
d.dirtyValues.selectedHoldingsModel.set(
|
||
tokensSelector.editedIndex,
|
||
{ type: Constants.TokenType.ENS, key: domain, amount: "1" })
|
||
dropdown.close()
|
||
}
|
||
|
||
onRemoveClicked: {
|
||
d.dirtyValues.selectedHoldingsModel.remove(tokensSelector.editedIndex)
|
||
dropdown.close()
|
||
}
|
||
|
||
onNavigateToMintTokenSettings: {
|
||
root.navigateToMintTokenSettings(isAssetType)
|
||
close()
|
||
}
|
||
}
|
||
|
||
addButton.onClicked: {
|
||
dropdown.parent = tokensSelector.addButton
|
||
dropdown.x = tokensSelector.addButton.width + d.dropdownHorizontalOffset
|
||
dropdown.y = 0
|
||
dropdown.open()
|
||
|
||
editedIndex = -1
|
||
}
|
||
|
||
onItemClicked: {
|
||
if (mouse.button !== Qt.LeftButton)
|
||
return
|
||
|
||
dropdown.parent = item
|
||
dropdown.x = mouse.x + d.dropdownHorizontalOffset
|
||
dropdown.y = d.dropdownVerticalOffset
|
||
|
||
const modelItem = tokensSelector.model.get(index)
|
||
|
||
switch(modelItem.type) {
|
||
case Constants.TokenType.ERC20:
|
||
dropdown.assetKey = modelItem.key
|
||
const decimals = PermissionsHelpers.getTokenByKey(root.assetsModel, modelItem.key).decimals
|
||
dropdown.assetAmount = AmountsArithmetic.toNumber(modelItem.amount, decimals)
|
||
break
|
||
case Constants.TokenType.ERC721:
|
||
dropdown.collectibleKey = modelItem.key
|
||
dropdown.collectibleAmount = modelItem.amount
|
||
break
|
||
case Constants.TokenType.ENS:
|
||
dropdown.ensDomainName = modelItem.key
|
||
break
|
||
default:
|
||
console.warn("Unsupported holdings type.")
|
||
}
|
||
|
||
dropdown.setActiveTab(modelItem.type)
|
||
dropdown.openUpdateFlow()
|
||
|
||
editedIndex = index
|
||
}
|
||
}
|
||
|
||
SequenceColumnLayout.Separator {}
|
||
|
||
StatusFlowSelector {
|
||
id: permissionsSelector
|
||
|
||
Layout.fillWidth: true
|
||
|
||
title: qsTr("Is allowed to")
|
||
placeholderText: qsTr("Example: View and post")
|
||
icon: Style.svg("profile/security")
|
||
|
||
readonly property bool empty:
|
||
d.dirtyValues.permissionType === PermissionTypes.Type.None
|
||
|
||
placeholderItem.visible: empty
|
||
addButton.visible: empty
|
||
|
||
StatusListItemTag {
|
||
readonly property int key: d.dirtyValues.permissionType
|
||
|
||
title: PermissionTypes.getName(key)
|
||
visible: !permissionsSelector.empty
|
||
|
||
asset.name: PermissionTypes.getIcon(key)
|
||
asset.bgColor: "transparent"
|
||
closeButtonVisible: false
|
||
titleText.font.pixelSize: Theme.primaryTextFontSize
|
||
leftPadding: 6
|
||
|
||
Binding on bgColor {
|
||
when: root.permissionTypeLimitReached && !root.isEditState
|
||
value: Theme.palette.dangerColor3
|
||
}
|
||
|
||
Binding on titleText.color {
|
||
when: root.permissionTypeLimitReached && !root.isEditState
|
||
value: Theme.palette.dangerColor1
|
||
}
|
||
|
||
Binding on asset.color {
|
||
when: root.permissionTypeLimitReached && !root.isEditState
|
||
value: Theme.palette.dangerColor1
|
||
}
|
||
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
cursorShape: Qt.PointingHandCursor
|
||
|
||
onClicked: {
|
||
permissionsDropdown.mode = PermissionsDropdown.Mode.Update
|
||
permissionsDropdown.parent = parent
|
||
permissionsDropdown.x = mouse.x + d.dropdownHorizontalOffset
|
||
permissionsDropdown.y = d.dropdownVerticalOffset
|
||
permissionsDropdown.open()
|
||
}
|
||
}
|
||
}
|
||
|
||
PermissionsDropdown {
|
||
id: permissionsDropdown
|
||
|
||
allowCommunityOptions: root.showChannelSelector
|
||
initialPermissionType: d.dirtyValues.permissionType
|
||
enableAdminPermission: root.communityDetails.owner
|
||
|
||
onDone: {
|
||
if (d.dirtyValues.permissionType === permissionType) {
|
||
permissionsDropdown.close()
|
||
return
|
||
}
|
||
|
||
d.dirtyValues.permissionType = permissionType
|
||
permissionsDropdown.close()
|
||
}
|
||
}
|
||
|
||
addButton.onClicked: {
|
||
permissionsDropdown.mode = PermissionsDropdown.Mode.Add
|
||
permissionsDropdown.parent = permissionsSelector.addButton
|
||
permissionsDropdown.x = permissionsSelector.addButton.width
|
||
+ d.dropdownHorizontalOffset
|
||
permissionsDropdown.y = 0
|
||
permissionsDropdown.open()
|
||
}
|
||
}
|
||
|
||
SequenceColumnLayout.Separator { visible: root.showChannelSelector }
|
||
|
||
StatusItemSelector {
|
||
id: inSelector
|
||
|
||
readonly property bool editable: !d.isCommunityPermission
|
||
|
||
addButton.visible: editable
|
||
itemsClickable: editable
|
||
visible: root.showChannelSelector
|
||
Layout.fillWidth: true
|
||
icon: d.isCommunityPermission ? Style.svg("communities") : Style.svg("create-category")
|
||
title: qsTr("In")
|
||
placeholderText: qsTr("Example: `#general` channel")
|
||
|
||
useLetterIdenticons: !wholeCommunitySelected || !inDropdown.communityImage
|
||
|
||
tagLeftPadding: wholeCommunitySelected ? 2 : 6
|
||
asset.width: wholeCommunitySelected ? 28 : 20
|
||
asset.height: asset.width
|
||
|
||
property bool wholeCommunitySelected: false
|
||
|
||
function openInDropdown(parent, x, y) {
|
||
inDropdown.parent = parent
|
||
inDropdown.x = x
|
||
inDropdown.y = y
|
||
|
||
const selectedChannels = []
|
||
|
||
if (!inSelector.wholeCommunitySelected) {
|
||
const model = d.dirtyValues.selectedChannelsModel
|
||
const count = model.count
|
||
|
||
for (let i = 0; i < count; i++)
|
||
selectedChannels.push(model.get(i).key)
|
||
}
|
||
|
||
inDropdown.setSelectedChannels(selectedChannels)
|
||
inDropdown.open()
|
||
}
|
||
|
||
ListModel {
|
||
id: inModelCommunity
|
||
|
||
Component.onCompleted: {
|
||
append({
|
||
imageSource: inDropdown.communityImage,
|
||
isIcon: false,
|
||
text: inDropdown.communityName,
|
||
operator: OperatorsUtils.Operators.None,
|
||
color: inDropdown.communityColor
|
||
})
|
||
}
|
||
}
|
||
|
||
LeftJoinModel {
|
||
id: channelsSelectionModel
|
||
|
||
leftModel: d.dirtyValues.selectedChannelsModel
|
||
rightModel: root.channelsModel
|
||
joinRole: "key"
|
||
}
|
||
|
||
InDropdown {
|
||
id: inDropdown
|
||
|
||
model: root.channelsModel
|
||
|
||
communityName: root.communityDetails.name
|
||
communityImage: root.communityDetails.image
|
||
communityColor: root.communityDetails.color
|
||
|
||
onChannelsSelected: {
|
||
d.dirtyValues.selectedChannelsModel.clear()
|
||
inSelector.model = 0
|
||
inSelector.wholeCommunitySelected = false
|
||
|
||
const modelData = channels.map(key => ({ key }))
|
||
d.dirtyValues.selectedChannelsModel.append(modelData)
|
||
|
||
inSelector.model = channelsSelectionModel
|
||
close()
|
||
}
|
||
|
||
onCommunitySelected: {
|
||
d.dirtyValues.selectedChannelsModel.clear()
|
||
inSelector.wholeCommunitySelected = true
|
||
inSelector.model = inModelCommunity
|
||
close()
|
||
}
|
||
}
|
||
|
||
addButton.onClicked: {
|
||
inDropdown.acceptMode = InDropdown.AcceptMode.Add
|
||
openInDropdown(inSelector.addButton,
|
||
inSelector.addButton.width + d.dropdownHorizontalOffset, 0)
|
||
}
|
||
|
||
onItemClicked: {
|
||
if (mouse.button !== Qt.LeftButton)
|
||
return
|
||
|
||
inDropdown.acceptMode = InDropdown.AcceptMode.Update
|
||
openInDropdown(item, mouse.x + d.dropdownHorizontalOffset,
|
||
d.dropdownVerticalOffset)
|
||
}
|
||
}
|
||
Separator {
|
||
Layout.topMargin: 24
|
||
color: Theme.palette.baseColor2
|
||
}
|
||
|
||
StatusIconSwitch {
|
||
Layout.topMargin: 12
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 16
|
||
Layout.rightMargin: Layout.leftMargin
|
||
|
||
enabled: d.dirtyValues.permissionType !== PermissionTypes.Type.Admin
|
||
checked: d.dirtyValues.isPrivate
|
||
title: qsTr("Hide permission")
|
||
subTitle: qsTr("Make this permission hidden from members who don’t meet its requirements")
|
||
icon: "hide"
|
||
onToggled: d.dirtyValues.isPrivate = checked
|
||
}
|
||
|
||
WarningPanel {
|
||
id: duplicationPanel
|
||
objectName: "duplicationPanel"
|
||
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: 50 // by desing
|
||
|
||
text: {
|
||
if (root.permissionTypeLimitReached)
|
||
return PermissionTypes.getPermissionsLimitWarning(
|
||
d.dirtyValues.permissionType)
|
||
|
||
if (root.permissionDuplicated)
|
||
return qsTr("Permission with same properties is already active, edit properties to create a new permission.")
|
||
|
||
return ""
|
||
}
|
||
|
||
visible: root.permissionDuplicated || (root.permissionTypeLimitReached && !root.isEditState)
|
||
}
|
||
|
||
StatusWarningBox {
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: Style.current.padding
|
||
visible: root.showChannelSelector
|
||
icon: "desktop"
|
||
text: qsTr("Any changes to community permissions will take effect after the control node receives and processes them")
|
||
borderColor: Theme.palette.baseColor1
|
||
iconColor: textColor
|
||
}
|
||
|
||
StatusButton {
|
||
Layout.preferredHeight: 44
|
||
Layout.alignment: Qt.AlignHCenter
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: Style.current.bigPadding
|
||
|
||
visible: !root.isEditState && root.showChannelSelector
|
||
text: qsTr("Create permission")
|
||
enabled: root.saveEnabled
|
||
|
||
onClicked: root.createPermissionClicked()
|
||
}
|
||
}
|
||
}
|