diff --git a/assets/scripts/datetime-moment.js b/assets/scripts/datetime-moment.js new file mode 100644 index 0000000..ae0cce5 --- /dev/null +++ b/assets/scripts/datetime-moment.js @@ -0,0 +1,74 @@ +/** + * This plug-in for DataTables represents the ultimate option in extensibility + * for sorting date / time strings correctly. It uses + * [Moment.js](http://momentjs.com) to create automatic type detection and + * sorting plug-ins for DataTables based on a given format. This way, DataTables + * will automatically detect your temporal information and sort it correctly. + * + * For usage instructions, please see the DataTables blog + * post that [introduces it](//datatables.net/blog/2014-12-18). + * + * @name Ultimate Date / Time sorting + * @summary Sort date and time in any format using Moment.js + * @author [Allan Jardine](//datatables.net) + * @depends DataTables 1.10+, Moment.js 1.7+ + * + * @example + * $.fn.dataTable.moment( 'HH:mm MMM D, YY' ); + * $.fn.dataTable.moment( 'dddd, MMMM Do, YYYY' ); + * + * $('#example').DataTable(); + */ + +(function (factory) { + if (typeof define === "function" && define.amd) { + define(["jquery", "moment", "datatables.net"], factory); + } else { + factory(jQuery, moment); + } +}(function ($, moment) { + +$.fn.dataTable.moment = function ( format, locale ) { + var types = $.fn.dataTable.ext.type; + + // Add type detection + types.detect.unshift( function ( d ) { + if ( d ) { + // Strip HTML tags and newline characters if possible + if ( d.replace ) { + d = d.replace(/(<.*?>)|(\r?\n|\r)/g, ''); + } + + // Strip out surrounding white space + d = $.trim( d ); + } + + // Null and empty values are acceptable + if ( d === '' || d === null ) { + return 'moment-'+format; + } + + return moment( d, format, locale, true ).isValid() ? + 'moment-'+format : + null; + } ); + + // Add sorting method - use an integer for the sorting + types.order[ 'moment-'+format+'-pre' ] = function ( d ) { + if ( d ) { + // Strip HTML tags and newline characters if possible + if ( d.replace ) { + d = d.replace(/(<.*?>)|(\r?\n|\r)/g, ''); + } + + // Strip out surrounding white space + d = $.trim( d ); + } + + return !moment(d, format, locale, true).isValid() ? + Infinity : + parseInt( moment( d, format, locale, true ).format( 'x' ), 10 ); + }; +}; + +})); diff --git a/assets/styles/forms.css b/assets/styles/forms.css index 6734d6a..06e3227 100644 --- a/assets/styles/forms.css +++ b/assets/styles/forms.css @@ -33,4 +33,12 @@ #tableTransactionsForAll .fa-arrows-alt-h { color: #DCDCDC; +} + +#tableTransactionsForAll .fa-question { + color: #DCDCDC; +} + +#tableTransactionsForAll .fa-check { + color: #228B22; } \ No newline at end of file diff --git a/assets/templates/transactions.html b/assets/templates/transactions.html index a5cfce7..de87273 100644 --- a/assets/templates/transactions.html +++ b/assets/templates/transactions.html @@ -8,6 +8,7 @@ From To Value + @@ -18,6 +19,7 @@ From To Value + diff --git a/assets/templates/wallets.html b/assets/templates/wallets.html index feb90e7..176e53e 100644 --- a/assets/templates/wallets.html +++ b/assets/templates/wallets.html @@ -1,5 +1,7 @@
- + + +
Total ETHO: {{sumBalance}}
diff --git a/index.html b/index.html index eb78be5..200f6e9 100644 --- a/index.html +++ b/index.html @@ -30,6 +30,7 @@ + diff --git a/main.js b/main.js index 28c37bf..4d34178 100644 --- a/main.js +++ b/main.js @@ -64,4 +64,5 @@ ipcMain.on('getTemplateContent', (event, arg) => { }); require('./modules/geth.js'); +require('./modules/accounts.js'); require('./modules/database.js'); \ No newline at end of file diff --git a/modules/accounts.js b/modules/accounts.js new file mode 100644 index 0000000..0671cca --- /dev/null +++ b/modules/accounts.js @@ -0,0 +1,54 @@ +const {app, dialog, ipcMain} = require('electron'); +const admZip = require('adm-zip'); +const path = require('path'); +const fs = require('fs'); + +class Accounts { + constructor() { + } + + exportAccounts() { + var savePath = dialog.showSaveDialog({ + defaultPath: path.join(app.getPath('documents'), 'accounts.zip') + }); + + if (savePath) { + const accPath = path.join(path.join(process.env.APPDATA, 'Ether1'), 'keystore'); + + fs.readdir(accPath, function(err, files) { + var zip = new admZip(); + + for(let filePath of files) { + zip.addFile(filePath, fs.readFileSync(path.join(accPath, filePath))); + } + + // store zip to path + zip.writeZip(savePath); + }); + } + } + + importAccounts() { + const accPath = path.join(path.join(process.env.APPDATA, 'Ether1'), 'keystore'); + + var openPath = dialog.showOpenDialog({ + defaultPath: app.getPath('documents') + }); + + if (openPath) { + var zip = new admZip(openPath[0]); + zip.extractAllTo(accPath, true); + } + } +} + +ipcMain.on('exportAccounts', (event, arg) => { + EthoAccounts.exportAccounts(); +}); + +ipcMain.on('importAccounts', (event, arg) => { + EthoAccounts.importAccounts(); + event.returnValue = true; +}); + +EthoAccounts = new Accounts(); \ No newline at end of file diff --git a/modules/database.js b/modules/database.js index dce5085..1650dac 100644 --- a/modules/database.js +++ b/modules/database.js @@ -29,7 +29,8 @@ ipcMain.on('getTransactions', (event, arg) => { docs[i].timestamp, docs[i].fromaddr, docs[i].toaddr, - docs[i].value + docs[i].value, + docs[i].confirmed || 1 ]); } diff --git a/modules/geth.js b/modules/geth.js index d9b5587..4499a08 100644 --- a/modules/geth.js +++ b/modules/geth.js @@ -1,5 +1,5 @@ -const {app, dialog, BrowserWindow} = require('electron') const child_process = require('child_process'); +const {app, dialog} = require('electron'); const path = require('path'); class Geth { @@ -14,6 +14,7 @@ class Geth { this.gethProcess = child_process.spawn(gethPath, ['--ws', '--wsorigins', '*', '--wsaddr', '127.0.0.1', '--wsport', '8546', '--wsapi', 'admin,db,eth,net,miner,personal,web3']); this.gethProcess.on('error', function(err) { dialog.showErrorBox("Error starting application", "Geth failed to start!"); + app.quit(); }); this.gethProcess.stderr.on('data', function(data) { console.log(data.toString()); diff --git a/package.json b/package.json index 23f88c0..67e1eda 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,11 @@ "author": "Ether1", "license": "CC0-1.0", "dependencies": { + "adm-zip": "^0.4.13", "electron-storage": "^1.0.7", "handlebars": "^4.0.12", - "nedb": "^1.8.0" + "nedb": "^1.8.0", + "typescript": "^3.2.2" }, "devDependencies": { "electron": "^3.0.12", diff --git a/renderer/blockchain.js b/renderer/blockchain.js index 915e293..b88bda6 100644 --- a/renderer/blockchain.js +++ b/renderer/blockchain.js @@ -28,6 +28,16 @@ class Blockchain { return web3Local.utils.isAddress(address); } + getTransaction(thxid, clbError, clbSuccess) { + web3Local.eth.getTransaction(thxid, function( error, result ) { + if (error) { + clbError(error); + } else { + clbSuccess(result); + } + }); + } + getTranasctionFee(fromAddress, toAddress, value, clbError, clbSuccess) { web3Local.eth.getTransactionCount(fromAddress, function( error, result ) { if (error) { @@ -65,7 +75,7 @@ class Blockchain { clbError("Wrong password for the selected address!"); } else { - web3Local.eth.getTransactionCount(fromAddress, function( error, result ) { + web3Local.eth.getTransactionCount(fromAddress, 'pending', function( error, result ) { if (error) { clbError(error); } else { diff --git a/renderer/send.js b/renderer/send.js index 5ece6bb..5c74b4f 100644 --- a/renderer/send.js +++ b/renderer/send.js @@ -139,6 +139,24 @@ $(document).on("render_send", function() { position: 'topRight', timeout: 5000 }); + + EthoBlockchain.getTransaction(data, + function(error) { + EthoMainGUI.showGeneralError(error); + }, + function(transaction) { + /* + ipcRenderer.send('storeTransaction', { + block: element.block, + fromaddr: element.fromaddr, + timestamp: element.timestamp, + toaddr: element.toaddr, + value: element.value, + confirmed: "1", + }); + */ + } + ); } ); } diff --git a/renderer/transactions.js b/renderer/transactions.js index 03c3e69..e00bc41 100644 --- a/renderer/transactions.js +++ b/renderer/transactions.js @@ -1,4 +1,3 @@ -// In renderer process (web page). const {ipcRenderer} = require('electron'); class Transactions { @@ -23,14 +22,15 @@ class Transactions { $.getJSON("https://richlist.ether1.org/transactions_list.php" + params, function( result ) { result.data.forEach(element => { - var Transaction = { + ipcRenderer.send('storeTransaction', { block: element.block, + txhash: element.hash, fromaddr: element.fromaddr, timestamp: element.timestamp, toaddr: element.toaddr, - value: element.value - } - ipcRenderer.send('storeTransaction', Transaction); + value: element.value, + confirmed: "1" + }); }); // call the transaction sync for the next address @@ -93,6 +93,9 @@ class Transactions { } }); + // register the sort datetime format + $.fn.dataTable.moment('MMM Do YYYY'); + // render the transactions $('#tableTransactionsForAll').DataTable({ "paging": false, @@ -129,6 +132,16 @@ class Transactions { "render": function ( data, type, row ) { return parseFloat(web3Local.utils.fromWei(EthoUtils.toFixed(parseFloat(data)).toString(), 'ether')).toFixed(2); } + }, + { + "targets": 6, + "render": function ( data, type, row ) { + if (data == 0) { + return ''; + } else if (data == 1) { + return ''; + } + } } ], "drawCallback": function( settings ) { @@ -172,7 +185,8 @@ $(document).on("onSyncInterval", function() { fromaddr: element.from.toLowerCase(), timestamp: moment().format('YYYY-MM-DD HH:mm:ss'), toaddr: element.to.toLowerCase(), - value: Number(element.value).toExponential(5).toString().replace('+','') + value: Number(element.value).toExponential(5).toString().replace('+',''), + confirmed: "0", } // store transaction and notify about new transactions diff --git a/renderer/utils.js b/renderer/utils.js index 9f41127..6b6c22c 100644 --- a/renderer/utils.js +++ b/renderer/utils.js @@ -39,6 +39,19 @@ class Utils { } } } + + createToolTip(element, text) { + tippy(element, { + content: text, + delay: 500, + arrow: true, + arrowType: 'round', + size: 'large', + duration: 500, + animation: 'scale' + }); + } + } EthoUtils = new Utils(); \ No newline at end of file diff --git a/renderer/wallets.js b/renderer/wallets.js index 28744ae..89091e8 100644 --- a/renderer/wallets.js +++ b/renderer/wallets.js @@ -1,4 +1,3 @@ -// In renderer process (web page). const {ipcRenderer} = require('electron'); class Wallets { @@ -22,6 +21,12 @@ class Wallets { this.addressList.push(address.toLowerCase()); } + enableButtonTooltips() { + EthoUtils.createToolTip("#btnNewAddress", "Create New Address"); + EthoUtils.createToolTip("#btnExportAccounts", "Export Accounts"); + EthoUtils.createToolTip("#btnImportAccounts", "Import Accounts"); + } + validateNewAccountForm() { if (EthoMainGUI.getAppState() == "account") { if (!$("#walletPasswordFirst").val()) { @@ -61,6 +66,7 @@ renderWalletsState() { // render the wallets current state EthoMainGUI.renderTemplate("wallets.html", data); $(document).trigger("render_wallets"); + EthoWallets.enableButtonTooltips(); } ); } @@ -148,6 +154,14 @@ $(document).on("render_wallets", function() { }); }); + $("#btnExportAccounts").off('click').on('click', function() { + ipcRenderer.send('exportAccounts', {}); + }); + + $("#btnImportAccounts").off('click').on('click', function() { + ipcRenderer.sendSync('importAccounts', {}); + }); + $(".textAddress").off('click').on('click', function() { EthoMainGUI.copyToClipboard($(this).html()); @@ -168,7 +182,7 @@ $(document).on("onGethReady", function() { $(document).on("onNewAccountTransaction", function() { if (EthoMainGUI.getAppState() == "account") { - EthoWallets.renderWalletsState(); + EthoWallets.renderWalletsState(); } });