+ tooltips
+ sort transactions by date correctly + confirmed transactions indicator
This commit is contained in:
74
assets/scripts/datetime-moment.js
Normal file
74
assets/scripts/datetime-moment.js
Normal file
@@ -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 );
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}));
|
||||||
@@ -34,3 +34,11 @@
|
|||||||
#tableTransactionsForAll .fa-arrows-alt-h {
|
#tableTransactionsForAll .fa-arrows-alt-h {
|
||||||
color: #DCDCDC;
|
color: #DCDCDC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tableTransactionsForAll .fa-question {
|
||||||
|
color: #DCDCDC;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tableTransactionsForAll .fa-check {
|
||||||
|
color: #228B22;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<th>From</th>
|
<th>From</th>
|
||||||
<th>To</th>
|
<th>To</th>
|
||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
<th>From</th>
|
<th>From</th>
|
||||||
<th>To</th>
|
<th>To</th>
|
||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<div id="walletsToolbar">
|
<div id="walletsToolbar">
|
||||||
<button type="button" class="btn btn-etho" id="btnNewAddress">New Address</button>
|
<button type="button" class="btn btn-etho" id="btnNewAddress"><i class="fas fa-plus"></i></button>
|
||||||
|
<button type="button" class="btn btn-etho" id="btnExportAccounts"><i class="fas fa-file-export"></i></button>
|
||||||
|
<button type="button" class="btn btn-etho" id="btnImportAccounts"><i class="fas fa-file-import"></i></button>
|
||||||
<div id="sumBalance">Total ETHO: <span class="sumBalance">{{sumBalance}}</span></div>
|
<div id="sumBalance">Total ETHO: <span class="sumBalance">{{sumBalance}}</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="addressList" class="{{#if addressData.length}}walletsWrapper{{else}}noWalletsWrapper{{/if}}">
|
<div id="addressList" class="{{#if addressData.length}}walletsWrapper{{else}}noWalletsWrapper{{/if}}">
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
<script src="./assets/scripts/sprintf.min.js"></script>
|
<script src="./assets/scripts/sprintf.min.js"></script>
|
||||||
<script src="./assets/scripts/please-wait.js "></script>
|
<script src="./assets/scripts/please-wait.js "></script>
|
||||||
<script src="./assets/scripts/moment.min.js"></script>
|
<script src="./assets/scripts/moment.min.js"></script>
|
||||||
|
<script src="./assets/scripts/datetime-moment.js"></script>
|
||||||
<script src="./assets/scripts/jquery.floatThead.min.js"></script>
|
<script src="./assets/scripts/jquery.floatThead.min.js"></script>
|
||||||
<script src="./assets/scripts/all.min.js"></script>
|
<script src="./assets/scripts/all.min.js"></script>
|
||||||
|
|
||||||
|
|||||||
1
main.js
1
main.js
@@ -64,4 +64,5 @@ ipcMain.on('getTemplateContent', (event, arg) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
require('./modules/geth.js');
|
require('./modules/geth.js');
|
||||||
|
require('./modules/accounts.js');
|
||||||
require('./modules/database.js');
|
require('./modules/database.js');
|
||||||
54
modules/accounts.js
Normal file
54
modules/accounts.js
Normal file
@@ -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();
|
||||||
@@ -29,7 +29,8 @@ ipcMain.on('getTransactions', (event, arg) => {
|
|||||||
docs[i].timestamp,
|
docs[i].timestamp,
|
||||||
docs[i].fromaddr,
|
docs[i].fromaddr,
|
||||||
docs[i].toaddr,
|
docs[i].toaddr,
|
||||||
docs[i].value
|
docs[i].value,
|
||||||
|
docs[i].confirmed || 1
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const {app, dialog, BrowserWindow} = require('electron')
|
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
|
const {app, dialog} = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
class Geth {
|
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 = 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) {
|
this.gethProcess.on('error', function(err) {
|
||||||
dialog.showErrorBox("Error starting application", "Geth failed to start!");
|
dialog.showErrorBox("Error starting application", "Geth failed to start!");
|
||||||
|
app.quit();
|
||||||
});
|
});
|
||||||
this.gethProcess.stderr.on('data', function(data) {
|
this.gethProcess.stderr.on('data', function(data) {
|
||||||
console.log(data.toString());
|
console.log(data.toString());
|
||||||
|
|||||||
@@ -25,9 +25,11 @@
|
|||||||
"author": "Ether1",
|
"author": "Ether1",
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"adm-zip": "^0.4.13",
|
||||||
"electron-storage": "^1.0.7",
|
"electron-storage": "^1.0.7",
|
||||||
"handlebars": "^4.0.12",
|
"handlebars": "^4.0.12",
|
||||||
"nedb": "^1.8.0"
|
"nedb": "^1.8.0",
|
||||||
|
"typescript": "^3.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^3.0.12",
|
"electron": "^3.0.12",
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ class Blockchain {
|
|||||||
return web3Local.utils.isAddress(address);
|
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) {
|
getTranasctionFee(fromAddress, toAddress, value, clbError, clbSuccess) {
|
||||||
web3Local.eth.getTransactionCount(fromAddress, function( error, result ) {
|
web3Local.eth.getTransactionCount(fromAddress, function( error, result ) {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -65,7 +75,7 @@ class Blockchain {
|
|||||||
clbError("Wrong password for the selected address!");
|
clbError("Wrong password for the selected address!");
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
web3Local.eth.getTransactionCount(fromAddress, function( error, result ) {
|
web3Local.eth.getTransactionCount(fromAddress, 'pending', function( error, result ) {
|
||||||
if (error) {
|
if (error) {
|
||||||
clbError(error);
|
clbError(error);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -139,6 +139,24 @@ $(document).on("render_send", function() {
|
|||||||
position: 'topRight',
|
position: 'topRight',
|
||||||
timeout: 5000
|
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",
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// In renderer process (web page).
|
|
||||||
const {ipcRenderer} = require('electron');
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
class Transactions {
|
class Transactions {
|
||||||
@@ -23,14 +22,15 @@ class Transactions {
|
|||||||
|
|
||||||
$.getJSON("https://richlist.ether1.org/transactions_list.php" + params, function( result ) {
|
$.getJSON("https://richlist.ether1.org/transactions_list.php" + params, function( result ) {
|
||||||
result.data.forEach(element => {
|
result.data.forEach(element => {
|
||||||
var Transaction = {
|
ipcRenderer.send('storeTransaction', {
|
||||||
block: element.block,
|
block: element.block,
|
||||||
|
txhash: element.hash,
|
||||||
fromaddr: element.fromaddr,
|
fromaddr: element.fromaddr,
|
||||||
timestamp: element.timestamp,
|
timestamp: element.timestamp,
|
||||||
toaddr: element.toaddr,
|
toaddr: element.toaddr,
|
||||||
value: element.value
|
value: element.value,
|
||||||
}
|
confirmed: "1"
|
||||||
ipcRenderer.send('storeTransaction', Transaction);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// call the transaction sync for the next address
|
// 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
|
// render the transactions
|
||||||
$('#tableTransactionsForAll').DataTable({
|
$('#tableTransactionsForAll').DataTable({
|
||||||
"paging": false,
|
"paging": false,
|
||||||
@@ -129,6 +132,16 @@ class Transactions {
|
|||||||
"render": function ( data, type, row ) {
|
"render": function ( data, type, row ) {
|
||||||
return parseFloat(web3Local.utils.fromWei(EthoUtils.toFixed(parseFloat(data)).toString(), 'ether')).toFixed(2);
|
return parseFloat(web3Local.utils.fromWei(EthoUtils.toFixed(parseFloat(data)).toString(), 'ether')).toFixed(2);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"targets": 6,
|
||||||
|
"render": function ( data, type, row ) {
|
||||||
|
if (data == 0) {
|
||||||
|
return '<i class="fas fa-question"></i>';
|
||||||
|
} else if (data == 1) {
|
||||||
|
return '<i class="fas fa-check"></i>';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"drawCallback": function( settings ) {
|
"drawCallback": function( settings ) {
|
||||||
@@ -172,7 +185,8 @@ $(document).on("onSyncInterval", function() {
|
|||||||
fromaddr: element.from.toLowerCase(),
|
fromaddr: element.from.toLowerCase(),
|
||||||
timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
|
timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
toaddr: element.to.toLowerCase(),
|
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
|
// store transaction and notify about new transactions
|
||||||
|
|||||||
@@ -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();
|
EthoUtils = new Utils();
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// In renderer process (web page).
|
|
||||||
const {ipcRenderer} = require('electron');
|
const {ipcRenderer} = require('electron');
|
||||||
|
|
||||||
class Wallets {
|
class Wallets {
|
||||||
@@ -22,6 +21,12 @@ class Wallets {
|
|||||||
this.addressList.push(address.toLowerCase());
|
this.addressList.push(address.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableButtonTooltips() {
|
||||||
|
EthoUtils.createToolTip("#btnNewAddress", "Create New Address");
|
||||||
|
EthoUtils.createToolTip("#btnExportAccounts", "Export Accounts");
|
||||||
|
EthoUtils.createToolTip("#btnImportAccounts", "Import Accounts");
|
||||||
|
}
|
||||||
|
|
||||||
validateNewAccountForm() {
|
validateNewAccountForm() {
|
||||||
if (EthoMainGUI.getAppState() == "account") {
|
if (EthoMainGUI.getAppState() == "account") {
|
||||||
if (!$("#walletPasswordFirst").val()) {
|
if (!$("#walletPasswordFirst").val()) {
|
||||||
@@ -61,6 +66,7 @@ renderWalletsState() {
|
|||||||
// render the wallets current state
|
// render the wallets current state
|
||||||
EthoMainGUI.renderTemplate("wallets.html", data);
|
EthoMainGUI.renderTemplate("wallets.html", data);
|
||||||
$(document).trigger("render_wallets");
|
$(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() {
|
$(".textAddress").off('click').on('click', function() {
|
||||||
EthoMainGUI.copyToClipboard($(this).html());
|
EthoMainGUI.copyToClipboard($(this).html());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user