mirror of
https://github.com/logos-blockchain/logos-execution-zone-wallet-ui.git
synced 2026-07-29 22:23:30 +00:00
278 lines
11 KiB
QML
278 lines
11 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import QtQuick.Dialogs
|
|
|
|
import Logos.Theme
|
|
import Logos.Controls
|
|
|
|
import "../controls"
|
|
|
|
Control {
|
|
id: root
|
|
|
|
property string configPath: ""
|
|
property string storePath: ""
|
|
property string createError: ""
|
|
property bool recoverMode: false
|
|
// Create/restore is a blocking round-trip to the wallet backend (it can take
|
|
// a while, especially restoring), which freezes the UI thread for its
|
|
// duration — this at least shows a message right before that happens
|
|
// instead of the form just going inert with no explanation.
|
|
property bool busy: false
|
|
|
|
signal createWallet(string configPath, string storagePath, string password, string sequencerUrl)
|
|
signal recoverWallet(string configPath, string storagePath, string mnemonic, string password, string sequencerUrl)
|
|
|
|
readonly property string testnetUrl: "https://testnet.lez.logos.co"
|
|
readonly property string localhostUrl: "http://127.0.0.1:3040"
|
|
|
|
onCreateErrorChanged: if (createError.length > 0) root.busy = false
|
|
|
|
QtObject {
|
|
id: d
|
|
function configParentFolderUrl(path) {
|
|
if (!path || path.length === 0) return ""
|
|
var p = path
|
|
var i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"))
|
|
if (i <= 0) return ""
|
|
var dir = p.substring(0, i)
|
|
return dir.indexOf("file://") === 0 ? dir : "file://" + dir
|
|
}
|
|
}
|
|
|
|
// Fires the actual (blocking) request one tick after the button click, so
|
|
// the "Restoring…"/"Creating…" busy state above has a chance to actually
|
|
// render before the UI thread freezes for the call.
|
|
Timer {
|
|
id: submitTimer
|
|
interval: 50
|
|
onTriggered: {
|
|
if (root.recoverMode) {
|
|
root.recoverWallet(configPathField.text, storagePathField.text, mnemonicField.text.trim(), passwordField.text, sequencerUrlField.text)
|
|
} else {
|
|
root.createWallet(configPathField.text, storagePathField.text, passwordField.text, sequencerUrlField.text)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Scrollable so the form stays reachable (including the submit button)
|
|
// when its content is taller than the window, e.g. with the recovery
|
|
// phrase field shown — rather than silently clipping/overflowing.
|
|
LogosScrollView {
|
|
id: scrollView
|
|
anchors.fill: parent
|
|
|
|
Item {
|
|
width: scrollView.availableWidth
|
|
implicitHeight: cardColumn.implicitHeight + 2 * Theme.spacing.xlarge
|
|
|
|
ColumnLayout {
|
|
id: cardColumn
|
|
|
|
x: Theme.spacing.xlarge
|
|
y: Theme.spacing.xlarge
|
|
width: parent.width - 2 * Theme.spacing.xlarge
|
|
spacing: Theme.spacing.large
|
|
|
|
LogosText {
|
|
text: qsTr("Set up LEZ Wallet")
|
|
font.pixelSize: Theme.typography.titleText
|
|
font.weight: Theme.typography.weightBold
|
|
color: Theme.palette.text
|
|
}
|
|
LogosText {
|
|
text: qsTr("Configure storage and secure with a password.")
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
color: Theme.palette.textSecondary
|
|
Layout.topMargin: -Theme.spacing.small
|
|
}
|
|
|
|
LogosTabBar {
|
|
id: modeTabBar
|
|
Layout.fillWidth: true
|
|
currentIndex: root.recoverMode ? 1 : 0
|
|
onCurrentIndexChanged: {
|
|
root.recoverMode = currentIndex === 1
|
|
root.createError = ""
|
|
}
|
|
|
|
LogosTabButton { text: qsTr("Create New") }
|
|
LogosTabButton { text: qsTr("Recover from Mnemonic") }
|
|
}
|
|
|
|
LogosText {
|
|
text: qsTr("Storage")
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
font.weight: Theme.typography.weightMedium
|
|
color: Theme.palette.text
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Theme.spacing.small
|
|
LogosTextField {
|
|
id: storagePathField
|
|
Layout.fillWidth: true
|
|
placeholderText: qsTr("Add store path")
|
|
text: root.storePath
|
|
}
|
|
FeedbackButton {
|
|
text: qsTr("Browse")
|
|
onClicked: storageFolderDialog.open()
|
|
}
|
|
}
|
|
LogosText {
|
|
text: qsTr("Config file")
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
font.weight: Theme.typography.weightMedium
|
|
color: Theme.palette.text
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Theme.spacing.small
|
|
LogosTextField {
|
|
id: configPathField
|
|
Layout.fillWidth: true
|
|
placeholderText: qsTr("Add path to config")
|
|
text: root.configPath
|
|
}
|
|
FeedbackButton {
|
|
Layout.preferredHeight: configPathField.height
|
|
text: qsTr("Browse")
|
|
onClicked: configFileDialog.open()
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Theme.spacing.medium
|
|
spacing: Theme.spacing.small
|
|
LogosText {
|
|
text: qsTr("Network")
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
font.weight: Theme.typography.weightMedium
|
|
color: Theme.palette.text
|
|
}
|
|
LogosTextField {
|
|
id: sequencerUrlField
|
|
Layout.fillWidth: true
|
|
placeholderText: qsTr("Sequencer URL")
|
|
text: root.testnetUrl
|
|
}
|
|
}
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Theme.spacing.small
|
|
FeedbackButton {
|
|
text: qsTr("Testnet")
|
|
opacity: sequencerUrlField.text === root.testnetUrl ? 1.0 : 0.4
|
|
onClicked: sequencerUrlField.text = root.testnetUrl
|
|
}
|
|
FeedbackButton {
|
|
text: qsTr("Localhost")
|
|
opacity: sequencerUrlField.text === root.localhostUrl ? 1.0 : 0.4
|
|
onClicked: sequencerUrlField.text = root.localhostUrl
|
|
}
|
|
}
|
|
|
|
LogosText {
|
|
text: qsTr("Recovery phrase")
|
|
visible: root.recoverMode
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
font.weight: Theme.typography.weightMedium
|
|
color: Theme.palette.text
|
|
Layout.topMargin: Theme.spacing.medium
|
|
}
|
|
LogosTextArea {
|
|
id: mnemonicField
|
|
visible: root.recoverMode
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 90
|
|
placeholderText: qsTr("Enter your 12 or 24-word recovery phrase")
|
|
}
|
|
|
|
LogosText {
|
|
text: qsTr("Security")
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
font.weight: Theme.typography.weightMedium
|
|
color: Theme.palette.text
|
|
Layout.topMargin: Theme.spacing.medium
|
|
}
|
|
LogosTextField {
|
|
id: passwordField
|
|
Layout.fillWidth: true
|
|
placeholderText: qsTr("Password")
|
|
echoMode: TextInput.Password
|
|
}
|
|
LogosTextField {
|
|
id: confirmField
|
|
Layout.fillWidth: true
|
|
placeholderText: qsTr("Confirm")
|
|
echoMode: TextInput.Password
|
|
}
|
|
|
|
LogosText {
|
|
Layout.fillWidth: true
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
color: Theme.palette.textSecondary
|
|
wrapMode: Text.WordWrap
|
|
visible: root.busy
|
|
text: root.recoverMode
|
|
? qsTr("Restoring your wallet from the recovery phrase… this can take a little while. Please don't close the app.")
|
|
: qsTr("Creating your wallet…")
|
|
}
|
|
|
|
LogosText {
|
|
id: errorLabel
|
|
Layout.fillWidth: true
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
color: Theme.palette.error
|
|
wrapMode: Text.WordWrap
|
|
visible: !root.busy && text.length > 0
|
|
text: root.createError
|
|
}
|
|
|
|
FeedbackButton {
|
|
Layout.alignment: Qt.AlignRight
|
|
enabled: !root.busy
|
|
text: root.busy
|
|
? (root.recoverMode ? qsTr("Restoring…") : qsTr("Creating…"))
|
|
: (root.recoverMode ? qsTr("Restore Wallet") : qsTr("Create Wallet"))
|
|
font.pixelSize: Theme.typography.secondaryText
|
|
onClicked: {
|
|
if (passwordField.text.length === 0) {
|
|
root.createError = qsTr("Password cannot be empty.")
|
|
} else if (passwordField.text !== confirmField.text) {
|
|
root.createError = qsTr("Passwords do not match.")
|
|
} else if (root.recoverMode && mnemonicField.text.trim().length === 0) {
|
|
root.createError = qsTr("Recovery phrase cannot be empty.")
|
|
} else {
|
|
root.createError = ""
|
|
root.busy = true
|
|
submitTimer.start()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FileDialog {
|
|
id: storageFolderDialog
|
|
modality: Qt.NonModal
|
|
nameFilters: ["JSON files (*.json)"]
|
|
currentFolder: root.storePath ? d.configParentFolderUrl(root.storePath) : ""
|
|
onAccepted: storagePathField.text = selectedFile.toString().replace(/^file:\/\//, "")
|
|
}
|
|
|
|
FileDialog {
|
|
id: configFileDialog
|
|
modality: Qt.NonModal
|
|
nameFilters: ["JSON files (*.json)"]
|
|
currentFolder: root.configPath ? d.configParentFolderUrl(root.configPath) : ""
|
|
onAccepted: {
|
|
if (selectedFile) configPathField.text = selectedFile.toString().replace(/^file:\/\//, "")
|
|
}
|
|
}
|
|
}
|