Merge branch 'master' into localstorage

pull/2137/head
bunsenstraat 3 years ago committed by GitHub
commit 55b3adb678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      apps/remix-ide-e2e/src/tests/editor.test.ts
  2. 6
      apps/remix-ide/src/app/editor/editor.js
  3. 2
      apps/remix-ide/src/app/plugins/config.ts
  4. 1
      apps/remix-ide/src/remixAppManager.js
  5. 49
      libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts
  6. 59
      libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts
  7. 46
      libs/remix-core-plugin/src/lib/helpers/fetch-sourcify.ts
  8. 4
      libs/remix-solidity/src/compiler/compiler-input.ts
  9. 11
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  10. 5
      libs/remix-ui/editor/src/lib/remix-ui-editor.css
  11. 2
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  12. 3
      libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx
  13. 79
      libs/remix-ui/publish-to-storage/src/lib/publishOnSwarm.tsx
  14. 23
      libs/remix-ui/settings/src/lib/constants.ts
  15. 116
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  16. 14
      libs/remix-ui/settings/src/lib/settingsAction.ts
  17. 4
      libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx
  18. 2
      libs/remix-ui/solidity-unit-testing/src/lib/css/style.css
  19. 1021
      package-lock.json
  20. 3
      package.json

@ -92,13 +92,13 @@ module.exports = {
.executeScript('remix.exeCurrent()') .executeScript('remix.exeCurrent()')
.scrollToLine(32) .scrollToLine(32)
.waitForElementPresent('.highlightLine33', 60000) .waitForElementPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine33', 'background-color', 'rgb(52, 152, 219)') .checkElementStyle('.highlightLine33', 'background-color', 'rgb(44, 62, 80)')
.scrollToLine(40) .scrollToLine(40)
.waitForElementPresent('.highlightLine41', 60000) .waitForElementPresent('.highlightLine41', 60000)
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)') .checkElementStyle('.highlightLine41', 'background-color', 'rgb(44, 62, 80)')
.scrollToLine(50) .scrollToLine(50)
.waitForElementPresent('.highlightLine51', 60000) .waitForElementPresent('.highlightLine51', 60000)
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)') .checkElementStyle('.highlightLine51', 'background-color', 'rgb(44, 62, 80)')
}, },
'Should remove 1 highlight from source code #group1': '' + function (browser: NightwatchBrowser) { 'Should remove 1 highlight from source code #group1': '' + function (browser: NightwatchBrowser) {
@ -111,8 +111,8 @@ module.exports = {
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') .click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.waitForElementNotPresent('.highlightLine33', 60000) .waitForElementNotPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine41', 'background-color', 'rgb(52, 152, 219)') .checkElementStyle('.highlightLine41', 'background-color', 'rgb(44, 62, 80)')
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)') .checkElementStyle('.highlightLine51', 'background-color', 'rgb(44, 62, 80)')
}, },
'Should remove all highlights from source code #group1': function (browser: NightwatchBrowser) { 'Should remove all highlights from source code #group1': function (browser: NightwatchBrowser) {

@ -139,7 +139,11 @@ class Editor extends Plugin {
this.on('sidePanel', 'pluginDisabled', (name) => { this.on('sidePanel', 'pluginDisabled', (name) => {
this.clearAllDecorationsFor(name) this.clearAllDecorationsFor(name)
}) })
this.on('fileManager', 'fileClosed', (name) => {
if (name === this.currentFile) {
this.currentFile = null
}
})
this.on('theme', 'themeLoaded', (theme) => { this.on('theme', 'themeLoaded', (theme) => {
this.currentThemeType = theme.quality this.currentThemeType = theme.quality
this.renderComponent() this.renderComponent()

@ -18,7 +18,7 @@ export class ConfigPlugin extends Plugin {
const queryParams = new QueryParams() const queryParams = new QueryParams()
const params = queryParams.get() const params = queryParams.get()
const config = Registry.getInstance().get('config').api const config = Registry.getInstance().get('config').api
const param = params[name] ? params[name] : config.get(name) let param = params[name] || config.get(name) || config.get('settings/' + name)
if (param === 'true') return true if (param === 'true') return true
if (param === 'false') return false if (param === 'false') return false
return param return param

@ -132,7 +132,6 @@ export class RemixAppManager extends PluginManager {
} }
} }
return plugins.map(plugin => { return plugins.map(plugin => {
if (plugin.name === 'scriptRunner') plugin.url = 'https://script-runner.surge.sh'
return new IframePlugin(plugin) return new IframePlugin(plugin)
// return new IframeReactPlugin(plugin) // return new IframeReactPlugin(plugin)
}) })

@ -3,6 +3,8 @@ import { Plugin } from '@remixproject/engine'
import { compile } from '@remix-project/remix-solidity' import { compile } from '@remix-project/remix-solidity'
import { util } from '@remix-project/remix-lib' import { util } from '@remix-project/remix-lib'
import { toChecksumAddress } from 'ethereumjs-util' import { toChecksumAddress } from 'ethereumjs-util'
import { fetchContractFromEtherscan } from './helpers/fetch-etherscan'
import { fetchContractFromSourcify } from './helpers/fetch-sourcify'
const profile = { const profile = {
name: 'fetchAndCompile', name: 'fetchAndCompile',
@ -68,48 +70,31 @@ export class FetchAndCompile extends Plugin {
let data let data
try { try {
data = await this.call('sourcify', 'fetchByNetwork', contractAddress, network.id) data = await fetchContractFromSourcify(this, network, contractAddress, targetPath)
} catch (e) { } catch (e) {
this.call('notification', 'toast', e.message)
console.log(e) // and fallback to getting the compilation result from etherscan
}
if (!data) {
this.call('notification', 'toast', `contract ${contractAddress} not found in Sourcify, checking in Etherscan..`)
try {
data = await fetchContractFromEtherscan(this, network, contractAddress, targetPath)
} catch (e) {
this.call('notification', 'toast', e.message)
setTimeout(_ => this.emit('notFound', contractAddress), 0) // plugin framework returns a time out error although it actually didn't find the source... setTimeout(_ => this.emit('notFound', contractAddress), 0) // plugin framework returns a time out error although it actually didn't find the source...
this.unresolvedAddresses.push(contractAddress) this.unresolvedAddresses.push(contractAddress)
return localCompilation() return localCompilation()
} }
if (!data || !data.metadata) { }
if (!data) {
setTimeout(_ => this.emit('notFound', contractAddress), 0) setTimeout(_ => this.emit('notFound', contractAddress), 0)
this.unresolvedAddresses.push(contractAddress) this.unresolvedAddresses.push(contractAddress)
return localCompilation() return localCompilation()
} }
const { settings, compilationTargets } = data
// set the solidity contract code using metadata
await this.call('fileManager', 'setFile', `${targetPath}/${network.id}/${contractAddress}/metadata.json`, JSON.stringify(data.metadata, null, '\t'))
const compilationTargets = {}
for (let file in data.metadata.sources) {
const urls = data.metadata.sources[file].urls
for (const url of urls) {
if (url.includes('ipfs')) {
const stdUrl = `ipfs://${url.split('/')[2]}`
const source = await this.call('contentImport', 'resolve', stdUrl)
if (await this.call('contentImport', 'isExternalUrl', file)) {
// nothing to do, the compiler callback will handle those
} else {
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
const path = `${targetPath}/${network.id}/${contractAddress}/${file}`
await this.call('fileManager', 'setFile', path, source.content)
compilationTargets[path] = { content: source.content }
}
break
}
}
}
// compile
const settings = {
version: data.metadata.compiler.version,
language: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled,
runs: data.metadata.settings.optimizer.runs
}
try { try {
setTimeout(_ => this.emit('compiling', settings), 0) setTimeout(_ => this.emit('compiling', settings), 0)
const compData = await compile( const compData = await compile(

@ -0,0 +1,59 @@
export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath) => {
let data
const compilationTargets = {}
const etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token')
if (etherscanKey) {
const endpoint = network.id == 1 ? 'api.etherscan.io' : 'api-' + network.name + '.etherscan.io'
data = await fetch('https://' + endpoint + '/api?module=contract&action=getsourcecode&address=' + contractAddress + '&apikey=' + etherscanKey)
data = await data.json()
// etherscan api doc https://docs.etherscan.io/api-endpoints/contracts
if (data.message === 'OK' && data.status === "1") {
if (data.result.length) {
if (data.result[0].SourceCode === '') throw new Error('contract not verified')
if (data.result[0].SourceCode.startsWith('{')) {
data.result[0].SourceCode = JSON.parse(data.result[0].SourceCode.replace(/(?:\r\n|\r|\n)/g, '').replace(/^{{/,'{').replace(/}}$/,'}'))
}
}
} else throw new Error('unable to retrieve contract data ' + data.message)
} else throw new Error('unable to try fetching the source code from etherscan: etherscan access token not found. please go to the Remix settings page and provide an access token.')
if (!data || !data.result) {
return null
}
if (typeof data.result[0].SourceCode === 'string') {
const fileName = `${targetPath}/${network.id}/${contractAddress}/${data.result[0].ContractName}.sol`
await plugin.call('fileManager', 'setFile', fileName , data.result[0].SourceCode)
compilationTargets[fileName] = { content: data.result[0].SourceCode }
} else if (data.result[0].SourceCode && typeof data.result[0].SourceCode == 'object') {
const sources = data.result[0].SourceCode.sources
for (let [file, source] of Object.entries(sources)) { // eslint-disable-line
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
file = file.replace(/^\//g, '') // remove first slash.
if (await plugin.call('contentImport', 'isExternalUrl', file)) {
// nothing to do, the compiler callback will handle those
} else {
const path = `${targetPath}/${network.id}/${contractAddress}/${file}`
const content = (source as any).content
await plugin.call('fileManager', 'setFile', path, content)
compilationTargets[path] = { content }
}
}
}
let runs = 0
try {
runs = parseInt(data.result[0].Runs)
} catch (e) {}
const settings = {
version: data.result[0].CompilerVersion.replace(/^v/, ''),
language: 'Solidity',
evmVersion: data.result[0].EVMVersion.toLowerCase(),
optimize: data.result[0].OptimizationUsed === '1',
runs
}
return {
settings,
compilationTargets
}
}

@ -0,0 +1,46 @@
export const fetchContractFromSourcify = async (plugin, network, contractAddress, targetPath) => {
let data
const compilationTargets = {}
try {
data = await plugin.call('sourcify', 'fetchByNetwork', contractAddress, network.id)
} catch (e) {
console.log(e)
}
if (!data || !data.metadata) {
return null
}
// set the solidity contract code using metadata
await plugin.call('fileManager', 'setFile', `${targetPath}/${network.id}/${contractAddress}/metadata.json`, JSON.stringify(data.metadata, null, '\t'))
for (let file in data.metadata.sources) {
const urls = data.metadata.sources[file].urls
for (const url of urls) {
if (url.includes('ipfs')) {
const stdUrl = `ipfs://${url.split('/')[2]}`
const source = await plugin.call('contentImport', 'resolve', stdUrl)
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
if (await plugin.call('contentImport', 'isExternalUrl', file)) {
// nothing to do, the compiler callback will handle those
} else {
const path = `${targetPath}/${network.id}/${contractAddress}/${file}`
await plugin.call('fileManager', 'setFile', path, source.content)
compilationTargets[path] = { content: source.content }
}
break
}
}
}
const settings = {
version: data.metadata.compiler.version,
language: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled,
runs: data.metadata.settings.optimizer.runs
}
return {
settings,
compilationTargets
}
}

@ -21,8 +21,12 @@ export default (sources: Source, opts: CompilerInputOptions): string => {
} }
} }
if (opts.evmVersion) { if (opts.evmVersion) {
if (opts.evmVersion.toLowerCase() == 'default') {
opts.evmVersion = null
} else {
o.settings.evmVersion = opts.evmVersion o.settings.evmVersion = opts.evmVersion
} }
}
if (opts.language) { if (opts.language) {
o.language = opts.language o.language = opts.language
} }

@ -91,7 +91,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
if (!lineColumnPos) { if (!lineColumnPos) {
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
setState(prevState => { setState(prevState => {
return { ...prevState, sourceLocationStatus: 'Source location not available.' } return { ...prevState, sourceLocationStatus: 'Source location not available, neither in Sourcify nor in Etherscan. Please make sure the Etherscan api key is provided in the settings.' }
}) })
return return
} }
@ -334,6 +334,15 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
</div> </div>
<TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } /> <TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } />
{ state.debugging && state.sourceLocationStatus && <div className="text-warning"><i className="fas fa-exclamation-triangle" aria-hidden="true"></i> {state.sourceLocationStatus}</div> } { state.debugging && state.sourceLocationStatus && <div className="text-warning"><i className="fas fa-exclamation-triangle" aria-hidden="true"></i> {state.sourceLocationStatus}</div> }
{ !state.debugging &&
<div>
<i className="fas fa-info-triangle" aria-hidden="true"></i>
<span>
When Debugging with a transaction hash,
if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings.
For supported networks, please see: <a href="https://sourcify.dev" target="__blank" >https://sourcify.dev</a> & <a href="https://sourcify.dev" target="__blank">https://etherscan.io/contractsVerified</a>
</span>
</div> }
{ state.debugging && <StepManager stepManager={ stepManager } /> } { state.debugging && <StepManager stepManager={ stepManager } /> }
{ state.debugging && <VmDebuggerHead vmDebugger={ vmDebugger } /> } { state.debugging && <VmDebuggerHead vmDebugger={ vmDebugger } /> }
</div> </div>

@ -19,3 +19,8 @@
opacity: 1; opacity: 1;
position: absolute; position: absolute;
} }
.inline-class {
background: var(--primary) !important;
color: var(--text) !important;
}

@ -263,7 +263,7 @@ export const EditorUI = (props: EditorUIProps) => {
range: new monacoRef.current.Range(decoration.position.start.line + 1, decoration.position.start.column + 1, decoration.position.end.line + 1, decoration.position.end.column + 1), range: new monacoRef.current.Range(decoration.position.start.line + 1, decoration.position.start.column + 1, decoration.position.end.line + 1, decoration.position.end.column + 1),
options: { options: {
isWholeLine, isWholeLine,
inlineClassName: `alert-info border-0 highlightLine${decoration.position.start.line + 1}` inlineClassName: `inline-class border-0 selectionHighlight highlightLine${decoration.position.start.line + 1}`
} }
} }
} }

@ -34,7 +34,8 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => {
let parseError = err let parseError = err
try { try {
parseError = JSON.stringify(err) parseError = JSON.stringify(err)
} catch (e) {} } catch (e) {
}
modal('Swarm Publish Failed', publishMessageFailed(storage, parseError)) modal('Swarm Publish Failed', publishMessageFailed(storage, parseError))
} }
} else { } else {

@ -1,6 +1,12 @@
import swarm from 'swarmgw' import { Bee } from '@ethersphere/bee-js'
// eslint-disable-next-line no-unused-vars
import type { UploadResult } from '@ethersphere/bee-js'
const swarmgw = swarm() // public gateway node address
const publicBeeNode = new Bee('https://api.gateway.ethswarm.org/')
// on the public gateway the postage stamp id is not relevant, so we use all zeroes
const defaultPostageStampId = '0000000000000000000000000000000000000000000000000000000000000000'
export const publishToSwarm = async (contract, api) => { export const publishToSwarm = async (contract, api) => {
// gather list of files to publish // gather list of files to publish
@ -31,7 +37,7 @@ export const publishToSwarm = async (contract, api) => {
// TODO: refactor this with publishOnIpfs // TODO: refactor this with publishOnIpfs
if (metadata.sources[fileName].urls) { if (metadata.sources[fileName].urls) {
metadata.sources[fileName].urls.forEach(url => { metadata.sources[fileName].urls.forEach(url => {
if (url.includes('bzz')) hash = url.match('(bzzr|bzz-raw)://(.+)')[1] if (url.includes('bzz')) hash = url.match('bzz-raw://(.+)')[1]
}) })
} }
} catch (e) { } catch (e) {
@ -48,11 +54,24 @@ export const publishToSwarm = async (contract, api) => {
console.log(error) console.log(error)
}) })
})) }))
// publish the list of sources in order, fail if any failed
// the list of nodes to publish to
const beeNodes = [
publicBeeNode
]
// add custom private Bee node to the list
const postageStampId = api.config.get('settings/swarm-postage-stamp-id') || defaultPostageStampId
const privateBeeAddress = api.config.get('settings/swarm-private-bee-address')
if (privateBeeAddress) {
const privateBee = new Bee(privateBeeAddress)
beeNodes.push(privateBee)
}
// publish the list of sources in order, fail if any failed
await Promise.all(sources.map(async (item) => { await Promise.all(sources.map(async (item) => {
try { try {
const result = await swarmVerifiedPublish(item.content, item.hash) const result = await swarmVerifiedPublish(beeNodes, postageStampId, item.content, item.hash)
try { try {
item.hash = result.url.match('bzz-raw://(.+)')[1] item.hash = result.url.match('bzz-raw://(.+)')[1]
@ -64,13 +83,14 @@ export const publishToSwarm = async (contract, api) => {
// TODO this is a fix cause Solidity metadata does not contain the right swarm hash (poc 0.3) // TODO this is a fix cause Solidity metadata does not contain the right swarm hash (poc 0.3)
metadata.sources[item.filename].urls[0] = result.url metadata.sources[item.filename].urls[0] = result.url
} catch (error) { } catch (error) {
console.error(error)
throw new Error(error) throw new Error(error)
} }
})) }))
const metadataContent = JSON.stringify(metadata) const metadataContent = JSON.stringify(metadata)
try { try {
const result = await swarmVerifiedPublish(metadataContent, '') const result = await swarmVerifiedPublish(beeNodes, postageStampId, metadataContent, '')
try { try {
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1] contract.metadataHash = result.url.match('bzz-raw://(.+)')[1]
@ -86,22 +106,49 @@ export const publishToSwarm = async (contract, api) => {
output: result output: result
}) })
} catch (error) { } catch (error) {
console.error(error)
throw new Error(error) throw new Error(error)
} }
return { uploaded, item } return { uploaded, item }
} }
const swarmVerifiedPublish = async (content, expectedHash): Promise<Record<string, any>> => { const swarmVerifiedPublish = async (beeNodes: Bee[], postageStampId: string, content, expectedHash): Promise<Record<string, any>> => {
return new Promise((resolve, reject) => { try {
swarmgw.put(content, function (err, ret) { const results = await uploadToBeeNodes(beeNodes, postageStampId, content)
if (err) { const hash = hashFromResults(results)
reject(err)
} else if (expectedHash && ret !== expectedHash) { if (expectedHash && hash !== expectedHash) {
resolve({ message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret }) return { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + hash, hash }
} else { } else {
resolve({ message: 'ok', url: 'bzz-raw://' + ret, hash: ret }) return { message: 'ok', url: 'bzz-raw://' + hash, hash }
} }
}) } catch (error) {
}) throw new Error(error)
}
}
const hashFromResults = (results: UploadResult[]) => {
for (const result of results) {
if (result != null) {
return result.reference
}
}
throw new Error('no result')
}
const uploadToBee = async (bee: Bee, postageStampId: string, content) => {
try {
if (bee.url === publicBeeNode.url) {
postageStampId = defaultPostageStampId
}
return await bee.uploadData(postageStampId, content)
} catch {
// ignore errors for now
return null
}
}
const uploadToBeeNodes = (beeNodes: Bee[], postageBatchId: string, content) => {
return Promise.all(beeNodes.map(node => uploadToBee(node, postageBatchId, content)))
} }

@ -2,11 +2,34 @@ export const generateContractMetadataText = 'Generate contract metadata. Generat
export const textSecondary = 'text-secondary' export const textSecondary = 'text-secondary'
export const textDark = 'text-dark' export const textDark = 'text-dark'
export const warnText = 'Be sure the endpoint is opened before enabling it. \nThis mode allows a user to provide a 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, ...). Remix never persists any passphrase'.split('\n').map(s => s.trim()).join(' ') export const warnText = 'Be sure the endpoint is opened before enabling it. \nThis mode allows a user to provide a 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, ...). Remix never persists any passphrase'.split('\n').map(s => s.trim()).join(' ')
export const gitAccessTokenTitle = 'Github Access Token' export const gitAccessTokenTitle = 'Github Access Token'
export const gitAccessTokenText = 'Manage the access token used to publish to Gist and retrieve Github contents.' export const gitAccessTokenText = 'Manage the access token used to publish to Gist and retrieve Github contents.'
export const gitAccessTokenText2 = '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.' export const gitAccessTokenText2 = '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.'
export const gitAccessTokenLink = 'https://github.com/settings/tokens' export const gitAccessTokenLink = 'https://github.com/settings/tokens'
export const etherscanTokenTitle = 'EtherScan Access Token'
export const etherscanTokenLink = 'https://etherscan.io/myapikey'
export const etherscanAccessTokenText = 'Manage the api key used to interact with Etherscan.'
export const etherscanAccessTokenText2 = 'Go to Etherscan api key page (link below) to create a new api key and save it in Remix.'
export const ethereunVMText = 'Always use Javascript VM at load' export const ethereunVMText = 'Always use Javascript VM at load'
export const wordWrapText = 'Word wrap in editor' export const wordWrapText = 'Word wrap in editor'
export const enablePersonalModeText = ' Enable Personal Mode for web3 provider. Transaction sent over Web3 will use the web3.personal API.\n' export const enablePersonalModeText = ' Enable Personal Mode for web3 provider. Transaction sent over Web3 will use the web3.personal API.\n'
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about ' export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '
export const swarmSettingsTitle = 'Swarm Settings'
export const swarmSettingsText = 'Swarm Settings'
export const labels = {
'gist': {
'link': gitAccessTokenLink,
'title': gitAccessTokenTitle,
'message1': gitAccessTokenText,
'message2': gitAccessTokenText2,
'key': 'gist-access-token'
},
'etherscan': {
'link': etherscanTokenLink,
'title': etherscanTokenTitle,
'message1':etherscanAccessTokenText,
'message2':etherscanAccessTokenText2,
'key': 'etherscan-access-token'
}
}

@ -1,10 +1,10 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, generateContractMetadataText, gitAccessTokenLink, gitAccessTokenText, gitAccessTokenText2, gitAccessTokenTitle, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText } from './constants' import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle } from './constants'
import './remix-ui-settings.css' import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast } from './settingsAction' import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast } from './settingsAction'
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer' import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer'
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module' import { RemixUiThemeModule, ThemeModule} from '@remix-ui/theme-module'
@ -21,18 +21,38 @@ export interface RemixUiSettingsProps {
export const RemixUiSettings = (props: RemixUiSettingsProps) => { export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [, dispatch] = useReducer(settingReducer, initialState) const [, dispatch] = useReducer(settingReducer, initialState)
const [state, dispatchToast] = useReducer(toastReducer, toastInitialState) const [state, dispatchToast] = useReducer(toastReducer, toastInitialState)
const [tokenValue, setTokenValue] = useState('') const [tokenValue, setTokenValue] = useState({})
const [themeName, ] = useState('')
const [privateBeeAddress, setPrivateBeeAddress] = useState('')
const [postageStampId, setPostageStampId] = useState('')
useEffect(() => { useEffect(() => {
const token = props.config.get('settings/gist-access-token') const token = props.config.get('settings/' + labels['gist'].key)
if (token === undefined) { if (token === undefined) {
props.config.set('settings/generate-contract-metadata', true) props.config.set('settings/generate-contract-metadata', true)
dispatch({ type: 'contractMetadata', payload: { name: 'contractMetadata', isChecked: true, textClass: textDark } }) dispatch({ type: 'contractMetadata', payload: { name: 'contractMetadata', isChecked: true, textClass: textDark } })
} }
if (token) { if (token) {
setTokenValue(token) setTokenValue(prevState => {
return { ...prevState, gist: token }
})
} }
}, [state.message])
const etherscantoken = props.config.get('settings/' + labels['etherscan'].key)
if (etherscantoken) {
setTokenValue(prevState => {
return { ...prevState, etherscan: etherscantoken }
})
}
const configPrivateBeeAddress = props.config.get('settings/swarm-private-bee-address')
if (configPrivateBeeAddress) {
setPrivateBeeAddress(configPrivateBeeAddress)
}
const configPostageStampId = props.config.get('settings/swarm-postage-stamp-id')
if (configPostageStampId) {
setPostageStampId(configPostageStampId)
}
}, [themeName, state.message])
useEffect(() => { useEffect(() => {
if (props.useMatomoAnalytics !== null) useMatomoAnalytics(props.config, props.useMatomoAnalytics, dispatch) if (props.useMatomoAnalytics !== null) useMatomoAnalytics(props.config, props.useMatomoAnalytics, dispatch)
@ -114,47 +134,97 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
) )
} }
const saveToken = () => { // api key settings
saveTokenToast(props.config, dispatchToast, tokenValue) const saveToken = (type: string) => {
saveTokenToast(props.config, dispatchToast, tokenValue[type], labels[type].key)
} }
const removeToken = () => { const removeToken = (type: string) => {
setTokenValue('') setTokenValue(prevState => {
removeTokenToast(props.config, dispatchToast) return { ...prevState, [type]: ''}
})
removeTokenToast(props.config, dispatchToast, labels[type].key)
} }
const handleSaveTokenState = useCallback( const handleSaveTokenState = useCallback(
(event) => { (event, type) => {
setTokenValue(event.target.value) setTokenValue(prevState => {
return { ...prevState, [type]: event.target.value}
})
}, },
[tokenValue] [tokenValue]
) )
const gistToken = () => ( const token = (type: string) => (
<div className="border-top"> <div className="border-top">
<div className="card-body pt-3 pb-2"> <div className="card-body pt-3 pb-2">
<h6 className="card-title">{ gitAccessTokenTitle }</h6> <h6 className="card-title">{ labels[type].title }</h6>
<p className="mb-1">{ gitAccessTokenText }</p> <p className="mb-1">{ labels[type].message1 }</p>
<p className="">{ gitAccessTokenText2 }</p> <p className="">{ labels[type].message2 }</p>
<p className="mb-1"><a className="text-primary" target="_blank" href="https://github.com/settings/tokens">{ gitAccessTokenLink }</a></p> <p className="mb-1"><a className="text-primary" target="_blank" href={labels[type].link}>{ labels[type].link }</a></p>
<div className=""><label>TOKEN:</label> <div className=""><label>TOKEN:</label>
<div className="text-secondary mb-0 h6"> <div className="text-secondary mb-0 h6">
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={handleSaveTokenState} value={ tokenValue } /> <input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={(e) => handleSaveTokenState(e, type)} value={ tokenValue[type] } />
<div className="d-flex justify-content-end pt-2"> <div className="d-flex justify-content-end pt-2">
<CopyToClipboard content={tokenValue} data-id='copyToClipboardCopyIcon' /> <CopyToClipboard content={tokenValue[type]} data-id='copyToClipboardCopyIcon' />
<input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken()} value="Save" type="button" disabled={tokenValue === ''}></input> <input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken(type)} value="Save" type="button" disabled={tokenValue === ''}></input>
<button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onClick={() => removeToken()}>Remove</button> <button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onClick={() => removeToken(type)}>Remove</button>
</div> </div>
</div></div> </div></div>
</div> </div>
</div> </div>
) )
// swarm settings
const handleSavePrivateBeeAddress = useCallback(
(event) => {
setPrivateBeeAddress(event.target.value)
},
[privateBeeAddress]
)
const handleSavePostageStampId = useCallback(
(event) => {
setPostageStampId(event.target.value)
},
[postageStampId]
)
const saveSwarmSettings = () => {
saveSwarmSettingsToast(props.config, dispatchToast, privateBeeAddress, postageStampId)
}
const swarmSettings = () => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ swarmSettingsTitle }</h6>
<div className=""><label>PRIVATE BEE ADDRESS:</label>
<div className="text-secondary mb-0 h6">
<input id="swarmprivatebeeaddress" data-id="settingsPrivateBeeAddress" className="form-control" onChange={handleSavePrivateBeeAddress} value={ privateBeeAddress } />
</div>
</div>
<div className=""><label>POSTAGE STAMP ID:</label>
<div className="text-secondary mb-0 h6">
<input id="swarmpostagestamp" data-id="settingsPostageStampId" className="form-control" onChange={handleSavePostageStampId} value={ postageStampId } />
<div className="d-flex justify-content-end pt-2">
</div>
</div>
</div>
<div className="d-flex justify-content-end pt-2">
<input className="btn btn-sm btn-primary ml-2" id="saveswarmsettings" data-id="settingsTabSaveSwarmSettings" onClick={() => saveSwarmSettings()} value="Save" type="button" disabled={privateBeeAddress === ''}></input>
</div>
</div>
</div>
)
return ( return (
<div> <div>
{state.message ? <Toaster message= {state.message}/> : null} {state.message ? <Toaster message= {state.message}/> : null}
{generalConfig()} {generalConfig()}
{gistToken()} {token('gist')}
{token('etherscan')}
{swarmSettings()}
<RemixUiThemeModule themeModule={props._deps.themeModule} /> <RemixUiThemeModule themeModule={props._deps.themeModule} />
</div> </div>
) )

@ -41,12 +41,18 @@ export const useMatomoAnalytics = (config, checked, dispatch) => {
} }
} }
export const saveTokenToast = (config, dispatch, tokenValue) => { export const saveTokenToast = (config, dispatch, tokenValue, key) => {
config.set('settings/gist-access-token', tokenValue) config.set('settings/' + key, tokenValue)
dispatch({ type: 'save', payload: { message: 'Access token has been saved' } }) dispatch({ type: 'save', payload: { message: 'Access token has been saved' } })
} }
export const removeTokenToast = (config, dispatch) => { export const removeTokenToast = (config, dispatch, key) => {
config.set('settings/gist-access-token', '') config.set('settings/' + key, '')
dispatch({ type: 'removed', payload: { message: 'Access token removed' } }) dispatch({ type: 'removed', payload: { message: 'Access token removed' } })
} }
export const saveSwarmSettingsToast = (config, dispatch, privateBeeAddress, postageStampId) => {
config.set('settings/swarm-private-bee-address', privateBeeAddress)
config.set('settings/swarm-postage-stamp-id', postageStampId)
dispatch({ type: 'save', payload: { message: 'Swarm settings have been saved' } })
}

@ -193,6 +193,10 @@ export const ContractSelection = (props: ContractSelectionProps) => {
<span>Publish on Ipfs</span> <span>Publish on Ipfs</span>
<img id="ipfsLogo" className="remixui_storageLogo ml-2" src="assets/img/ipfs.webp" /> <img id="ipfsLogo" className="remixui_storageLogo ml-2" src="assets/img/ipfs.webp" />
</button> </button>
<button id="publishOnSwarm" className="btn btn-secondary btn-block" title="Publish on Swarm" onClick={() => { handlePublishToStorage('swarm') }}>
<span>Publish on Swarm</span>
<img id="swarmLogo" className="remixui_storageLogo ml-2" src="assets/img/swarm.webp" />
</button>
<button data-id="compilation-details" className="btn btn-secondary btn-block" title="Display Contract Details" onClick={() => { details() }}> <button data-id="compilation-details" className="btn btn-secondary btn-block" title="Display Contract Details" onClick={() => { details() }}>
Compilation Details Compilation Details
</button> </button>

@ -9,10 +9,8 @@
margin: 5%; margin: 5%;
max-height: 300px; max-height: 300px;
overflow-y: auto; overflow-y: auto;
} }
.container { .container {
margin: 2%;
padding-bottom: 5%; padding-bottom: 5%;
max-height: 300px; max-height: 300px;
overflow-y: auto; overflow-y: auto;

1021
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -149,6 +149,7 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@ethersphere/bee-js": "^3.2.0",
"@isomorphic-git/lightning-fs": "^4.4.1", "@isomorphic-git/lightning-fs": "^4.4.1",
"@monaco-editor/react": "^4.3.1", "@monaco-editor/react": "^4.3.1",
"@remixproject/engine": "^0.3.28", "@remixproject/engine": "^0.3.28",
@ -198,6 +199,7 @@
"selenium": "^2.20.0", "selenium": "^2.20.0",
"signale": "^1.4.0", "signale": "^1.4.0",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"swarmgw": "^0.3.1",
"time-stamp": "^2.2.0", "time-stamp": "^2.2.0",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"tslib": "^2.3.0", "tslib": "^2.3.0",
@ -313,7 +315,6 @@
"selenium-standalone": "^8.0.4", "selenium-standalone": "^8.0.4",
"semver": "^6.3.0", "semver": "^6.3.0",
"solc": "0.7.4", "solc": "0.7.4",
"swarmgw": "^0.3.1",
"tap-spec": "^5.0.0", "tap-spec": "^5.0.0",
"tape": "^4.13.3", "tape": "^4.13.3",
"terser-webpack-plugin": "^4.2.3", "terser-webpack-plugin": "^4.2.3",

Loading…
Cancel
Save