feat(Import Control Node): Adding ImportControlNode flow
1. Create a new popup as per Design: ImportControlNodePopup 2. Add the popup in storybook 3. Integrate ImportControlNodePopup in the app
This commit is contained in:
parent
83303a011e
commit
4aaae242b5
|
@ -289,6 +289,10 @@ ListModel {
|
|||
title: "ExportControlNodePopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "ImportControlNodePopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "StatusButton"
|
||||
section: "Controls"
|
||||
|
|
|
@ -253,5 +253,8 @@
|
|||
],
|
||||
"ExportControlNodePopup": [
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba⎜Desktop?type=design&node-id=31171-627949&mode=design&t=WxK2N6sL8idHBKMZ-0"
|
||||
],
|
||||
"ImportControlNodePopup": [
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba⎜Desktop?type=design&node-id=31171-628434&mode=design&t=IFFCNUpRS3oQbzAR-0"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ SplitView {
|
|||
id: communityColumnHeader
|
||||
|
||||
width: widthSlider.value
|
||||
anchors.centerIn: parent
|
||||
anchors.centerIn: parent
|
||||
name: d.name
|
||||
membersCount: d.membersCount
|
||||
image: d.image
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml 2.15
|
||||
|
||||
import AppLayouts.Communities.popups 1.0
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
orientation: Qt.Vertical
|
||||
|
||||
Logs { id: logs }
|
||||
|
||||
SplitView {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
Pane {
|
||||
id: mainPane
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
PopupBackground {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.centerIn: parent
|
||||
text: "Reopen"
|
||||
|
||||
onClicked: popupComponent.createObject(mainPane)
|
||||
}
|
||||
Component.onCompleted: popupComponent.createObject(mainPane)
|
||||
}
|
||||
Pane {
|
||||
SplitView.preferredWidth: 300
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
|
||||
Label {
|
||||
text: "Matching private key"
|
||||
}
|
||||
TextEdit {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: "red"
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
id: matchingPrivateKey
|
||||
Layout.fillWidth: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
readOnly: true
|
||||
text: "0x0454f2231543ba02583e4c55e513a75092a4f2c86c04d0796b195e964656d6cd"
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Copy"
|
||||
onClicked: {
|
||||
matchingPrivateKey.selectAll()
|
||||
matchingPrivateKey.copy()
|
||||
matchingPrivateKey.deselect()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Mismatching private key"
|
||||
}
|
||||
TextEdit {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: "red"
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
id: mismatchingPrivateKey
|
||||
Layout.fillWidth: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
readOnly: true
|
||||
text: "0x0454f2231543ba02583e4c55e513a75092a4f2c86c04d0796b195e964656d6ce"
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Copy"
|
||||
onClicked: {
|
||||
mismatchingPrivateKey.selectAll()
|
||||
mismatchingPrivateKey.copy()
|
||||
mismatchingPrivateKey.deselect()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Load in progress private key"
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: "red"
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
id: loadInProgressPrivateKey
|
||||
Layout.fillWidth: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
readOnly: true
|
||||
text: "0x0454f2231543ba02583e4c55e513a75092a4f2c86c04d0796b195e964656d6ca"
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Copy"
|
||||
onClicked: {
|
||||
loadInProgressPrivateKey.selectAll()
|
||||
loadInProgressPrivateKey.copy()
|
||||
loadInProgressPrivateKey.deselect()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property var community: QtObject {
|
||||
property string id: "1"
|
||||
property string name: "Socks"
|
||||
property var members: { "count": 5 }
|
||||
property string image: Style.png("tokens/UNI")
|
||||
property string color: "orchid"
|
||||
}
|
||||
|
||||
readonly property var otherCommunity: QtObject {
|
||||
property string id: "2"
|
||||
property string name: "Socks"
|
||||
property var members: { "count": 5 }
|
||||
property string image: Style.png("tokens/UNI")
|
||||
property string color: "orchid"
|
||||
}
|
||||
|
||||
readonly property Timer timer: Timer {
|
||||
//id: _timer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
function callWithDelay(cb) {
|
||||
d.timer.triggered.connect(cb);
|
||||
d.timer.triggered.connect(function release () {
|
||||
d.timer.triggered.disconnect(cb);
|
||||
d.timer.triggered.disconnect(release);
|
||||
});
|
||||
d.timer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: popupComponent
|
||||
ImportControlNodePopup {
|
||||
id: popup
|
||||
anchors.centerIn: parent
|
||||
modal: false
|
||||
visible: true
|
||||
|
||||
onRequestCommunityInfo: {
|
||||
logs.logEvent("ImportControlNodePopup::onRequestCommunityInfo", ["private key"], [privateKey])
|
||||
if(privateKey === matchingPrivateKey.text)
|
||||
d.timer.callWithDelay(() => popup.setCommunityInfo(d.community))
|
||||
else if (privateKey === mismatchingPrivateKey.text)
|
||||
d.timer.callWithDelay(() => popup.setCommunityInfo(d.otherCommunity))
|
||||
}
|
||||
|
||||
community: d.community
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumHeight: 100
|
||||
SplitView.preferredHeight: 160
|
||||
|
||||
logsView.logText: logs.logText
|
||||
}
|
||||
}
|
|
@ -77,6 +77,8 @@ QtObject {
|
|||
|
||||
signal importingCommunityStateChanged(string communityId, int state, string errorMsg)
|
||||
|
||||
signal communityAdded(string communityId)
|
||||
|
||||
signal communityInfoAlreadyRequested()
|
||||
|
||||
signal communityAccessRequested(string communityId)
|
||||
|
@ -631,6 +633,10 @@ QtObject {
|
|||
function onCommunityAccessRequested(communityId) {
|
||||
root.communityAccessRequested(communityId)
|
||||
}
|
||||
|
||||
function onCommunityAdded(communityId) {
|
||||
root.communityAdded(communityId)
|
||||
}
|
||||
}
|
||||
|
||||
readonly property Connections mainModuleInstConnections: Connections {
|
||||
|
|
|
@ -222,7 +222,6 @@ Item {
|
|||
target: root.store
|
||||
|
||||
function onImportingCommunityStateChanged(communityId, state, errorMsg) {
|
||||
|
||||
const community = root.store.getCommunityDetailsAsJson(communityId)
|
||||
let title = ""
|
||||
let subTitle = ""
|
||||
|
|
|
@ -52,6 +52,7 @@ StackLayout {
|
|||
signal inviteNewPeopleClicked
|
||||
signal airdropTokensClicked
|
||||
signal exportControlNodeClicked
|
||||
signal importControlNodeClicked
|
||||
|
||||
clip: true
|
||||
|
||||
|
@ -137,6 +138,7 @@ StackLayout {
|
|||
communityName: root.name
|
||||
isControlNode: root.isControlNode
|
||||
onExportControlNodeClicked: root.exportControlNodeClicked()
|
||||
onImportControlNodeClicked: root.importControlNodeClicked()
|
||||
//TODO update once the domain changes
|
||||
onLearnMoreClicked: Global.openLink(Constants.statusHelpLinkPrefix + "status-communities/about-the-control-node-in-status-communities")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml 2.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
|
||||
|
||||
required property var community
|
||||
|
||||
signal importControlNode(string privateKey)
|
||||
signal requestCommunityInfo(string privateKey)
|
||||
|
||||
function setCommunityInfo(communityInfo) {
|
||||
d.requestedCommunityInfo = communityInfo
|
||||
d.privateKeyCheckInProgress = false
|
||||
}
|
||||
|
||||
onRequestCommunityInfo: d.privateKeyCheckInProgress = true
|
||||
|
||||
width: 640
|
||||
height: Math.max(552, implicitHeight)
|
||||
title: qsTr("Make this device the control node for %1").arg(root.community.name)
|
||||
|
||||
closePolicy: Popup.NoAutoClose
|
||||
|
||||
component Paragraph: StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
lineHeightMode: Text.FixedHeight
|
||||
lineHeight: 22
|
||||
wrapMode: Text.Wrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
component PasteButton: StatusButton {
|
||||
id: pasteButton
|
||||
borderColor: textColor
|
||||
text: qsTr("Paste")
|
||||
size: StatusButton.Size.Tiny
|
||||
}
|
||||
|
||||
component ChatDetails: Control {
|
||||
verticalPadding: 6
|
||||
horizontalPadding: 4
|
||||
|
||||
contentItem: RowLayout {
|
||||
StatusChatInfoButton {
|
||||
id: communityInfoButton
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
title: community.name
|
||||
subTitle: qsTr("%n member(s)", "", community.members.count || 0)
|
||||
asset.name: community.image
|
||||
asset.color: community.color
|
||||
asset.isImage: true
|
||||
type: StatusChatInfoButton.Type.OneToOneChat
|
||||
hoverEnabled: false
|
||||
visible: false
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
StatusBaseText {
|
||||
id: detectionLabel
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: Style.current.additionalTextSize
|
||||
visible: !!text
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "matchingPrivateKey"
|
||||
when: d.isPrivateKeyMatching
|
||||
PropertyChanges { target: detectionLabel; text: qsTr("Private key is valid") }
|
||||
PropertyChanges { target: detectionLabel; color: Theme.palette.successColor1 }
|
||||
PropertyChanges { target: communityInfoButton; visible: true }
|
||||
},
|
||||
State {
|
||||
name: "mismatchingPrivateKey"
|
||||
when: !d.isPrivateKeyMatching && d.isPrivateKey && !d.privateKeyCheckInProgress
|
||||
PropertyChanges { target: detectionLabel; text: qsTr("This is not the correct private key for %1").arg(root.community.name) }
|
||||
PropertyChanges { target: detectionLabel; color: Theme.palette.dangerColor1 }
|
||||
},
|
||||
State {
|
||||
name: "checking"
|
||||
when: d.privateKeyCheckInProgress
|
||||
PropertyChanges { target: detectionLabel; text: qsTr("Checking private key...") }
|
||||
PropertyChanges { target: detectionLabel; color: Theme.palette.baseColor1 }
|
||||
},
|
||||
State {
|
||||
name: "invalidPrivateKey"
|
||||
when: !d.isPrivateKey && d.isPrivateKeyInserted
|
||||
PropertyChanges { target: detectionLabel; text: qsTr("This is not a private key") }
|
||||
PropertyChanges { target: detectionLabel; color: Theme.palette.dangerColor1 }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property bool isPrivateKey: Utils.isPrivateKey(privateKeyTextArea.text)
|
||||
readonly property bool isPrivateKeyMatching: d.requestedCommunityInfo ? d.requestedCommunityInfo.id === community.id : false
|
||||
readonly property bool isPrivateKeyInserted: privateKeyTextArea.text.length > 0
|
||||
|
||||
property bool privateKeyCheckInProgress: false
|
||||
property var requestedCommunityInfo: undefined
|
||||
|
||||
onIsPrivateKeyChanged: {
|
||||
if(!isPrivateKey) {
|
||||
requestedCommunityInfo = undefined
|
||||
privateKeyCheckInProgress = false
|
||||
return
|
||||
}
|
||||
|
||||
privateKeyCheckInProgress = true
|
||||
requestedCommunityInfo = undefined
|
||||
requestCommunityInfo(privateKeyTextArea.text)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
Paragraph {
|
||||
Layout.preferredHeight: 22
|
||||
Layout.bottomMargin: Style.current.halfPadding
|
||||
text: qsTr("To move the %1 control node to this device: ").arg(root.community.name)
|
||||
}
|
||||
Paragraph {
|
||||
text: qsTr("1. Stop using any other devices as the control node for this Community")
|
||||
}
|
||||
Paragraph {
|
||||
text: qsTr("2. Paste the Community’s private key below:")
|
||||
}
|
||||
StatusBaseInput {
|
||||
id: privateKeyTextArea
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 86
|
||||
rightPadding: Style.current.padding
|
||||
multiline: true
|
||||
valid: d.isPrivateKey || !d.isPrivateKeyInserted
|
||||
placeholderText: qsTr("e.g. %1").arg("0x0454f2231543ba02583e4c55e513a75092a4f2c86c04d0796b195e964656d6cd94b8237c64ef668eb0fe268387adc3fe699bce97190a631563c82b718c19cf1fb8")
|
||||
rightComponent: PasteButton {
|
||||
onClicked: {
|
||||
privateKeyTextArea.edit.clear()
|
||||
privateKeyTextArea.edit.paste()
|
||||
}
|
||||
}
|
||||
}
|
||||
ChatDetails {
|
||||
Layout.topMargin: Style.current.halfPadding
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 46
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: Style.current.xlPadding
|
||||
}
|
||||
ColumnLayout {
|
||||
id: agreementLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
spacing: mainLayout.spacing
|
||||
|
||||
visible: d.isPrivateKeyMatching
|
||||
|
||||
StatusDialogDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Paragraph {
|
||||
Layout.topMargin: Style.current.padding
|
||||
text: qsTr("I acknowledge that...")
|
||||
}
|
||||
StatusCheckBox {
|
||||
id: agreementCheckBox
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
text: qsTr("I must keep this device online and running Status for the Community to function")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
rightButtons: ObjectModel {
|
||||
StatusButton {
|
||||
text: qsTr("Make this device the control node for %1").arg(root.community.name)
|
||||
enabled: d.isPrivateKeyMatching && agreementCheckBox.checked
|
||||
onClicked: {
|
||||
root.importControlNode(privateKeyTextArea.text)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ CreateCommunityPopup 1.0 CreateCommunityPopup.qml
|
|||
DiscordImportProgressDialog 1.0 DiscordImportProgressDialog.qml
|
||||
ExportControlNodePopup 1.0 ExportControlNodePopup.qml
|
||||
HoldingsDropdown 1.0 HoldingsDropdown.qml
|
||||
ImportControlNodePopup 1.0 ImportControlNodePopup.qml
|
||||
InDropdown 1.0 InDropdown.qml
|
||||
InviteFriendsToCommunityPopup 1.0 InviteFriendsToCommunityPopup.qml
|
||||
MembersDropdown 1.0 MembersDropdown.qml
|
||||
|
|
|
@ -37,8 +37,7 @@ 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 bool isControlNode: community.isControlNode
|
||||
|
||||
readonly property string filteredSelectedTags: {
|
||||
let tagsArray = []
|
||||
|
@ -223,6 +222,13 @@ StatusSectionLayout {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
onImportControlNodeClicked: {
|
||||
if(root.isControlNode)
|
||||
return
|
||||
|
||||
Global.openImportControlNodePopup(root.community, d.importControlNodePopupOpened)
|
||||
}
|
||||
}
|
||||
|
||||
MembersSettingsPanel {
|
||||
|
@ -535,6 +541,44 @@ StatusSectionLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestCommunityInfoWithCallback(privateKey, callback) {
|
||||
if(!callback) return
|
||||
|
||||
//success
|
||||
root.rootStore.communityAdded.connect(function communityAddedHandler(communityId) {
|
||||
root.rootStore.communityAdded.disconnect(communityAddedHandler)
|
||||
let community = null
|
||||
try {
|
||||
const communityJson = root.rootStore.getSectionByIdJson(communityId)
|
||||
community = JSON.parse(communityJson)
|
||||
} catch (e) {
|
||||
console.warn("Error parsing community json: ", communityJson, " error: ", e.message)
|
||||
}
|
||||
|
||||
callback(community)
|
||||
})
|
||||
|
||||
//error
|
||||
root.rootStore.importingCommunityStateChanged.connect(function communityImportingStateChangedHandler(communityId, status) {
|
||||
root.rootStore.importingCommunityStateChanged.disconnect(communityImportingStateChangedHandler)
|
||||
if(status === Constants.communityImportingError) {
|
||||
callback(null)
|
||||
}
|
||||
})
|
||||
|
||||
root.rootStore.requestCommunityInfo(privateKey, false)
|
||||
}
|
||||
|
||||
function importControlNodePopupOpened(popup) {
|
||||
popup.requestCommunityInfo.connect((privateKey) => {
|
||||
requestCommunityInfoWithCallback(privateKey, popup.setCommunityInfo)
|
||||
})
|
||||
|
||||
popup.importControlNode.connect((privateKey) => {
|
||||
root.rootStore.importCommunity(privateKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
StatusQUtils.ModelChangeTracker {
|
||||
|
|
|
@ -57,6 +57,7 @@ QtObject {
|
|||
Global.leaveCommunityRequested.connect(openLeaveCommunityPopup)
|
||||
Global.openTestnetPopup.connect(openTestnetPopup)
|
||||
Global.openExportControlNodePopup.connect(openExportControlNodePopup)
|
||||
Global.openImportControlNodePopup.connect(openImportControlNodePopup)
|
||||
}
|
||||
|
||||
property var currentPopup
|
||||
|
@ -256,6 +257,10 @@ QtObject {
|
|||
}, cb)
|
||||
}
|
||||
|
||||
function openImportControlNodePopup(community, cb) {
|
||||
openPopup(importControlNodePopup, {community: community}, cb)
|
||||
}
|
||||
|
||||
readonly property list<Component> _components: [
|
||||
Component {
|
||||
id: removeContactConfirmationDialog
|
||||
|
@ -603,6 +608,14 @@ QtObject {
|
|||
ExportControlNodePopup {
|
||||
onClosed: destroy()
|
||||
}
|
||||
},
|
||||
|
||||
Component {
|
||||
id: importControlNodePopup
|
||||
ImportControlNodePopup {
|
||||
onClosed: destroy()
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
|
|
@ -45,7 +45,8 @@ 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 openExportControlNodePopup(string communityName, string privateKey, var cb)
|
||||
signal openImportControlNodePopup(var community, var cb)
|
||||
signal contactRenamed(string publicKey)
|
||||
|
||||
signal openLink(string link)
|
||||
|
|
Loading…
Reference in New Issue