From 46f34ec8fc4889bfc3394150f599330e673908f9 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 3 Oct 2024 15:14:27 +0200 Subject: [PATCH] fix: optimizes send modal flow for ens usernames --- .../profile_section/ens_usernames/module.nim | 2 +- .../profile_section/ens_usernames/view.nim | 6 +- .../service/transaction/service.nim | 3 + ui/app/AppLayouts/Profile/ProfileLayout.qml | 4 +- .../Profile/views/EnsDetailsView.qml | 40 +----- .../Profile/views/EnsSearchView.qml | 41 +----- .../views/EnsTermsAndConditionsView.qml | 55 +------- ui/app/AppLayouts/Profile/views/EnsView.qml | 126 +++++++++++++++--- ui/app/AppLayouts/Wallet/WalletUtils.qml | 3 + ui/app/mainui/AppMain.qml | 35 ++++- ui/imports/utils/Constants.qml | 1 + 11 files changed, 156 insertions(+), 160 deletions(-) diff --git a/src/app/modules/main/profile_section/ens_usernames/module.nim b/src/app/modules/main/profile_section/ens_usernames/module.nim index 89bbd5e766..81c0305c7c 100644 --- a/src/app/modules/main/profile_section/ens_usernames/module.nim +++ b/src/app/modules/main/profile_section/ens_usernames/module.nim @@ -127,7 +127,7 @@ method connectOwnedUsername*(self: Module, ensUsername: string, isStatus: bool) method ensTransactionSent*(self: Module, trxType: string, chainId: int, ensUsername: string, txHash: string, err: string) = var finalError = err defer: - self.view.emitTransactionWasSentSignal(chainId, txHash, finalError) + self.view.emitTransactionWasSentSignal(trxType, chainId, txHash, ensUsername, finalError) if (err.len != 0): error "sending ens tx failed", errMsg=err, methodName="ensTransactionSent" return diff --git a/src/app/modules/main/profile_section/ens_usernames/view.nim b/src/app/modules/main/profile_section/ens_usernames/view.nim index 61389934a4..ed06d76333 100644 --- a/src/app/modules/main/profile_section/ens_usernames/view.nim +++ b/src/app/modules/main/profile_section/ens_usernames/view.nim @@ -62,9 +62,9 @@ QtObject: self.loading(false) self.detailsObtained(chainId, ensUsername, address, pubkey, isStatus, expirationTime) - proc transactionWasSent(self: View, chainId: int, txHash: string, error: string) {.signal.} - proc emitTransactionWasSentSignal*(self: View, chainId: int, txHash: string, error: string) = - self.transactionWasSent(chainId, txHash, error) + proc transactionWasSent(self: View, trxType: string, chainId: int, txHash: string, username: string, error: string) {.signal.} + proc emitTransactionWasSentSignal*(self: View, trxType: string, chainId: int, txHash: string, username: string, error: string) = + self.transactionWasSent(trxType, chainId, txHash, username, error) proc getEtherscanLink*(self: View): string {.slot.} = return self.etherscanLink diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index 416d5b862a..b15b403e98 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -47,6 +47,8 @@ const SIGNAL_OWNER_TOKEN_SENT* = "ownerTokenSent" const SIGNAL_TRANSACTION_SENDING_COMPLETE* = "transactionSendingComplete" const SIGNAL_TRANSACTION_STATUS_CHANGED* = "transactionStatusChanged" +const InternalErrorCode = -1 + type TokenTransferMetadata* = object tokenName*: string isOwnerToken*: bool @@ -428,6 +430,7 @@ QtObject: tokenIsOwnerToken, toToken, disabledFromChainIDs, disabledToChainIDs, lockedInAmounts, extraParamsTable) except CatchableError as e: error "suggestedRoutes", exception=e.msg + self.suggestedRoutesReady(uuid, @[], "", $InternalErrorCode, e.msg) proc stopSuggestedRoutesAsyncCalculation*(self: Service) = try: diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 3b1e103998..3c70e658ac 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -38,11 +38,11 @@ StatusSectionLayout { property SharedStores.RootStore sharedRootStore property ProfileSectionStore store property AppLayoutsStores.RootStore globalStore + required property var sendModalPopup property var systemPalette property var emojiPopup property SharedStores.NetworkConnectionStore networkConnectionStore required property TokensStore tokensStore - required property TransactionStore transactionStore required property WalletAssetsStore walletAssetsStore required property CollectiblesStore collectiblesStore required property SharedStores.CurrenciesStore currencyStore @@ -220,9 +220,9 @@ StatusSectionLayout { implicitHeight: parent.height ensUsernamesStore: root.store.ensUsernamesStore walletAssetsStore: root.walletAssetsStore + sendModalPopup: root.sendModalPopup contactsStore: root.store.contactsStore networkConnectionStore: root.networkConnectionStore - transactionStore: root.transactionStore profileContentWidth: d.contentWidth } } diff --git a/ui/app/AppLayouts/Profile/views/EnsDetailsView.qml b/ui/app/AppLayouts/Profile/views/EnsDetailsView.qml index d3be86f5eb..83fe78d781 100644 --- a/ui/app/AppLayouts/Profile/views/EnsDetailsView.qml +++ b/ui/app/AppLayouts/Profile/views/EnsDetailsView.qml @@ -19,15 +19,13 @@ import AppLayouts.Profile.stores 1.0 Item { id: root property EnsUsernamesStore ensUsernamesStore - property ContactsStore contactsStore - required property TransactionStore transactionStore property string username: "" property string chainId: "" property string walletAddress: "-" property string key: "-" - signal backBtnClicked(); - signal usernameReleased() + signal backBtnClicked() + signal releaseUsernameRequested() QtObject { id: d @@ -117,38 +115,6 @@ Item { } } - Component { - id: transactionDialogComponent - SendModal { - id: releaseEnsModal - modalHeader: qsTr("Release your username") - interactive: false - store: root.transactionStore - preSelectedSendType: Constants.SendType.ENSRelease - preSelectedRecipient: root.ensUsernamesStore.getEnsRegisteredAddress() - preDefinedAmountToSend: LocaleUtils.numberToLocaleString(0) - preSelectedHoldingID: Constants.ethToken - preSelectedHoldingType: Constants.TokenType.ERC20 - publicKey: root.contactsStore.myPublicKey - ensName: root.username - - Connections { - target: root.ensUsernamesStore.ensUsernamesModule - function onTransactionWasSent(chainId: int, txHash: string, error: string) { - if (!!error) { - if (error.includes(Constants.walletSection.cancelledMessage)) { - return - } - releaseEnsModal.sendingError.text = error - return releaseEnsModal.sendingError.open() - } - usernameReleased() - releaseEnsModal.close() - } - } - } - } - RowLayout { id: actionsLayout @@ -174,7 +140,7 @@ Item { enabled: false text: qsTr("Release username") onClicked: { - Global.openPopup(transactionDialogComponent) + root.releaseUsernameRequested() } } } diff --git a/ui/app/AppLayouts/Profile/views/EnsSearchView.qml b/ui/app/AppLayouts/Profile/views/EnsSearchView.qml index ed6150cfa5..3fa9557f26 100644 --- a/ui/app/AppLayouts/Profile/views/EnsSearchView.qml +++ b/ui/app/AppLayouts/Profile/views/EnsSearchView.qml @@ -22,13 +22,10 @@ Item { id: root property EnsUsernamesStore ensUsernamesStore - property ContactsStore contactsStore - required property TransactionStore transactionStore property int profileContentWidth + signal connectUsername(string username) signal continueClicked(string output, string username) - signal usernameUpdated(username: string); - property string validationMessage: "" property bool valid: false @@ -62,39 +59,6 @@ Item { Qt.callLater(validateENS, ensUsername, isStatus) } - Component { - id: transactionDialogComponent - SendModal { - id: connectEnsModal - modalHeader: qsTr("Connect username with your pubkey") - interactive: false - store: root.transactionStore - preSelectedSendType: Constants.SendType.ENSSetPubKey - preSelectedRecipient: root.ensUsernamesStore.getEnsRegisteredAddress() - preDefinedAmountToSend: LocaleUtils.numberToLocaleString(0) - preSelectedHoldingID: Constants.ethToken - preSelectedHoldingType: Constants.TokenType.ERC20 - publicKey: root.contactsStore.myPublicKey - ensName: ensUsername.text - - Connections { - target: root.ensUsernamesStore.ensUsernamesModule - function onTransactionWasSent(chainId: int, txHash: string, error: string) { - if (!!error) { - if (error.includes(Constants.walletSection.cancelledMessage)) { - return - } - connectEnsModal.sendingError.text = error - return connectEnsModal.sendingError.open() - } - usernameUpdated(ensUsername.text); - connectEnsModal.close() - } - } - } - - } - Item { id: ensContainer anchors.top: parent.top @@ -206,8 +170,7 @@ Item { } if(ensStatus === Constants.ens_connected_dkey || ensStatus === Constants.ens_owned){ - Global.openPopup(transactionDialogComponent, {ensUsername: ensUsername.text}) - return; + root.connectUsername(ensUsername.text) } } } diff --git a/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml b/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml index 1548cd4e6d..9357d1d040 100644 --- a/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml +++ b/ui/app/AppLayouts/Profile/views/EnsTermsAndConditionsView.qml @@ -16,30 +16,16 @@ import StatusQ.Core.Utils 0.1 import StatusQ.Controls 0.1 import StatusQ.Components 0.1 -import AppLayouts.Wallet.stores 1.0 import AppLayouts.Profile.stores 1.0 Item { id: root property EnsUsernamesStore ensUsernamesStore - property ContactsStore contactsStore - required property TransactionStore transactionStore - property WalletAssetsStore walletAssetsStore property string username: "" signal backBtnClicked() - signal usernameRegistered(userName: string) - - QtObject { - id: d - readonly property var sntToken: ModelUtils.getByKey(root.walletAssetsStore.groupedAccountAssetsModel, "tokensKey", root.ensUsernamesStore.getStatusTokenKey()) - readonly property SumAggregator aggregator: SumAggregator { - model: !!d.sntToken && !!d.sntToken.balances ? d.sntToken.balances: nil - roleName: "balance" - } - property real sntBalance: !!sntToken && !!sntToken.decimals ? aggregator.value/(10 ** sntToken.decimals): 0 - } + signal registerUsername() StatusBaseText { id: sectionTitle @@ -59,43 +45,6 @@ Item { } } - Loader { - id: transactionDialog - function open() { - this.active = true - this.item.open() - } - function closed() { - this.active = false // kill an opened instance - } - sourceComponent: SendModal { - id: buyEnsModal - interactive: false - store: root.transactionStore - preSelectedSendType: Constants.SendType.ENSRegister - preSelectedRecipient: root.ensUsernamesStore.getEnsRegisteredAddress() - preDefinedAmountToSend: LocaleUtils.numberToLocaleString(10) - preSelectedHoldingID: !!d.sntToken && !!d.sntToken.symbol ? d.sntToken.symbol: "" - preSelectedHoldingType: Constants.TokenType.ERC20 - publicKey: root.contactsStore.myPublicKey - ensName: root.username - - Connections { - target: root.ensUsernamesStore.ensUsernamesModule - function onTransactionWasSent(chainId: int, txHash: string, error: string) { - if (!!error) { - if (error.includes(Constants.walletSection.cancelledMessage)) { - return - } - buyEnsModal.sendingError.text = error - return buyEnsModal.sendingError.open() - } - usernameRegistered(username) - } - } - } - } - // TODO: Replace with StatusModal ModalPopup { id: popup @@ -395,6 +344,6 @@ Item { qsTr("Not enough SNT") : qsTr("Register") enabled: d.sntBalance >= 10 && termsAndConditionsCheckbox.checked - onClicked: transactionDialog.open() + onClicked: root.registerUsername(root.username) } } diff --git a/ui/app/AppLayouts/Profile/views/EnsView.qml b/ui/app/AppLayouts/Profile/views/EnsView.qml index 08931d317a..2352ef3cae 100644 --- a/ui/app/AppLayouts/Profile/views/EnsView.qml +++ b/ui/app/AppLayouts/Profile/views/EnsView.qml @@ -3,10 +3,14 @@ import QtQuick.Layouts 1.3 import QtQuick.Controls 2.14 import QtQml.StateMachine 1.14 as DSM +import StatusQ 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 + import utils 1.0 import shared 1.0 import shared.stores 1.0 as SharedStores -import shared.stores.send 1.0 +import shared.popups.send 1.0 import AppLayouts.Wallet.stores 1.0 @@ -18,9 +22,10 @@ Item { property EnsUsernamesStore ensUsernamesStore property WalletAssetsStore walletAssetsStore + required property var sendModalPopup + property ContactsStore contactsStore property SharedStores.NetworkConnectionStore networkConnectionStore - required property TransactionStore transactionStore property int profileContentWidth property bool showSearchScreen: false @@ -40,6 +45,21 @@ Item { Layout.fillWidth: true clip: true + QtObject { + id: d + + readonly property string registerENS: "RegisterENS" + readonly property string setPubKey: "SetPubKey" + readonly property string releaseENS: "ReleaseENS" + + readonly property var sntToken: ModelUtils.getByKey(ensView.walletAssetsStore.groupedAccountAssetsModel, "tokensKey", ensView.ensUsernamesStore.getStatusTokenKey()) + readonly property SumAggregator aggregator: SumAggregator { + model: !!d.sntToken && !!d.sntToken.balances ? d.sntToken.balances: nil + roleName: "balance" + } + property real sntBalance: !!sntToken && !!sntToken.decimals ? aggregator.value/(10 ** sntToken.decimals): 0 + } + DSM.StateMachine { id: stateMachine initialState: ensView.ensUsernamesStore.ensUsernamesModel.count > 0 ? listState : welcomeState @@ -226,9 +246,8 @@ Item { id: search EnsSearchView { ensUsernamesStore: ensView.ensUsernamesStore - contactsStore: ensView.contactsStore - transactionStore: ensView.transactionStore profileContentWidth: ensView.profileContentWidth + onContinueClicked: { if(output === "connected"){ connect(username) @@ -237,9 +256,32 @@ Item { next(output); } } - onUsernameUpdated: { - selectedUsername = username; - done(username); + + onConnectUsername: { + ensView.selectedUsername = username + + ensView.sendModalPopup.modalHeaderText = qsTr("Connect username with your pubkey") + ensView.sendModalPopup.interactive = false + ensView.sendModalPopup.preSelectedRecipient = ensView.ensUsernamesStore.getEnsRegisteredAddress() + ensView.sendModalPopup.preSelectedRecipientType = Helpers.RecipientAddressObjectType.Address + ensView.sendModalPopup.preSelectedHoldingID = Constants.ethToken + ensView.sendModalPopup.preSelectedHoldingType = Constants.TokenType.ERC20 + ensView.sendModalPopup.preSelectedSendType = Constants.SendType.ENSSetPubKey + ensView.sendModalPopup.preDefinedAmountToSend = LocaleUtils.numberToLocaleString(0) + ensView.sendModalPopup.preSelectedChainId = ensView.selectedChainId + ensView.sendModalPopup.publicKey = ensView.contactsStore.myPublicKey + ensView.sendModalPopup.ensName = ensView.selectedUsername + ensView.sendModalPopup.open() + } + + Connections { + target: ensView.ensUsernamesStore.ensUsernamesModule + function onTransactionWasSent(trxType: string, chainId: int, txHash: string, username: string, error: string) { + if (!!error || trxType !== d.setPubKey) { + return + } + done(ensView.selectedUsername) + } } } } @@ -248,12 +290,33 @@ Item { id: termsAndConditions EnsTermsAndConditionsView { ensUsernamesStore: ensView.ensUsernamesStore - contactsStore: ensView.contactsStore - transactionStore: ensView.transactionStore - walletAssetsStore: ensView.walletAssetsStore username: selectedUsername + onBackBtnClicked: back(); - onUsernameRegistered: done(userName); + + onRegisterUsername: { + ensView.sendModalPopup.interactive = false + ensView.sendModalPopup.preSelectedRecipient = ensView.ensUsernamesStore.getEnsRegisteredAddress() + ensView.sendModalPopup.preSelectedRecipientType = Helpers.RecipientAddressObjectType.Address + ensView.sendModalPopup.preSelectedHoldingID = !!d.sntToken && !!d.sntToken.symbol ? d.sntToken.symbol: "" + ensView.sendModalPopup.preSelectedHoldingType = Constants.TokenType.ERC20 + ensView.sendModalPopup.preSelectedSendType = Constants.SendType.ENSRegister + ensView.sendModalPopup.preDefinedAmountToSend = LocaleUtils.numberToLocaleString(10) + ensView.sendModalPopup.preSelectedChainId = ensView.selectedChainId + ensView.sendModalPopup.publicKey = ensView.contactsStore.myPublicKey + ensView.sendModalPopup.ensName = ensView.selectedUsername + ensView.sendModalPopup.open() + } + + Connections { + target: ensView.ensUsernamesStore.ensUsernamesModule + function onTransactionWasSent(trxType: string, chainId: int, txHash: string, username: string, error: string) { + if (!!error || trxType !== d.registerENS) { + return + } + done(ensView.selectedUsername) + } + } } } @@ -308,15 +371,34 @@ Item { id: details EnsDetailsView { ensUsernamesStore: ensView.ensUsernamesStore - contactsStore: ensView.contactsStore - transactionStore: ensView.transactionStore username: selectedUsername chainId: selectedChainId + onBackBtnClicked: back() - onUsernameReleased: { - selectedUsername = username - selectedChainId = chainId - done(username) + + onReleaseUsernameRequested: { + ensView.sendModalPopup.modalHeaderText = qsTr("Release your username") + ensView.sendModalPopup.interactive = false + ensView.sendModalPopup.preSelectedRecipient = ensView.ensUsernamesStore.getEnsRegisteredAddress() + ensView.sendModalPopup.preSelectedRecipientType = Helpers.RecipientAddressObjectType.Address + ensView.sendModalPopup.preSelectedHoldingID = Constants.ethToken + ensView.sendModalPopup.preSelectedHoldingType = Constants.TokenType.Native + ensView.sendModalPopup.preSelectedSendType = Constants.SendType.ENSRelease + ensView.sendModalPopup.preDefinedAmountToSend = LocaleUtils.numberToLocaleString(0) + ensView.sendModalPopup.preSelectedChainId = ensView.selectedChainId + ensView.sendModalPopup.publicKey = ensView.contactsStore.myPublicKey + ensView.sendModalPopup.ensName = ensView.selectedUsername + ensView.sendModalPopup.open() + } + + Connections { + target: ensView.ensUsernamesStore.ensUsernamesModule + function onTransactionWasSent(trxType: string, chainId: int, txHash: string, username: string, error: string) { + if (!!error || trxType !== d.releaseENS) { + return + } + done(ensView.selectedUsername) + } } } } @@ -333,18 +415,24 @@ Item { function onTransactionCompleted(success: bool, txHash: string, username: string, trxType: string) { let title = "" switch(trxType){ - case "RegisterENS": + case d.registerENS: title = !success ? qsTr("ENS Registration failed") : qsTr("ENS Registration completed"); break; - case "SetPubKey": + case d.setPubKey: title = !success ? qsTr("Updating ENS pubkey failed") : qsTr("Updating ENS pubkey completed"); break; + case d.releaseENS: + title = !success ? + qsTr("Releasing ENS name failed") + : + qsTr("ENS name released"); + break default: console.error("unknown transaction type: ", trxType); return diff --git a/ui/app/AppLayouts/Wallet/WalletUtils.qml b/ui/app/AppLayouts/Wallet/WalletUtils.qml index d8c8460a56..f57b39d29c 100644 --- a/ui/app/AppLayouts/Wallet/WalletUtils.qml +++ b/ui/app/AppLayouts/Wallet/WalletUtils.qml @@ -139,6 +139,8 @@ QtObject { } switch(code) { + case Constants.routerErrorCodes.errInternal: + return qsTr("an internal error occurred") case Constants.routerErrorCodes.errGeneric: return qsTr("unknown error occurred, try again later") case Constants.routerErrorCodes.processor.errFailedToParseBaseFee: @@ -225,6 +227,7 @@ QtObject { } switch(code) { + case Constants.routerErrorCodes.errInternal: case Constants.routerErrorCodes.errGeneric: return details case Constants.routerErrorCodes.processor.errFailedToParseBaseFee: diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 577603cd8d..15806ecfe1 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -1434,11 +1434,11 @@ Item { sharedRootStore: appMain.sharedRootStore store: appMain.rootStore.profileSectionStore globalStore: appMain.rootStore + sendModalPopup: sendModal systemPalette: appMain.sysPalette emojiPopup: statusEmojiPopup.item networkConnectionStore: appMain.networkConnectionStore tokensStore: appMain.tokensStore - transactionStore: appMain.transactionStore walletAssetsStore: appMain.walletAssetsStore collectiblesStore: appMain.walletCollectiblesStore currencyStore: appMain.currencyStore @@ -1625,6 +1625,8 @@ Item { this.active = false } + property string modalHeaderText + property bool interactive: true property string preSelectedAccountAddress property var preSelectedRecipient property int preSelectedRecipientType @@ -1635,7 +1637,12 @@ Item { property int preSelectedChainId: 0 property bool onlyAssets: false + property string stickersPackId: "" + property string publicKey: "" + property string ensName: "" + sourceComponent: SendPopups.SendModal { + interactive: sendModal.interactive onlyAssets: sendModal.onlyAssets loginType: appMain.rootStore.loginType @@ -1647,6 +1654,8 @@ Item { onClosed: { sendModal.closed() + sendModal.modalHeaderText = "" + sendModal.interactive = true sendModal.preSelectedSendType = Constants.SendType.Unknown sendModal.preSelectedHoldingID = "" sendModal.preSelectedHoldingType = Constants.TokenType.Unknown @@ -1654,6 +1663,10 @@ Item { sendModal.preSelectedRecipient = undefined sendModal.preDefinedAmountToSend = "" sendModal.preSelectedChainId = 0 + + sendModal.stickersPackId = "" + sendModal.publicKey = "" + sendModal.ensName = "" } } onLoaded: { @@ -1665,19 +1678,29 @@ Item { item.preSelectedRecipientType = sendModal.preSelectedRecipientType item.preSelectedRecipient = sendModal.preSelectedRecipient } - if(sendModal.preSelectedSendType !== Constants.SendType.Unknown) { + if (sendModal.preSelectedSendType !== Constants.SendType.Unknown) { item.preSelectedSendType = sendModal.preSelectedSendType } - if(preSelectedHoldingType !== Constants.TokenType.Unknown) { + if (sendModal.preSelectedHoldingType !== Constants.TokenType.Unknown) { item.preSelectedHoldingID = sendModal.preSelectedHoldingID item.preSelectedHoldingType = sendModal.preSelectedHoldingType } - if(preDefinedAmountToSend != "") { - item.preDefinedAmountToSend = preDefinedAmountToSend + if (sendModal.preDefinedAmountToSend != "") { + item.preDefinedAmountToSend = sendModal.preDefinedAmountToSend } - if(!!sendModal.preSelectedChainId) { + if (!!sendModal.preSelectedChainId) { item.preSelectedChainId = sendModal.preSelectedChainId } + + if (!!sendModal.stickersPackId) { + item.stickersPackId = sendModal.stickersPackId + } + if (!!sendModal.publicKey) { + item.publicKey = sendModal.publicKey + } + if (!!sendModal.ensName) { + item.ensName = sendModal.ensName + } } } diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index c79045bfc6..5f83be9190 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -1075,6 +1075,7 @@ QtObject { readonly property QtObject routerErrorCodes: QtObject { + readonly property string errInternal: "-1" // comes from Nim side readonly property string errGeneric: "0" readonly property QtObject processor: QtObject {