@ -1,24 +1,35 @@
const $ = require ( 'jquery' )
import { Settings } from '@remix-ui/run-tab' // eslint-disable-line
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
const asyncJS = require ( 'async' )
const yo = require ( 'yo-yo' )
const remixLib = require ( '@remix-project/remix-lib' )
const EventManager = remixLib . EventManager
const css = require ( '../styles/run-tab-styles' )
const copyToClipboard = require ( '../../ui/copy-to-clipboard' )
const modalDialogCustom = require ( '../../ui/modal-dialog-custom' )
const addTooltip = require ( '../../ui/tooltip' )
const helper = require ( '../../../lib/helper.js' )
const globalRegistry = require ( '../../../global/registry' )
const defaultOptions = [
{ value : 'vm' , name : 'JavaScript VM' , title : 'Execution environment does not connect to any node, everything is local and in memory only.' } ,
{ value : 'injected' , name : 'Injected Web3' , title : 'Execution environment has been provided by Metamask or similar provider.' } ,
{ value : 'web3' , name : 'Web3 Provider' , title : 'Execution environment connects to node at localhost (or via IPC if available), transactions will be sent to the network and can cause loss of money or worse! If this page is served via https and you access your node via http, it might not work. In this case, try cloning the repository and serving it via http.' }
]
class SettingsUI {
constructor ( blockchain , networkModule ) {
this . blockchain = blockchain
this . event = new EventManager ( )
this . _components = { }
this . options = defaultOptions
this . accounts = [ ]
this . blockchain . event . register ( 'transactionExecuted' , ( error , from , to , data , lookupOnly , txResult ) => {
if ( ! lookupOnly ) this . el . querySelector ( '#value' ) . value = 0
if ( error ) return
this . updateAccountBalances ( )
this . updateAccountsAnd Balances ( )
} )
this . _components = {
registry : globalRegistry ,
@ -29,205 +40,40 @@ class SettingsUI {
config : this . _components . registry . get ( 'config' ) . api
}
this . _deps . config . events . on ( 'settings/personal-mode_changed' , this . onPersonalChange . bind ( this ) )
setInterval ( ( ) => {
this . updateAccountBalances ( )
} , 1000 )
this . accountListCallId = 0
this . loadedAccounts = { }
}
updateAccountBalances ( ) {
if ( ! this . el ) return
var accounts = $ ( this . el . querySelector ( '#txorigin' ) ) . children ( 'option' )
accounts . each ( ( index , account ) => {
this . blockchain . getBalanceInEther ( account . value , ( err , balance ) => {
if ( err ) return
const updated = helper . shortenAddress ( account . value , balance )
if ( updated !== account . innerText ) { // check if the balance has been updated and update UI accordingly.
account . innerText = updated
}
} )
} )
this . _deps . config . events . on ( 'settings/personal-mode_changed' , this . renderSettings . bind ( this ) )
setInterval ( this . updateAccountsAndBalances . bind ( this ) , 2000 )
}
validateInputKey ( e ) {
// preventing not numeric keys
// preventing 000 case
if ( ! helper . isNumeric ( e . key ) ||
( e . key === '0' && ! parseInt ( this . el . querySelector ( '#value' ) . value ) && this . el . querySelector ( '#value' ) . value . length > 0 ) ) {
e . preventDefault ( )
e . stopImmediatePropagation ( )
}
}
renderSettings ( ) {
const personalModeChecked = this . _deps . config . get ( 'settings/personal-mode' )
const selectedProvider = this . blockchain . getProvider ( )
validateValue ( ) {
const valueEl = this . el . querySelector ( '#value' )
valueEl . value = parseInt ( valueEl . value )
// assign 0 if given value is
// - empty
// - not valid (for ex 4345-54)
// - contains only '0's (for ex 0000) copy past or edit
if ( ! valueEl . value ) valueEl . value = 0
// if giveen value is negative(possible with copy-pasting) set to 0
if ( valueEl . value < 0 ) valueEl . value = 0
ReactDOM . render ( < Settings accounts = { this . accounts } options = { this . options } selectedProvider = { selectedProvider } personalModeChecked = { personalModeChecked } updateNetwork = { this . updateNetwork . bind ( this ) } newAccount = { this . newAccount . bind ( this ) } signMessage = { this . signMessage . bind ( this ) } copyToClipboard = { copyToClipboard } / > , this . el )
}
render ( ) {
this . netUI = yo ` <span class=" ${ css . network } badge badge-secondary" ></span> `
this . el = yo ` <span></span> `
var environmentEl = yo `
< div class = "${css.crow}" >
< label id = "selectExEnv" class = "${css.settingsLabel}" >
Environment
< / l a b e l >
< div class = "${css.environment}" >
< select id = "selectExEnvOptions" data - id = "settingsSelectEnvOptions" onchange = $ { ( ) => { this . updateNetwork ( ) } } class = "form-control ${css.select} custom-select" >
< option id = "vm-mode"
title = "Execution environment does not connect to any node, everything is local and in memory only."
value = "vm" name = "executionContext" > JavaScript VM
< / o p t i o n >
< option id = "injected-mode"
title = "Execution environment has been provided by Metamask or similar provider."
value = "injected" name = "executionContext" > Injected Web3
< / o p t i o n >
< option id = "web3-mode" data - id = "settingsWeb3Mode"
title = " Execution environment connects to node at localhost ( or via IPC if available ) , transactions will be sent to the network and can cause loss of money or worse !
If this page is served via https and you access your node via http , it might not work . In this case , try cloning the repository and serving it via http . "
value = "web3" name = "executionContext" > Web3 Provider
< / o p t i o n >
< / s e l e c t >
< a href = "https://remix-ide.readthedocs.io/en/latest/run.html#run-setup" target = "_blank" > < i class = "${css.infoDeployAction} ml-2 fas fa-info" title = "check out docs to setup Environment" > < / i > < / a >
< / d i v >
< / d i v >
`
const networkEl = yo `
< div class = "${css.crow}" >
< div class = "${css.settingsLabel}" >
< / d i v >
< div class = "${css.environment}" data - id = "settingsNetworkEnv" >
$ { this . netUI }
< / d i v >
< / d i v >
`
const accountEl = yo `
< div class = "${css.crow}" >
< label class = "${css.settingsLabel}" >
Account
< span id = "remixRunPlusWraper" title = "Create a new account" onload = $ { this . updatePlusButton . bind ( this ) } >
< i id = "remixRunPlus" class = "fas fa-plus-circle ${css.icon}" aria - hidden = "true" onclick = $ { this . newAccount . bind ( this ) } " > < / i >
< / s p a n >
< / l a b e l >
< div class = "${css.account}" >
< select data - id = "runTabSelectAccount" name = "txorigin" class = "form-control ${css.select} custom-select pr-4" id = "txorigin" > < / s e l e c t >
< div style = "margin-left: -5px;" > $ { copyToClipboard ( ( ) => document . querySelector ( '#runTabView #txorigin' ) . value ) } < / d i v >
< i id = "remixRunSignMsg" data - id = "settingsRemixRunSignMsg" class = "mx-1 fas fa-edit ${css.icon}" aria - hidden = "true" onclick = $ { this . signMessage . bind ( this ) } title = "Sign a message using this account key" > < / i >
< / d i v >
< / d i v >
`
this . renderSettings ( )
const gasPriceEl = yo `
< div class = "${css.crow}" >
< label class = "${css.settingsLabel}" > Gas limit < / l a b e l >
< input type = "number" class = "form-control ${css.gasNval} ${css.col2}" id = "gasLimit" value = "3000000" >
< / d i v >
`
this . blockchain . event . register ( 'addProvider' , ( network ) => {
this . options . push ( { title : ` provider name: ${ network . name } ` , value : ` ${ network . name } ` , name : 'executionContext' } )
this . renderSettings ( )
const valueEl = yo `
< div class = "${css.crow}" >
< label class = "${css.settingsLabel}" data - id = "remixDRValueLabel" > Value < / l a b e l >
< div class = "${css.gasValueContainer}" >
< input
type = "number"
min = "0"
pattern = "^[0-9]"
step = "1"
class = "form-control ${css.gasNval} ${css.col2}"
id = "value"
data - id = "dandrValue"
value = "0"
title = "Enter the value and choose the unit"
onkeypress = $ { ( e ) => this . validateInputKey ( e ) }
onchange = $ { ( ) => this . validateValue ( ) }
>
< select name = "unit" class = "form-control p-1 ${css.gasNvalUnit} ${css.col2_2} custom-select" id = "unit" >
< option data - unit = "wei" > wei < / o p t i o n >
< option data - unit = "gwei" > gwei < / o p t i o n >
< option data - unit = "finney" > finney < / o p t i o n >
< option data - unit = "ether" > ether < / o p t i o n >
< / s e l e c t >
< / d i v >
< / d i v >
`
const el = yo `
< div class = "${css.settings}" >
$ { environmentEl }
$ { networkEl }
$ { accountEl }
$ { gasPriceEl }
$ { valueEl }
< / d i v >
`
var selectExEnv = environmentEl . querySelector ( '#selectExEnvOptions' )
this . setDropdown ( selectExEnv )
this . blockchain . event . register ( 'contextChanged' , ( context , silent ) => {
this . setFinalContext ( )
} )
setInterval ( ( ) => {
this . updateNetwork ( )
} , 1000 )
this . el = el
this . fillAccountsList ( )
return el
}
setDropdown ( selectExEnv ) {
this . selectExEnv = selectExEnv
const addProvider = ( network ) => {
selectExEnv . appendChild ( yo ` <option
title = "provider name: ${network.name}"
value = "${network.name}"
name = "executionContext"
>
$ { network . name }
< / o p t i o n > ` )
addTooltip ( yo ` <span><b> ${ network . name } </b> provider added</span> ` )
}
} )
const removeProvider = ( name ) => {
var env = selectExEnv . querySelector ( ` option[value=" ${ name } "] ` )
if ( env ) {
selectExEnv . removeChild ( env )
addTooltip ( yo ` <span><b> ${ name } </b> provider removed</span> ` )
}
}
this . blockchain . event . register ( 'addProvider' , provider => addProvider ( provider ) )
this . blockchain . event . register ( 'removeProvider' , name => removeProvider ( name ) )
this . blockchain . event . register ( 'removeProvider' , ( name ) => {
this . options = this . options . filter ( ( option ) => option . name !== name )
this . renderSettings ( )
selectExEnv . addEventListener ( 'change' , ( event ) => {
const context = selectExEnv . options [ selectExEnv . selectedIndex ] . value
this . blockchain . changeExecutionContext ( context , ( ) => {
modalDialogCustom . prompt ( 'External node request' , this . web3ProviderDialogBody ( ) , 'http://127.0.0.1:8545' , ( target ) => {
this . blockchain . setProviderFromEndpoint ( target , context , ( alertMsg ) => {
if ( alertMsg ) addTooltip ( alertMsg )
this . setFinalContext ( )
} )
} , this . setFinalContext . bind ( this ) )
} , ( alertMsg ) => {
addTooltip ( alertMsg )
} , this . setFinalContext . bind ( this ) )
addTooltip ( yo ` <span><b> ${ name } </b> provider removed</span> ` )
} )
selectExEnv . value = this . blockchain . getProvider ( )
this . blockchain . event . register ( 'contextChanged' , this . setFinalContext . bind ( this ) )
setInterval ( this . updateNetwork . bind ( this ) , 1000 )
return this . el
}
web3ProviderDialogBody ( ) {
@ -252,51 +98,10 @@ class SettingsUI {
`
}
// set the final context. Cause it is possible that this is not the one we've originaly selected
setFinalContext ( ) {
// set the final context. Cause it is possible that this is not the one we've originaly selected
this . selectExEnv . value = this . blockchain . getProvider ( )
this . event . trigger ( 'clearInstance' , [ ] )
this . updateNetwork ( )
this . updatePlusButton ( )
}
updatePlusButton ( ) {
// enable/disable + button
const plusBtn = document . getElementById ( 'remixRunPlus' )
const plusTitle = document . getElementById ( 'remixRunPlusWraper' )
switch ( this . selectExEnv . value ) {
case 'injected' :
plusBtn . classList . add ( css . disableMouseEvents )
plusTitle . title = "Unfortunately it's not possible to create an account using injected web3. Please create the account directly from your provider (i.e metamask or other of the same type)."
break
case 'vm' :
plusBtn . classList . remove ( css . disableMouseEvents )
plusTitle . title = 'Create a new account'
break
case 'web3' :
this . onPersonalChange ( )
break
default : {
plusBtn . classList . add ( css . disableMouseEvents )
plusTitle . title = ` Unfortunately it's not possible to create an account using an external wallet ( ${ this . selectExEnv . value } ). `
}
}
}
onPersonalChange ( ) {
const plusBtn = document . getElementById ( 'remixRunPlus' )
const plusTitle = document . getElementById ( 'remixRunPlusWraper' )
if ( ! this . _deps . config . get ( 'settings/personal-mode' ) ) {
plusBtn . classList . add ( css . disableMouseEvents )
plusTitle . title = 'Creating an account is possible only in Personal mode. Please go to Settings to enable it.'
} else {
plusBtn . classList . remove ( css . disableMouseEvents )
plusTitle . title = 'Create a new account'
}
}
newAccount ( ) {
@ -365,41 +170,44 @@ class SettingsUI {
} )
}
updateNetwork ( ) {
updateNetwork ( context , cb ) {
if ( context ) {
this . blockchain . changeExecutionContext ( context , ( ) => {
modalDialogCustom . prompt ( 'External node request' , this . web3ProviderDialogBody ( ) , 'http://127.0.0.1:8545' , ( target ) => {
this . blockchain . setProviderFromEndpoint ( target , context , ( alertMsg ) => {
if ( alertMsg ) addTooltip ( alertMsg )
this . setFinalContext ( )
} )
} , this . setFinalContext . bind ( this ) )
} , ( alertMsg ) => {
addTooltip ( alertMsg )
} , this . setFinalContext . bind ( this ) )
}
this . blockchain . updateNetwork ( ( err , { id , name } = { } ) => {
if ( ! cb ) return
if ( err ) {
this . netUI . innerHTML = 'can\'t detect network '
return
return cb ( 'can\'t detect network ' )
}
const network = this . _components . networkModule . getNetworkProvider . bind ( this . _components . networkModule )
this . netUI . innerHTML = ( network ( ) !== 'vm' ) ? ` ${ name } ( ${ id || '-' } ) network ` : ''
this . renderSettings ( )
cb ( ( network ( ) !== 'vm' ) ? ` ${ name } ( ${ id || '-' } ) network ` : '' )
} )
this . fillAccountsList ( )
this . updateAccountsAndBalances ( )
}
// TODO: unclear what's the goal of accountListCallId, feels like it can be simplified
fillAccountsList ( ) {
this . accountListCallId ++
var callid = this . accountListCallId
var txOrigin = this . el . querySelector ( '#txorigin' )
this . blockchain . getAccounts ( ( err , accounts ) => {
if ( this . accountListCallId > callid ) return
this . accountListCallId ++
if ( err ) { addTooltip ( ` Cannot get account list: ${ err } ` ) }
for ( var loadedaddress in this . loadedAccounts ) {
if ( accounts . indexOf ( loadedaddress ) === - 1 ) {
txOrigin . removeChild ( txOrigin . querySelector ( 'option[value="' + loadedaddress + '"]' ) )
delete this . loadedAccounts [ loadedaddress ]
}
}
for ( var i in accounts ) {
var address = accounts [ i ]
if ( ! this . loadedAccounts [ address ] ) {
txOrigin . appendChild ( yo ` <option value=" ${ address } " > ${ address } </option> ` )
this . loadedAccounts [ address ] = 1
}
}
txOrigin . setAttribute ( 'value' , accounts [ 0 ] )
async updateAccountsAndBalances ( ) {
const accounts = await this . blockchain . getAccounts ( )
asyncJS . map ( accounts , async ( address , next ) => {
this . blockchain . getBalanceInEther ( address , ( err , balance ) => {
if ( err ) { return next ( err ) }
const updated = helper . shortenAddress ( address , balance )
const newAccount = { address , name : updated }
next ( null , newAccount )
} )
} , ( _err , results ) => {
this . accounts = results
this . renderSettings ( )
} )
}
}