feat(CommunityPermissions): prevent adding tokens and ens names that are already chosen
Closes: #8817
This commit is contained in:
parent
25bb970ea4
commit
9ac0c159fe
|
@ -14,12 +14,16 @@ ColumnLayout {
|
|||
property alias domainNameValid: domainNameInput.valid
|
||||
property alias addButtonEnabled: addOrUpdateButton.enabled
|
||||
|
||||
property var reservedNames: []
|
||||
|
||||
signal addClicked
|
||||
signal updateClicked
|
||||
signal removeClicked
|
||||
|
||||
spacing: 0
|
||||
|
||||
onReservedNamesChanged: domainNameInput.validate()
|
||||
|
||||
StatusInput {
|
||||
id: domainNameInput
|
||||
|
||||
|
@ -33,13 +37,22 @@ ColumnLayout {
|
|||
font.pixelSize: 13
|
||||
input.placeholderText: "name.eth"
|
||||
|
||||
errorMessageCmp.visible: false
|
||||
|
||||
validators: StatusRegularExpressionValidator {
|
||||
// TODO: check ens domain validator
|
||||
regularExpression: /^(\*\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?$/i
|
||||
errorMessage: qsTr("Subdomain not recognized")
|
||||
errorMessage: alreadyUsed
|
||||
? qsTr("This condition has already been added")
|
||||
: qsTr("This is not ENS name")
|
||||
|
||||
property bool alreadyUsed: false
|
||||
|
||||
validate: function (value) {
|
||||
return value === "*.eth" || regularExpression.test(value)
|
||||
alreadyUsed = reservedNames.includes(value)
|
||||
|
||||
return (value === "*.eth" || regularExpression.test(value))
|
||||
&& !alreadyUsed
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,11 +77,34 @@ ColumnLayout {
|
|||
lineHeightMode: Text.FixedHeight
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.topMargin: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.max(18, errorText.height) // by design
|
||||
|
||||
StatusBaseText {
|
||||
id: errorText
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
text: domainNameInput.errorMessageCmp.text
|
||||
|
||||
font.pixelSize: 13
|
||||
lineHeight: 18
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: Theme.palette.dangerColor1
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
id: addOrUpdateButton
|
||||
|
||||
text: (root.mode === HoldingTypes.Mode.Add) ? qsTr("Add") : qsTr("Update")
|
||||
Layout.topMargin: 40
|
||||
Layout.topMargin: 13 // by design
|
||||
Layout.preferredHeight: 44 // by design
|
||||
Layout.fillWidth: true
|
||||
onClicked: root.mode === HoldingTypes.Mode.Add
|
||||
|
@ -80,7 +116,7 @@ ColumnLayout {
|
|||
Layout.topMargin: 16 // by design
|
||||
Layout.preferredHeight: 44 // by design
|
||||
Layout.fillWidth: true
|
||||
visible: root.mode === HoldingTypes.Mode.Update
|
||||
visible: root.mode === HoldingTypes.Mode.UpdateOrRemove
|
||||
type: StatusBaseButton.Type.Danger
|
||||
|
||||
onClicked: root.removeClicked()
|
||||
|
|
|
@ -16,6 +16,7 @@ Item {
|
|||
id: root
|
||||
|
||||
property var store
|
||||
property var checkedKeys: []
|
||||
property int type: ExtendedDropdownContent.Type.Assets
|
||||
|
||||
readonly property bool canGoBack: root.state !== d.listView_depth1_State
|
||||
|
@ -272,10 +273,14 @@ Item {
|
|||
|
||||
TokenItem {
|
||||
id: tokenGroupItem
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
key: d.currentItemKey
|
||||
name: d.currentItemName
|
||||
iconSource: d.currentItemSource
|
||||
|
||||
selected: root.checkedKeys.includes(key)
|
||||
enabled: true
|
||||
onItemClicked: root.itemClicked(d.currentItemKey,
|
||||
d.currentItemName,
|
||||
|
@ -301,6 +306,8 @@ Item {
|
|||
}
|
||||
isHeaderVisible: false // TEMPORARILY hidden. These 2 header options will be implemented after MVP.
|
||||
model: d.currentModel
|
||||
checkedKeys: root.checkedKeys
|
||||
|
||||
onHeaderItemClicked: {
|
||||
if(key === "MINT") console.log("TODO: Mint asset")
|
||||
else if(key === "IMPORT") console.log("TODO: Import existing asset")
|
||||
|
@ -319,6 +326,7 @@ Item {
|
|||
}
|
||||
|
||||
model: d.currentModel
|
||||
checkedKeys: root.checkedKeys
|
||||
|
||||
onHeaderItemClicked: {
|
||||
if(key === "MINT") console.log("TODO: Mint collectible")
|
||||
|
@ -351,6 +359,8 @@ Item {
|
|||
padding: 0
|
||||
|
||||
model: d.currentModel
|
||||
checkedKeys: root.checkedKeys
|
||||
|
||||
onItemClicked: {
|
||||
d.reset()
|
||||
root.itemClicked(key, name, iconSource)
|
||||
|
|
|
@ -6,6 +6,6 @@ QtObject {
|
|||
}
|
||||
|
||||
enum Mode {
|
||||
Add, Update
|
||||
Add, Update, UpdateOrRemove
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ StatusDropdown {
|
|||
id: root
|
||||
|
||||
property var store
|
||||
property var usedTokens: []
|
||||
property var usedEnsNames: []
|
||||
|
||||
property string assetKey: ""
|
||||
property real assetAmount: 0
|
||||
|
@ -37,7 +39,7 @@ StatusDropdown {
|
|||
}
|
||||
|
||||
function openUpdateFlow() {
|
||||
d.currentHoldingMode = HoldingTypes.Mode.Update
|
||||
d.initialHoldingMode = HoldingTypes.Mode.UpdateOrRemove
|
||||
if(d.currentHoldingType !== HoldingTypes.Type.Ens) {
|
||||
if(statesStack.size === 0)
|
||||
statesStack.push(HoldingsDropdown.FlowType.List_Deep1)
|
||||
|
@ -53,16 +55,13 @@ StatusDropdown {
|
|||
|
||||
function reset() {
|
||||
d.currentHoldingType = HoldingTypes.Type.Asset
|
||||
d.currentHoldingMode = HoldingTypes.Mode.Add
|
||||
d.initialHoldingMode = HoldingTypes.Mode.Add
|
||||
|
||||
d.assetAmountText = ""
|
||||
d.collectibleAmountText = ""
|
||||
root.assetKey = ""
|
||||
root.collectibleKey = ""
|
||||
root.assetAmount = 0
|
||||
root.collectibleAmount = 1
|
||||
root.ensDomainName = ""
|
||||
|
||||
d.setDefaultAmounts()
|
||||
d.setInitialFlow()
|
||||
}
|
||||
|
||||
|
@ -79,7 +78,14 @@ StatusDropdown {
|
|||
|
||||
property int extendedDropdownType: ExtendedDropdownContent.Type.Assets
|
||||
property int currentHoldingType: HoldingTypes.Type.Asset
|
||||
property int currentHoldingMode: HoldingTypes.Mode.Add
|
||||
|
||||
property bool updateSelected: false
|
||||
|
||||
property int initialHoldingMode: HoldingTypes.Mode.Add
|
||||
property int effectiveHoldingMode: initialHoldingMode === HoldingTypes.Mode.UpdateOrRemove
|
||||
? HoldingTypes.Mode.UpdateOrRemove
|
||||
: (updateSelected ? HoldingTypes.Mode.Update : HoldingTypes.Mode.Add)
|
||||
|
||||
property bool extendedDeepNavigation: false
|
||||
property var currentSubItems
|
||||
property string currentItemKey: ""
|
||||
|
@ -105,6 +111,13 @@ StatusDropdown {
|
|||
else
|
||||
statesStack.push(HoldingsDropdown.FlowType.Selected)
|
||||
}
|
||||
|
||||
function setDefaultAmounts() {
|
||||
d.assetAmountText = ""
|
||||
d.collectibleAmountText = ""
|
||||
root.assetAmount = 0
|
||||
root.collectibleAmount = 1
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
|
@ -236,9 +249,27 @@ StatusDropdown {
|
|||
id: listPanel
|
||||
|
||||
store: root.store
|
||||
checkedKeys: root.usedTokens.map(entry => entry.key)
|
||||
type: d.extendedDropdownType
|
||||
|
||||
onItemClicked: {
|
||||
d.assetAmountText = ""
|
||||
d.collectibleAmountText = ""
|
||||
|
||||
if (checkedKeys.includes(key)) {
|
||||
const amount = root.usedTokens.find(entry => entry.key === key).amount
|
||||
|
||||
if(d.extendedDropdownType === ExtendedDropdownContent.Type.Assets)
|
||||
root.assetAmount = amount
|
||||
else
|
||||
root.collectibleAmount = amount
|
||||
|
||||
d.updateSelected = true
|
||||
} else {
|
||||
d.setDefaultAmounts()
|
||||
d.updateSelected = false
|
||||
}
|
||||
|
||||
if(d.extendedDropdownType === ExtendedDropdownContent.Type.Assets)
|
||||
root.assetKey = key
|
||||
else
|
||||
|
@ -293,7 +324,7 @@ StatusDropdown {
|
|||
amountText: d.assetAmountText
|
||||
tokenCategoryText: qsTr("Asset")
|
||||
addOrUpdateButtonEnabled: d.assetsReady
|
||||
mode: d.currentHoldingMode
|
||||
mode: d.effectiveHoldingMode
|
||||
|
||||
onEffectiveAmountChanged: root.assetAmount = effectiveAmount
|
||||
onAmountTextChanged: d.assetAmountText = amountText
|
||||
|
@ -329,7 +360,7 @@ StatusDropdown {
|
|||
tokenCategoryText: qsTr("Collectible")
|
||||
addOrUpdateButtonEnabled: d.collectiblesReady
|
||||
allowDecimals: false
|
||||
mode: d.currentHoldingMode
|
||||
mode: d.effectiveHoldingMode
|
||||
|
||||
onEffectiveAmountChanged: root.collectibleAmount = effectiveAmount
|
||||
onAmountTextChanged: d.collectibleAmountText = amountText
|
||||
|
@ -356,7 +387,8 @@ StatusDropdown {
|
|||
EnsPanel {
|
||||
addButtonEnabled: d.ensReady
|
||||
domainName: root.ensDomainName
|
||||
mode: d.currentHoldingMode
|
||||
mode: d.initialHoldingMode
|
||||
reservedNames: root.usedEnsNames
|
||||
|
||||
onDomainNameChanged: root.ensDomainName = domainName
|
||||
onDomainNameValidChanged: d.ensDomainNameValid = domainNameValid
|
||||
|
|
|
@ -12,6 +12,8 @@ import StatusQ.Components 0.1
|
|||
StatusListView {
|
||||
id: root
|
||||
|
||||
property var checkedKeys: []
|
||||
|
||||
property var headerModel
|
||||
property bool isHeaderVisible: true
|
||||
property int maxHeight: 381 // default by design
|
||||
|
@ -60,7 +62,8 @@ StatusListView {
|
|||
shortName: !!model.shortName ? model.shortName : ""
|
||||
iconSource: model.iconSource
|
||||
subItems: model.subItems
|
||||
enabled: true
|
||||
selected: root.checkedKeys.includes(model.key)
|
||||
|
||||
onItemClicked: root.itemClicked(model.key,
|
||||
model.name,
|
||||
model.shortName,
|
||||
|
|
|
@ -13,6 +13,8 @@ StatusScrollView {
|
|||
property url titleImage: ""
|
||||
property string subtitle: ""
|
||||
property ListModel model
|
||||
property var checkedKeys: []
|
||||
|
||||
property int maxHeight: 381 // default by design
|
||||
|
||||
signal itemClicked(var key, string name, url iconSource)
|
||||
|
@ -47,6 +49,29 @@ StatusScrollView {
|
|||
Image {
|
||||
source: model.imageSource ? model.imageSource : ""
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
width: 32
|
||||
height: 32
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 8
|
||||
|
||||
radius: width / 2
|
||||
visible: root.checkedKeys.includes(model.key)
|
||||
// TODO: use color from theme when defined properly in the design
|
||||
color: "#F5F6F8"
|
||||
|
||||
StatusIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "checkmark"
|
||||
|
||||
color: Theme.palette.baseColor1
|
||||
width: 16
|
||||
height: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
|
|
@ -15,6 +15,7 @@ Control {
|
|||
property string shortName
|
||||
property url iconSource
|
||||
property var subItems
|
||||
property bool selected: false
|
||||
|
||||
signal itemClicked(string key, string name, string shortName, url iconSource, var subItems)
|
||||
|
||||
|
@ -71,8 +72,10 @@ Control {
|
|||
}
|
||||
|
||||
StatusIcon {
|
||||
icon: "tiny/chevron-right"
|
||||
visible: !!root.subItems && root.subItems.count > 0
|
||||
readonly property bool hasSubItems: !!root.subItems && root.subItems.count > 0
|
||||
|
||||
icon: root.selected && !hasSubItems ? "checkmark" : "tiny/chevron-right"
|
||||
visible: root.selected || hasSubItems
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: 16
|
||||
color: Theme.palette.baseColor1
|
||||
|
|
|
@ -83,7 +83,7 @@ ColumnLayout {
|
|||
Layout.preferredHeight: d.defaultHeight
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: d.defaultSpacing
|
||||
visible: root.mode === HoldingTypes.Mode.Update
|
||||
visible: root.mode === HoldingTypes.Mode.UpdateOrRemove
|
||||
type: StatusBaseButton.Type.Danger
|
||||
|
||||
onClicked: root.removeClicked()
|
||||
|
|
|
@ -87,6 +87,48 @@ StatusScrollView {
|
|||
}
|
||||
property bool isPrivateDirty: false
|
||||
|
||||
function getIndexOfKey(key) {
|
||||
const count = holdingsModel.count
|
||||
|
||||
for (let i = 0; i < count; i++)
|
||||
if (holdingsModel.get(i).key === key)
|
||||
return i
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
function getTokenKeysAndAmounts() {
|
||||
const keysAndAmounts = []
|
||||
const count = holdingsModel.count
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const item = holdingsModel.get(i)
|
||||
|
||||
if (item.type === HoldingTypes.Type.Ens)
|
||||
continue
|
||||
|
||||
keysAndAmounts.push({ key: item.key, amount: item.amount })
|
||||
}
|
||||
|
||||
return keysAndAmounts
|
||||
}
|
||||
|
||||
function getEnsNames() {
|
||||
const names = []
|
||||
const count = holdingsModel.count
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const item = holdingsModel.get(i)
|
||||
|
||||
if (item.type !== HoldingTypes.Type.Ens)
|
||||
continue
|
||||
|
||||
names.push(item.name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// TODO: Channels
|
||||
}
|
||||
|
||||
|
@ -195,7 +237,7 @@ StatusScrollView {
|
|||
StatusItemSelector {
|
||||
id: tokensSelector
|
||||
|
||||
property int editedIndex
|
||||
property int editedIndex: -1
|
||||
|
||||
Layout.fillWidth: true
|
||||
icon: Style.svg("contact_verified")
|
||||
|
@ -227,6 +269,28 @@ StatusScrollView {
|
|||
d.dirtyValues.holdingsModel.append({ type, key, name, amount, imageSource })
|
||||
}
|
||||
|
||||
function prepareUpdateIndex(key) {
|
||||
const itemIndex = tokensSelector.editedIndex
|
||||
const existingIndex = d.dirtyValues.getIndexOfKey(key)
|
||||
|
||||
if (itemIndex !== -1 && existingIndex !== -1 && itemIndex !== existingIndex) {
|
||||
const previousKey = d.dirtyValues.holdingsModel.get(itemIndex).key
|
||||
d.dirtyValues.holdingsModel.remove(existingIndex)
|
||||
return d.dirtyValues.getIndexOfKey(previousKey)
|
||||
}
|
||||
|
||||
if (itemIndex === -1) {
|
||||
return existingIndex
|
||||
}
|
||||
|
||||
return itemIndex
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
usedTokens = d.dirtyValues.getTokenKeysAndAmounts()
|
||||
usedEnsNames = d.dirtyValues.getEnsNames().filter(item => item !== ensDomainName)
|
||||
}
|
||||
|
||||
onAddAsset: {
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(store.assetsModel, key)
|
||||
addItem(HoldingTypes.Type.Asset, modelItem, amount)
|
||||
|
@ -240,7 +304,7 @@ StatusScrollView {
|
|||
}
|
||||
|
||||
onAddEns: {
|
||||
const key = "ENS"
|
||||
const key = "ENS_" + domain
|
||||
const icon = Style.svg("profile/ensUsernames")
|
||||
|
||||
d.dirtyValues.holdingsModel.append({type: HoldingTypes.Type.Ens, key, name: domain, amount: 1, imageSource: icon })
|
||||
|
@ -248,27 +312,31 @@ StatusScrollView {
|
|||
}
|
||||
|
||||
onUpdateAsset: {
|
||||
const itemIndex = prepareUpdateIndex(key)
|
||||
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(store.assetsModel, key)
|
||||
const name = modelItem.shortName ? modelItem.shortName : modelItem.name
|
||||
const imageSource = modelItem.iconSource.toString()
|
||||
|
||||
d.dirtyValues.holdingsModel.set(tokensSelector.editedIndex, { type: HoldingTypes.Type.Asset, key, name, amount, imageSource })
|
||||
d.dirtyValues.holdingsModel.set(itemIndex, { type: HoldingTypes.Type.Asset, key, name, amount, imageSource })
|
||||
d.triggerDirtyTool = !d.triggerDirtyTool
|
||||
dropdown.close()
|
||||
}
|
||||
|
||||
onUpdateCollectible: {
|
||||
const itemIndex = prepareUpdateIndex(key)
|
||||
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(store.collectiblesModel, key)
|
||||
const name = modelItem.name
|
||||
const imageSource = modelItem.iconSource.toString()
|
||||
|
||||
d.dirtyValues.holdingsModel.set(tokensSelector.editedIndex, { type: HoldingTypes.Type.Collectible, key, name, amount, imageSource })
|
||||
d.dirtyValues.holdingsModel.set(itemIndex, { type: HoldingTypes.Type.Collectible, key, name, amount, imageSource })
|
||||
d.triggerDirtyTool = !d.triggerDirtyTool
|
||||
dropdown.close()
|
||||
}
|
||||
|
||||
onUpdateEns: {
|
||||
const key = "ENS"
|
||||
const key = "ENS_" + domain
|
||||
const icon = Style.svg("profile/ensUsernames")
|
||||
|
||||
d.dirtyValues.holdingsModel.set(tokensSelector.editedIndex, { type: HoldingTypes.Type.Ens, key, name: domain, amount: 1, imageSource: icon })
|
||||
|
@ -287,6 +355,8 @@ StatusScrollView {
|
|||
dropdown.x = tokensSelector.addButton.width + d.dropdownHorizontalOffset
|
||||
dropdown.y = 0
|
||||
dropdown.open()
|
||||
|
||||
editedIndex = -1
|
||||
}
|
||||
|
||||
onItemClicked: {
|
||||
|
|
Loading…
Reference in New Issue