refactor: remove wei2Token in favour of wei2Eth. Essentially de-duplicated very similar procs and lessened errors
fix: SignTransactionModal - set default focused account when none is found refactor: move token lookup from QML to nim in the toMessage procedure. fix: 1:1 tx requests - handle case where token contract is not found (ie sending SNT from mainnet and receiving message on testnet) feat: error checking for building a token transaction feat: TransactionPreview - add a validation check that disallows continuation if the selected "from" account has insufficient funds
This commit is contained in:
parent
41e5626bfa
commit
e455586990
|
@ -46,12 +46,6 @@ QtObject:
|
|||
let uintValue = status_utils.eth2Wei(parseFloat(eth), decimals)
|
||||
return uintValue.toString()
|
||||
|
||||
proc wei2Token*(self: UtilsView, wei: string, decimals: int): string {.slot.} =
|
||||
var weiValue = wei
|
||||
if(weiValue.startsWith("0x")):
|
||||
weiValue = fromHex(Stuint[256], weiValue).toString()
|
||||
return status_utils.wei2Token(weiValue, decimals)
|
||||
|
||||
proc getStickerMarketAddress(self: UtilsView): string {.slot.} =
|
||||
$self.status.stickers.getStickerMarketAddress
|
||||
|
||||
|
|
|
@ -156,8 +156,8 @@ QtObject:
|
|||
proc setFocusedAccountByAddress*(self: WalletView, address: string) {.slot.} =
|
||||
if(self.accounts.rowCount() == 0): return
|
||||
|
||||
let index = self.accounts.getAccountindexByAddress(address)
|
||||
if index == -1: return
|
||||
var index = self.accounts.getAccountindexByAddress(address)
|
||||
if index == -1: index = 0
|
||||
let selectedAccount = self.accounts.getAccount(index)
|
||||
if self.focusedAccount.address == selectedAccount.address: return
|
||||
self.focusedAccount.setAccountItem(selectedAccount)
|
||||
|
|
|
@ -70,7 +70,7 @@ type
|
|||
symbol*: string
|
||||
hasIcon*: bool
|
||||
|
||||
proc newErc20Contract(name: string, network: Network, address: Address, symbol: string, decimals: int, hasIcon: bool): Erc20Contract =
|
||||
proc newErc20Contract*(name: string, network: Network, address: Address, symbol: string, decimals: int, hasIcon: bool): Erc20Contract =
|
||||
Erc20Contract(name: name, network: network, address: address, methods: ERC20_METHODS.toTable, symbol: symbol, decimals: decimals, hasIcon: hasIcon)
|
||||
|
||||
proc newErc721Contract(name: string, network: Network, address: Address, symbol: string, hasIcon: bool, addlMethods: seq[tuple[name: string, meth: Method]] = @[]): Erc721Contract =
|
||||
|
|
|
@ -53,27 +53,28 @@ proc eth2Wei*(eth: float, decimals: int = 18): UInt256 =
|
|||
proc gwei2Wei*(gwei: float): UInt256 =
|
||||
eth2Wei(gwei, 9)
|
||||
|
||||
proc wei2Eth*(input: Stuint[256]): string =
|
||||
var one_eth = fromHex(Stuint[256], "DE0B6B3A7640000")
|
||||
proc wei2Eth*(input: Stuint[256], decimals: int = 18): string =
|
||||
var one_eth = u256(10).pow(decimals) # fromHex(Stuint[256], "DE0B6B3A7640000")
|
||||
|
||||
var (eth, remainder) = divmod(input, one_eth)
|
||||
let leading_zeros = "0".repeat(($one_eth).len - ($remainder).len - 1)
|
||||
|
||||
fmt"{eth}.{leading_zeros}{remainder}"
|
||||
|
||||
proc wei2Token*(input: string, decimals: int): string =
|
||||
proc wei2Eth*(input: string, decimals: int): string =
|
||||
try:
|
||||
var value = input.parse(Stuint[256])
|
||||
var p = u256(10).pow(decimals)
|
||||
var i = value.div(p)
|
||||
var r = value.mod(p)
|
||||
var leading_zeros = "0".repeat(decimals - ($r).len)
|
||||
var d = fmt"{leading_zeros}{$r}"
|
||||
result = $i
|
||||
if(r > 0): result = fmt"{result}.{d}"
|
||||
result. trimZeros()
|
||||
var input256: Stuint[256]
|
||||
if input.contains("e+"): # we have a js string BN, ie 1e+21
|
||||
let
|
||||
inputSplit = input.split("e+")
|
||||
whole = inputSplit[0].u256
|
||||
remainder = u256(10).pow(inputSplit[1].parseInt)
|
||||
input256 = whole * remainder
|
||||
else:
|
||||
input256 = input.u256
|
||||
result = wei2Eth(input256, decimals)
|
||||
except Exception as e:
|
||||
error "Error parsing this wei value", input
|
||||
error "Error parsing this wei value", input, msg=e.msg
|
||||
result = "0"
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import json, random, strutils, sequtils, sugar, chronicles
|
||||
import json_serialization
|
||||
import ../libstatus/accounts as status_accounts
|
||||
import ../libstatus/accounts/constants as constants
|
||||
import ../libstatus/settings as status_settings
|
||||
import ../libstatus/tokens as status_tokens
|
||||
import ../libstatus/types as status_types
|
||||
import ../libstatus/eth/contracts as status_contracts
|
||||
import ../chat/[chat, message]
|
||||
import ../profile/[profile, devices]
|
||||
import types
|
||||
import web3/conversions
|
||||
from ../libstatus/utils import parseAddress, wei2Eth
|
||||
|
||||
proc toMessage*(jsonMsg: JsonNode): Message
|
||||
|
||||
|
@ -201,13 +206,22 @@ proc toMessage*(jsonMsg: JsonNode): Message =
|
|||
message.stickerHash = jsonMsg["sticker"]["hash"].getStr
|
||||
|
||||
if message.contentType == ContentType.Transaction:
|
||||
let
|
||||
allContracts = getErc20Contracts().concat(getCustomTokens())
|
||||
ethereum = newErc20Contract("Ethereum", Network.Mainnet, parseAddress(constants.ZERO_ADDRESS), "ETH", 18, true)
|
||||
tokenAddress = jsonMsg["commandParameters"]["contract"].getStr
|
||||
tokenContract = if tokenAddress == "": ethereum else: allContracts.getErc20ContractByAddress(parseAddress(tokenAddress))
|
||||
tokenContractStr = if tokenContract == nil: "{}" else: $(Json.encode(tokenContract))
|
||||
var weiStr = if tokenContract == nil: "0" else: wei2Eth(jsonMsg["commandParameters"]["value"].getStr, tokenContract.decimals)
|
||||
weiStr.trimZeros()
|
||||
|
||||
# TODO find a way to use json_seralization for this. When I try, I get an error
|
||||
message.commandParameters = CommandParameters(
|
||||
id: jsonMsg["commandParameters"]["id"].getStr,
|
||||
fromAddress: jsonMsg["commandParameters"]["from"].getStr,
|
||||
address: jsonMsg["commandParameters"]["address"].getStr,
|
||||
contract: jsonMsg["commandParameters"]["contract"].getStr,
|
||||
value: jsonMsg["commandParameters"]["value"].getStr,
|
||||
contract: tokenContractStr,
|
||||
value: weiStr,
|
||||
transactionHash: jsonMsg["commandParameters"]["transactionHash"].getStr,
|
||||
commandState: jsonMsg["commandParameters"]["commandState"].getInt,
|
||||
signature: jsonMsg["commandParameters"]["signature"].getStr
|
||||
|
|
|
@ -64,6 +64,8 @@ proc delete*(self: WalletModel) =
|
|||
|
||||
proc buildTokenTransaction(self: WalletModel, source, to, assetAddress: Address, value: float, transfer: var Transfer, contract: var Erc20Contract, gas = "", gasPrice = ""): EthSend =
|
||||
contract = getErc20Contract(assetAddress)
|
||||
if contract == nil:
|
||||
raise newException(ValueError, fmt"Could not find ERC-20 contract with address '{assetAddress}' for the current network")
|
||||
transfer = Transfer(to: to, value: eth2Wei(value, contract.decimals))
|
||||
transactions.buildTokenTransaction(source, assetAddress, gas, gasPrice)
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ ModalPopup {
|
|||
property var selectedAsset
|
||||
property var selectedAmount
|
||||
property var selectedFiatAmount
|
||||
property bool outgoing: true
|
||||
|
||||
property string trxData: ""
|
||||
|
||||
|
@ -61,6 +62,7 @@ ModalPopup {
|
|||
|
||||
onClosed: {
|
||||
stack.reset()
|
||||
stack.pop(groupPreview, StackView.Immediate)
|
||||
}
|
||||
|
||||
TransactionStackView {
|
||||
|
@ -197,6 +199,7 @@ ModalPopup {
|
|||
onNextClicked: function() {
|
||||
stack.push(groupSignTx, StackView.Immediate)
|
||||
}
|
||||
isValid: groupSelectAcct.isValid && groupSelectGas.isValid && gasValidator.isValid && pvwTransaction.isValid
|
||||
|
||||
TransactionPreview {
|
||||
id: pvwTransaction
|
||||
|
@ -211,6 +214,7 @@ ModalPopup {
|
|||
asset: root.selectedAsset
|
||||
amount: { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount }
|
||||
currency: walletModel.defaultCurrency
|
||||
outgoing: root.outgoing
|
||||
reset: function() {
|
||||
fromAccount = Qt.binding(function() { return root.selectedAccount })
|
||||
gas = Qt.binding(function() {
|
||||
|
@ -224,8 +228,25 @@ ModalPopup {
|
|||
asset = Qt.binding(function() { return root.selectedAsset })
|
||||
amount = Qt.binding(function() { return { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount } })
|
||||
}
|
||||
onFromClicked: stack.push(groupSelectAcct, StackView.Immediate)
|
||||
onGasClicked: stack.push(groupSelectGas, StackView.Immediate)
|
||||
isFromEditable: true
|
||||
isGasEditable: true
|
||||
onFromClicked: { stack.push(groupSelectAcct, StackView.Immediate) }
|
||||
onGasClicked: { stack.push(groupSelectGas, StackView.Immediate) }
|
||||
}
|
||||
GasValidator {
|
||||
id: gasValidator2
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
selectedAccount: root.selectedAccount
|
||||
selectedAmount: parseFloat(root.selectedAmount)
|
||||
selectedAsset: root.selectedAsset
|
||||
selectedGasEthValue: gasSelector.selectedGasEthValue
|
||||
reset: function() {
|
||||
selectedAccount = Qt.binding(function() { return root.selectedAccount })
|
||||
selectedAmount = Qt.binding(function() { return parseFloat(root.selectedAmount) })
|
||||
selectedAsset = Qt.binding(function() { return root.selectedAsset })
|
||||
selectedGasEthValue = Qt.binding(function() { return gasSelector.selectedGasEthValue })
|
||||
}
|
||||
}
|
||||
}
|
||||
TransactionFormGroup {
|
||||
|
|
|
@ -24,53 +24,9 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
property var tokens: {
|
||||
const count = walletModel.defaultTokenList.rowCount()
|
||||
const toks = []
|
||||
for (var i = 0; i < count; i++) {
|
||||
toks.push({
|
||||
"address": walletModel.defaultTokenList.rowData(i, 'address'),
|
||||
"name": walletModel.defaultTokenList.rowData(i, 'name'),
|
||||
"decimals": parseInt(walletModel.defaultTokenList.rowData(i, 'decimals'), 10),
|
||||
"symbol": walletModel.defaultTokenList.rowData(i, 'symbol')
|
||||
})
|
||||
}
|
||||
return toks
|
||||
}
|
||||
property var token: {
|
||||
if (commandParametersObject.contract === "") {
|
||||
return {
|
||||
symbol: "ETH",
|
||||
name: "Ethereum",
|
||||
address: Constants.zeroAddress,
|
||||
decimals: 18,
|
||||
hasIcon: true
|
||||
}
|
||||
}
|
||||
|
||||
const count = root.tokens.length
|
||||
for (var i = 0; i < count; i++) {
|
||||
let token = root.tokens[i]
|
||||
if (token.address === commandParametersObject.contract) {
|
||||
return token
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
property string tokenAmount: {
|
||||
if (!commandParametersObject.value) {
|
||||
return "0"
|
||||
}
|
||||
try {
|
||||
return utilsModel.wei2Token(commandParametersObject.value.toString(), token.decimals)
|
||||
} catch (e) {
|
||||
console.error("Error getting the ETH value of:", commandParametersObject.value)
|
||||
console.error("Error:", e.message)
|
||||
return "0"
|
||||
}
|
||||
}
|
||||
property string tokenSymbol: token.symbol
|
||||
property var token: JSON.parse(commandParametersObject.contract) // TODO: handle {}
|
||||
property string tokenAmount: commandParametersObject.value
|
||||
property string tokenSymbol: token.symbol || ""
|
||||
property string fiatValue: {
|
||||
if (!tokenAmount || !token.symbol) {
|
||||
return "0"
|
||||
|
@ -83,8 +39,8 @@ Item {
|
|||
switch (root.state) {
|
||||
case Constants.pending:
|
||||
case Constants.confirmed:
|
||||
case Constants.transactionRequested:
|
||||
case Constants.addressRequested: return isCurrentUser
|
||||
case Constants.transactionRequested:
|
||||
case Constants.declined:
|
||||
case Constants.transactionDeclined:
|
||||
case Constants.addressReceived: return !isCurrentUser
|
||||
|
@ -92,6 +48,12 @@ Item {
|
|||
}
|
||||
}
|
||||
property int innerMargin: 12
|
||||
property bool isError: commandParametersObject.contract === "{}"
|
||||
onTokenSymbolChanged: {
|
||||
if (!!tokenSymbol) {
|
||||
tokenImage.source = `../../../../img/tokens/${root.tokenSymbol}.png`
|
||||
}
|
||||
}
|
||||
|
||||
id: root
|
||||
anchors.left: parent.left
|
||||
|
@ -118,11 +80,17 @@ Item {
|
|||
StyledText {
|
||||
id: title
|
||||
color: Style.current.secondaryText
|
||||
text: {
|
||||
if (root.state === Constants.transactionRequested) {
|
||||
let prefix = root.outgoing ? "↑ " : "↓ "
|
||||
return prefix + qsTr("Transaction request")
|
||||
}
|
||||
return root.outgoing ?
|
||||
//% "↑ Outgoing transaction"
|
||||
text: root.outgoing ?
|
||||
qsTrId("--outgoing-transaction") :
|
||||
//% "↓ Incoming transaction"
|
||||
qsTrId("--incoming-transaction")
|
||||
}
|
||||
font.weight: Font.Medium
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.current.halfPadding
|
||||
|
@ -140,9 +108,16 @@ Item {
|
|||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.innerMargin
|
||||
|
||||
StyledText {
|
||||
id: txtError
|
||||
color: Style.current.danger
|
||||
visible: root.isError
|
||||
text: qsTr("Something has gone wrong")
|
||||
}
|
||||
|
||||
Image {
|
||||
id: tokenImage
|
||||
source: `../../../../img/tokens/${root.tokenSymbol}.png`
|
||||
visible: !root.isError
|
||||
width: 24
|
||||
height: 24
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@ -150,6 +125,7 @@ Item {
|
|||
|
||||
StyledText {
|
||||
id: tokenText
|
||||
visible: !root.isError
|
||||
color: Style.current.textColor
|
||||
text: `${root.tokenAmount} ${root.tokenSymbol}`
|
||||
anchors.left: tokenImage.right
|
||||
|
@ -159,6 +135,7 @@ Item {
|
|||
|
||||
StyledText {
|
||||
id: fiatText
|
||||
visible: !root.isError
|
||||
color: Style.current.secondaryText
|
||||
text: root.fiatValue
|
||||
anchors.top: tokenText.bottom
|
||||
|
@ -169,7 +146,15 @@ Item {
|
|||
|
||||
Loader {
|
||||
id: bubbleLoader
|
||||
active: isCurrentUser || (!isCurrentUser && !(root.state === Constants.addressRequested || root.state === Constants.transactionRequested))
|
||||
active: {
|
||||
return !root.isError && (
|
||||
isCurrentUser ||
|
||||
(!isCurrentUser &&
|
||||
!(root.state === Constants.addressRequested ||
|
||||
root.state === Constants.transactionRequested)
|
||||
)
|
||||
)
|
||||
}
|
||||
sourceComponent: stateBubbleComponent
|
||||
anchors.top: valueContainer.bottom
|
||||
anchors.topMargin: Style.current.halfPadding
|
||||
|
@ -188,9 +173,11 @@ Item {
|
|||
|
||||
Loader {
|
||||
id: buttonsLoader
|
||||
active: (root.state === Constants.addressRequested && !root.outgoing) ||
|
||||
active: !root.isError && (
|
||||
(root.state === Constants.addressRequested && !root.outgoing) ||
|
||||
(root.state === Constants.addressReceived && root.outgoing) ||
|
||||
(root.state === Constants.transactionRequested && !root.outgoing)
|
||||
(root.state === Constants.transactionRequested && root.outgoing)
|
||||
)
|
||||
sourceComponent: root.outgoing ? signAndSendComponent : acceptTransactionComponent
|
||||
anchors.top: bubbleLoader.active ? bubbleLoader.bottom : valueContainer.bottom
|
||||
anchors.topMargin: bubbleLoader.active ? root.innerMargin : 20
|
||||
|
@ -208,7 +195,9 @@ Item {
|
|||
Component {
|
||||
id: signAndSendComponent
|
||||
|
||||
SendTransactionButton {}
|
||||
SendTransactionButton {
|
||||
outgoing: root.outgoing
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
|
|
|
@ -4,8 +4,10 @@ import "../../../../../../imports"
|
|||
import "../../ChatComponents"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: parent.width
|
||||
height: childrenRect.height + Style.current.halfPadding
|
||||
property bool outgoing: true
|
||||
|
||||
Separator {
|
||||
id: separator
|
||||
|
@ -55,15 +57,10 @@ Item {
|
|||
type: RecipientSelector.Type.Contact
|
||||
}
|
||||
}
|
||||
selectedAsset: {
|
||||
return {
|
||||
name: token.name,
|
||||
symbol: token.symbol,
|
||||
address: commandParametersObject.contract
|
||||
}
|
||||
}
|
||||
selectedAsset: token
|
||||
selectedAmount: tokenAmount
|
||||
selectedFiatAmount: fiatValue
|
||||
outgoing: root.outgoing
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ Rectangle {
|
|||
border.width: 1
|
||||
border.color: Style.current.border
|
||||
radius: 24
|
||||
color: Style.current.background
|
||||
|
||||
SVGImage {
|
||||
id: stateImage
|
||||
|
|
|
@ -17,6 +17,14 @@ Item {
|
|||
property var reset: function() {}
|
||||
signal fromClicked
|
||||
signal gasClicked
|
||||
// Creates a mouse area around the "from account". When clicked, triggers
|
||||
// the "fromClicked" signal
|
||||
property bool isFromEditable: false
|
||||
// Creates a mouse area around the "network fee". When clicked, triggers
|
||||
// the "gasClicked" signal
|
||||
property bool isGasEditable: false
|
||||
property bool isValid: true
|
||||
property bool outgoing: true
|
||||
|
||||
function resetInternal() {
|
||||
fromAccount = undefined
|
||||
|
@ -24,8 +32,33 @@ Item {
|
|||
asset = undefined
|
||||
amount = undefined
|
||||
gas = undefined
|
||||
isValid = true
|
||||
}
|
||||
|
||||
function validate() {
|
||||
let isValid = true
|
||||
imgInsufficientBalance.visible = false
|
||||
console.log(">>> [TransactionPreview.validate] outgoing:", outgoing)
|
||||
if (outgoing && hasInsufficientBalance()) {
|
||||
isValid = false
|
||||
imgInsufficientBalance.visible = true
|
||||
}
|
||||
root.isValid = isValid
|
||||
return isValid
|
||||
}
|
||||
|
||||
function hasInsufficientBalance() {
|
||||
if (!root.asset || !root.fromAccount || !root.fromAccount.assets || !root.amount) {
|
||||
return true
|
||||
}
|
||||
const currAcctAsset = Utils.findAssetBySymbol(root.fromAccount.assets, root.asset.symbol)
|
||||
if (!currAcctAsset) return true
|
||||
return currAcctAsset.value < root.amount.value
|
||||
}
|
||||
|
||||
onAssetChanged: validate()
|
||||
onFromAccountChanged: validate()
|
||||
|
||||
Column {
|
||||
id: content
|
||||
anchors.left: parent.left
|
||||
|
@ -36,17 +69,23 @@ Item {
|
|||
//% "From"
|
||||
label: qsTrId("from")
|
||||
value: Item {
|
||||
id: itmFromValue
|
||||
anchors.fill: parent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
function needsRightPadding() {
|
||||
return imgInsufficientBalance.visible || fromArrow.visible
|
||||
}
|
||||
Row {
|
||||
spacing: Style.current.halfPadding
|
||||
rightPadding: itmFromValue.needsRightPadding() ? Style.current.halfPadding : 0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: 15
|
||||
height: 22
|
||||
text: root.fromAccount ? root.fromAccount.name : ""
|
||||
elide: Text.ElideRight
|
||||
anchors.left: parent.left
|
||||
anchors.right: imgFromWallet.left
|
||||
anchors.rightMargin: Style.current.halfPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
@ -55,8 +94,8 @@ Item {
|
|||
id: imgFromWallet
|
||||
sourceSize.height: 18
|
||||
sourceSize.width: 18
|
||||
anchors.right: fromArrow.visible ? fromArrow.left : parent.right
|
||||
anchors.rightMargin: fromArrow.visible ? Style.current.padding : 0
|
||||
horizontalAlignment: Image.AlignLeft
|
||||
width: itmFromValue.needsRightPadding() ? (Style.current.halfPadding + sourceSize.width) : undefined // adding width to add addl spacing to image
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../app/img/walletIcon.svg"
|
||||
|
@ -66,12 +105,18 @@ Item {
|
|||
color: root.fromAccount ? root.fromAccount.iconColor : Style.current.blue
|
||||
}
|
||||
}
|
||||
SVGImage {
|
||||
id: imgInsufficientBalance
|
||||
width: 13
|
||||
visible: false
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../app/img/exclamation_outline.svg"
|
||||
}
|
||||
SVGImage {
|
||||
id: fromArrow
|
||||
width: 13
|
||||
visible: typeof root.fromClicked === "function"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 7
|
||||
visible: root.isFromEditable
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../app/img/caret.svg"
|
||||
|
@ -83,6 +128,7 @@ Item {
|
|||
color: Style.current.secondaryText
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
visible: fromArrow.visible
|
||||
|
@ -398,7 +444,7 @@ Item {
|
|||
SVGImage {
|
||||
id: gasArrow
|
||||
width: 13
|
||||
visible: typeof root.gasClicked === "function"
|
||||
visible: root.isGasEditable
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
|
Loading…
Reference in New Issue