aboutsummaryrefslogtreecommitdiffstats
path: root/ethereal/assets/qml
diff options
context:
space:
mode:
Diffstat (limited to 'ethereal/assets/qml')
-rw-r--r--ethereal/assets/qml/QmlApp.qml22
-rw-r--r--ethereal/assets/qml/first_run.qml155
-rw-r--r--ethereal/assets/qml/muted.qml74
-rw-r--r--ethereal/assets/qml/test_app.qml70
-rw-r--r--ethereal/assets/qml/transactions.qml9
-rw-r--r--ethereal/assets/qml/wallet.qml1075
-rw-r--r--ethereal/assets/qml/webapp.qml246
7 files changed, 1651 insertions, 0 deletions
diff --git a/ethereal/assets/qml/QmlApp.qml b/ethereal/assets/qml/QmlApp.qml
new file mode 100644
index 000000000..f5c503f4c
--- /dev/null
+++ b/ethereal/assets/qml/QmlApp.qml
@@ -0,0 +1,22 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.0;
+import QtQuick.Layouts 1.0;
+import Ethereum 1.0
+
+ApplicationWindow {
+ minimumWidth: 500
+ maximumWidth: 500
+ maximumHeight: 400
+ minimumHeight: 400
+
+ function onNewBlockCb(block) {
+ console.log("Please overwrite onNewBlock(block):", block)
+ }
+ function onObjectChangeCb(stateObject) {
+ console.log("Please overwrite onObjectChangeCb(object)", stateObject)
+ }
+ function onStorageChangeCb(storageObject) {
+ var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":");
+ console.log("Please overwrite onStorageChangeCb(object)", ev)
+ }
+}
diff --git a/ethereal/assets/qml/first_run.qml b/ethereal/assets/qml/first_run.qml
new file mode 100644
index 000000000..0b1dac4c6
--- /dev/null
+++ b/ethereal/assets/qml/first_run.qml
@@ -0,0 +1,155 @@
+import QtQuick 2.0
+import Ethereum 1.0
+
+// Which ones do we actually need?
+import QtQuick.Controls 1.0;
+import QtQuick.Layouts 1.0;
+import QtQuick.Dialogs 1.0;
+import QtQuick.Window 2.1;
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Dialogs 1.1
+
+ApplicationWindow {
+ id: wizardRoot
+ width: 500
+ height: 400
+ title: "Ethereal first run setup"
+
+ Column {
+ spacing: 5
+ anchors.leftMargin: 10
+ anchors.left: parent.left
+
+ Text {
+ visible: true
+ text: "<h2>Ethereal setup</h2>"
+ }
+
+ Column {
+ id: restoreColumn
+ spacing: 5
+ Text {
+ visible: true
+ font.pointSize: 14
+ text: "Restore your Ethereum account"
+ id: restoreLabel
+ }
+
+ TextField {
+ id: txPrivKey
+ width: 480
+ placeholderText: "Private key or mnemonic words"
+ focus: true
+ onTextChanged: {
+ if(this.text.length == 64){
+ detailLabel.text = "Private (hex) key detected."
+ actionButton.enabled = true
+ }
+ else if(this.text.split(" ").length == 24){
+ detailLabel.text = "Mnemonic key detected."
+ actionButton.enabled = true
+ }else{
+ detailLabel.text = ""
+ actionButton.enabled = false
+ }
+ }
+ }
+ Row {
+ spacing: 10
+ Button {
+ id: actionButton
+ text: "Restore"
+ enabled: false
+ onClicked: {
+ var success = lib.importAndSetPrivKey(txPrivKey.text)
+ if(success){
+ importedDetails.visible = true
+ restoreColumn.visible = false
+ newKey.visible = false
+ wizardRoot.height = 120
+ }
+ }
+ }
+ Text {
+ id: detailLabel
+ font.pointSize: 12
+ anchors.topMargin: 10
+ }
+ }
+ }
+ Column {
+ id: importedDetails
+ visible: false
+ Text {
+ text: "<b>Your account has been imported. Please close the application and restart it again to let the changes take effect.</b>"
+ wrapMode: Text.WordWrap
+ width: 460
+ }
+ }
+ Column {
+ spacing: 5
+ id: newDetailsColumn
+ visible: false
+ Text {
+ font.pointSize: 14
+ text: "Your account details"
+ }
+ Label {
+ text: "Address"
+ }
+ TextField {
+ id: addressInput
+ readOnly:true
+ width: 480
+ }
+ Label {
+ text: "Private key"
+ }
+ TextField {
+ id: privkeyInput
+ readOnly:true
+ width: 480
+ }
+ Label {
+ text: "Mnemonic words"
+ }
+ TextField {
+ id: mnemonicInput
+ readOnly:true
+ width: 480
+ }
+ Label {
+ text: "<b>A new account has been created. Please take the time to write down the <i>24 words</i>. You can use those to restore your account at a later date.</b>"
+ wrapMode: Text.WordWrap
+ width: 480
+ }
+ Label {
+ text: "Please restart the application once you have completed the steps above."
+ wrapMode: Text.WordWrap
+ width: 480
+ }
+ }
+
+ }
+ Button {
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: 10
+ anchors.bottomMargin: 10
+ id: newKey
+ text: "I don't have an account yet"
+ onClicked: {
+ var res = lib.createAndSetPrivKey()
+ mnemonicInput.text = res[0]
+ addressInput.text = res[1]
+ privkeyInput.text = res[2]
+
+ // Hide restore
+ restoreColumn.visible = false
+
+ // Show new details
+ newDetailsColumn.visible = true
+ newKey.visible = false
+ }
+ }
+}
diff --git a/ethereal/assets/qml/muted.qml b/ethereal/assets/qml/muted.qml
new file mode 100644
index 000000000..fac8267c4
--- /dev/null
+++ b/ethereal/assets/qml/muted.qml
@@ -0,0 +1,74 @@
+import QtQuick 2.0
+import QtWebKit 3.0
+import QtWebKit.experimental 1.0
+import QtQuick.Controls 1.0;
+import QtQuick.Layouts 1.0;
+import QtQuick.Window 2.1;
+import Ethereum 1.0
+
+ApplicationWindow {
+ id: window
+ title: "muted"
+ width: 900
+ height: 600
+ minimumHeight: 300
+
+ property alias url: webView.url
+ property alias webView: webView
+
+
+ Item {
+ id: root
+ anchors.fill: parent
+ WebView {
+ objectName: "webView"
+ id: webView
+ anchors {
+ top: root.top
+ right: root.right
+ left: root.left
+ bottom: root.bottom
+ //bottom: sizeGrip.top
+ }
+
+ experimental.preferences.javascriptEnabled: true
+ experimental.preferences.navigatorQtObjectEnabled: true
+ experimental.onMessageReceived: {
+ var data = JSON.parse(message.data)
+
+ switch(data.call) {
+ case "log":
+ console.log.apply(this, data.args)
+ break;
+ }
+ }
+ function postData(seed, data) {
+ webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed}))
+ }
+ function postEvent(event, data) {
+ webview.experimental.postMessage(JSON.stringify({data: data, _event: event}))
+ }
+ }
+
+ /*
+ Rectangle {
+ id: sizeGrip
+ color: "gray"
+ height: 5
+ anchors {
+ left: root.left
+ right: root.right
+ }
+ y: Math.round(root.height * 2 / 3)
+
+ MouseArea {
+ anchors.fill: parent
+ drag.target: sizeGrip
+ drag.minimumY: 0
+ drag.maximumY: root.height - sizeGrip.height
+ drag.axis: Drag.YAxis
+ }
+ }
+ */
+ }
+}
diff --git a/ethereal/assets/qml/test_app.qml b/ethereal/assets/qml/test_app.qml
new file mode 100644
index 000000000..c69587839
--- /dev/null
+++ b/ethereal/assets/qml/test_app.qml
@@ -0,0 +1,70 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.0;
+import QtQuick.Layouts 1.0;
+import Ethereum 1.0
+
+QmlApp {
+ minimumWidth: 350
+ maximumWidth: 350
+ maximumHeight: 80
+ minimumHeight: 80
+
+ title: "Generic Coin"
+
+ property string contractAddr: "f299f6c74515620e4c4cd8fe3d205b5c4f2e25c8"
+ property string addr: "2ef47100e0787b915105fd5e3f4ff6752079d5cb"
+
+ Component.onCompleted: {
+ eth.watch(contractAddr, addr)
+ eth.watch(addr, contractAddr)
+ setAmount()
+ }
+
+ function onStorageChangeCb(storageObject) {
+ setAmount()
+ }
+
+ function setAmount(){
+ var state = eth.getStateObject(contractAddr)
+ var storage = state.getStorage(addr)
+ amountLabel.text = storage
+ }
+ Column {
+ spacing: 5
+ Row {
+ spacing: 20
+ Label {
+ id: genLabel
+ text: "Generic coin balance:"
+ }
+ Label {
+ id: amountLabel
+ }
+ }
+ Row {
+ spacing: 20
+ TextField {
+ id: address
+ placeholderText: "Address"
+ }
+ TextField {
+ id: amount
+ placeholderText: "Amount"
+ }
+ }
+ Button {
+ text: "Send coins"
+ onClicked: {
+ var privKey = eth.getKey().privateKey
+ if(privKey){
+ var result = eth.transact(privKey, contractAddr, 0,"100000","250", "0x" + address.text + "\n" + amount.text)
+ resultTx.text = result.hash
+ }
+ }
+ }
+ Label {
+ id: resultTx
+ }
+ }
+
+}
diff --git a/ethereal/assets/qml/transactions.qml b/ethereal/assets/qml/transactions.qml
new file mode 100644
index 000000000..e9a035a85
--- /dev/null
+++ b/ethereal/assets/qml/transactions.qml
@@ -0,0 +1,9 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.0;
+import QtQuick.Layouts 1.0;
+
+Rectangle {
+ id: transactionView
+ visible: false
+ Text { text: "TX VIEW" }
+}
diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml
new file mode 100644
index 000000000..84f8fd5cf
--- /dev/null
+++ b/ethereal/assets/qml/wallet.qml
@@ -0,0 +1,1075 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.0;
+import QtQuick.Layouts 1.0;
+import QtQuick.Dialogs 1.0;
+import QtQuick.Window 2.1;
+import QtQuick.Controls.Styles 1.1
+import Ethereum 1.0
+
+
+ApplicationWindow {
+ id: root
+
+ property alias miningButtonText: miningButton.text
+
+ width: 900
+ height: 600
+ minimumHeight: 300
+
+ title: "Ethereal"
+
+ MenuBar {
+ Menu {
+ title: "File"
+ MenuItem {
+ text: "Import App"
+ shortcut: "Ctrl+o"
+ onTriggered: openAppDialog.open()
+ }
+ }
+
+ Menu {
+ title: "Developer"
+ MenuItem {
+ text: "Debugger"
+ shortcut: "Ctrl+d"
+ onTriggered: ui.startDebugger()
+ }
+ }
+
+ Menu {
+ title: "Network"
+ MenuItem {
+ text: "Add Peer"
+ shortcut: "Ctrl+p"
+ onTriggered: {
+ addPeerWin.visible = true
+ }
+ }
+ MenuItem {
+ text: "Show Peers"
+ shortcut: "Ctrl+e"
+ onTriggered: {
+ peerWindow.visible = true
+ }
+ }
+ }
+
+ Menu {
+ title: "Help"
+ MenuItem {
+ text: "About"
+ onTriggered: {
+ aboutWin.visible = true
+ }
+ }
+ }
+
+ }
+
+
+ property var blockModel: ListModel {
+ id: blockModel
+ }
+
+ function setView(view) {
+ networkView.visible = false
+ historyView.visible = false
+ newTxView.visible = false
+ infoView.visible = false
+ view.visible = true
+ //root.title = "Ethereal - " = view.title
+ }
+
+ SplitView {
+ anchors.fill: parent
+ resizing: false
+
+ Rectangle {
+ id: menu
+ Layout.minimumWidth: 80
+ Layout.maximumWidth: 80
+ anchors.bottom: parent.bottom
+ anchors.top: parent.top
+ //color: "#D9DDE7"
+ color: "#252525"
+
+ ColumnLayout {
+ y: 50
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 200
+ Image {
+ source: ui.assetPath("tx.png")
+ anchors.horizontalCenter: parent.horizontalCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ setView(historyView)
+ }
+ }
+ }
+ Image {
+ source: ui.assetPath("new.png")
+ anchors.horizontalCenter: parent.horizontalCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ setView(newTxView)
+ }
+ }
+ }
+ Image {
+ source: ui.assetPath("net.png")
+ anchors.horizontalCenter: parent.horizontalCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ setView(networkView)
+ }
+ }
+ }
+
+ Image {
+ source: ui.assetPath("heart.png")
+ anchors.horizontalCenter: parent.horizontalCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ setView(infoView)
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: mainView
+ color: "#00000000"
+ anchors.right: parent.right
+ anchors.left: menu.right
+ anchors.bottom: parent.bottom
+ anchors.top: parent.top
+
+ property var txModel: ListModel {
+ id: txModel
+ }
+
+ Rectangle {
+ id: historyView
+ anchors.fill: parent
+
+ property var title: "Transactions"
+ TableView {
+ id: txTableView
+ anchors.fill: parent
+ TableViewColumn{ role: "inout" ; title: "" ; width: 40 }
+ TableViewColumn{ role: "value" ; title: "Value" ; width: 100 }
+ TableViewColumn{ role: "address" ; title: "Address" ; width: 430 }
+ TableViewColumn{ role: "contract" ; title: "Contract" ; width: 100 }
+
+ model: txModel
+ }
+ }
+
+ Rectangle {
+ id: newTxView
+ property var title: "New transaction"
+ visible: false
+ anchors.fill: parent
+ color: "#00000000"
+ /*
+ TabView{
+ anchors.fill: parent
+ anchors.rightMargin: 5
+ anchors.leftMargin: 5
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+ id: newTransactionTab
+ Component.onCompleted:{
+ addTab("Simple send", newTransaction)
+ addTab("Contracts", newContract)
+ }
+ }
+ */
+ Component.onCompleted: {
+ newContract.createObject(newTxView)
+ }
+ }
+
+ Rectangle {
+ id: networkView
+ property var title: "Network"
+ visible: false
+ anchors.fill: parent
+
+ TableView {
+ id: blockTable
+ width: parent.width
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ TableViewColumn{ role: "number" ; title: "#" ; width: 100 }
+ TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 }
+ TableViewColumn{ role: "txAmount" ; title: "Tx amount" ; width: 100 }
+
+ model: blockModel
+
+ onDoubleClicked: {
+ popup.visible = true
+ popup.setDetails(blockModel.get(row))
+ }
+ }
+
+ }
+
+ Rectangle {
+ id: infoView
+ property var title: "Information"
+ visible: false
+ color: "#00000000"
+ anchors.fill: parent
+
+ Column {
+ spacing: 3
+ anchors.fill: parent
+ anchors.topMargin: 5
+ anchors.leftMargin: 5
+
+ Label {
+ id: addressLabel
+ text: "Address"
+ }
+ TextField {
+ text: pub.getKey().address
+ width: 500
+ }
+
+ Label {
+ text: "Client ID"
+ }
+ TextField {
+ text: eth.clientId()
+ onTextChanged: {
+ eth.changeClientId(text)
+ }
+ }
+ }
+
+ property var addressModel: ListModel {
+ id: addressModel
+ }
+ TableView {
+ id: addressView
+ width: parent.width - 200
+ height: 200
+ anchors.bottom: logLayout.top
+ TableViewColumn{ role: "name"; title: "name" }
+ TableViewColumn{ role: "address"; title: "address"; width: 300}
+
+ model: addressModel
+ }
+
+ Rectangle {
+ anchors.top: addressView.top
+ anchors.left: addressView.right
+ anchors.leftMargin: 20
+
+ TextField {
+ placeholderText: "Name to register"
+ id: nameToReg
+ width: 150
+ }
+
+ Button {
+ anchors.top: nameToReg.bottom
+ text: "Register"
+ MouseArea{
+ anchors.fill: parent
+ onClicked: {
+ eth.registerName(nameToReg.text)
+ nameToReg.text = ""
+ }
+ }
+ }
+ }
+
+
+ property var logModel: ListModel {
+ id: logModel
+ }
+ RowLayout {
+ id: logLayout
+ width: parent.width
+ height: 200
+ anchors.bottom: parent.bottom
+ TableView {
+ id: logView
+ headerVisible: false
+ anchors {
+ right: logLevelSlider.left
+ left: parent.left
+ bottom: parent.bottom
+ top: parent.top
+ }
+
+ TableViewColumn{ role: "description" ; title: "log" }
+
+ model: logModel
+ }
+
+ Slider {
+ id: logLevelSlider
+ value: eth.getLogLevelInt()
+ anchors {
+ right: parent.right
+ top: parent.top
+ bottom: parent.bottom
+
+ rightMargin: 5
+ leftMargin: 5
+ topMargin: 5
+ bottomMargin: 5
+ }
+
+ orientation: Qt.Vertical
+ maximumValue: 5
+ stepSize: 1
+
+ onValueChanged: {
+ eth.setLogLevel(value)
+ }
+ }
+ }
+ }
+
+ /*
+ signal addPlugin(string name)
+ Component {
+ id: pluginWindow
+ Rectangle {
+ anchors.fill: parent
+ Label {
+ id: pluginTitle
+ anchors.centerIn: parent
+ text: "Hello world"
+ }
+ Component.onCompleted: setView(this)
+ }
+ }
+
+ onAddPlugin: {
+ var pluginWin = pluginWindow.createObject(mainView)
+ console.log(pluginWin)
+ pluginWin.pluginTitle.text = "Test"
+ }
+ */
+ }
+ }
+
+ FileDialog {
+ id: openAppDialog
+ title: "Open QML Application"
+ onAccepted: {
+ //ui.open(openAppDialog.fileUrl.toString())
+ //ui.openHtml(Qt.resolvedUrl(ui.assetPath("test.html")))
+ var path = openAppDialog.fileUrl.toString()
+ console.log(path)
+ var ext = path.split('.').pop()
+ console.log(ext)
+ if(ext == "html" || ext == "htm") {
+ ui.openHtml(path)
+ }else if(ext == "qml"){
+ ui.openQml(path)
+ }
+ }
+ }
+
+ statusBar: StatusBar {
+ height: 30
+ RowLayout {
+ Button {
+ id: miningButton
+ onClicked: {
+ eth.toggleMining()
+ }
+ text: "Start Mining"
+ }
+
+ Button {
+ property var enabled: true
+ id: debuggerWindow
+ onClicked: {
+ ui.startDebugger()
+ }
+ text: "Debugger"
+ }
+
+ Button {
+ id: importAppButton
+ anchors.left: debuggerWindow.right
+ anchors.leftMargin: 5
+ onClicked: openAppDialog.open()
+ text: "Import App"
+ }
+
+ Label {
+ anchors.left: importAppButton.right
+ anchors.leftMargin: 5
+ id: walletValueLabel
+ }
+ }
+
+ Label {
+ y: 7
+ anchors.right: peerImage.left
+ anchors.rightMargin: 5
+ id: peerLabel
+ font.pixelSize: 8
+ text: "0 / 0"
+ }
+ Image {
+ y: 7
+ id: peerImage
+ anchors.right: parent.right
+ width: 10; height: 10
+ MouseArea {
+ onDoubleClicked: peerWindow.visible = true
+ anchors.fill: parent
+ }
+ source: ui.assetPath("network.png")
+ }
+ }
+
+ Window {
+ id: popup
+ visible: false
+ property var block
+ width: root.width
+ height: 300
+ Component{
+ id: blockDetailsDelegate
+ Rectangle {
+ color: "#252525"
+ width: popup.width
+ height: 150
+ Column {
+ anchors.leftMargin: 10
+ anchors.topMargin: 5
+ anchors.top: parent.top
+ anchors.left: parent.left
+ Text { text: '<h3>Block details</h3>'; color: "#F2F2F2"}
+ Text { text: '<b>Block number:</b> ' + number; color: "#F2F2F2"}
+ Text { text: '<b>Hash:</b> ' + hash; color: "#F2F2F2"}
+ Text { text: '<b>Coinbase:</b> ' + coinbase; color: "#F2F2F2"}
+ Text { text: '<b>Block found at:</b> ' + prettyTime; color: "#F2F2F2"}
+ Text { text: '<b>Gas used:</b> ' + gasUsed + " / " + gasLimit; color: "#F2F2F2"}
+ }
+ }
+ }
+ ListView {
+ model: singleBlock
+ delegate: blockDetailsDelegate
+ anchors.top: parent.top
+ height: 100
+ anchors.leftMargin: 20
+ id: listViewThing
+ Layout.maximumHeight: 40
+ }
+ TableView {
+ id: txView
+ anchors.top: listViewThing.bottom
+ anchors.topMargin: 50
+ width: parent.width
+
+ TableViewColumn{width: 90; role: "value" ; title: "Value" }
+ TableViewColumn{width: 200; role: "hash" ; title: "Hash" }
+ TableViewColumn{width: 200; role: "sender" ; title: "Sender" }
+ TableViewColumn{width: 200;role: "address" ; title: "Receiver" }
+ TableViewColumn{width: 60; role: "gas" ; title: "Gas" }
+ TableViewColumn{width: 60; role: "gasPrice" ; title: "Gas Price" }
+ TableViewColumn{width: 60; role: "isContract" ; title: "Contract" }
+
+ model: transactionModel
+ onClicked: {
+ var tx = transactionModel.get(row)
+ if(tx.data) {
+ popup.showContractData(tx)
+ }else{
+ popup.height = 440
+ }
+ }
+ }
+
+ function showContractData(tx) {
+ txDetailsDebugButton.tx = tx
+ if(tx.createsContract) {
+ contractData.text = tx.data
+ contractLabel.text = "<h4> Transaction created contract " + tx.address + "</h4>"
+ }else{
+ contractLabel.text = "<h4> Transaction ran contract " + tx.address + "</h4>"
+ contractData.text = tx.rawData
+ }
+ popup.height = 540
+ }
+
+ Rectangle {
+ id: txDetails
+ width: popup.width
+ height: 300
+ anchors.left: listViewThing.left
+ anchors.top: txView.bottom
+ Label {
+ text: "<h4>Contract data</h4>"
+ anchors.top: parent.top
+ anchors.left: parent.left
+ id: contractLabel
+ anchors.leftMargin: 10
+ }
+ Button {
+ property var tx
+ id: txDetailsDebugButton
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ text: "Debug contract"
+ onClicked: {
+ if(tx.createsContract){
+ ui.startDbWithCode(tx.rawData)
+ }else {
+ ui.startDbWithContractAndData(tx.address, tx.rawData)
+ }
+ }
+ }
+ TextArea {
+ id: contractData
+ text: "Contract"
+ anchors.top: contractLabel.bottom
+ anchors.left: parent.left
+ anchors.bottom: popup.bottom
+ wrapMode: Text.Wrap
+ width: parent.width - 30
+ height: 80
+ anchors.leftMargin: 10
+ }
+ }
+ property var transactionModel: ListModel {
+ id: transactionModel
+ }
+ property var singleBlock: ListModel {
+ id: singleBlock
+ }
+ function setDetails(block){
+ singleBlock.set(0,block)
+ popup.height = 300
+ transactionModel.clear()
+ if(block.txs != undefined){
+ for(var i = 0; i < block.txs.count; ++i) {
+ transactionModel.insert(0, block.txs.get(i))
+ }
+ if(block.txs.get(0).data){
+ popup.showContractData(block.txs.get(0))
+ }
+ }
+ txView.forceActiveFocus()
+ }
+ }
+
+ Window {
+ id: addPeerWin
+ visible: false
+ minimumWidth: 230
+ maximumWidth: 230
+ maximumHeight: 50
+ minimumHeight: 50
+
+ TextField {
+ id: addrField
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: 10
+ placeholderText: "address:port"
+ onAccepted: {
+ ui.connectToPeer(addrField.text)
+ addPeerWin.visible = false
+ }
+ }
+ Button {
+ anchors.left: addrField.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 5
+ text: "Add"
+ onClicked: {
+ ui.connectToPeer(addrField.text)
+ addPeerWin.visible = false
+ }
+ }
+ Component.onCompleted: {
+ addrField.focus = true
+ }
+ }
+
+ Window {
+ id: aboutWin
+ visible: false
+ title: "About"
+ minimumWidth: 350
+ maximumWidth: 350
+ maximumHeight: 200
+ minimumHeight: 200
+
+ Image {
+ id: aboutIcon
+ height: 150
+ width: 150
+ fillMode: Image.PreserveAspectFit
+ smooth: true
+ source: ui.assetPath("facet.png")
+ x: 10
+ y: 10
+ }
+
+ Text {
+ anchors.left: aboutIcon.right
+ anchors.leftMargin: 10
+ font.pointSize: 12
+ text: "<h2>Ethereal</h2><br><h3>Development</h3>Jeffrey Wilcke<br>Maran Hidskes<br>"
+ }
+ }
+
+ function addDebugMessage(message){
+ debuggerLog.append({value: message})
+ }
+
+ function addAddress(address) {
+ addressModel.append({name: address.name, address: address.address})
+ }
+ function clearAddress() {
+ addressModel.clear()
+ }
+
+ function loadPlugin(name) {
+ console.log("Loading plugin" + name)
+ mainView.addPlugin(name)
+ }
+
+ function setWalletValue(value) {
+ walletValueLabel.text = value
+ }
+
+ function addTx(tx, inout) {
+ var isContract
+ if (tx.contract == true){
+ isContract = "Yes"
+ }else{
+ isContract = "No"
+ }
+
+ var address;
+ if(inout == "recv") {
+ address = tx.sender;
+ } else {
+ address = tx.address;
+ }
+ txModel.insert(0, {inout: inout, hash: tx.hash, address: address, value: tx.value, contract: isContract})
+ }
+
+ function addBlock(block, initial) {
+ var txs = JSON.parse(block.transactions);
+ var amount = 0
+ if(initial == undefined){
+ initial = false
+ }
+
+ if(txs != null){
+ amount = txs.length
+ }
+
+ if(initial){
+ blockModel.append({number: block.number, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)})
+ }else{
+ blockModel.insert(0, {number: block.number, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)})
+ }
+ }
+
+ function addLog(str) {
+ // Remove first item once we've reached max log items
+ if(logModel.count > 250) {
+ logModel.remove(0)
+ }
+
+ if(str.len != 0) {
+ if(logView.flickableItem.atYEnd) {
+ logModel.append({description: str})
+ logView.positionViewAtRow(logView.rowCount - 1, ListView.Contain)
+ } else {
+ logModel.append({description: str})
+ }
+ }
+
+ }
+
+ function setPeers(text) {
+ peerLabel.text = text
+ }
+
+ function addPeer(peer) {
+ // We could just append the whole peer object but it cries if you try to alter them
+ peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version})
+ }
+
+ function resetPeers(){
+ peerModel.clear()
+ }
+
+ function timeAgo(unixTs){
+ var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000
+ return (lapsed + " seconds ago")
+ }
+ function convertToPretty(unixTs){
+ var a = new Date(unixTs*1000);
+ var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
+ var year = a.getFullYear();
+ var month = months[a.getMonth()];
+ var date = a.getDate();
+ var hour = a.getHours();
+ var min = a.getMinutes();
+ var sec = a.getSeconds();
+ var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ;
+ return time;
+ }
+ // ******************************************
+ // Windows
+ // ******************************************
+ Window {
+ id: peerWindow
+ height: 200
+ width: 700
+ Rectangle {
+ anchors.fill: parent
+ property var peerModel: ListModel {
+ id: peerModel
+ }
+ TableView {
+ anchors.fill: parent
+ id: peerTable
+ model: peerModel
+ TableViewColumn{width: 100; role: "ip" ; title: "IP" }
+ TableViewColumn{width: 60; role: "port" ; title: "Port" }
+ TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" }
+ TableViewColumn{width: 100; role: "latency"; title: "Latency" }
+ TableViewColumn{width: 260; role: "version" ; title: "Version" }
+ }
+ }
+ }
+
+ // *******************************************
+ // Components
+ // *******************************************
+
+ // New Contract component
+ Component {
+ id: newContract
+ Column {
+ id: mainContractColumn
+ anchors.fill: parent
+ function contractFormReady(){
+ if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) {
+ txButton.state = "READY"
+ }else{
+ txButton.state = "NOTREADY"
+ }
+ }
+ states: [
+ State{
+ name: "ERROR"
+ PropertyChanges { target: txResult; visible:true}
+ PropertyChanges { target: codeView; visible:true}
+ },
+ State {
+ name: "DONE"
+ PropertyChanges { target: txValue; visible:false}
+ PropertyChanges { target: txGas; visible:false}
+ PropertyChanges { target: txGasPrice; visible:false}
+ PropertyChanges { target: codeView; visible:false}
+ PropertyChanges { target: txButton; visible:false}
+ PropertyChanges { target: txDataLabel; visible:false}
+ PropertyChanges { target: atLabel; visible:false}
+ PropertyChanges { target: txFuelRecipient; visible:false}
+
+ PropertyChanges { target: txResult; visible:true}
+ PropertyChanges { target: txOutput; visible:true}
+ PropertyChanges { target: newTxButton; visible:true}
+ },
+ State {
+ name: "SETUP"
+ PropertyChanges { target: txValue; visible:true; text: ""}
+ PropertyChanges { target: txGas; visible:true; text: ""}
+ PropertyChanges { target: txGasPrice; visible:true; text: ""}
+ PropertyChanges { target: codeView; visible:true; text: ""}
+ PropertyChanges { target: txButton; visible:true}
+ PropertyChanges { target: txDataLabel; visible:true}
+
+ PropertyChanges { target: txResult; visible:false}
+ PropertyChanges { target: txOutput; visible:false}
+ PropertyChanges { target: newTxButton; visible:false}
+ }
+ ]
+ width: 400
+ spacing: 5
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.leftMargin: 5
+ anchors.topMargin: 5
+
+ ListModel {
+ id: denomModel
+ ListElement { text: "Wei" ; zeros: "" }
+ ListElement { text: "Ada" ; zeros: "000" }
+ ListElement { text: "Babbage" ; zeros: "000000" }
+ ListElement { text: "Shannon" ; zeros: "000000000" }
+ ListElement { text: "Szabo" ; zeros: "000000000000" }
+ ListElement { text: "Finney" ; zeros: "000000000000000" }
+ ListElement { text: "Ether" ; zeros: "000000000000000000" }
+ ListElement { text: "Einstein" ;zeros: "000000000000000000000" }
+ ListElement { text: "Douglas" ; zeros: "000000000000000000000000000000000000000000" }
+ }
+
+
+ TextField {
+ id: txFuelRecipient
+ placeholderText: "Address / Name or empty for contract"
+ //validator: RegExpValidator { regExp: /[a-f0-9]{40}/ }
+ width: 400
+ }
+
+ RowLayout {
+ TextField {
+ id: txValue
+ width: 222
+ placeholderText: "Amount"
+ validator: RegExpValidator { regExp: /\d*/ }
+ onTextChanged: {
+ contractFormReady()
+ }
+ }
+
+ ComboBox {
+ id: valueDenom
+ currentIndex: 6
+ model: denomModel
+ }
+ }
+
+ RowLayout {
+ TextField {
+ id: txGas
+ width: 50
+ validator: RegExpValidator { regExp: /\d*/ }
+ placeholderText: "Gas"
+ text: "500"
+ /*
+ onTextChanged: {
+ contractFormReady()
+ }
+ */
+ }
+ Label {
+ id: atLabel
+ text: "@"
+ }
+
+ TextField {
+ id: txGasPrice
+ width: 200
+ placeholderText: "Gas price"
+ text: "10"
+ validator: RegExpValidator { regExp: /\d*/ }
+ /*
+ onTextChanged: {
+ contractFormReady()
+ }
+ */
+ }
+
+ ComboBox {
+ id: gasDenom
+ currentIndex: 4
+ model: denomModel
+ }
+ }
+
+ Label {
+ id: txDataLabel
+ text: "Data"
+ }
+
+ TextArea {
+ id: codeView
+ height: 300
+ anchors.topMargin: 5
+ width: 400
+ onTextChanged: {
+ contractFormReady()
+ }
+ }
+
+
+ Button {
+ id: txButton
+ /* enabled: false */
+ states: [
+ State {
+ name: "READY"
+ PropertyChanges { target: txButton; /*enabled: true*/}
+ },
+ State {
+ name: "NOTREADY"
+ PropertyChanges { target: txButton; /*enabled:false*/}
+ }
+ ]
+ text: "Send"
+ onClicked: {
+ var value = txValue.text + denomModel.get(valueDenom.currentIndex).zeros;
+ var gasPrice = txGasPrice.text + denomModel.get(gasDenom.currentIndex).zeros;
+ var res = eth.create(txFuelRecipient.text, value, txGas.text, gasPrice, codeView.text)
+ if(res[1]) {
+ txResult.text = "Your contract <b>could not</b> be send over the network:\n<b>"
+ txResult.text += res[1].error()
+ txResult.text += "</b>"
+ mainContractColumn.state = "ERROR"
+ } else {
+ txResult.text = "Your transaction has been submitted:\n"
+ txOutput.text = res[0].address
+ mainContractColumn.state = "DONE"
+ }
+ }
+ }
+ Text {
+ id: txResult
+ visible: false
+ }
+ TextField {
+ id: txOutput
+ visible: false
+ width: 530
+ }
+ Button {
+ id: newTxButton
+ visible: false
+ text: "Create a new transaction"
+ onClicked: {
+ this.visible = false
+ txResult.text = ""
+ txOutput.text = ""
+ mainContractColumn.state = "SETUP"
+ }
+ }
+ }
+ }
+ // New Transaction component
+ Component {
+ id: newTransaction
+ Column {
+ id: simpleSendColumn
+ states: [
+ State{
+ name: "ERROR"
+ },
+ State {
+ name: "DONE"
+ PropertyChanges { target: txSimpleValue; visible:false}
+ PropertyChanges { target: txSimpleRecipient; visible:false}
+ PropertyChanges { target:newSimpleTxButton; visible:false}
+
+ PropertyChanges { target: txSimpleResult; visible:true}
+ PropertyChanges { target: txSimpleOutput; visible:true}
+ PropertyChanges { target:newSimpleTxButton; visible:true}
+ },
+ State {
+ name: "SETUP"
+ PropertyChanges { target: txSimpleValue; visible:true; text: ""}
+ PropertyChanges { target: txSimpleRecipient; visible:true; text: ""}
+ PropertyChanges { target: txSimpleButton; visible:true}
+ PropertyChanges { target:newSimpleTxButton; visible:false}
+ }
+ ]
+ spacing: 5
+ anchors.leftMargin: 5
+ anchors.topMargin: 5
+ anchors.top: parent.top
+ anchors.left: parent.left
+
+ function checkFormState(){
+ if(txSimpleRecipient.text.length == 40 && txSimpleValue.text.length > 0) {
+ txSimpleButton.state = "READY"
+ }else{
+ txSimpleButton.state = "NOTREADY"
+ }
+ }
+
+ TextField {
+ id: txSimpleRecipient
+ placeholderText: "Recipient address"
+ Layout.fillWidth: true
+ //validator: RegExpValidator { regExp: /[a-f0-9]{40}/ }
+ width: 530
+ onTextChanged: { checkFormState() }
+ }
+ TextField {
+ id: txSimpleValue
+ width: 200
+ placeholderText: "Amount"
+ anchors.rightMargin: 5
+ validator: RegExpValidator { regExp: /\d*/ }
+ onTextChanged: { checkFormState() }
+ }
+ Button {
+ id: txSimpleButton
+ /*enabled: false*/
+ states: [
+ State {
+ name: "READY"
+ PropertyChanges { target: txSimpleButton; /*enabled: true*/}
+ },
+ State {
+ name: "NOTREADY"
+ PropertyChanges { target: txSimpleButton; /*enabled: false*/}
+ }
+ ]
+ text: "Send"
+ onClicked: {
+ //this.enabled = false
+ var res = eth.transact(txSimpleRecipient.text, txSimpleValue.text, "500", "1000000", "")
+ if(res[1]) {
+ txSimpleResult.text = "There has been an error broadcasting your transaction:" + res[1].error()
+ } else {
+ txSimpleResult.text = "Your transaction has been broadcasted over the network.\nYour transaction id is:"
+ txSimpleOutput.text = res[0].hash
+ this.visible = false
+ simpleSendColumn.state = "DONE"
+ }
+ }
+ }
+ Text {
+ id: txSimpleResult
+ visible: false
+
+ }
+ TextField {
+ id: txSimpleOutput
+ visible: false
+ width: 530
+ }
+ Button {
+ id: newSimpleTxButton
+ visible: false
+ text: "Create an other transaction"
+ onClicked: {
+ this.visible = false
+ simpleSendColumn.state = "SETUP"
+ }
+ }
+ }
+ }
+}
diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml
new file mode 100644
index 000000000..63927f0eb
--- /dev/null
+++ b/ethereal/assets/qml/webapp.qml
@@ -0,0 +1,246 @@
+import QtQuick 2.0
+import QtWebKit 3.0
+import QtWebKit.experimental 1.0
+import QtQuick.Controls 1.0;
+import QtQuick.Layouts 1.0;
+import QtQuick.Window 2.1;
+import Ethereum 1.0
+
+ApplicationWindow {
+ id: window
+ title: "Ethereum"
+ width: 900
+ height: 600
+ minimumHeight: 300
+
+ property alias url: webview.url
+ property alias webView: webview
+
+ Item {
+ objectName: "root"
+ id: root
+ anchors.fill: parent
+ state: "inspectorShown"
+
+ WebView {
+ objectName: "webView"
+ id: webview
+ anchors.fill: parent
+ /*
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: sizeGrip.top
+ top: parent.top
+ }
+ */
+ onTitleChanged: { window.title = title }
+ experimental.preferences.javascriptEnabled: true
+ experimental.preferences.navigatorQtObjectEnabled: true
+ experimental.preferences.developerExtrasEnabled: true
+ experimental.userScripts: [ui.assetPath("ext/pre.js"), ui.assetPath("ext/big.js"), ui.assetPath("ext/string.js"), ui.assetPath("ext/ethereum.js")]
+ experimental.onMessageReceived: {
+ console.log("[onMessageReceived]: ", message.data)
+ // TODO move to messaging.js
+ var data = JSON.parse(message.data)
+
+ try {
+ switch(data.call) {
+ case "getCoinBase":
+ postData(data._seed, eth.getCoinBase())
+
+ break
+ case "getIsListening":
+ postData(data._seed, eth.getIsListening())
+
+ break
+ case "getIsMining":
+ postData(data._seed, eth.getIsMining())
+
+ break
+ case "getPeerCount":
+ postData(data._seed, eth.getPeerCount())
+
+ break
+
+ case "getTxCountAt":
+ require(1)
+ postData(data._seed, eth.getTxCountAt(data.args[0]))
+
+ break
+ case "getBlockByNumber":
+ var block = eth.getBlock(data.args[0])
+ postData(data._seed, block)
+
+ break
+ case "getBlockByHash":
+ var block = eth.getBlock(data.args[0])
+ postData(data._seed, block)
+
+ break
+ case "transact":
+ require(5)
+
+ var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5])
+ postData(data._seed, tx)
+
+ break
+ case "create":
+ postData(data._seed, null)
+
+ break
+ case "getStorage":
+ require(2);
+
+ var stateObject = eth.getStateObject(data.args[0])
+ var storage = stateObject.getStorage(data.args[1])
+ postData(data._seed, storage)
+
+ break
+ case "getStateKeyVals":
+ require(1);
+ var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true)
+ postData(data._seed,stateObject)
+
+ break
+ case "getTransactionsFor":
+ require(1);
+ var txs = eth.getTransactionsFor(data.args[0], true)
+ postData(data._seed, txs)
+
+ break
+ case "getBalance":
+ require(1);
+
+ postData(data._seed, eth.getStateObject(data.args[0]).value());
+
+ break
+ case "getKey":
+ var key = eth.getKey().privateKey;
+
+ postData(data._seed, key)
+ break
+ case "watch":
+ require(1)
+ eth.watch(data.args[0], data.args[1]);
+ break
+ case "disconnect":
+ require(1)
+ postData(data._seed, null)
+ break;
+ case "set":
+ console.log("'Set' has been depcrecated")
+ /*
+ for(var key in data.args) {
+ if(webview.hasOwnProperty(key)) {
+ window[key] = data.args[key];
+ }
+ }
+ */
+ break;
+ case "getSecretToAddress":
+ require(1)
+ postData(data._seed, eth.secretToAddress(data.args[0]))
+ break;
+ case "debug":
+ console.log(data.args[0]);
+ break;
+ }
+ } catch(e) {
+ console.log(data.call + ": " + e)
+
+ postData(data._seed, null);
+ }
+ }
+
+ function require(args, num) {
+ if(args.length < num) {
+ throw("required argument count of "+num+" got "+args.length);
+ }
+ }
+ function postData(seed, data) {
+ webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed}))
+ }
+ function postEvent(event, data) {
+ webview.experimental.postMessage(JSON.stringify({data: data, _event: event}))
+ }
+
+ function onNewBlockCb(block) {
+ postEvent("block:new", block)
+ }
+ function onObjectChangeCb(stateObject) {
+ postEvent("object:"+stateObject.address(), stateObject)
+ }
+ function onStorageChangeCb(storageObject) {
+ var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":");
+ postEvent(ev, [storageObject.address, storageObject.value])
+ }
+ }
+ Rectangle {
+ id: toggleInspector
+ color: "#bcbcbc"
+ visible: true
+ height: 12
+ width: 12
+ anchors {
+ right: root.right
+ }
+ MouseArea {
+ onClicked: {
+ if(inspector.visible == true){
+ inspector.visible = false
+ }else{
+ inspector.visible = true
+ }
+ }
+ onDoubleClicked: {
+ console.log('refreshing')
+ webView.reload()
+ }
+ anchors.fill: parent
+ }
+ }
+
+ Rectangle {
+ id: sizeGrip
+ color: "gray"
+ visible: false
+ height: 10
+ anchors {
+ left: root.left
+ right: root.right
+ }
+ y: Math.round(root.height * 2 / 3)
+
+ MouseArea {
+ anchors.fill: parent
+ drag.target: sizeGrip
+ drag.minimumY: 0
+ drag.maximumY: root.height
+ drag.axis: Drag.YAxis
+ }
+ }
+
+ WebView {
+ id: inspector
+ visible: false
+ url: webview.experimental.remoteInspectorUrl
+ anchors {
+ left: root.left
+ right: root.right
+ top: sizeGrip.bottom
+ bottom: root.bottom
+ }
+ }
+
+ states: [
+ State {
+ name: "inspectorShown"
+ PropertyChanges {
+ target: inspector
+ url: webview.experimental.remoteInspectorUrl
+ }
+ }
+ ]
+ }
+}