status-desktop/ui/app/AppLayouts/Chat/views/communities/CommunityNewAirdropView.qml

399 lines
13 KiB
QML

import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import shared.panels 1.0
import AppLayouts.Chat.helpers 1.0
import AppLayouts.Chat.panels.communities 1.0
import AppLayouts.Chat.controls.community 1.0
import SortFilterProxyModel 0.2
StatusScrollView {
id: root
// Token models:
required property var assetsModel
required property var collectiblesModel
// Community members model:
required property var membersModel
property int viewWidth: 560 // by design
readonly property var selectedHoldingsModel: ListModel {}
readonly property bool isFullyFilled: tokensSelector.count > 0 &&
airdropRecipientsSelector.count > 0 &&
airdropRecipientsSelector.valid
signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
signal navigateToMintTokenSettings
function selectCollectible(key, amount) {
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
root.collectiblesModel, key)
const entry = d.prepareEntry(key, amount)
entry.valid = true
selectedHoldingsModel.append(entry)
}
QtObject {
id: d
readonly property int maxAirdropTokens: 5
readonly property int dropdownHorizontalOffset: 4
readonly property int dropdownVerticalOffset: 1
function prepareEntry(key, amount) {
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
root.collectiblesModel, key)
return {
key, amount,
tokenText: amount + " " + modelItem.name,
tokenImage: modelItem.iconSource,
networkText: modelItem.chainName,
networkImage: Style.svg(modelItem.chainIcon),
supply: modelItem.supply,
infiniteSupply: modelItem.infiniteSupply
}
}
}
Instantiator {
id: recipientsCountInstantiator
model: selectedHoldingsModel
property bool infinity: true
property int maximumRecipientsCount
function findRecipientsCount() {
let min = Number.MAX_SAFE_INTEGER
for (let i = 0; i < count; i++) {
const item = objectAt(i)
if (!item || item.infiniteSupply)
continue
min = Math.min(item.supply / item.amount, min)
}
infinity = min === Number.MAX_SAFE_INTEGER
maximumRecipientsCount = infinity ? 0 : min
}
delegate: QtObject {
readonly property int supply: model.supply
readonly property real amount: model.amount
readonly property bool infiniteSupply: model.infiniteSupply
readonly property bool valid:
infiniteSupply || amount * airdropRecipientsSelector.count <= supply
onSupplyChanged: recipientsCountInstantiator.findRecipientsCount()
onAmountChanged: recipientsCountInstantiator.findRecipientsCount()
onInfiniteSupplyChanged: recipientsCountInstantiator.findRecipientsCount()
onValidChanged: model.valid = valid
Component.onCompleted: model.valid = valid
}
onCountChanged: findRecipientsCount()
}
contentWidth: mainLayout.width
contentHeight: mainLayout.height
SequenceColumnLayout {
id: mainLayout
width: root.viewWidth
spacing: 0
AirdropTokensSelector {
id: tokensSelector
property int editedIndex: -1
Layout.fillWidth: true
icon: Style.svg("token")
title: qsTr("What")
placeholderText: qsTr("Example: 1 SOCK")
addButton.visible: model.count < d.maxAirdropTokens
model: root.selectedHoldingsModel
HoldingsDropdown {
id: dropdown
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
isENSTab: false
isCollectiblesOnly: true
noDataText: qsTr("First you need to mint or import a collectible before you can perform an airdrop")
function getHoldingIndex(key) {
return ModelUtils.indexOf(root.selectedHoldingsModel, "key", key)
}
function prepareUpdateIndex(key) {
const itemIndex = tokensSelector.editedIndex
const existingIndex = getHoldingIndex(key)
if (itemIndex !== -1 && existingIndex !== -1 && itemIndex !== existingIndex) {
const previousKey = root.selectedHoldingsModel.get(itemIndex).key
root.selectedHoldingsModel.remove(existingIndex)
return getHoldingIndex(previousKey)
}
if (itemIndex === -1) {
return existingIndex
}
return itemIndex
}
onOpened: {
usedTokens = ModelUtils.modelToArray(
root.selectedHoldingsModel, ["key", "amount"])
}
onAddCollectible: {
const entry = d.prepareEntry(key, amount)
entry.valid = true
selectedHoldingsModel.append(entry)
dropdown.close()
}
onUpdateCollectible: {
const itemIndex = prepareUpdateIndex(key)
const entry = d.prepareEntry(key, amount)
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
root.collectiblesModel, key)
root.selectedHoldingsModel.set(itemIndex, entry)
dropdown.close()
}
onRemoveClicked: {
root.selectedHoldingsModel.remove(tokensSelector.editedIndex)
dropdown.close()
}
onNavigateToMintTokenSettings: {
root.navigateToMintTokenSettings()
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 = selectedHoldingsModel.get(index)
dropdown.collectibleKey = modelItem.key
dropdown.collectibleAmount = modelItem.amount
dropdown.setActiveTab(HoldingTypes.Type.Collectible)
dropdown.openUpdateFlow()
editedIndex = index
}
}
SequenceColumnLayout.Separator {}
AirdropRecipientsSelector {
id: airdropRecipientsSelector
addressesModel: addresses
infiniteMaxNumberOfRecipients:
recipientsCountInstantiator.infinity
maxNumberOfRecipients:
recipientsCountInstantiator.maximumRecipientsCount
membersModel: SortFilterProxyModel {
sourceModel: membersModel
filters: ExpressionFilter {
id: selectedKeysFilter
property var keys: new Set()
expression: keys.has(model.pubKey)
}
}
onRemoveMemberRequested: {
const pubKey = ModelUtils.get(membersModel, index, "pubKey")
selectedKeysFilter.keys.delete(pubKey)
selectedKeysFilter.keys = new Set([...selectedKeysFilter.keys])
}
onAddAddressesRequested: (addresses_) => {
addresses.addAddressesFromString(addresses_)
airdropRecipientsSelector.clearAddressesInput()
airdropRecipientsSelector.positionAddressesListAtEnd()
}
onRemoveAddressRequested: addresses.remove(index)
ListModel {
id: addresses
function addAddressesFromString(addresses) {
const words = addresses.trim().split(/[\s+,]/)
const existing = new Set()
for (let i = 0; i < count; i++)
existing.add(get(i).address)
words.forEach(word => {
if (word === "" || existing.has(word))
return
const valid = Utils.isValidAddress(word)
append({ valid, address: word })
})
}
}
function openPopup(popup) {
popup.parent = addButton
popup.x = addButton.width + d.dropdownHorizontalOffset
popup.y = 0
popup.open()
}
addButton.onClicked: openPopup(recipientTypeSelectionDropdown)
RecipientTypeSelectionDropdown {
id: recipientTypeSelectionDropdown
onEthAddressesSelected: {
airdropRecipientsSelector.showAddressesInputWhenEmpty = true
airdropRecipientsSelector.forceInputFocus()
recipientTypeSelectionDropdown.close()
}
onCommunityMembersSelected: {
recipientTypeSelectionDropdown.close()
membersDropdown.selectedKeys = selectedKeysFilter.keys
const hasSelection = selectedKeysFilter.keys.size !== 0
membersDropdown.mode = hasSelection
? MembersDropdown.Mode.Update
: MembersDropdown.Mode.Add
airdropRecipientsSelector.openPopup(membersDropdown)
}
}
MembersDropdown {
id: membersDropdown
forceButtonDisabled:
mode === MembersDropdown.Mode.Update &&
[...selectedKeys].sort().join() === [...selectedKeysFilter.keys].sort().join()
model: SortFilterProxyModel {
sourceModel: membersModel
filters: [
ExpressionFilter {
enabled: membersDropdown.searchText !== ""
function matchesAlias(name, filter) {
return name.split(" ").some(p => p.startsWith(filter))
}
expression: {
membersDropdown.searchText
const filter = membersDropdown.searchText.toLowerCase()
return matchesAlias(model.alias.toLowerCase(), filter)
|| model.displayName.toLowerCase().includes(filter)
|| model.ensName.toLowerCase().includes(filter)
|| model.localNickname.toLowerCase().includes(filter)
|| model.pubKey.toLowerCase().includes(filter)
}
}
]
}
onBackButtonClicked: {
close()
airdropRecipientsSelector.openPopup(
recipientTypeSelectionDropdown)
}
onAddButtonClicked: {
selectedKeysFilter.keys = selectedKeys
close()
}
}
}
WarningPanel {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
text: qsTr("Not enough tokens to send to all recipients. Reduce the number of recipients or change the number of tokens sent to each recipient.")
visible: !recipientsCountInstantiator.infinity &&
recipientsCountInstantiator.maximumRecipientsCount < airdropRecipientsSelector.count
}
StatusButton {
Layout.preferredHeight: 44
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: Style.current.bigPadding
text: qsTr("Create airdrop")
enabled: root.isFullyFilled
onClicked: {
const airdropTokens = ModelUtils.modelToArray(
root.selectedHoldingsModel,
["key", "amount"])
const addresses_ = ModelUtils.modelToArray(
addresses, ["address"]).map(e => e.address)
const pubKeys = [...selectedKeysFilter.keys]
root.airdropClicked(airdropTokens, addresses_, pubKeys)
}
}
}
}