mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-13 15:55:18 +00:00
8b4cbc59a8
The new account selector expects a generic account model. It will display all the account data if provided, including preferred chains, balance or asset balance. Otherwise it will display only the available data. The account selector can receive an initial selection based on account address and will provide the current selected address and the current selected model item. - Unify the account selector between communities and wallet - Update the account selector to work with addresses instead of model indexes - Adapt all components using the account selector or the account selection - Move/reuse qml components involved in the account selector UI - Remove nim logic used to handle index based account selection. - Adding storybook page
461 lines
18 KiB
QML
461 lines
18 KiB
QML
import QtQuick 2.14
|
||
import QtQuick.Layouts 1.14
|
||
|
||
import StatusQ.Core 0.1
|
||
import StatusQ.Core.Theme 0.1
|
||
import StatusQ.Controls 0.1
|
||
import StatusQ.Controls.Validators 0.1
|
||
import StatusQ.Components 0.1
|
||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||
|
||
import utils 1.0
|
||
|
||
import AppLayouts.Communities.helpers 1.0
|
||
import AppLayouts.Communities.panels 1.0
|
||
import AppLayouts.Wallet.controls 1.0
|
||
import shared.panels 1.0
|
||
import shared.popups 1.0
|
||
|
||
import SortFilterProxyModel 0.2
|
||
|
||
StatusScrollView {
|
||
id: root
|
||
|
||
property int viewWidth: 560 // by design
|
||
property bool isAssetView: false
|
||
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
|
||
|
||
property var tokensModel
|
||
property TokenObject token: TokenObject {
|
||
type: root.isAssetView ? Constants.TokenType.ERC20 : Constants.TokenType.ERC721
|
||
}
|
||
|
||
// Used for reference validation
|
||
required property var referenceAssetsBySymbolModel
|
||
|
||
// Used for reference validation when editing a failed deployment
|
||
property string referenceName: ""
|
||
property string referenceSymbol: ""
|
||
|
||
// Account expected roles: address, name, color, emoji, walletType
|
||
property var accounts
|
||
|
||
property string feeText
|
||
property string feeErrorText
|
||
property bool isFeeLoading
|
||
|
||
readonly property string feeLabel:
|
||
isAssetView ? qsTr("Mint asset on %1").arg(root.token.chainName)
|
||
: qsTr("Mint collectible on %1").arg(root.token.chainName)
|
||
|
||
signal chooseArtWork
|
||
signal previewClicked
|
||
|
||
QtObject {
|
||
id: d
|
||
|
||
readonly property bool isFullyFilled: dropAreaItem.artworkSource.toString().length > 0
|
||
&& nameInput.valid
|
||
&& descriptionInput.valid
|
||
&& symbolInput.valid
|
||
&& (unlimitedSupplyChecker.checked || (!unlimitedSupplyChecker.checked && parseInt(supplyInput.text) > 0))
|
||
&& (!root.isAssetView || (root.isAssetView && assetDecimalsInput.valid))
|
||
&& deployFeeSubscriber.feeText !== "" && deployFeeSubscriber.feeErrorText === ""
|
||
|
||
readonly property int imageSelectorRectWidth: root.isAssetView ? 128 : 290
|
||
|
||
readonly property bool containsAssetReferenceName: root.isAssetView ? checkNameProxy.count > 0 : false
|
||
readonly property SortFilterProxyModel checkNameProxy : SortFilterProxyModel {
|
||
sourceModel: root.referenceAssetsBySymbolModel
|
||
filters: ValueFilter {
|
||
roleName: "name"
|
||
value: nameInput.text
|
||
}
|
||
}
|
||
|
||
readonly property bool containsAssetReferenceSymbol: root.isAssetView ? checkSymbolProxy.count > 0 : false
|
||
readonly property SortFilterProxyModel checkSymbolProxy: SortFilterProxyModel {
|
||
sourceModel: root.referenceAssetsBySymbolModel
|
||
filters: ValueFilter {
|
||
roleName: "symbol"
|
||
value: symbolInput.text
|
||
}
|
||
}
|
||
|
||
function hasEmoji(text) {
|
||
return SQUtils.Emoji.hasEmoji(SQUtils.Emoji.parse(text));
|
||
}
|
||
}
|
||
|
||
padding: 0
|
||
contentWidth: mainLayout.width
|
||
contentHeight: mainLayout.height
|
||
|
||
onVisibleChanged: if (visible) nameInput.forceActiveFocus()
|
||
|
||
ColumnLayout {
|
||
id: mainLayout
|
||
|
||
width: root.viewWidth
|
||
spacing: Style.current.padding
|
||
|
||
StatusBaseText {
|
||
elide: Text.ElideRight
|
||
text: root.isAssetView ? qsTr("Icon") : qsTr("Artwork")
|
||
}
|
||
|
||
DropAndEditImagePanel {
|
||
id: dropAreaItem
|
||
|
||
Layout.fillWidth: true
|
||
Layout.preferredHeight: d.imageSelectorRectWidth
|
||
dataImage: root.token.artworkSource
|
||
artworkSource: root.token.artworkSource
|
||
editorAnchorLeft: false
|
||
editorRoundedImage: root.isAssetView
|
||
uploadTextLabel.uploadText: root.isAssetView ? qsTr("Upload") : qsTr("Drag and Drop or Upload Artwork")
|
||
uploadTextLabel.additionalText: qsTr("Images only")
|
||
uploadTextLabel.showAdditionalInfo: !root.isAssetView
|
||
editorTitle: root.isAssetView ? qsTr("Asset icon") : qsTr("Collectible artwork")
|
||
acceptButtonText: root.isAssetView ? qsTr("Upload asset icon") : qsTr("Upload collectible artwork")
|
||
|
||
onArtworkSourceChanged: root.token.artworkSource = artworkSource
|
||
onArtworkCropRectChanged: root.token.artworkCropRect = artworkCropRect
|
||
}
|
||
|
||
CustomStatusInput {
|
||
id: nameInput
|
||
|
||
label: qsTr("Name")
|
||
text: root.token.name
|
||
charLimit: 15
|
||
placeholderText: qsTr("Name")
|
||
validationMode: root.validationMode
|
||
minLengthValidator.errorMessage: qsTr("Please name your token name (use A-Z and 0-9, hyphens and underscores only)")
|
||
regexValidator.errorMessage: d.hasEmoji(text) ?
|
||
qsTr("Your token name is too cool (use A-Z and 0-9, hyphens and underscores only)") :
|
||
qsTr("Your token name contains invalid characters (use A-Z and 0-9, hyphens and underscores only)")
|
||
extraValidator.validate: function (value) {
|
||
// If minting failed, we can retry same deployment, so same name allowed
|
||
const allowRepeatedName = root.token.deployState === Constants.ContractTransactionStatus.Failed
|
||
if(allowRepeatedName)
|
||
if(nameInput.text === root.referenceName)
|
||
return true
|
||
|
||
// Otherwise, no repeated names allowed:
|
||
return (!SQUtils.ModelUtils.contains(root.tokensModel, "name", nameInput.text, Qt.CaseInsensitive) && !d.containsAssetReferenceName)
|
||
}
|
||
extraValidator.errorMessage: d.containsAssetReferenceName ? qsTr("Asset name already exists") :
|
||
qsTr("You have used this token name before")
|
||
input.tabNavItem: descriptionInput
|
||
onTextChanged: root.token.name = text
|
||
}
|
||
|
||
CustomStatusInput {
|
||
id: descriptionInput
|
||
|
||
label: qsTr("Description")
|
||
text: root.token.description
|
||
charLimit: 280
|
||
placeholderText: root.isAssetView ? qsTr("Describe your asset (will be shown in hodler’s wallets)") : qsTr("Describe your collectible (will be shown in hodler’s wallets)")
|
||
input.multiline: true
|
||
input.verticalAlignment: Qt.AlignTop
|
||
input.placeholder.verticalAlignment: Qt.AlignTop
|
||
minimumHeight: 108
|
||
maximumHeight: minimumHeight
|
||
validationMode: root.validationMode
|
||
minLengthValidator.errorMessage: qsTr("Please enter a token description")
|
||
regexValidator.regularExpression: Constants.regularExpressions.ascii
|
||
regexValidator.errorMessage: qsTr("Only A-Z, 0-9 and standard punctuation allowed")
|
||
input.tabNavItem: symbolInput
|
||
onTextChanged: root.token.description = text
|
||
}
|
||
|
||
CustomStatusInput {
|
||
id: symbolInput
|
||
|
||
label: qsTr("Symbol")
|
||
text: root.token.symbol
|
||
charLimit: 6
|
||
placeholderText: root.isAssetView ? qsTr("e.g. ETH"): qsTr("e.g. DOODLE")
|
||
validationMode: root.validationMode
|
||
minLengthValidator.errorMessage: qsTr("Please enter your token symbol (use A-Z only)")
|
||
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your token symbol is too cool (use A-Z only)") :
|
||
qsTr("Your token symbol contains invalid characters (use A-Z only)")
|
||
regexValidator.regularExpression: Constants.regularExpressions.capitalOnly
|
||
extraValidator.validate: function (value) {
|
||
// If minting failed, we can retry same deployment, so same symbol allowed
|
||
const allowRepeatedName = root.token.deployState === Constants.ContractTransactionStatus.Failed
|
||
if(allowRepeatedName)
|
||
if(symbolInput.text.toUpperCase() === root.referenceSymbol.toUpperCase())
|
||
return true
|
||
|
||
// Otherwise, no repeated names allowed:
|
||
return (!SQUtils.ModelUtils.contains(root.tokensModel, "symbol", symbolInput.text) && !d.containsAssetReferenceSymbol)
|
||
}
|
||
extraValidator.errorMessage: d.containsAssetReferenceSymbol ? qsTr("Symbol already exists") : qsTr("You have used this token symbol before")
|
||
input.tabNavItem: supplyInput.visible ? supplyInput : assetDecimalsInput
|
||
|
||
onTextChanged: {
|
||
const cursorPos = input.edit.cursorPosition
|
||
const upperSymbol = text.toUpperCase()
|
||
root.token.symbol = upperSymbol
|
||
text = upperSymbol // breaking the binding on purpose but so does validate() and onTextChanged() internal handler
|
||
input.edit.cursorPosition = cursorPos
|
||
}
|
||
}
|
||
|
||
StatusBaseText {
|
||
text: qsTr("Network")
|
||
color: Theme.palette.directColor1
|
||
}
|
||
|
||
Rectangle {
|
||
Layout.preferredHeight: 44
|
||
Layout.fillWidth: true
|
||
radius: 8
|
||
color: "transparent"
|
||
border.color: Theme.palette.directColor7
|
||
|
||
RowLayout {
|
||
id: networkRow
|
||
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
spacing: 16
|
||
|
||
StatusSmartIdenticon {
|
||
Layout.alignment: Qt.AlignVCenter
|
||
Layout.leftMargin: Style.current.padding
|
||
|
||
asset.height: 24
|
||
asset.width: asset.height
|
||
asset.isImage: true
|
||
asset.name: Style.svg(token.chainIcon)
|
||
active: true
|
||
visible: active
|
||
}
|
||
|
||
StatusBaseText {
|
||
Layout.alignment: Qt.AlignVCenter
|
||
Layout.fillWidth: true
|
||
Layout.rightMargin: Style.current.padding
|
||
|
||
font.pixelSize: 13
|
||
font.weight: Font.Medium
|
||
elide: Text.ElideRight
|
||
lineHeight: 24
|
||
lineHeightMode: Text.FixedHeight
|
||
verticalAlignment: Text.AlignVCenter
|
||
text: token.chainName
|
||
color: Theme.palette.baseColor1
|
||
visible: !!text
|
||
}
|
||
}
|
||
}
|
||
|
||
CustomSwitchRowComponent {
|
||
id: unlimitedSupplyChecker
|
||
|
||
label: qsTr("Unlimited supply")
|
||
description: qsTr("Enable to allow the minting of additional tokens in the future. Disable to specify a finite supply")
|
||
checked: root.token.infiniteSupply
|
||
|
||
onCheckedChanged: {
|
||
if(!checked) supplyInput.forceActiveFocus()
|
||
|
||
root.token.infiniteSupply = checked
|
||
}
|
||
}
|
||
|
||
CustomStatusInput {
|
||
id: supplyInput
|
||
|
||
visible: !unlimitedSupplyChecker.checked
|
||
label: qsTr("Total finite supply")
|
||
text: SQUtils.AmountsArithmetic.toNumber(root.token.supply,
|
||
root.token.multiplierIndex)
|
||
|
||
placeholderText: qsTr("e.g. 300")
|
||
minLengthValidator.errorMessage: qsTr("Please enter a total finite supply")
|
||
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your total finite supply is too cool (use 0-9 only)") :
|
||
qsTr("Your total finite supply contains invalid characters (use 0-9 only)")
|
||
regexValidator.regularExpression: Constants.regularExpressions.numerical
|
||
extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 999999999 }
|
||
extraValidator.errorMessage: qsTr("Enter a number between 1 and 999,999,999")
|
||
input.tabNavItem: assetDecimalsInput.visible ? assetDecimalsInput : previewButton
|
||
|
||
onTextChanged: {
|
||
const supplyNumber = parseInt(text)
|
||
if (Number.isNaN(supplyNumber) || Object.values(errors).length)
|
||
return
|
||
|
||
token.supply = SQUtils.AmountsArithmetic.fromNumber(
|
||
supplyNumber, root.token.multiplierIndex).toFixed(0)
|
||
}
|
||
}
|
||
|
||
CustomSwitchRowComponent {
|
||
id: transferableChecker
|
||
|
||
visible: !root.isAssetView
|
||
label: checked ? qsTr("Not transferable (Soulbound)") : qsTr("Transferable")
|
||
description: qsTr("If enabled, the token is locked to the first address it is sent to and can never be transferred to another address. Useful for tokens that represent Admin permissions")
|
||
checked: !root.token.transferable
|
||
|
||
onCheckedChanged: root.token.transferable = !checked
|
||
}
|
||
|
||
CustomSwitchRowComponent {
|
||
id: remotelyDestructChecker
|
||
|
||
visible: !root.isAssetView
|
||
label: qsTr("Remotely destructible")
|
||
description: qsTr("Enable to allow you to destroy tokens remotely. Useful for revoking permissions from individuals")
|
||
checked: !!root.token ? root.token.remotelyDestruct : true
|
||
onCheckedChanged: root.token.remotelyDestruct = checked
|
||
}
|
||
|
||
CustomStatusInput {
|
||
id: assetDecimalsInput
|
||
|
||
visible: root.isAssetView
|
||
label: qsTr("Decimals (DP)")
|
||
charLimit: 2
|
||
charLimitLabel: qsTr("Max 10")
|
||
placeholderText: "2"
|
||
text: root.token.decimals
|
||
validationMode: StatusInput.ValidationMode.Always
|
||
minLengthValidator.errorMessage: qsTr("Please enter how many decimals your token should have")
|
||
regexValidator.errorMessage: d.hasEmoji(text) ? qsTr("Your decimal amount is too cool (use 0-9 only)") :
|
||
qsTr("Your decimal amount contains invalid characters (use 0-9 only)")
|
||
regexValidator.regularExpression: Constants.regularExpressions.numerical
|
||
extraValidator.validate: function (value) { return parseInt(value) > 0 && parseInt(value) <= 10 }
|
||
extraValidator.errorMessage: qsTr("Enter a number between 1 and 10")
|
||
input.tabNavItem: previewButton
|
||
onTextChanged: root.token.decimals = parseInt(text)
|
||
}
|
||
|
||
FeesBox {
|
||
id: feesBox
|
||
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: Style.current.padding
|
||
|
||
accountErrorText: root.feeErrorText
|
||
implicitWidth: 0
|
||
|
||
model: QtObject {
|
||
id: singleFeeModel
|
||
|
||
readonly property string title: root.feeLabel
|
||
readonly property string feeText: root.isFeeLoading ?
|
||
"" : root.feeText
|
||
readonly property bool error: root.feeErrorText !== ""
|
||
}
|
||
|
||
accountsSelector.model: root.accounts
|
||
accountsSelector.selectedAddress: root.token.accountAddress
|
||
|
||
Binding {
|
||
target: root.token
|
||
property: "accountAddress"
|
||
value: feesBox.accountsSelector.currentAccountAddress
|
||
}
|
||
|
||
Binding {
|
||
target: root.token
|
||
property: "accountName"
|
||
value: feesBox.accountsSelector.currentAccount.name
|
||
}
|
||
}
|
||
|
||
StatusButton {
|
||
id: previewButton
|
||
Layout.preferredHeight: 44
|
||
Layout.alignment: Qt.AlignHCenter
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: Style.current.padding
|
||
Layout.bottomMargin: Style.current.padding
|
||
text: qsTr("Preview")
|
||
enabled: d.isFullyFilled
|
||
highlighted: visualFocus
|
||
|
||
onClicked: root.previewClicked()
|
||
}
|
||
}
|
||
|
||
// Inline components definition:
|
||
component CustomStatusInput: StatusInput {
|
||
id: customInput
|
||
|
||
property alias minLengthValidator: minLengthValidatorItem
|
||
property alias regexValidator: regexValidatorItem
|
||
property alias extraValidator: extraValidatorItem
|
||
|
||
Layout.fillWidth: true
|
||
validators: [
|
||
StatusMinLengthValidator {
|
||
id: minLengthValidatorItem
|
||
minLength: 1
|
||
},
|
||
StatusRegularExpressionValidator {
|
||
id: regexValidatorItem
|
||
regularExpression: Constants.regularExpressions.alphanumericalExpanded
|
||
onErrorMessageChanged: {
|
||
customInput.validate();
|
||
}
|
||
},
|
||
StatusValidator {
|
||
id: extraValidatorItem
|
||
onErrorMessageChanged: {
|
||
customInput.validate();
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
component CustomLabelDescriptionComponent: ColumnLayout {
|
||
id: labelDescComponent
|
||
|
||
property string label
|
||
property string description
|
||
|
||
Layout.fillWidth: true
|
||
|
||
StatusBaseText {
|
||
text: labelDescComponent.label
|
||
color: Theme.palette.directColor1
|
||
}
|
||
|
||
StatusBaseText {
|
||
Layout.fillWidth: true
|
||
Layout.fillHeight: true
|
||
text: labelDescComponent.description
|
||
color: Theme.palette.baseColor1
|
||
lineHeight: 1.2
|
||
wrapMode: Text.WordWrap
|
||
}
|
||
}
|
||
|
||
component CustomSwitchRowComponent: RowLayout {
|
||
id: rowComponent
|
||
|
||
property string label
|
||
property string description
|
||
property alias checked: switch_.checked
|
||
|
||
Layout.fillWidth: true
|
||
Layout.topMargin: Style.current.padding
|
||
spacing: 64
|
||
|
||
CustomLabelDescriptionComponent {
|
||
label: rowComponent.label
|
||
description: rowComponent.description
|
||
}
|
||
|
||
StatusSwitch {
|
||
id: switch_
|
||
}
|
||
}
|
||
}
|