feat: Export control node (except backend for primary action)
Adding the export control node popup. The popup is behind an authentication wall. 1. Create ExportControlNodePopup qml 2. Add the popup in storybook 3. Create authentication flow with qml callback to open the popup after authentication 4. Add the popup open action in Global.qml
This commit is contained in:
parent
64422afed7
commit
27c159d464
|
@ -49,6 +49,7 @@ type
|
|||
tmpRequestToJoinEnsName: string
|
||||
tmpAddressesToShare: seq[string]
|
||||
tmpAirdropAddress: string
|
||||
tmpAuthenticationWithCallbackInProgress: bool
|
||||
|
||||
proc newController*(delegate: io_interface.AccessInterface, sectionId: string, isCommunity: bool, events: EventEmitter,
|
||||
settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service,
|
||||
|
@ -82,7 +83,8 @@ proc newController*(delegate: io_interface.AccessInterface, sectionId: string, i
|
|||
result.tmpRequestToJoinEnsName = ""
|
||||
result.tmpAirdropAddress = ""
|
||||
result.tmpAddressesToShare = @[]
|
||||
|
||||
result.tmpAuthenticationWithCallbackInProgress = true
|
||||
|
||||
proc delete*(self: Controller) =
|
||||
self.events.disconnect()
|
||||
|
||||
|
@ -232,6 +234,10 @@ proc init*(self: Controller) =
|
|||
return
|
||||
if self.tmpAuthenticationForJoinInProgress or self.tmpAuthenticationForEditSharedAddresses:
|
||||
self.delegate.onUserAuthenticated(args.pin, args.password, args.keyUid)
|
||||
if self.tmpAuthenticationWithCallbackInProgress:
|
||||
let authenticated = not (args.password == "" and args.pin == "")
|
||||
self.delegate.callbackFromAuthentication(authenticated)
|
||||
self.tmpAuthenticationWithCallbackInProgress = false
|
||||
|
||||
if (self.isCommunitySection):
|
||||
self.events.on(SIGNAL_COMMUNITY_CHANNEL_CREATED) do(e:Args):
|
||||
|
@ -730,3 +736,7 @@ proc getContractAddressesForToken*(self: Controller, symbol: string): Table[int,
|
|||
|
||||
proc getCommunityTokenList*(self: Controller): seq[CommunityTokenDto] =
|
||||
return self.communityTokensService.getCommunityTokens(self.getMySectionId())
|
||||
|
||||
proc authenticateWithCallback*(self: Controller) =
|
||||
self.tmpAuthenticationWithCallbackInProgress = true
|
||||
self.authenticate()
|
|
@ -400,3 +400,9 @@ method onCommunityCheckChannelPermissionsResponse*(self: AccessInterface, chatId
|
|||
|
||||
method onCommunityCheckAllChannelsPermissionsResponse*(self: AccessInterface, checkAllChannelsPermissionsResponse: CheckAllChannelsPermissionsResponseDto) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method authenticateWithCallback*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method callbackFromAuthentication*(self: AccessInterface, authenticated: bool) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
|
@ -1336,3 +1336,8 @@ method editSharedAddressesWithAuthentication*(self: Module, addressesToShare: se
|
|||
method onDeactivateChatLoader*(self: Module, chatId: string) =
|
||||
self.view.chatsModel().disableChatLoader(chatId)
|
||||
|
||||
method authenticateWithCallback*(self: Module) =
|
||||
self.controller.authenticateWithCallback()
|
||||
|
||||
method callbackFromAuthentication*(self: Module, authenticated: bool) =
|
||||
self.view.callbackFromAuthentication(authenticated)
|
|
@ -429,3 +429,8 @@ QtObject:
|
|||
notify = allTokenRequirementsMetChanged
|
||||
|
||||
proc userAuthenticationCanceled*(self: View) {.signal.}
|
||||
|
||||
proc authenticateWithCallback*(self: View) {.slot.} =
|
||||
self.delegate.authenticateWithCallback()
|
||||
|
||||
proc callbackFromAuthentication*(self: View, authenticated: bool) {.signal.}
|
||||
|
|
|
@ -273,6 +273,10 @@ ListModel {
|
|||
title: "UserAgreementPopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "ExportControlNodePopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "StatusButton"
|
||||
section: "Controls"
|
||||
|
|
|
@ -250,5 +250,8 @@
|
|||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=31461%3A564367&mode=dev",
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=31461%3A563905&mode=dev",
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=31461%3A579875&mode=dev"
|
||||
],
|
||||
"ExportControlNodePopup": [
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba⎜Desktop?type=design&node-id=31171-627949&mode=design&t=WxK2N6sL8idHBKMZ-0"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import AppLayouts.Communities.popups 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
orientation: Qt.Vertical
|
||||
|
||||
Logs { id: logs }
|
||||
|
||||
Item {
|
||||
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
PopupBackground {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.centerIn: parent
|
||||
text: "Reopen"
|
||||
|
||||
onClicked: popupComponent.createObject(parent)
|
||||
}
|
||||
Component.onCompleted: popupComponent.createObject(parent)
|
||||
}
|
||||
|
||||
Component {
|
||||
id: popupComponent
|
||||
ExportControlNodePopup {
|
||||
id: popup
|
||||
anchors.centerIn: parent
|
||||
modal: false
|
||||
visible: true
|
||||
communityName: "Socks"
|
||||
privateKey: "0x0454f2231543ba02583e4c55e513a75092a4f2c86c04d0796b195e964656d6cd94b8237c64ef668eb0fe268387adc3fe699bce97190a631563c82b718c19cf1fb8"
|
||||
onDeletePrivateKey: logs.logEvent("ExportControlNodePopup::onDeletePrivateKey")
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumHeight: 100
|
||||
SplitView.preferredHeight: 160
|
||||
|
||||
logsView.logText: logs.logText
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ ColumnLayout {
|
|||
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "search"
|
||||
selectByMouse: true
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
clear()
|
||||
|
|
|
@ -613,6 +613,11 @@ QtObject {
|
|||
return Constants.LoginType.Password
|
||||
}
|
||||
|
||||
function authenticateWithCallback(callback) {
|
||||
_d.authenticationCallbacks.push(callback)
|
||||
chatCommunitySectionModule.authenticateWithCallback()
|
||||
}
|
||||
|
||||
readonly property Connections communitiesModuleConnections: Connections {
|
||||
target: communitiesModuleInst
|
||||
function onImportingCommunityStateChanged(communityId, state, errorMsg) {
|
||||
|
@ -640,6 +645,19 @@ QtObject {
|
|||
}
|
||||
|
||||
readonly property QtObject _d: QtObject {
|
||||
property var authenticationCallbacks: []
|
||||
|
||||
readonly property Connections chatCommunitySectionModuleConnections: Connections {
|
||||
target: chatCommunitySectionModule
|
||||
function onCallbackFromAuthentication(authenticated: bool) {
|
||||
_d.authenticationCallbacks.forEach((callback) => {
|
||||
if(!!callback)
|
||||
callback(authenticated)
|
||||
})
|
||||
_d.authenticationCallbacks = []
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var sectionDetailsInstantiator: Instantiator {
|
||||
model: SortFilterProxyModel {
|
||||
sourceModel: mainModuleInst.sectionsModel
|
||||
|
|
|
@ -16,8 +16,9 @@ Control {
|
|||
property int loginType: Constants.LoginType.Password
|
||||
property string communityName: ""
|
||||
|
||||
signal primaryButtonClicked
|
||||
signal secondaryButtonClicked
|
||||
signal exportControlNodeClicked
|
||||
signal importControlNodeClicked
|
||||
signal learnMoreClicked
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
@ -33,6 +34,7 @@ Control {
|
|||
property string secondaryButtonIcon
|
||||
property string indicatorBgColor
|
||||
property string indicatorColor
|
||||
property var primaryButtonAction: root.exportControlNodeClicked
|
||||
}
|
||||
|
||||
contentItem: GridLayout {
|
||||
|
@ -91,14 +93,14 @@ Control {
|
|||
size: StatusBaseButton.Size.Small
|
||||
text: d.secondaryButtonText
|
||||
icon.name: d.secondaryButtonIcon
|
||||
onClicked: root.secondaryButtonClicked()
|
||||
onClicked: root.learnMoreClicked()
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
size: StatusBaseButton.Size.Small
|
||||
text: d.primaryButtonText
|
||||
icon.name: d.primaryButtonIcon
|
||||
onClicked: root.primaryButtonClicked()
|
||||
onClicked: d.primaryButtonAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +118,7 @@ Control {
|
|||
PropertyChanges { target: d; primaryButtonIcon: Constants.authenticationIconByType[root.loginType] }
|
||||
PropertyChanges { target: d; secondaryButtonText: qsTr("Learn more") }
|
||||
PropertyChanges { target: d; secondaryButtonIcon: "external-link" }
|
||||
PropertyChanges { target: d; primaryButtonAction: root.exportControlNodeClicked }
|
||||
},
|
||||
State {
|
||||
name: "isNotControlNode"
|
||||
|
@ -128,6 +131,7 @@ Control {
|
|||
PropertyChanges { target: d; primaryButtonIcon: "" }
|
||||
PropertyChanges { target: d; secondaryButtonText: qsTr("Learn more") }
|
||||
PropertyChanges { target: d; secondaryButtonIcon: "external-link" }
|
||||
PropertyChanges { target: d; primaryButtonAction: root.importControlNodeClicked }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ StackLayout {
|
|||
|
||||
property bool editable: false
|
||||
property bool owned: false
|
||||
property bool isControlNode: false
|
||||
property int loginType: Constants.LoginType.Password
|
||||
|
||||
function navigateBack() {
|
||||
|
@ -48,7 +49,7 @@ StackLayout {
|
|||
|
||||
signal inviteNewPeopleClicked
|
||||
signal airdropTokensClicked
|
||||
signal backUpClicked
|
||||
signal exportControlNodeClicked
|
||||
|
||||
clip: true
|
||||
|
||||
|
@ -133,11 +134,10 @@ StackLayout {
|
|||
topPadding: 0
|
||||
loginType: root.loginType
|
||||
communityName: root.name
|
||||
//TODO connect to backend
|
||||
isControlNode: root.owned
|
||||
onPrimaryButtonClicked: isControlNode = !isControlNode
|
||||
isControlNode: root.isControlNode
|
||||
onExportControlNodeClicked: root.exportControlNodeClicked()
|
||||
//TODO update once the domain changes
|
||||
onSecondaryButtonClicked: Global.openLink(Constants.statusHelpLinkPrefix + "en/status-communities/about-the-control-node-in-status-communities")
|
||||
onLearnMoreClicked: Global.openLink(Constants.statusHelpLinkPrefix + "en/status-communities/about-the-control-node-in-status-communities")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.14
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
property string communityName: ""
|
||||
property string privateKey: ""
|
||||
|
||||
signal deletePrivateKey
|
||||
|
||||
width: 640
|
||||
title: qsTr("Move %1 community control node").arg(root.communityName)
|
||||
|
||||
closePolicy: Popup.NoAutoClose
|
||||
|
||||
component Paragraph: StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 40
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
lineHeightMode: Text.FixedHeight
|
||||
lineHeight: 22
|
||||
wrapMode: Text.Wrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
component CopyButton: StatusButton {
|
||||
id: copyButton
|
||||
borderColor: textColor
|
||||
disabledTextColor: textColor
|
||||
disabledColor: normalColor
|
||||
text: qsTr("Copy")
|
||||
size: StatusButton.Size.Tiny
|
||||
states: [
|
||||
State {
|
||||
name: "success"
|
||||
PropertyChanges {
|
||||
target: copyButton
|
||||
text: ""
|
||||
icon.name: "checkmark"
|
||||
normalColor: Theme.palette.successColor2
|
||||
textColor: Theme.palette.successColor1
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
onClicked: {
|
||||
width = width // break the biding to prevent the button from shrinking
|
||||
copyButton.state = "success"
|
||||
Backpressure.debounce(root, 2000, function () {
|
||||
copyButton.state = ""
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
StatusScrollView {
|
||||
id: scroll
|
||||
anchors.fill: parent
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
width: scroll.availableWidth
|
||||
spacing: 20
|
||||
Paragraph {
|
||||
text: qsTr("For a Status Community to function, it needs to have a single control node running. This installation of Status Desktop is currently the %1 community control node. To move the %1 control node to another device: ").arg(root.communityName)
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Paragraph {
|
||||
text: qsTr("1. Copy your Community’s private key:")
|
||||
}
|
||||
StatusBaseInput {
|
||||
id: privateKeyTextArea
|
||||
Layout.fillWidth: true
|
||||
multiline: true
|
||||
edit.readOnly: true
|
||||
text: root.privateKey
|
||||
rightComponent: CopyButton {
|
||||
onClicked: {
|
||||
privateKeyTextArea.edit.selectAll()
|
||||
privateKeyTextArea.edit.copy()
|
||||
privateKeyTextArea.edit.deselect()
|
||||
}
|
||||
}
|
||||
}
|
||||
Paragraph {
|
||||
text: qsTr("2. Stop using this computer as a control node")
|
||||
}
|
||||
Paragraph {
|
||||
text: qsTr("3. Import this Community via private key on another installation of Status desktop")
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
StatusDialogDivider { Layout.fillWidth: true }
|
||||
Item { Layout.fillHeight: true }
|
||||
Paragraph {
|
||||
text: qsTr("I acknowledge that...")
|
||||
}
|
||||
|
||||
StatusCheckBox {
|
||||
id: agreeToStopControl
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
text: qsTr("%1 will stop working without a control node").arg(root.communityName)
|
||||
}
|
||||
StatusCheckBox {
|
||||
id: agreeToSavePrivateKey
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 40
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
text: qsTr("I have saved the %1 private key").arg(root.communityName)
|
||||
}
|
||||
StatusCheckBox {
|
||||
id: agreeToDeletePrivateKey
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 40
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
text: qsTr("If I lose the private key, %1 will be unrecoverable").arg(root.communityName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
footer: StatusDialogFooter {
|
||||
rightButtons: ObjectModel {
|
||||
StatusButton {
|
||||
text: qsTr("Delete private key and stop control node")
|
||||
enabled: agreeToStopControl.checked && agreeToSavePrivateKey.checked && agreeToDeletePrivateKey.checked
|
||||
type: StatusBaseButton.Type.Danger
|
||||
onClicked: {
|
||||
root.deletePrivateKey()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ CreateCategoryPopup 1.0 CreateCategoryPopup.qml
|
|||
CreateChannelPopup 1.0 CreateChannelPopup.qml
|
||||
CreateCommunityPopup 1.0 CreateCommunityPopup.qml
|
||||
DiscordImportProgressDialog 1.0 DiscordImportProgressDialog.qml
|
||||
ExportControlNodePopup 1.0 ExportControlNodePopup.qml
|
||||
HoldingsDropdown 1.0 HoldingsDropdown.qml
|
||||
InDropdown 1.0 InDropdown.qml
|
||||
InviteFriendsToCommunityPopup 1.0 InviteFriendsToCommunityPopup.qml
|
||||
|
|
|
@ -36,6 +36,8 @@ StatusSectionLayout {
|
|||
|
||||
readonly property bool isOwner: community.memberRole === Constants.memberRole.owner
|
||||
readonly property bool isAdmin: isOwner || community.memberRole === Constants.memberRole.admin
|
||||
//TODO: get proper value from backend
|
||||
readonly property bool isControlNode: isOwner
|
||||
|
||||
readonly property string filteredSelectedTags: {
|
||||
let tagsArray = []
|
||||
|
@ -170,6 +172,7 @@ StatusSectionLayout {
|
|||
editable: true
|
||||
owned: root.community.memberRole === Constants.memberRole.owner
|
||||
loginType: root.rootStore.loginType
|
||||
isControlNode: root.isControlNode
|
||||
|
||||
onEdited: {
|
||||
const error = root.chatCommunitySectionModule.editCommunity(
|
||||
|
@ -199,10 +202,22 @@ StatusSectionLayout {
|
|||
}
|
||||
|
||||
onAirdropTokensClicked: root.goTo(Constants.CommunitySettingsSections.Airdrops)
|
||||
onBackUpClicked: {
|
||||
Global.openPopup(transferOwnershipPopup, {
|
||||
privateKey: root.chatCommunitySectionModule.exportCommunity(root.community.id),
|
||||
})
|
||||
onExportControlNodeClicked: {
|
||||
if(!root.isControlNode)
|
||||
return
|
||||
|
||||
root.rootStore.authenticateWithCallback((authenticated) => {
|
||||
if(!authenticated)
|
||||
return
|
||||
|
||||
Global.openExportControlNodePopup(root.community.name, root.chatCommunitySectionModule.exportCommunity(root.community.id), (popup) => {
|
||||
//TODO: connect to backend
|
||||
// Delete private key and remove control node status
|
||||
popup.onDeletePrivateKey.connect(() => {
|
||||
console.log("Delete private key")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ QtObject {
|
|||
Global.openDownloadImageDialog.connect(openDownloadImageDialog)
|
||||
Global.leaveCommunityRequested.connect(openLeaveCommunityPopup)
|
||||
Global.openTestnetPopup.connect(openTestnetPopup)
|
||||
Global.openExportControlNodePopup.connect(openExportControlNodePopup)
|
||||
}
|
||||
|
||||
property var currentPopup
|
||||
|
@ -248,6 +249,13 @@ QtObject {
|
|||
openPopup(testnetModal)
|
||||
}
|
||||
|
||||
function openExportControlNodePopup(communityName, privateKey, cb) {
|
||||
openPopup(exportControlNodePopup, {
|
||||
communityName: communityName,
|
||||
privateKey: privateKey
|
||||
}, cb)
|
||||
}
|
||||
|
||||
readonly property list<Component> _components: [
|
||||
Component {
|
||||
id: removeContactConfirmationDialog
|
||||
|
@ -588,6 +596,13 @@ QtObject {
|
|||
}
|
||||
onCancelClicked: close()
|
||||
}
|
||||
},
|
||||
|
||||
Component {
|
||||
id: exportControlNodePopup
|
||||
ExportControlNodePopup {
|
||||
onClosed: destroy()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ QtObject {
|
|||
signal openOutgoingIDRequestPopup(string publicKey, var cb)
|
||||
signal openDeleteMessagePopup(string messageId, var messageStore)
|
||||
signal openDownloadImageDialog(string imageSource)
|
||||
signal openExportControlNodePopup(string communityName, string privateKey, var ctaHandler)
|
||||
signal contactRenamed(string publicKey)
|
||||
|
||||
signal openLink(string link)
|
||||
|
|
Loading…
Reference in New Issue