@ -1,50 +1,98 @@
/* global Option, Worker */
var $ = require ( 'jquery' )
/* global Worker */
var yo = require ( 'yo-yo' )
var request = require ( 'reques t' )
var QueryParams = require ( '../../lib/query-params ' )
var csjs = require ( 'csjs-injec t' )
var minixhr = require ( 'minixhr ' )
var remixLib = require ( 'remix-lib' )
var Storage = remixLib . Storage
var styleGuide = require ( '../ui/styles-guide/theme-chooser' )
var QueryParams = require ( '../../lib/query-params' )
var helper = require ( '../../lib/helper' )
var modal = require ( '../ui/modal-dialog-custom' )
var tooltip = require ( '../ui/tooltip' )
var copyToClipboard = require ( '../ui/copy-to-clipboard' )
var styleGuide = require ( '../ui/styles-guide/theme-chooser' )
var css = require ( './styles/settings-tab-styles' )
function SettingsTab ( appAPI = { } , appEvents = { } , opts = { } ) {
var queryParams = new QueryParams ( )
var styles = styleGuide . chooser ( )
var Storage = remixLib . Storage
var EventManager = remixLib . EventManager
module . exports = class SettingsTab {
constructor ( api = { } , events = { } , opts = { } ) {
const self = this
self . _opts = opts
self . _api = api
self . _events = events
self . _components = { }
self . _view = { /* eslint-disable */
el : null ,
optionVM : null , personal : null , optimize : null , warnPersonalMode : null ,
pluginInput : null , versionSelector : null , version : null ,
theme : { dark : null , light : null } ,
config : {
solidity : null , general : null , themes : null ,
plugin : null , remixd : null , localremixd : null
}
} /* eslint-enable */
self . data = { allversions : null , selectedVersion : null }
self . event = new EventManager ( )
self . _components . queryParams = new QueryParams ( )
self . _components . themeStorage = new Storage ( 'style:' )
self . data . optimize = ! ! self . _components . queryParams . get ( ) . optimize
self . _components . queryParams . update ( { optimize : self . data . optimize } )
self . _api . setOptimize ( self . data . optimize , false )
self . data . currentTheme = self . _components . themeStorage . get ( 'theme' ) || 'light'
self . _events . compiler . register ( 'compilerLoaded' , ( version ) => self . setVersionText ( version ) )
self . fetchAllVersion ( ( allversions , selectedVersion ) => {
self . data . allversions = allversions
self . data . selectedVersion = selectedVersion
if ( self . _view . versionSelector ) self . _updateVersionSelector ( )
} )
}
render ( ) {
const self = this
if ( self . _view . el ) return self . _view . el
var optionVM = yo ` <input id="alwaysUseVM" type="checkbox"> `
var personal = yo ` <input id="personal" type="checkbox"> `
// Gist settings
var gistAccessToken = yo ` <input id="gistaccesstoken" type="password"> `
var token = self . _api . config . get ( 'settings/gist-access-token' )
if ( token ) gistAccessToken . value = token
var gistAddToken = yo ` <input class=" ${ css . savegisttoken } " id="savegisttoken" onclick= ${ ( ) => { self . _api . config . set ( 'settings/gist-access-token' , gistAccessToken . value ) ; tooltip ( 'Access token saved' ) } } value="Save" type="button"> `
var gistRemoveToken = yo ` <input id="removegisttoken" onclick= ${ ( ) => { gistAccessToken . value = '' ; self . _api . config . set ( 'settings/gist-access-token' , '' ) ; tooltip ( 'Access token removed' ) } } value="Remove" type="button"> `
self . _view . gistToken = yo ` <div class=" ${ css . checkboxText } "> ${ gistAccessToken } ${ copyToClipboard ( ( ) => self . _api . config . get ( 'settings/gist-access-token' ) ) } ${ gistAddToken } ${ gistRemoveToken } </div> `
//
self . _view . optionVM = yo ` <input onchange= ${ onchangeOption } id="alwaysUseVM" type="checkbox"> `
if ( self . _api . config . get ( 'settings/always-use-vm' ) ) self . _view . optionVM . setAttribute ( 'checked' , '' )
self . _view . personal = yo ` <input onchange= ${ onchangePersonal } id="personal" type="checkbox"> `
if ( self . _api . config . get ( 'settings/personal-mode' ) ) self . _view . personal . setAttribute ( 'checked' , '' )
self . _view . optimize = yo ` <input onchange= ${ onchangeOptimize } id="optimize" type="checkbox"> `
if ( self . data . optimize ) self . _view . optimize . setAttribute ( 'checked' , '' )
var warnText = ` Transaction sent over Web3 will use the web3.personal API - be sure the endpoint is opened before enabling it.
This mode allows to provide the passphrase in the Remix interface without having to unlock the account .
Although this is very convenient , you should completely trust the backend you are connected to ( Geth , Parity , ... ) .
It is not recommended ( and also most likely not relevant ) to use this mode with an injected provider ( Mist , Metamask , ... ) or with JavaScript VM .
Remix never persist any passphrase . `
var warnPersonalMode = yo ` <i title= ${ warnText } class=" ${ css . icon } fa fa-exclamation-triangle" aria-hidden="true"></i> `
// Gist settings
var gistAccessToken = yo ` <input id="gistaccesstoken" type="password"> `
var token = appAPI . config . get ( 'settings/gist-access-token' )
if ( token ) gistAccessToken . value = token
var gistAddToken = yo ` <input class=" ${ css . savegisttoken } " id="savegisttoken" onclick= ${ ( ) => { appAPI . config . set ( 'settings/gist-access-token' , gistAccessToken . value ) ; tooltip ( 'Access token saved' ) } } value="Save" type="button"> `
var gistRemoveToken = yo ` <input id="removegisttoken" onclick= ${ ( ) => { gistAccessToken . value = '' ; appAPI . config . set ( 'settings/gist-access-token' , '' ) ; tooltip ( 'Access token removed' ) } } value="Remove" type="button"> `
var el = yo `
< div class = "${css.settingsTabView} " id = "settingsView" >
Remix never persist any passphrase . ` .split(' \n ').map(s => s.trim()).join(' ')
self . _view . warnPersonalMode = yo ` <i title= ${ warnText } class=" ${ css . icon } fa fa-exclamation-triangle" aria-hidden="true"></i> `
self . _view . pluginInput = yo ` <textarea rows="4" cols="70" id="plugininput" type="text" class=" ${ css . pluginTextArea } " ></textarea> `
self . _view . versionSelector = yo `
< select onchange = $ { onchangeLoadVersion } class = "${css.select}" id = "versionSelector" disabled >
< option disabled selected > Select new compiler version < / o p t i o n >
< / s e l e c t > `
if ( self . data . allversions && self . data . selectedVersion ) self . _updateVersionSelector ( )
self . _view . version = yo ` <span id="version"></span> `
self . _view . theme . light = yo ` <input onchange= ${ onswitch2lightTheme } class=" ${ css . col1 } " name="theme" id="themeLight" type="radio"> `
self . _view . theme . dark = yo ` <input onchange= ${ onswitch2darkTheme } class=" ${ css . col1 } " name="theme" id="themeDark" type="radio"> `
self . _view . theme [ self . data . currentTheme ] . setAttribute ( 'checked' , 'checked' )
self . _view . config . solidity = yo `
< div class = "${css.info}" >
< div class = $ { css . title } > Solidity version < / d i v >
< span > Current version : < / s p a n > < s p a n i d = " v e r s i o n " > < / s p a n >
< span > Current version : < / s p a n > $ { s e l f . _ v i e w . v e r s i o n }
< div class = "${css.crow}" >
< select class = "${css.select}" id = "versionSelector" > < / s e l e c t >
< / d i v >
$ { self . _view . versionSelector }
< / d i v >
< / d i v > `
self . _view . config . general = yo `
< div class = "${css.info}" >
< div class = $ { css . title } > General settings < / d i v >
< div class = "${css.crow}" >
< div > $ { optionVM } < / d i v >
< div > $ { self . _view . optionVM } < / d i v >
< span class = "${css.checkboxText}" > Always use Ethereum VM at Load < / s p a n >
< / d i v >
< div class = "${css.crow}" >
@ -52,12 +100,12 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
< span class = "${css.checkboxText}" > Text Wrap < / s p a n >
< / d i v >
< div class = "${css.crow}" >
< div > < input id = "optimize" type = "checkbox" > < / d i v >
< div > $ { self . _view . optimize } < / d i v >
< span class = "${css.checkboxText}" > Enable Optimization < / s p a n >
< / d i v >
< div class = "${css.crow}" >
< div > $ { personal } < / d i v >
< span class = "${css.checkboxText}" > Enable Personal Mode $ { warnPersonalMode } < / s p a n >
< div > $ { self . _view . personal } > < / d i v >
< span class = "${css.checkboxText}" > Enable Personal Mode $ { self . _view . warnPersonalMode } > < / s p a n >
< / d i v >
< / d i v >
< div class = "${css.info}" >
@ -66,9 +114,10 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
< div class = "${css.crowNoFlex}" > Go to github token page ( link below ) to create a new token and save it in Remix . Make sure this token has only 'create gist' permission . < / d i v >
< div class = "${css.crowNoFlex}" > < a target = "_blank" href = "https://github.com/settings/tokens" > https : //github.com/settings/tokens</a></div>
< div class = "${css.crowNoFlex}" >
< div class = "${css.checkboxText}" > $ { gistAccessToken } $ { copyToClipboard ( ( ) => appAPI . config . get ( 'settings/gist-access-token' ) ) } $ { gistAddToken } $ { gistRemoveToken } < / d i v >
< / d i v >
$ { self . _view . gistToken }
< / d i v >
< / d i v > `
self . _view . config . themes = yo `
< div class = "${css.info}" >
< div class = $ { css . title } > Themes < / d i v >
< div class = $ { css . attention } >
@ -76,14 +125,15 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
< span > Selecting a theme will trigger a page reload < / s p a n >
< / d i v >
< div class = "${css.crow}" >
< input class = "${css.col1}" name = "theme" id = "themeLight" type = "checkbox" >
$ { self . _view . theme . light }
< label for = "themeLight" > Light Theme < / l a b e l >
< / d i v >
< div class = "${css.crow}" >
< input class = "${css.col1}" name = "theme" id = "themeDark" type = "checkbox" >
$ { self . _view . theme . dark }
< label for = "themeDark" > Dark Theme < / l a b e l >
< / d i v >
< / d i v >
< / d i v > `
self . _view . config . plugin = yo `
< div class = "${css.info}" >
< div class = $ { css . title } > Plugin < / d i v >
< div class = "${css.crowNoFlex}" >
@ -92,11 +142,12 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
< span > Do not use this alpha feature if you are not sure what you are doing ! < / s p a n >
< / d i v >
< div >
< textarea rows = "4" cols = "70" id = "plugininput" type = "text" class = "${css.pluginTextArea}" > < / t e x t a r e a >
< input onclick = $ { loadPlugin } type = "button" value = "Load" class = "${css.pluginLoad}" >
< / d i v >
$ { self . _view . pluginInput }
< input onclick = $ { onloadPlugin } type = "button" value = "Load" class = "${css.pluginLoad}" >
< / d i v >
< / d i v >
< / d i v > `
self . _view . config . remixd = yo `
< div class = "${css.info}" >
< div class = $ { css . title } > Remixd < / d i v >
< div class = "${css.crow}" >
@ -107,7 +158,8 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
< div class = "${css.crow}" > < a target = "_blank" href = "https://github.com/ethereum/remixd" > https : //github.com/ethereum/remixd</a></div>
< div class = "${css.crow}" > < a target = "_blank" href = "http://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html" > http : //remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html</a></div>
< div class = "${css.crow}" > Installation : < pre class = $ { css . remixdinstallation } > npm install remixd - g < / p r e > < / d i v >
< / d i v >
< / d i v > `
self . _view . config . localremixd = yo `
< div class = "${css.info}" >
< div class = $ { css . title } > Running Remix locally < / d i v >
< div class = "${css.crow}" >
@ -119,163 +171,180 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
as an electron app :
< / d i v >
< a target = "_blank" href = "https://github.com/horizon-games/remix-app" > https : //github.com/horizon-games/remix-app</a>
< / d i v >
< / d i v >
`
function loadPlugin ( ) {
var json = el . querySelector ( '#plugininput' ) . value
< / d i v > `
self . _view . el = yo `
< div class = "${css.settingsTabView} " id = "settingsView" >
$ { self . _view . config . solidity }
$ { self . _view . config . general }
$ { self . _view . config . themes }
$ { self . _view . config . plugin }
$ { self . _view . config . remixd }
$ { self . _view . config . localremixd }
< / d i v > `
function onchangeOption ( event ) {
self . _api . config . set ( 'settings/always-use-vm' , ! self . _api . config . get ( 'settings/always-use-vm' ) )
}
function onloadPlugin ( event ) {
try {
json = JSON . parse ( json )
var json = JSON . parse ( self . _view . pluginInput . value )
} catch ( e ) {
modal . alert ( 'cannot parse the plugin definition to JSON' )
return
}
appEvents . rhp . trigger ( 'plugin-loadRequest' , [ json ] )
}
appEvents . compiler . register ( 'compilerLoaded' , ( version ) => {
setVersionText ( version , el )
} )
optionVM . checked = appAPI . config . get ( 'settings/always-use-vm' ) || false
optionVM . addEventListener ( 'change' , event => {
appAPI . config . set ( 'settings/always-use-vm' , ! appAPI . config . get ( 'settings/always-use-vm' ) )
} )
personal . checked = appAPI . config . get ( 'settings/personal-mode' ) || false
personal . addEventListener ( 'change' , event => {
appAPI . config . set ( 'settings/personal-mode' , ! appAPI . config . get ( 'settings/personal-mode' ) )
} )
var optimize = el . querySelector ( '#optimize' )
if ( ( queryParams . get ( ) . optimize === 'true' ) ) {
optimize . setAttribute ( 'checked' , true )
appAPI . setOptimize ( true , false )
} else {
queryParams . update ( { optimize : false } )
appAPI . setOptimize ( false , false )
return modal . alert ( 'cannot parse the plugin definition to JSON' )
}
optimize . addEventListener ( 'change' , function ( ) {
var optimize = this . checked
queryParams . update ( { optimize : optimize } )
appAPI . setOptimize ( optimize , true )
} )
var themeStorage = new Storage ( 'style:' )
var currTheme = themeStorage . get ( 'theme' )
var themeDark = el . querySelector ( '#themeDark' )
var themeLight = el . querySelector ( '#themeLight' )
if ( currTheme === 'dark' ) {
themeDark . setAttribute ( 'checked' , 'checked' )
} else {
themeLight . setAttribute ( 'checked' , 'checked' )
// @TODO: BAD! REFACTOR: no module should trigger events of another modules emitter
self . _events . rhp . trigger ( 'plugin-loadRequest' , [ json ] )
}
themeDark . addEventListener ( 'change' , function ( ) {
console . log ( 'change dark theme' )
function onswitch2darkTheme ( event ) {
styleGuide . switchTheme ( 'dark' )
window . location . reload ( )
} )
themeLight . addEventListener ( 'change' , function ( ) {
console . log ( 'change to light theme' )
}
function onswitch2lightTheme ( event ) {
styleGuide . switchTheme ( 'light' )
window . location . reload ( )
} )
// ----------------- version selector-------------
// clear and disable the version selector
var versionSelector = el . querySelector ( '#versionSelector' )
versionSelector . innerHTML = ''
versionSelector . setAttribute ( 'disabled' , true )
// load the new version upon change
versionSelector . addEventListener ( 'change' , function ( ) {
loadVersion ( versionSelector . value , queryParams , opts . compiler , el )
} )
var header = new Option ( 'Select new compiler version' )
header . disabled = true
header . selected = true
versionSelector . appendChild ( header )
request . get ( {
url : 'https://solc-bin.ethereum.org/bin/list.json' ,
json : true
} , ( error , response , data ) => {
if ( error || ! data ) {
tooltip ( 'Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload.' )
// loading failed for some reason, fall back to local compiler
versionSelector . append ( new Option ( 'latest local version' , 'builtin' ) )
loadVersion ( 'builtin' , queryParams , opts . compiler , el )
return
}
// populate version dropdown with all available compiler versions (descending order)
$ . each ( data . builds . slice ( ) . reverse ( ) , function ( i , build ) {
versionSelector . appendChild ( new Option ( build . longVersion , build . path ) )
} )
versionSelector . removeAttribute ( 'disabled' )
// always include the local version
versionSelector . appendChild ( new Option ( 'latest local version' , 'builtin' ) )
// find latest release
var selectedVersion = data . releases [ data . latestRelease ]
// override with the requested version
if ( queryParams . get ( ) . version ) {
selectedVersion = queryParams . get ( ) . version
function onchangeOptimize ( event ) {
self . data . optimize = ! ! self . _view . optimize . checked
self . _components . queryParams . update ( { optimize : self . data . optimize } )
self . _api . setOptimize ( self . data . optimize , true )
}
loadVersion ( selectedVersion , queryParams , opts . compiler , el )
} )
return { render ( ) { return el } }
}
function setVersionText ( text , el ) {
el . querySelector ( '#version' ) . innerText = text
}
function loadVersion ( version , queryParams , compiler , el ) {
queryParams . update ( { version : version } )
function onchangeLoadVersion ( event ) {
self . data . selectedVersion = self . _view . versionSelector . value
self . _updateVersionSelector ( )
}
function onchangePersonal ( event ) {
self . _api . config . set ( 'settings/personal-mode' , ! self . _api . config . get ( 'settings/personal-mode' ) )
}
return self . _view . el
}
setVersionText ( text ) {
const self = this
self . data . version = text
if ( self . _view . version ) self . _view . version . innerText = text
}
_updateVersionSelector ( ) {
const self = this
self . _view . versionSelector . innerHTML = ''
self . _view . versionSelector . appendChild ( yo ` <option disabled selected>Select new compiler version</option> ` )
self . data . allversions . forEach ( build => self . _view . versionSelector . appendChild ( yo ` <option value= ${ build . path } > ${ build . longVersion } </option> ` ) )
self . _view . versionSelector . removeAttribute ( 'disabled' )
self . _components . queryParams . update ( { version : self . data . selectedVersion } )
var url
if ( version === 'builtin' ) {
if ( self . data . selectedVersion === 'builtin' ) {
var location = window . document . location
location = location . protocol + '//' + location . host + '/' + location . pathname
if ( location . endsWith ( 'index.html' ) ) {
location = location . substring ( 0 , location . length - 10 )
}
if ( ! location . endsWith ( '/' ) ) {
location += '/'
}
if ( location . endsWith ( 'index.html' ) ) location = location . substring ( 0 , location . length - 10 )
if ( ! location . endsWith ( '/' ) ) location += '/'
url = location + 'soljson.js'
} else {
if ( version . indexOf ( 'soljson' ) !== 0 || helper . checkSpecialChars ( version ) ) {
console . log ( 'loading ' + version + ' not allowed' )
return
if ( self . data . selectedVersion . indexOf ( 'soljson' ) !== 0 || helper . checkSpecialChars ( self . data . selectedVersion ) ) {
return console . log ( 'loading ' + self . data . selectedVersion + ' not allowed' )
}
url = 'https://solc-bin. ethereum.or g/bin/' + v ersion
url = 'https://ethereum.github.io/solc-bin /bin/' + self . data . selectedV ersion
}
var isFirefox = typeof InstallTrigger !== 'undefined'
if ( document . location . protocol !== 'file:' && Worker !== undefined && isFirefox ) {
// Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case.
compiler . loadVersion ( true , url )
setVersionText ( '(loading using worker)' , el )
self . _opts . compiler . loadVersion ( true , url )
self . setVersionText ( '(loading using worker)' )
} else {
self . _opts . compiler . loadVersion ( false , url )
self . setVersionText ( '(loading)' )
}
}
fetchAllVersion ( callback ) {
var self = this
minixhr ( 'https://ethereum.github.io/solc-bin/bin/list.json' , function ( json , event ) {
// @TODO: optimise and cache results to improve app loading times
var allversions , selectedVersion
if ( event . type !== 'error' ) {
try {
const data = JSON . parse ( json )
allversions = data . builds . slice ( ) . reverse ( )
selectedVersion = data . releases [ data . latestRelease ]
if ( self . _components . queryParams . get ( ) . version ) selectedVersion = self . _components . queryParams . get ( ) . version
} catch ( e ) {
tooltip ( 'Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload.' )
}
} else {
compiler . loadVersion ( false , url )
setVersionText ( '(loading)' , el )
allversions = [ { path : 'builtin' , longVersion : 'latest local version' } ]
selectedVersion = 'builtin'
}
callback ( allversions , selectedVersion )
} )
}
}
module . exports = SettingsTab
const css = csjs `
. settingsTabView {
padding : 2 % ;
display : flex ;
}
. info {
$ { styles . rightPanel . settingsTab . box _SolidityVersionInfo }
margin - bottom : 1 em ;
word - break : break - word ;
}
. title {
font - size : 1.1 em ;
font - weight : bold ;
margin - bottom : 1 em ;
}
. crow {
display : flex ;
overflow : auto ;
clear : both ;
padding : . 2 em ;
}
. checkboxText {
font - weight : normal ;
}
. crow label {
cursor : pointer ;
}
. crowNoFlex {
overflow : auto ;
clear : both ;
}
. attention {
margin - bottom : 1 em ;
padding : . 5 em ;
font - weight : bold ;
}
. select {
font - weight : bold ;
margin - top : 1 em ;
$ { styles . rightPanel . settingsTab . dropdown _SelectCompiler }
}
. heading {
margin - bottom : 0 ;
}
. explaination {
margin - top : 3 px ;
margin - bottom : 3 px ;
}
input {
margin - right : 5 px ;
cursor : pointer ;
}
input [ type = radio ] {
margin - top : 2 px ;
}
. pluginTextArea {
font - family : unset ;
}
. pluginLoad {
vertical - align : top ;
}
i . warnIt {
color : $ { styles . appProperties . warningText _Color } ;
}
. icon {
margin - right : . 5 em ;
}
. remixdinstallation {
padding : 3 px ;
border - radius : 2 px ;
margin - left : 5 px ;
}
`