diff --git a/ethereal/assets/ext/filter.js b/ethereal/assets/ext/filter.js new file mode 100644 index 000000000..20f0b36a3 --- /dev/null +++ b/ethereal/assets/ext/filter.js @@ -0,0 +1,23 @@ +var Filter = function(eth, options) { + this.callbacks = {}; + this.seed = Math.floor(Math.random() * 1000000); + this.eth = eth; + + eth.registerFilter(options, this.seed); +}; + +Filter.prototype.changed = function(callback) { + var cbseed = Math.floor(Math.random() * 1000000); + this.eth.registerFilterCallback(this.seed, cbseed); + + var self = this; + message.connect(function(messages, seed, callbackSeed) { + if(seed == self.seed && callbackSeed == cbseed) { + callback.call(self, messages); + } + }); +}; + +Filter.prototype.uninstall = function() { + eth.uninstallFilter(this.seed) +} diff --git a/ethereal/assets/qml/views/wallet.qml b/ethereal/assets/qml/views/wallet.qml index 22b09640b..9afb1f89e 100644 --- a/ethereal/assets/qml/views/wallet.qml +++ b/ethereal/assets/qml/views/wallet.qml @@ -17,43 +17,145 @@ Rectangle { function onReady() { menuItem.secondary = eth.numberToHuman(eth.balanceAt(eth.key().address)) + } + 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" } } ColumnLayout { spacing: 10 y: 40 - anchors { - left: parent.left - right: parent.right - } + anchors.fill: parent Text { + id: balance text: "Balance: " + eth.numberToHuman(eth.balanceAt(eth.key().address)) font.pixelSize: 24 anchors { horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 20 } } - TableView { - id: txTableView + Rectangle { + id: newTxPane + color: "#ececec" + border.color: "#cccccc" + border.width: 1 + anchors { + top: balance.bottom + topMargin: 10 + left: parent.left + leftMargin: 5 + right: parent.right + rightMargin: 5 + } + height: 100 + + RowLayout { + id: amountFields + spacing: 10 + anchors { + top: parent.top + topMargin: 20 + left: parent.left + leftMargin: 20 + } + + Text { + text: "Ξ " + } + + // There's something off with the row layout where textfields won't listen to the width setting + Rectangle { + width: 50 + height: 20 + TextField { + id: txValue + width: parent.width + placeholderText: "0.00" + } + } + + ComboBox { + id: valueDenom + currentIndex: 6 + model: denomModel + } + + } + + RowLayout { + id: toFields + spacing: 10 + anchors { + top: amountFields.bottom + topMargin: 5 + left: parent.left + leftMargin: 20 + } + + Text { + text: "To" + } + + Rectangle { + width: 200 + height: 20 + TextField { + id: txTo + width: parent.width + placeholderText: "Address or name" + } + } + + Button { + text: "Send" + onClicked: { + var value = txValue.text + denomModel.get(valueDenom.currentIndex).zeros; + var gasPrice = "10000000000000" + var res = eth.transact(eth.key().privateKey, txTo.text, value, "500", gasPrice, "") + console.log(res) + } + } + } + } + + Rectangle { anchors { left: parent.left right: parent.right + top: newTxPane.bottom + topMargin: 10 + bottom: parent.bottom } - TableViewColumn{ role: "num" ; title: "#" ; width: 30 } - TableViewColumn{ role: "from" ; title: "From" ; width: 280 } - TableViewColumn{ role: "to" ; title: "To" ; width: 280 } - TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } + TableView { + id: txTableView + anchors.fill : parent + TableViewColumn{ role: "num" ; title: "#" ; width: 30 } + TableViewColumn{ role: "from" ; title: "From" ; width: 280 } + TableViewColumn{ role: "to" ; title: "To" ; width: 280 } + TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } - model: ListModel { - id: txModel - Component.onCompleted: { - var messages = JSON.parse(eth.messages({latest: -1, from: "e6716f9544a56c530d868e4bfbacb172315bdead"})) - for(var i = 0; i < messages.length; i++) { - var message = messages[i]; - this.insert(0, {num: i, from: message.from, to: message.to, value: eth.numberToHuman(message.value)}) + model: ListModel { + id: txModel + Component.onCompleted: { + var messages = JSON.parse(eth.messages({latest: -1, from: "e6716f9544a56c530d868e4bfbacb172315bdead"})) + for(var i = 0; i < messages.length; i++) { + var message = messages[i]; + this.insert(0, {num: i, from: message.from, to: message.to, value: eth.numberToHuman(message.value)}) + } } } } diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 89126f600..122ee049e 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -6,17 +6,28 @@ import QtQuick.Window 2.1; import QtQuick.Controls.Styles 1.1 import Ethereum 1.0 +import "../ext/filter.js" as Eth ApplicationWindow { id: root property alias miningButtonText: miningButton.text + width: 900 height: 600 minimumHeight: 300 - title: "Ethereal" + title: "Ether browser" + + // This signal is used by the filter API. The filter API connects using this signal handler from + // the different QML files and plugins. + signal message(var callback, int seed, int seedCallback); + function invokeFilterCallback(data, receiverSeed, callbackSeed) { + var messages = JSON.parse(data) + // Signal handler + message(data, receiverSeed, callbackSeed); + } TextField { id: copyElementHax @@ -31,17 +42,17 @@ ApplicationWindow { // Takes care of loading all default plugins Component.onCompleted: { - var walletView = addPlugin("./views/wallet.qml", {section: "ethereum"}) - - var historyView = addPlugin("./views/history.qml", {section: "legacy"}) - var newTxView = addPlugin("./views/transaction.qml", {section: "legacy"}) - var chainView = addPlugin("./views/chain.qml", {section: "legacy"}) - var infoView = addPlugin("./views/info.qml", {section: "legacy"}) - var pendingTxView = addPlugin("./views/pending_tx.qml", {section: "legacy"}) - var pendingTxView = addPlugin("./views/javascript.qml", {section: "legacy"}) + var walletView = addPlugin("./views/wallet.qml", {noAdd: true, section: "ethereum", active: true}) + var historyView = addPlugin("./views/history.qml", {noAdd: true, section: "legacy"}) + var newTxView = addPlugin("./views/transaction.qml", {noAdd: true, section: "legacy"}) + var chainView = addPlugin("./views/chain.qml", {noAdd: true, section: "legacy"}) + var infoView = addPlugin("./views/info.qml", {noAdd: true, section: "legacy"}) + var pendingTxView = addPlugin("./views/pending_tx.qml", {noAdd: true, section: "legacy"}) + var pendingTxView = addPlugin("./views/javascript.qml", {noAdd: true, section: "legacy"}) // Call the ready handler gui.done() + } function addPlugin(path, options) { @@ -50,10 +61,20 @@ ApplicationWindow { if(component.status == Component.Error) { console.debug("Error:"+ component.errorString()); } + return } - return mainSplit.addComponent(component, options) + var views = mainSplit.addComponent(component, options) + views.menuItem.path = path + + mainSplit.views.push(views); + + if(!options.noAdd) { + gui.addPlugin(path) + } + + return views.view } MenuBar { @@ -76,7 +97,7 @@ ApplicationWindow { text: "Add plugin" onTriggered: { generalFileDialog.show(true, function(path) { - addPlugin(path, {canClose: true}) + addPlugin(path, {canClose: true, section: "apps"}) }) } } @@ -268,454 +289,441 @@ ApplicationWindow { function addComponent(component, options) { var view = mainView.createView(component, options) + view.visible = false + view.anchors.fill = mainView - if(!view.hasOwnProperty("iconFile")) { + if( !view.hasOwnProperty("iconFile") ) { console.log("Could not load plugin. Property 'iconFile' not found on view."); return; } var menuItem = menu.createMenuItem(view.iconFile, view, options); - if(view.hasOwnProperty("menuItem")) { + if( view.hasOwnProperty("menuItem") ) { view.menuItem = menuItem; } - mainSplit.views.push({view: view, menuItem: menuItem}); - if(view.hasOwnProperty("onReady")) { + if( view.hasOwnProperty("onReady") ) { view.onReady.call(view) } - return view + if( options.active ) { + setView(view, menuItem) + } + + + return {view: view, menuItem: menuItem} } /********************* * Main menu. ********************/ - Rectangle { - id: menu - Layout.minimumWidth: 180 - Layout.maximumWidth: 180 - anchors.top: parent.top - color: "#ececec" + Rectangle { + id: menu + Layout.minimumWidth: 180 + Layout.maximumWidth: 180 + anchors.top: parent.top + color: "#ececec" - Component { - id: menuItemTemplate - Rectangle { - id: menuItem - property var view; + Component { + id: menuItemTemplate + Rectangle { + id: menuItem + property var view; + property var path; - property alias title: label.text - property alias icon: icon.source - property alias secondary: secondary.text + property alias title: label.text + property alias icon: icon.source + property alias secondary: secondary.text - width: 180 - height: 28 - border.color: "#00000000" - border.width: 1 - radius: 5 - color: "#00000000" + width: 180 + height: 28 + border.color: "#00000000" + border.width: 1 + radius: 5 + color: "#00000000" - anchors { - left: parent.left - leftMargin: 4 - } + anchors { + left: parent.left + leftMargin: 4 + } - Image { - id: icon - height: 20 - width: 20 - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - leftMargin: 3 - } - } + MouseArea { + anchors.fill: parent + onClicked: { + mainSplit.setView(view, menuItem) + } + } - Text { - id: label - anchors { - left: icon.right - verticalCenter: parent.verticalCenter - leftMargin: 3 - } + Image { + id: icon + height: 20 + width: 20 + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: 3 + } + MouseArea { + anchors.fill: parent + onClicked: { + menuItem.closeApp() + } + } + } - //font.bold: true - color: "#0D0A01" - font.pixelSize: 12 - } + Text { + id: label + anchors { + left: icon.right + verticalCenter: parent.verticalCenter + leftMargin: 3 + } - Text { - id: secondary - anchors { - right: parent.right - rightMargin: 8 - verticalCenter: parent.verticalCenter - } - color: "#AEADBE" - font.pixelSize: 12 - } + color: "#0D0A01" + font.pixelSize: 12 + } - MouseArea { - anchors.fill: parent - onClicked: { - mainSplit.setView(view, menuItem) - } - } - } - } - - function createMenuItem(icon, view, options) { - if(options === undefined) { - options = {}; - } - - var section; - switch(options.section) { - case "ethereum": - section = menuDefault; - break; - case "legacy": - section = menuLegacy; - break; - default: - section = menuApps; - break; - } - - var comp = menuItemTemplate.createObject(section) - - comp.view = view - comp.title = view.title - comp.icon = view.iconFile - /* - if(view.secondary !== undefined) { - comp.secondary = view.secondary - } - */ - - return comp - - /* - if(options.canClose) { - //comp.closeButton.visible = options.canClose - } - */ - } - - ColumnLayout { - id: menuColumn - y: 10 - width: parent.width - anchors.left: parent.left - anchors.right: parent.right - spacing: 3 - - Text { - text: "ETHEREUM" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuDefault - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - Text { - text: "LEGACY" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuLegacy - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - Text { - text: "APPS" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - /* - Rectangle { - width: 180 - height: 28 - border.color: "#CCCCCC" - border.width: 1 - radius: 5 - color: "#FFFFFF" - - anchors { - left: parent.left - leftMargin: 4 - } - - Image { - id: icon - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - } - source: "../pick.png" - } - - Text { - anchors { - left: icon.right - verticalCenter: parent.verticalCenter - } - - text: "Wallet" - font.bold: true - color: "#0D0A01" - } - - Text { - anchors { - right: parent.right - rightMargin: 8 - verticalCenter: parent.verticalCenter - } - color: "#AEADBE" - text: "12e15 Ξ" - font.pixelSize: 12 - } - } - */ - } - } - - /********************* - * Main view - ********************/ - Rectangle { - id: mainView - color: "#00000000" - - anchors.right: parent.right - anchors.left: menu.right - anchors.bottom: parent.bottom - anchors.top: parent.top - - function createView(component) { - var view = component.createObject(mainView) - - return view; - } - } + Text { + id: secondary + anchors { + right: parent.right + rightMargin: 8 + verticalCenter: parent.verticalCenter + } + color: "#AEADBE" + font.pixelSize: 12 + } - } + function closeApp() { + if(this.view.hasOwnProperty("onDestroy")) { + this.view.onDestroy.call(this.view) + } + + this.view.destroy() + this.destroy() + gui.removePlugin(this.path) + } + } + } + + function createMenuItem(icon, view, options) { + if(options === undefined) { + options = {}; + } + + var section; + switch(options.section) { + case "ethereum": + section = menuDefault; + break; + case "legacy": + section = menuLegacy; + break; + default: + section = menuApps; + break; + } + + var comp = menuItemTemplate.createObject(section) + + comp.view = view + comp.title = view.title + comp.icon = view.iconFile + /* + if(view.secondary !== undefined) { + comp.secondary = view.secondary + } + */ + + return comp + + /* + if(options.canClose) { + //comp.closeButton.visible = options.canClose + } + */ + } + + ColumnLayout { + id: menuColumn + y: 10 + width: parent.width + anchors.left: parent.left + anchors.right: parent.right + spacing: 3 + + Text { + text: "ETHEREUM" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuDefault + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } - /****************** - * Dialogs - *****************/ - FileDialog { - id: generalFileDialog - property var callback; - onAccepted: { - var path = this.fileUrl.toString(); - callback.call(this, path); - } + Text { + text: "APPS" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } - function show(selectExisting, callback) { - generalFileDialog.callback = callback; - generalFileDialog.selectExisting = selectExisting; + ColumnLayout { + id: menuApps + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } - this.open(); - } - } + Text { + text: "DEBUG" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuLegacy + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + } + } + + /********************* + * Main view + ********************/ + Rectangle { + id: mainView + color: "#00000000" + + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + + function createView(component) { + var view = component.createObject(mainView) + + return view; + } + } - /****************** - * Wallet functions - *****************/ - function importApp(path) { - var ext = path.split('.').pop() - if(ext == "html" || ext == "htm") { - eth.openHtml(path) - }else if(ext == "qml"){ - eth.openQml(path) - } - } + } - function setWalletValue(value) { - walletValueLabel.text = value - } - function loadPlugin(name) { - console.log("Loading plugin" + name) - mainView.addPlugin(name) - } + /****************** + * Dialogs + *****************/ + FileDialog { + id: generalFileDialog + property var callback; + onAccepted: { + var path = this.fileUrl.toString(); + callback.call(this, path); + } - function setPeers(text) { - peerLabel.text = text - } + function show(selectExisting, callback) { + generalFileDialog.callback = callback; + generalFileDialog.selectExisting = selectExisting; - 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}) - } + this.open(); + } + } - function resetPeers(){ - peerModel.clear() - } - function timeAgo(unixTs){ - var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 - return (lapsed + " seconds ago") - } + /****************** + * Wallet functions + *****************/ + function importApp(path) { + var ext = path.split('.').pop() + if(ext == "html" || ext == "htm") { + eth.openHtml(path) + }else if(ext == "qml"){ + addPlugin(path, {canClose: true, section: "apps"}) + } + } - 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 - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint - 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" } - } - } - } + function setWalletValue(value) { + walletValueLabel.text = value + } - Window { - id: aboutWin - visible: false - title: "About" - minimumWidth: 350 - maximumWidth: 350 - maximumHeight: 200 - minimumHeight: 200 + function loadPlugin(name) { + console.log("Loading plugin" + name) + var view = mainView.addPlugin(name) + } - Image { - id: aboutIcon - height: 150 - width: 150 - fillMode: Image.PreserveAspectFit - smooth: true - source: "../facet.png" - x: 10 - y: 10 - } + function setPeers(text) { + peerLabel.text = text + } - Text { - anchors.left: aboutIcon.right - anchors.leftMargin: 10 - font.pointSize: 12 - text: "