refactor settings tab

pull/1/head
serapath 7 years ago committed by yann300
parent 000c6b1231
commit a9b2e59824
  1. 489
      src/app/tabs/settings-tab.js

@ -1,74 +1,123 @@
/* global Option, Worker */ /* global Worker */
var $ = require('jquery')
var yo = require('yo-yo') var yo = require('yo-yo')
var request = require('request') var csjs = require('csjs-inject')
var QueryParams = require('../../lib/query-params') var minixhr = require('minixhr')
var remixLib = require('remix-lib') var remixLib = require('remix-lib')
var Storage = remixLib.Storage var QueryParams = require('../../lib/query-params')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var helper = require('../../lib/helper') var helper = require('../../lib/helper')
var modal = require('../ui/modal-dialog-custom') var modal = require('../ui/modal-dialog-custom')
var tooltip = require('../ui/tooltip') var tooltip = require('../ui/tooltip')
var copyToClipboard = require('../ui/copy-to-clipboard') var copyToClipboard = require('../ui/copy-to-clipboard')
var styleGuide = require('../ui/styles-guide/theme-chooser')
var css = require('./styles/settings-tab-styles') var styles = styleGuide.chooser()
var Storage = remixLib.Storage
function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) { var EventManager = remixLib.EventManager
var queryParams = new QueryParams()
module.exports = class SettingsTab {
var optionVM = yo`<input id="alwaysUseVM" type="checkbox">` constructor (api = {}, events = {}, opts = {}) {
var personal = yo`<input id="personal" type="checkbox">` const self = this
var warnText = `Transaction sent over Web3 will use the web3.personal API - be sure the endpoint is opened before enabling it. self._opts = opts
This mode allows to provide the passphrase in the Remix interface without having to unlock the account. self._api = api
Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...). self._events = events
It is not recommended (and also most likely not relevant) to use this mode with an injected provider (Mist, Metamask, ...) or with JavaScript VM. self._components = {}
Remix never persist any passphrase.` self._view = { /* eslint-disable */
var warnPersonalMode = yo`<i title=${warnText} class="${css.icon} fa fa-exclamation-triangle" aria-hidden="true"></i>` el: null,
optionVM: null, personal: null, optimize: null, warnPersonalMode: null,
// Gist settings pluginInput: null, versionSelector: null, version: null,
var gistAccessToken = yo`<input id="gistaccesstoken" type="password">` theme: { dark: null, light: null },
var token = appAPI.config.get('settings/gist-access-token') config: {
if (token) gistAccessToken.value = token solidity: null, general: null, themes: null,
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">` plugin: null, remixd: null, localremixd: null
var gistRemoveToken = yo`<input id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; appAPI.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">` }
} /* eslint-enable */
var el = yo` self.data = { allversions: null, selectedVersion: null }
<div class="${css.settingsTabView} "id="settingsView"> 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
// 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.`.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</option>
</select>`
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.info}">
<div class=${css.title}>Solidity version</div> <div class=${css.title}>Solidity version</div>
<span>Current version:</span> <span id="version"></span> <span>Current version:</span> ${self._view.version}
<div class="${css.crow}"> <div class="${css.crow}">
<select class="${css.select}" id="versionSelector"></select> ${self._view.versionSelector}
</div> </div>
</div> </div>`
self._view.config.general = yo`
<div class="${css.info}"> <div class="${css.info}">
<div class=${css.title}>General settings</div> <div class=${css.title}>General settings</div>
<div class="${css.crow}"> <div class="${css.crow}">
<div>${optionVM}</div> <div>${self._view.optionVM}</div>
<span class="${css.checkboxText}">Always use Ethereum VM at Load</span> <span class="${css.checkboxText}">Always use Ethereum VM at Load</span>
</div> </div>
<div class="${css.crow}"> <div class="${css.crow}">
<div><input id="editorWrap" type="checkbox"></div> <div><input id="editorWrap" type="checkbox"></div>
<span class="${css.checkboxText}">Text Wrap</span> <span class="${css.checkboxText}">Text Wrap</span>
</div> </div>
<div class="${css.crow}"> <div class="${css.crow}">
<div><input id="optimize" type="checkbox"></div> <div>${self._view.optimize}</div>
<span class="${css.checkboxText}">Enable Optimization</span> <span class="${css.checkboxText}">Enable Optimization</span>
</div> </div>
<div class="${css.crow}"> <div class="${css.crow}">
<div>${personal}</div> <div>${self._view.personal}></div>
<span class="${css.checkboxText}">Enable Personal Mode ${warnPersonalMode}</span> <span class="${css.checkboxText}">Enable Personal Mode ${self._view.warnPersonalMode}></span>
</div>
</div> </div>
</div>
<div class="${css.info}"> <div class="${css.info}">
<div class=${css.title}>Gist Access Token</div> <div class=${css.title}>Gist Access Token</div>
<div class="${css.crowNoFlex}">Manage the access token used to publish to Gist.</div> <div class="${css.crowNoFlex}">Manage the access token used to publish to Gist.</div>
<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.</div> <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.</div>
<div class="${css.crowNoFlex}"><a target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></div> <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.crowNoFlex}">
<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => appAPI.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div> ${self._view.gistToken}
</div> </div>
</div> </div>`
self._view.config.themes = yo`
<div class="${css.info}"> <div class="${css.info}">
<div class=${css.title}>Themes</div> <div class=${css.title}>Themes</div>
<div class=${css.attention}> <div class=${css.attention}>
@ -76,14 +125,15 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
<span>Selecting a theme will trigger a page reload</span> <span>Selecting a theme will trigger a page reload</span>
</div> </div>
<div class="${css.crow}"> <div class="${css.crow}">
<input class="${css.col1}" name="theme" id="themeLight" type="checkbox"> ${self._view.theme.light}
<label for="themeLight">Light Theme</label> <label for="themeLight">Light Theme</label>
</div> </div>
<div class="${css.crow}"> <div class="${css.crow}">
<input class="${css.col1}" name="theme" id="themeDark" type="checkbox"> ${self._view.theme.dark}
<label for="themeDark">Dark Theme</label> <label for="themeDark">Dark Theme</label>
</div> </div>
</div> </div>`
self._view.config.plugin = yo`
<div class="${css.info}"> <div class="${css.info}">
<div class=${css.title}>Plugin</div> <div class=${css.title}>Plugin</div>
<div class="${css.crowNoFlex}"> <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!</span> <span> Do not use this alpha feature if you are not sure what you are doing!</span>
</div> </div>
<div> <div>
<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea> ${self._view.pluginInput}
<input onclick=${loadPlugin} type="button" value="Load" class="${css.pluginLoad}"> <input onclick=${onloadPlugin} type="button" value="Load" class="${css.pluginLoad}">
</div> </div>
</div> </div>
</div> </div>`
self._view.config.remixd = yo`
<div class="${css.info}"> <div class="${css.info}">
<div class=${css.title}>Remixd</div> <div class=${css.title}>Remixd</div>
<div class="${css.crow}"> <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="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}"><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</pre></div> <div class="${css.crow}">Installation: <pre class=${css.remixdinstallation}>npm install remixd -g</pre></div>
</div> </div>`
self._view.config.localremixd = yo`
<div class="${css.info}"> <div class="${css.info}">
<div class=${css.title}>Running Remix locally</div> <div class=${css.title}>Running Remix locally</div>
<div class="${css.crow}"> <div class="${css.crow}">
@ -116,166 +168,183 @@ function SettingsTab (appAPI = {}, appEvents = {}, opts = {}) {
<a target="_blank" href="https://www.npmjs.com/package/remix-ide">https://www.npmjs.com/package/remix-ide</a> <a target="_blank" href="https://www.npmjs.com/package/remix-ide">https://www.npmjs.com/package/remix-ide</a>
<pre class=${css.remixdinstallation}>npm install remix-ide -g</pre> <pre class=${css.remixdinstallation}>npm install remix-ide -g</pre>
<div class="${css.crow}"> <div class="${css.crow}">
as an electron app: as an electron app:
</div> </div>
<a target="_blank" href="https://github.com/horizon-games/remix-app">https://github.com/horizon-games/remix-app</a> <a target="_blank" href="https://github.com/horizon-games/remix-app">https://github.com/horizon-games/remix-app</a>
</div> </div>`
</div> self._view.el = yo`
` <div class="${css.settingsTabView} "id="settingsView">
${self._view.config.solidity}
function loadPlugin () { ${self._view.config.general}
var json = el.querySelector('#plugininput').value ${self._view.config.themes}
try { ${self._view.config.plugin}
json = JSON.parse(json) ${self._view.config.remixd}
} catch (e) { ${self._view.config.localremixd}
modal.alert('cannot parse the plugin definition to JSON') </div>`
return function onchangeOption (event) {
self._api.config.set('settings/always-use-vm', !self._api.config.get('settings/always-use-vm'))
} }
appEvents.rhp.trigger('plugin-loadRequest', [json]) function onloadPlugin (event) {
} try {
var json = JSON.parse(self._view.pluginInput.value)
appEvents.compiler.register('compilerLoaded', (version) => { } catch (e) {
setVersionText(version, el) return modal.alert('cannot parse the plugin definition to JSON')
}) }
// @TODO: BAD! REFACTOR: no module should trigger events of another modules emitter
optionVM.checked = appAPI.config.get('settings/always-use-vm') || false self._events.rhp.trigger('plugin-loadRequest', [json])
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)
}
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')
}
themeDark.addEventListener('change', function () {
console.log('change dark theme')
styleGuide.switchTheme('dark')
window.location.reload()
})
themeLight.addEventListener('change', function () {
console.log('change to light theme')
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
} }
function onswitch2darkTheme (event) {
// populate version dropdown with all available compiler versions (descending order) styleGuide.switchTheme('dark')
$.each(data.builds.slice().reverse(), function (i, build) { window.location.reload()
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 onswitch2lightTheme (event) {
loadVersion(selectedVersion, queryParams, opts.compiler, el) styleGuide.switchTheme('light')
}) window.location.reload()
return { render () { return el } }
}
function setVersionText (text, el) {
el.querySelector('#version').innerText = text
}
function loadVersion (version, queryParams, compiler, el) {
queryParams.update({ version: version })
var url
if (version === '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('/')) { function onchangeOptimize (event) {
location += '/' self.data.optimize = !!self._view.optimize.checked
self._components.queryParams.update({ optimize: self.data.optimize })
self._api.setOptimize(self.data.optimize, true)
} }
function onchangeLoadVersion (event) {
url = location + 'soljson.js' self.data.selectedVersion = self._view.versionSelector.value
} else { self._updateVersionSelector()
if (version.indexOf('soljson') !== 0 || helper.checkSpecialChars(version)) { }
console.log('loading ' + version + ' not allowed') function onchangePersonal (event) {
return 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 (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 += '/'
url = location + 'soljson.js'
} else {
if (self.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(self.data.selectedVersion)) {
return console.log('loading ' + self.data.selectedVersion + ' not allowed')
}
url = 'https://ethereum.github.io/solc-bin/bin/' + self.data.selectedVersion
}
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.
self._opts.compiler.loadVersion(true, url)
self.setVersionText('(loading using worker)')
} else {
self._opts.compiler.loadVersion(false, url)
self.setVersionText('(loading)')
} }
url = 'https://solc-bin.ethereum.org/bin/' + version
} }
var isFirefox = typeof InstallTrigger !== 'undefined' fetchAllVersion (callback) {
if (document.location.protocol !== 'file:' && Worker !== undefined && isFirefox) { var self = this
// Workers cannot load js on "file:"-URLs and we get a minixhr('https://ethereum.github.io/solc-bin/bin/list.json', function (json, event) {
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium, // @TODO: optimise and cache results to improve app loading times
// resort to non-worker version in that case. var allversions, selectedVersion
compiler.loadVersion(true, url) if (event.type !== 'error') {
setVersionText('(loading using worker)', el) try {
} else { const data = JSON.parse(json)
compiler.loadVersion(false, url) allversions = data.builds.slice().reverse()
setVersionText('(loading)', el) 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 {
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: 1em;
word-break: break-word;
}
.title {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 1em;
}
.crow {
display: flex;
overflow: auto;
clear: both;
padding: .2em;
}
.checkboxText {
font-weight: normal;
}
.crow label {
cursor:pointer;
}
.crowNoFlex {
overflow: auto;
clear: both;
}
.attention {
margin-bottom: 1em;
padding: .5em;
font-weight: bold;
}
.select {
font-weight: bold;
margin-top: 1em;
${styles.rightPanel.settingsTab.dropdown_SelectCompiler}
}
.heading {
margin-bottom: 0;
}
.explaination {
margin-top: 3px;
margin-bottom: 3px;
}
input {
margin-right: 5px;
cursor: pointer;
}
input[type=radio] {
margin-top: 2px;
}
.pluginTextArea {
font-family: unset;
}
.pluginLoad {
vertical-align: top;
}
i.warnIt {
color: ${styles.appProperties.warningText_Color};
}
.icon {
margin-right: .5em;
}
.remixdinstallation {
padding: 3px;
border-radius: 2px;
margin-left: 5px;
}
`

Loading…
Cancel
Save