parent
6aad3b74ce
commit
8ae133e442
@ -0,0 +1,4 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,7 @@ |
||||
# remix-ui-publish-to-storage |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-publish-to-storage` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-ui-publish-to-storage'; |
@ -0,0 +1,138 @@ |
||||
import React, { useEffect, useState } from 'react' // eslint-disable-line
|
||||
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||
import { RemixUiPublishToStorageProps } from './types' |
||||
import { publishToIPFS } from './publishToIPFS' |
||||
import { publishToSwarm } from './publishOnSwarm' |
||||
|
||||
import './css/publish-to-storage.css' |
||||
|
||||
export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { |
||||
const { storage, fileProvider, fileManager, contract } = props |
||||
const [state, setState] = useState({ |
||||
modal: { |
||||
title: '', |
||||
message: null, |
||||
hide: true, |
||||
ok: { |
||||
label: '', |
||||
fn: null |
||||
}, |
||||
cancel: { |
||||
label: '', |
||||
fn: null |
||||
} |
||||
} |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
const storageService = async () => { |
||||
if ((contract.metadata === undefined || contract.metadata.length === 0)) { |
||||
modal('Publish To Storage', 'This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.', { |
||||
label: 'OK', |
||||
fn: () => {} |
||||
}, null) |
||||
} else { |
||||
if (storage === 'swarm') { |
||||
try { |
||||
const result = await publishToSwarm(contract, fileManager) |
||||
|
||||
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded), { |
||||
label: 'OK', |
||||
fn: () => {} |
||||
}, null) |
||||
// triggered each time there's a new verified publish (means hash correspond)
|
||||
fileProvider.addExternal('swarm/' + result.item.hash, result.item.content) |
||||
} catch (err) { |
||||
try { |
||||
err = JSON.stringify(err) |
||||
} catch (e) {} |
||||
modal(`Swarm Publish Failed`, publishMessageFailed(storage, err), { |
||||
label: 'OK', |
||||
fn: () => {} |
||||
}, null) |
||||
} |
||||
} else { |
||||
try { |
||||
const result = await publishToIPFS(contract, fileManager) |
||||
|
||||
modal(`Published ${contract.name}'s Metadata`, publishMessage(result.uploaded), { |
||||
label: 'OK', |
||||
fn: () => {} |
||||
}, null) |
||||
// triggered each time there's a new verified publish (means hash correspond)
|
||||
fileProvider.addExternal('swarm/' + result.item.hash, result.item.content) |
||||
} catch (err) { |
||||
modal(`Swarm Publish Failed`, publishMessageFailed(storage, err), { |
||||
label: 'OK', |
||||
fn: () => {} |
||||
}, null) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (contract) { |
||||
storageService() |
||||
} |
||||
}, [contract]) |
||||
|
||||
const publishMessage = (uploaded) => { |
||||
return ( |
||||
<span> Metadata of {contract.name.toLowerCase()} was published successfully. <br /> |
||||
<pre> |
||||
<div> |
||||
{ uploaded.map((value) => { |
||||
<div><b>{ value.filename }</b> : <pre>{ value.output.url }</pre></div> |
||||
}) } |
||||
</div> |
||||
</pre> |
||||
</span> |
||||
) |
||||
} |
||||
|
||||
const publishMessageFailed = (storage, err) => { |
||||
return ( |
||||
<span>Failed to publish metadata file to { storage }, please check the { storage } gateways is available. <br /> |
||||
{err}
|
||||
</span> |
||||
) |
||||
} |
||||
|
||||
const handleHideModal = () => { |
||||
setState(prevState => { |
||||
return { ...prevState, modal: { ...prevState.modal, hide: true, message: null } } |
||||
}) |
||||
} |
||||
|
||||
const modal = async (title: string, message: string | JSX.Element, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => { |
||||
await setState(prevState => { |
||||
return { |
||||
...prevState, |
||||
modal: { |
||||
...prevState.modal, |
||||
hide: false, |
||||
message, |
||||
title, |
||||
ok, |
||||
cancel, |
||||
handleHide: handleHideModal |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<ModalDialog |
||||
id='publishToStorageModalDialog' |
||||
title={ state.modal.title } |
||||
message={ state.modal.message } |
||||
hide={ state.modal.hide } |
||||
ok={ state.modal.ok } |
||||
cancel={ state.modal.cancel } |
||||
handleHide={ handleHideModal }> |
||||
{ (typeof state.modal.message !== 'string') && state.modal.message } |
||||
</ModalDialog> |
||||
) |
||||
} |
||||
|
||||
export default PublishToStorage |
@ -0,0 +1,102 @@ |
||||
import swarm from 'swarmgw' |
||||
|
||||
const swarmgw = swarm() |
||||
|
||||
export const publishToSwarm = async (contract, fileManager) => { |
||||
// gather list of files to publish
|
||||
const sources = [] |
||||
let metadata |
||||
let item = null |
||||
const uploaded = [] |
||||
|
||||
try { |
||||
metadata = JSON.parse(contract.metadata) |
||||
} catch (e) { |
||||
throw new Error(e) |
||||
} |
||||
|
||||
if (metadata === undefined) { |
||||
throw new Error('No metadata') |
||||
} |
||||
|
||||
await Promise.all(Object.keys(metadata.sources).map(fileName => { |
||||
// find hash
|
||||
let hash = null |
||||
try { |
||||
// we try extract the hash defined in the metadata.json
|
||||
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
|
||||
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
|
||||
// if we don't find the hash in the metadata.json, the check is not done.
|
||||
//
|
||||
// TODO: refactor this with publishOnIpfs
|
||||
if (metadata.sources[fileName].urls) { |
||||
metadata.sources[fileName].urls.forEach(url => { |
||||
if (url.includes('bzz')) hash = url.match('(bzzr|bzz-raw)://(.+)')[1] |
||||
}) |
||||
} |
||||
} catch (e) { |
||||
throw new Error('Error while extracting the hash from metadata.json') |
||||
} |
||||
|
||||
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { |
||||
if (error) { |
||||
console.log(error) |
||||
} else { |
||||
sources.push({ |
||||
content: content, |
||||
hash: hash, |
||||
filename: fileName |
||||
}) |
||||
} |
||||
}) |
||||
})) |
||||
// publish the list of sources in order, fail if any failed
|
||||
|
||||
await Promise.all(sources.map((item) => { |
||||
swarmVerifiedPublish(item.content, item.hash, (error, result) => { |
||||
try { |
||||
item.hash = result.url.match('bzz-raw://(.+)')[1] |
||||
} catch (e) { |
||||
item.hash = '<Metadata inconsistency> - ' + item.fileName |
||||
} |
||||
item.output = result |
||||
uploaded.push(item) |
||||
// 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 |
||||
}) |
||||
})) |
||||
|
||||
const metadataContent = JSON.stringify(metadata) |
||||
|
||||
swarmVerifiedPublish(metadataContent, '', (error, result) => { |
||||
try { |
||||
contract.metadataHash = result.url.match('bzz-raw://(.+)')[1] |
||||
} catch (e) { |
||||
contract.metadataHash = '<Metadata inconsistency> - metadata.json' |
||||
} |
||||
if (!error) { |
||||
item.content = metadataContent |
||||
item.hash = contract.metadataHash |
||||
} |
||||
uploaded.push({ |
||||
content: contract.metadata, |
||||
hash: contract.metadataHash, |
||||
filename: 'metadata.json', |
||||
output: result |
||||
}) |
||||
}) |
||||
|
||||
return { uploaded, item } |
||||
} |
||||
|
||||
const swarmVerifiedPublish = (content, expectedHash, cb) => { |
||||
swarmgw.put(content, function (err, ret) { |
||||
if (err) { |
||||
cb(err) |
||||
} else if (expectedHash && ret !== expectedHash) { |
||||
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'bzz-raw://' + ret, hash: ret }) |
||||
} else { |
||||
cb(null, { message: 'ok', url: 'bzz-raw://' + ret, hash: ret }) |
||||
} |
||||
}) |
||||
} |
@ -0,0 +1,110 @@ |
||||
import IpfsClient from 'ipfs-mini' |
||||
|
||||
const ipfsNodes = [ |
||||
new IpfsClient({ host: 'ipfs.komputing.org', port: 443, protocol: 'https' }), |
||||
new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }), |
||||
new IpfsClient({ host: '127.0.0.1', port: 5001, protocol: 'http' }) |
||||
] |
||||
|
||||
export const publishToIPFS = async (contract, fileManager) => { |
||||
// gather list of files to publish
|
||||
const sources = [] |
||||
let metadata |
||||
let item = null |
||||
const uploaded = [] |
||||
|
||||
try { |
||||
metadata = JSON.parse(contract.metadata) |
||||
} catch (e) { |
||||
throw new Error(e) |
||||
} |
||||
|
||||
if (metadata === undefined) { |
||||
throw new Error('No metadata') |
||||
} |
||||
|
||||
await Promise.all(Object.keys(metadata.sources).map(fileName => { |
||||
// find hash
|
||||
let hash = null |
||||
try { |
||||
// we try extract the hash defined in the metadata.json
|
||||
// in order to check if the hash that we get after publishing is the same as the one located in metadata.json
|
||||
// if it's not the same, we throw "hash mismatch between solidity bytecode and uploaded content"
|
||||
// if we don't find the hash in the metadata.json, the check is not done.
|
||||
//
|
||||
// TODO: refactor this with publishOnSwarm
|
||||
if (metadata.sources[fileName].urls) { |
||||
metadata.sources[fileName].urls.forEach(url => { |
||||
if (url.includes('ipfs')) hash = url.match('dweb:/ipfs/(.+)')[1] |
||||
}) |
||||
} |
||||
} catch (e) { |
||||
throw new Error('Error while extracting the hash from metadata.json') |
||||
} |
||||
|
||||
fileManager.fileProviderOf(fileName).get(fileName, (error, content) => { |
||||
if (error) { |
||||
console.log(error) |
||||
} else { |
||||
sources.push({ |
||||
content: content, |
||||
hash: hash, |
||||
filename: fileName |
||||
}) |
||||
} |
||||
}) |
||||
})) |
||||
// publish the list of sources in order, fail if any failed
|
||||
await Promise.all(sources.map(item => { |
||||
ipfsVerifiedPublish(item.content, item.hash, (error, result) => { |
||||
try { |
||||
item.hash = result.url.match('dweb:/ipfs/(.+)')[1] |
||||
} catch (e) { |
||||
item.hash = '<Metadata inconsistency> - ' + item.fileName |
||||
} |
||||
item.output = result |
||||
uploaded.push(item) |
||||
}) |
||||
})) |
||||
const metadataContent = JSON.stringify(metadata) |
||||
|
||||
ipfsVerifiedPublish(metadataContent, '', (error, result) => { |
||||
try { |
||||
contract.metadataHash = result.url.match('dweb:/ipfs/(.+)')[1] |
||||
} catch (e) { |
||||
contract.metadataHash = '<Metadata inconsistency> - metadata.json' |
||||
} |
||||
if (!error) { |
||||
item.content = metadataContent |
||||
item.hash = contract.metadataHash |
||||
} |
||||
uploaded.push({ |
||||
content: contract.metadata, |
||||
hash: contract.metadataHash, |
||||
filename: 'metadata.json', |
||||
output: result |
||||
}) |
||||
}) |
||||
|
||||
return { uploaded, item } |
||||
} |
||||
|
||||
const ipfsVerifiedPublish = async (content, expectedHash, cb) => { |
||||
try { |
||||
const results = await severalGatewaysPush(content) |
||||
if (expectedHash && results !== expectedHash) { |
||||
cb(null, { message: 'hash mismatch between solidity bytecode and uploaded content.', url: 'dweb:/ipfs/' + results, hash: results }) |
||||
} else { |
||||
cb(null, { message: 'ok', url: 'dweb:/ipfs/' + results, hash: results }) |
||||
} |
||||
} catch (error) { |
||||
cb(error) |
||||
} |
||||
} |
||||
|
||||
const severalGatewaysPush = (content) => { |
||||
const invert = p => new Promise((resolve, reject) => p.then(reject).catch(resolve)) // Invert res and rej
|
||||
const promises = ipfsNodes.map((node) => invert(node.add(content))) |
||||
|
||||
return invert(Promise.all(promises)) |
||||
} |
@ -0,0 +1,6 @@ |
||||
export interface RemixUiPublishToStorageProps { |
||||
storage: string, |
||||
fileProvider: any, |
||||
fileManager: any, |
||||
contract: any |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,7 @@ |
||||
# remix-ui-solidity-compiler |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-solidity-compiler` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-ui-solidity-compiler'; |
@ -0,0 +1,90 @@ |
||||
var Compiler = require('@remix-project/remix-solidity').Compiler |
||||
|
||||
export default class CompileTab { |
||||
public compiler |
||||
public optimize |
||||
public runs: number |
||||
public evmVersion: string |
||||
public compilerImport |
||||
|
||||
constructor (public queryParams, public fileManager, public editor, public config, public fileProvider, public contentImport, public miscApi) { |
||||
// this.event = new EventEmitter()
|
||||
this.compiler = new Compiler((url, cb) => this.compilerImport.resolveAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message))) |
||||
} |
||||
|
||||
init () { |
||||
this.optimize = this.queryParams.get().optimize |
||||
this.optimize = this.optimize === 'true' |
||||
this.queryParams.update({ optimize: this.optimize }) |
||||
this.compiler.set('optimize', this.optimize) |
||||
|
||||
this.runs = this.queryParams.get().runs |
||||
this.runs = this.runs || 200 |
||||
this.queryParams.update({ runs: this.runs }) |
||||
this.compiler.set('runs', this.runs) |
||||
|
||||
this.evmVersion = this.queryParams.get().evmVersion |
||||
if (this.evmVersion === 'undefined' || this.evmVersion === 'null' || !this.evmVersion) { |
||||
this.evmVersion = null |
||||
} |
||||
this.queryParams.update({ evmVersion: this.evmVersion }) |
||||
this.compiler.set('evmVersion', this.evmVersion) |
||||
} |
||||
|
||||
setOptimize (newOptimizeValue) { |
||||
this.optimize = newOptimizeValue |
||||
this.queryParams.update({ optimize: this.optimize }) |
||||
this.compiler.set('optimize', this.optimize) |
||||
} |
||||
|
||||
setRuns (runs) { |
||||
this.runs = runs |
||||
this.queryParams.update({ runs: this.runs }) |
||||
this.compiler.set('runs', this.runs) |
||||
} |
||||
|
||||
setEvmVersion (newEvmVersion) { |
||||
this.evmVersion = newEvmVersion |
||||
this.queryParams.update({ evmVersion: this.evmVersion }) |
||||
this.compiler.set('evmVersion', this.evmVersion) |
||||
} |
||||
|
||||
/** |
||||
* Set the compiler to using Solidity or Yul (default to Solidity) |
||||
* @params lang {'Solidity' | 'Yul'} ... |
||||
*/ |
||||
setLanguage (lang) { |
||||
this.compiler.set('language', lang) |
||||
} |
||||
|
||||
/** |
||||
* Compile a specific file of the file manager |
||||
* @param {string} target the path to the file to compile |
||||
*/ |
||||
compileFile (target) { |
||||
if (!target) throw new Error('No target provided for compiliation') |
||||
const provider = this.fileManager.fileProviderOf(target) |
||||
|
||||
if (!provider) throw new Error(`cannot compile ${target}. Does not belong to any explorer`) |
||||
return new Promise((resolve, reject) => { |
||||
provider.get(target, (error, content) => { |
||||
if (error) return reject(error) |
||||
const sources = { [target]: { content } } |
||||
// this.event.emit('startingCompilation')
|
||||
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
|
||||
setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
runCompiler () { |
||||
try { |
||||
this.fileManager.saveCurrentFile() |
||||
this.miscApi.clearAnnotations() |
||||
var currentFile = this.config.get('currentFile') |
||||
return this.compileFile(currentFile) |
||||
} catch (err) { |
||||
console.error(err) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,521 @@ |
||||
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
|
||||
import semver from 'semver' |
||||
import { CompilerContainerProps } from './types' |
||||
import * as helper from '../../../../../apps/remix-ide/src/lib/helper' |
||||
import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '../../../../../apps/remix-ide/src/app/compiler/compiler-utils' // eslint-disable-line
|
||||
|
||||
import './css/style.css' |
||||
|
||||
export const CompilerContainer = (props: CompilerContainerProps) => { |
||||
const { editor, config, queryParams, compileTabLogic, tooltip, modal } = props // eslint-disable-line
|
||||
const [state, setState] = useState({ |
||||
hideWarnings: false, |
||||
autoCompile: false, |
||||
optimise: false, |
||||
compileTimeout: null, |
||||
timeout: 300, |
||||
allversions: [], |
||||
selectedVersion: null, |
||||
defaultVersion: 'soljson-v0.7.4+commit.3f05b770.js', // this default version is defined: in makeMockCompiler (for browser test)
|
||||
selectedLanguage: '', |
||||
runs: '', |
||||
compiledFileName: '', |
||||
includeNightlies: false, |
||||
language: '', |
||||
evmVersion: '' |
||||
}) |
||||
const compileIcon = useRef(null) |
||||
const warningIcon = useRef(null) |
||||
const promptMessageInput = useRef(null) |
||||
|
||||
useEffect(() => { |
||||
fetchAllVersion((allversions, selectedVersion, isURL) => { |
||||
setState(prevState => { |
||||
return { ...prevState, allversions } |
||||
}) |
||||
if (isURL) _updateVersionSelector(selectedVersion) |
||||
else { |
||||
setState(prevState => { |
||||
return { ...prevState, selectedVersion } |
||||
}) |
||||
// if (this._view.versionSelector) this._updateVersionSelector()
|
||||
} |
||||
}) |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
if (compileTabLogic && compileTabLogic.compiler) { |
||||
compileTabLogic.compiler.event.register('compilerLoaded', compilerLoaded) |
||||
|
||||
setState(prevState => { |
||||
return { ...prevState, hideWarnings: config.get('hideWarnings'), autoCompile: config.get('autoCompile'), optimise: config.get('optimise') } |
||||
}) |
||||
} |
||||
}, [compileTabLogic]) |
||||
|
||||
const compilerLoaded = (version: string) => { |
||||
setVersionText(version) |
||||
} |
||||
|
||||
const setVersionText = (text) => { |
||||
// if (this._view.version) this._view.version.innerText = text
|
||||
} |
||||
|
||||
// fetching both normal and wasm builds and creating a [version, baseUrl] map
|
||||
const fetchAllVersion = async (callback) => { |
||||
let selectedVersion, allVersionsWasm, isURL |
||||
let allVersions = [{ path: 'builtin', longVersion: 'latest local version - 0.7.4' }] |
||||
// fetch normal builds
|
||||
const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`) |
||||
// fetch wasm builds
|
||||
const wasmRes = await promisedMiniXhr(`${baseURLWasm}/list.json`) |
||||
if (binRes.event.type === 'error' && wasmRes.event.type === 'error') { |
||||
selectedVersion = 'builtin' |
||||
return callback(allVersions, selectedVersion) |
||||
} |
||||
try { |
||||
const versions = JSON.parse(binRes.json).builds.slice().reverse() |
||||
|
||||
allVersions = [...allVersions, ...versions] |
||||
selectedVersion = state.defaultVersion |
||||
if (queryParams.get().version) selectedVersion = queryParams.get().version |
||||
// Check if version is a URL and corresponding filename starts with 'soljson'
|
||||
if (selectedVersion.startsWith('https://')) { |
||||
const urlArr = selectedVersion.split('/') |
||||
|
||||
if (urlArr[urlArr.length - 1].startsWith('soljson')) isURL = true |
||||
} |
||||
if (wasmRes.event.type !== 'error') { |
||||
allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse() |
||||
} |
||||
} 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. Error: ' + e) |
||||
} |
||||
// replace in allVersions those compiler builds which exist in allVersionsWasm with new once
|
||||
if (allVersionsWasm && allVersions) { |
||||
allVersions.forEach((compiler, index) => { |
||||
const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion }) |
||||
if (wasmIndex !== -1) { |
||||
allVersions[index] = allVersionsWasm[wasmIndex] |
||||
pathToURL[compiler.path] = baseURLWasm |
||||
} else { |
||||
pathToURL[compiler.path] = baseURLBin |
||||
} |
||||
}) |
||||
} |
||||
callback(allVersions, selectedVersion, isURL) |
||||
} |
||||
|
||||
/** |
||||
* Update the compilation button with the name of the current file |
||||
*/ |
||||
const currentFile = (name = '') => { |
||||
if (name && name !== '') { |
||||
_setCompilerVersionFromPragma(name) |
||||
} |
||||
const compiledFileName = name.split('/').pop() |
||||
|
||||
setState(prevState => { |
||||
return { ...prevState, compiledFileName } |
||||
}) |
||||
} |
||||
|
||||
// Load solc compiler version according to pragma in contract file
|
||||
const _setCompilerVersionFromPragma = (filename: string) => { |
||||
if (!state.allversions) return |
||||
compileTabLogic.fileManager.readFile(filename).then(data => { |
||||
const pragmaArr = data.match(/(pragma solidity (.+?);)/g) |
||||
if (pragmaArr && pragmaArr.length === 1) { |
||||
const pragmaStr = pragmaArr[0].replace('pragma solidity', '').trim() |
||||
const pragma = pragmaStr.substring(0, pragmaStr.length - 1) |
||||
const releasedVersions = state.allversions.filter(obj => !obj.prerelease).map(obj => obj.version) |
||||
const allVersions = state.allversions.map(obj => _retrieveVersion(obj.version)) |
||||
const currentCompilerName = _retrieveVersion(/** this._view.versionSelector.selectedOptions[0].label **/) |
||||
// contains only numbers part, for example '0.4.22'
|
||||
const pureVersion = _retrieveVersion() |
||||
// is nightly build newer than the last release
|
||||
const isNewestNightly = currentCompilerName.includes('nightly') && semver.gt(pureVersion, releasedVersions[0]) |
||||
// checking if the selected version is in the pragma range
|
||||
const isInRange = semver.satisfies(pureVersion, pragma) |
||||
// checking if the selected version is from official compilers list(excluding custom versions) and in range or greater
|
||||
const isOfficial = allVersions.includes(currentCompilerName) |
||||
if (isOfficial && (!isInRange && !isNewestNightly)) { |
||||
const compilerToLoad = semver.maxSatisfying(releasedVersions, pragma) |
||||
const compilerPath = state.allversions.filter(obj => !obj.prerelease && obj.version === compilerToLoad)[0].path |
||||
if (state.selectedVersion !== compilerPath) { |
||||
state.selectedVersion = compilerPath |
||||
_updateVersionSelector() |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
const isSolFileSelected = (currentFile = '') => { |
||||
if (!currentFile) currentFile = config.get('currentFile') |
||||
if (!currentFile) return false |
||||
const extention = currentFile.substr(currentFile.length - 3, currentFile.length) |
||||
return extention.toLowerCase() === 'sol' || extention.toLowerCase() === 'yul' |
||||
} |
||||
|
||||
const deactivate = () => { |
||||
// deactivate editor listeners
|
||||
editor.event.unregister('contentChanged') |
||||
editor.event.unregister('sessionSwitched') |
||||
} |
||||
|
||||
const activate = () => { |
||||
const currentFileName = config.get('currentFile') |
||||
|
||||
currentFile(currentFileName) |
||||
listenToEvents() |
||||
} |
||||
|
||||
const listenToEvents = () => { |
||||
editor.event.register('sessionSwitched', () => { |
||||
if (!compileIcon.current) return |
||||
scheduleCompilation() |
||||
}) |
||||
|
||||
compileTabLogic.event.on('startingCompilation', () => { |
||||
if (!compileIcon.current) return |
||||
compileIcon.current.setAttribute('title', 'compiling...') |
||||
compileIcon.current.classList.remove('remixui_bouncingIcon') |
||||
compileIcon.current.classList.add('remixui_spinningIcon') |
||||
}) |
||||
|
||||
compileTabLogic.compiler.event.register('compilationDuration', (speed) => { |
||||
if (!warningIcon.current) return |
||||
if (speed > 1000) { |
||||
const msg = `Last compilation took ${speed}ms. We suggest to turn off autocompilation.` |
||||
|
||||
warningIcon.current.setAttribute('title', msg) |
||||
warningIcon.current.style.visibility = 'visible' |
||||
} else { |
||||
warningIcon.current.style.visibility = 'hidden' |
||||
} |
||||
}) |
||||
|
||||
editor.event.register('contentChanged', () => { |
||||
if (!compileIcon.current) return |
||||
scheduleCompilation() |
||||
compileIcon.current.classList.add('remixui_bouncingIcon') // @TODO: compileView tab
|
||||
}) |
||||
|
||||
compileTabLogic.compiler.event.register('loadingCompiler', () => { |
||||
if (!compileIcon.current) return |
||||
// _disableCompileBtn(true)
|
||||
compileIcon.current.setAttribute('title', 'compiler is loading, please wait a few moments.') |
||||
compileIcon.current.classList.add('remixui_spinningIcon') |
||||
warningIcon.current.style.visibility = 'hidden' |
||||
_updateLanguageSelector() |
||||
}) |
||||
|
||||
compileTabLogic.compiler.event.register('compilerLoaded', () => { |
||||
if (!compileIcon.current) return |
||||
// _disableCompileBtn(false)
|
||||
compileIcon.current.setAttribute('title', '') |
||||
compileIcon.current.classList.remove('remixui_spinningIcon') |
||||
if (state.autoCompile) compileIfAutoCompileOn() |
||||
}) |
||||
|
||||
compileTabLogic.compiler.event.register('compilationFinished', (success, data, source) => { |
||||
if (!compileIcon.current) return |
||||
compileIcon.current.setAttribute('title', 'idle') |
||||
compileIcon.current.classList.remove('remixui_spinningIcon') |
||||
compileIcon.current.classList.remove('remixui_bouncingIcon') |
||||
}) |
||||
} |
||||
|
||||
const scheduleCompilation = () => { |
||||
if (!state.autoCompile) return |
||||
if (state.compileTimeout) window.clearTimeout(state.compileTimeout) |
||||
const compileTimeout = window.setTimeout(() => compileIfAutoCompileOn(), state.timeout) |
||||
|
||||
setState(prevState => { |
||||
return { ...prevState, compileTimeout } |
||||
}) |
||||
} |
||||
|
||||
const compileIfAutoCompileOn = () => { |
||||
if (config.get('autoCompile')) { |
||||
compile() |
||||
} |
||||
} |
||||
|
||||
const compile = () => { |
||||
const currentFile = config.get('currentFile') |
||||
|
||||
if (!isSolFileSelected()) return |
||||
|
||||
_setCompilerVersionFromPragma(currentFile) |
||||
compileTabLogic.runCompiler() |
||||
} |
||||
|
||||
const _retrieveVersion = (version?) => { |
||||
// if (!version) version = this._view.versionSelector.value
|
||||
return semver.coerce(version) ? semver.coerce(version).version : '' |
||||
} |
||||
|
||||
const _updateVersionSelector = (customUrl = '') => { |
||||
// update selectedversion of previous one got filtered out
|
||||
let selectedVersion = state.selectedVersion |
||||
if (!selectedVersion || !_shouldBeAdded(selectedVersion)) { |
||||
selectedVersion = state.defaultVersion |
||||
setState(prevState => { |
||||
return { ...prevState, selectedVersion } |
||||
}) |
||||
} |
||||
queryParams.update({ version: selectedVersion }) |
||||
let url |
||||
|
||||
if (customUrl !== '') { |
||||
selectedVersion = customUrl |
||||
setState(prevState => { |
||||
return { ...prevState, selectedVersion } |
||||
}) |
||||
// this._view.versionSelector.appendChild(yo`<option value="${customUrl}" selected>custom</option>`)
|
||||
url = customUrl |
||||
queryParams.update({ version: selectedVersion }) |
||||
} else if (selectedVersion === 'builtin') { |
||||
let location: string | Location = window.document.location |
||||
let path = location.pathname |
||||
if (!path.startsWith('/')) path = '/' + path |
||||
location = `${location.protocol}//${location.host}${path}assets/js` |
||||
if (location.endsWith('index.html')) location = location.substring(0, location.length - 10) |
||||
if (!location.endsWith('/')) location += '/' |
||||
url = location + 'soljson.js' |
||||
} else { |
||||
console.log('selectedVersion: ', selectedVersion) |
||||
if (selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(selectedVersion)) { |
||||
return console.log('loading ' + selectedVersion + ' not allowed') |
||||
} |
||||
url = `${urlFromVersion(selectedVersion)}` |
||||
} |
||||
|
||||
// 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.
|
||||
if (selectedVersion !== 'builtin' && canUseWorker(selectedVersion)) { |
||||
compileTabLogic.compiler.loadVersion(true, url) |
||||
// setVersionText('(loading using worker)')
|
||||
} else { |
||||
compileTabLogic.compiler.loadVersion(false, url) |
||||
// setVersionText('(loading)')
|
||||
} |
||||
} |
||||
|
||||
const _shouldBeAdded = (version) => { |
||||
return !version.includes('nightly') || |
||||
(version.includes('nightly') && state.includeNightlies) |
||||
} |
||||
|
||||
// const setVersionText = (text) => {
|
||||
// if (this._view.version) this._view.version.innerText = text
|
||||
// }
|
||||
|
||||
const promtCompiler = () => { |
||||
modal('Add a custom compiler', promptMessage('URL'), 'OK', addCustomCompiler, 'Cancel', () => {}) |
||||
// modalDialogCustom.prompt(
|
||||
// 'Add a custom compiler',
|
||||
// 'URL',
|
||||
// '',
|
||||
// (url) => this.addCustomCompiler(url)
|
||||
// )
|
||||
} |
||||
|
||||
const promptMessage = (message) => { |
||||
return ( |
||||
<> |
||||
<span>{ message }</span> |
||||
<input type="text" data-id="modalDialogCustomPromptCompiler" className="form-control" ref={promptMessageInput} /> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
const addCustomCompiler = () => { |
||||
const url = promptMessageInput.current.value |
||||
|
||||
setState(prevState => { |
||||
return { ...prevState, selectedVersion: url } |
||||
}) |
||||
_updateVersionSelector(url) |
||||
} |
||||
|
||||
const handleLoadVersion = (value) => { |
||||
setState(prevState => { |
||||
return { ...prevState, selectedVersion: value } |
||||
}) |
||||
_updateVersionSelector() |
||||
_updateLanguageSelector() |
||||
} |
||||
|
||||
const _updateLanguageSelector = () => { |
||||
// This is the first version when Yul is available
|
||||
if (!semver.valid(_retrieveVersion()) || semver.lt(_retrieveVersion(), 'v0.5.7+commit.6da8b019.js')) { |
||||
// this._view.languageSelector.setAttribute('disabled', '')
|
||||
// this._view.languageSelector.value = 'Solidity'
|
||||
// this.compileTabLogic.setLanguage('Solidity')
|
||||
} else { |
||||
// this._view.languageSelector.removeAttribute('disabled')
|
||||
} |
||||
} |
||||
|
||||
const handleAutoCompile = (e) => { |
||||
const checked = e.target.checked |
||||
|
||||
config.set('autoCompile', checked) |
||||
setState(prevState => { |
||||
return { ...prevState, autoCompile: checked } |
||||
}) |
||||
} |
||||
|
||||
const handleOptimizeChange = (e) => { |
||||
const checked = !!e.target.checked |
||||
|
||||
config.set('optimise', checked) |
||||
compileTabLogic.setOptimize(checked) |
||||
if (compileTabLogic.optimize) { |
||||
compileTabLogic.setRuns(parseInt(state.runs)) |
||||
} else { |
||||
compileTabLogic.setRuns(200) |
||||
} |
||||
compileIfAutoCompileOn() |
||||
setState(prevState => { |
||||
return { ...prevState, optimise: checked } |
||||
}) |
||||
} |
||||
|
||||
const onChangeRuns = (e) => { |
||||
const runs = e.target.value |
||||
|
||||
compileTabLogic.setRuns(parseInt(runs)) |
||||
compileIfAutoCompileOn() |
||||
setState(prevState => { |
||||
return { ...prevState, runs } |
||||
}) |
||||
} |
||||
|
||||
const handleHideWarningsChange = (e) => { |
||||
const checked = e.target.checked |
||||
|
||||
config.set('hideWarnings', checked) |
||||
compileIfAutoCompileOn() |
||||
setState(prevState => { |
||||
return { ...prevState, hideWarnings: checked } |
||||
}) |
||||
} |
||||
|
||||
const handleNightliesChange = () => { |
||||
setState(prevState => { |
||||
return { ...prevState, includeNightlies: !prevState.includeNightlies } |
||||
}) |
||||
} |
||||
|
||||
const handleLanguageChange = (value) => { |
||||
compileTabLogic.setLanguage(value) |
||||
compileIfAutoCompileOn() |
||||
setState(prevState => { |
||||
return { ...prevState, language: value } |
||||
}) |
||||
} |
||||
|
||||
const handleEvmVersionChange = (value) => { |
||||
let v = value |
||||
if (v === 'default') { |
||||
v = null |
||||
} |
||||
compileTabLogic.setEvmVersion(v) |
||||
compileIfAutoCompileOn() |
||||
setState(prevState => { |
||||
return { ...prevState, evmVersion: value } |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<section> |
||||
<article> |
||||
<header className='remixui_compilerSection border-bottom'> |
||||
<div className="mb-2"> |
||||
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector"> |
||||
Compiler |
||||
<button className="far fa-plus-square border-0 p-0 mx-2 btn-sm" onClick={promtCompiler} title="Add a custom compiler with URL"></button> |
||||
</label> |
||||
<select onChange={(e) => handleLoadVersion(e.target.value) } className="custom-select" id="versionSelector" disabled={state.allversions.length <= 0}> |
||||
{ state.allversions.length <= 0 && <option disabled selected>{ state.defaultVersion }</option> } |
||||
{ state.allversions.length <= 0 && <option disabled>builtin</option> } |
||||
{ state.allversions.map((build, i) => { |
||||
return _shouldBeAdded(build.longVersion) |
||||
? <option key={i} value={build.path} selected={build.path === state.selectedVersion}>{build.longVersion}</option> |
||||
: null |
||||
}) |
||||
} |
||||
</select> |
||||
</div> |
||||
<div className="mb-2 remixui_nightlyBuilds custom-control custom-checkbox"> |
||||
<input className="mr-2 custom-control-input" id="nightlies" type="checkbox" onChange={handleNightliesChange} /> |
||||
<label htmlFor="nightlies" className="form-check-label custom-control-label">Include nightly builds</label> |
||||
</div> |
||||
<div className="mb-2"> |
||||
<label className="remixui_compilerLabel form-check-label" htmlFor="compilierLanguageSelector">Language</label> |
||||
<select onChange={(e) => handleLanguageChange(e.target.value)} className="custom-select" id="compilierLanguageSelector" title="Available since v0.5.7"> |
||||
<option>Solidity</option> |
||||
<option>Yul</option> |
||||
</select> |
||||
</div> |
||||
<div className="mb-2"> |
||||
<label className="remixui_compilerLabel form-check-label" htmlFor="evmVersionSelector">EVM Version</label> |
||||
<select onChange={(e) => handleEvmVersionChange(e.target.value)} className="custom-select" id="evmVersionSelector"> |
||||
<option value="default">compiler default</option> |
||||
<option value="muirGlacier">muirGlacier</option> |
||||
<option value="istanbul">istanbul</option> |
||||
<option value="petersburg">petersburg</option> |
||||
<option value="constantinople">constantinople</option> |
||||
<option value="byzantium">byzantium</option> |
||||
<option value="spuriousDragon">spuriousDragon</option> |
||||
<option value="tangerineWhistle">tangerineWhistle</option> |
||||
<option value="homestead">homestead</option> |
||||
</select> |
||||
</div> |
||||
<div className="mt-3"> |
||||
<p className="mt-2 remixui_compilerLabel">Compiler Configuration</p> |
||||
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||
<input className="remixui_autocompile custom-control-input" onChange={handleAutoCompile} data-id="compilerContainerAutoCompile" id="autoCompile" type="checkbox" title="Auto compile" checked={state.autoCompile} /> |
||||
<label className="form-check-label custom-control-label" htmlFor="autoCompile">Auto compile</label> |
||||
</div> |
||||
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||
<div className="justify-content-between align-items-center d-flex"> |
||||
<input onChange={handleOptimizeChange} className="custom-control-input" id="optimize" type="checkbox" checked={state.optimise} /> |
||||
<label className="form-check-label custom-control-label" htmlFor="optimize">Enable optimization</label> |
||||
<input |
||||
min="1" |
||||
className="custom-select ml-2 remixui_runs" |
||||
id="runs" |
||||
placeholder="200" |
||||
value="200" |
||||
type="number" |
||||
title="Estimated number of times each opcode of the deployed code will be executed across the life-time of the contract." |
||||
onChange={onChangeRuns} |
||||
disabled={!state.optimise} |
||||
/> |
||||
</div> |
||||
</div> |
||||
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||
<input className="remixui_autocompile custom-control-input" onChange={handleHideWarningsChange} id="hideWarningsBox" type="checkbox" title="Hide warnings" checked={state.hideWarnings} /> |
||||
<label className="form-check-label custom-control-label" htmlFor="hideWarningsBox">Hide warnings</label> |
||||
</div> |
||||
</div> |
||||
<button id="compileBtn" data-id="compilerContainerCompileBtn" className="btn btn-primary btn-block remixui_disabled mt-3" title="Compile" onClick={compile} disabled={!state.compiledFileName || (state.compiledFileName && !isSolFileSelected(state.compiledFileName))}> |
||||
<span> |
||||
<i ref={warningIcon} title="Compilation Slow" style={{ visibility: 'hidden' }} className="remixui_warnCompilationSlow fas fa-exclamation-triangle" aria-hidden="true"></i> |
||||
<i ref={compileIcon} className="fas fa-sync remixui_icon" aria-hidden="true"></i> |
||||
Compile { state.compiledFileName || '<no file selected>' } |
||||
</span> |
||||
</button> |
||||
</header> |
||||
</article> |
||||
</section> |
||||
) |
||||
} |
||||
|
||||
export default CompilerContainer |
@ -0,0 +1,159 @@ |
||||
import React, { useState, useEffect } from 'react' // eslint-disable-line
|
||||
import { ContractSelectionProps } from './types' |
||||
import { PublishToStorage } from '@remix-ui/publish-to-storage' |
||||
|
||||
import './css/style.css' |
||||
|
||||
export const ContractSelection = (props: ContractSelectionProps) => { |
||||
const { contractMap, fileManager, fileProvider } = props |
||||
const [state, setState] = useState({ |
||||
contractList: null, |
||||
selectedContract: '' |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
const contractList = contractMap ? Object.keys(contractMap).map((key) => ({ |
||||
name: key, |
||||
file: getFileName(contractMap[key].file) |
||||
})) : [] |
||||
|
||||
setState(prevState => { |
||||
return { ...prevState, contractList } |
||||
}) |
||||
}, []) |
||||
// Return the file name of a path: ex "browser/ballot.sol" -> "ballot.sol"
|
||||
const getFileName = (path) => { |
||||
const part = path.split('/') |
||||
|
||||
return part[part.length - 1] |
||||
} |
||||
|
||||
const selectContract = (contractName: string) => { |
||||
setState(prevState => { |
||||
return { ...prevState, selectedContract: contractName } |
||||
}) |
||||
} |
||||
|
||||
const handlePublishToStorage = (type) => { |
||||
|
||||
} |
||||
|
||||
const copyABI = () => { |
||||
copyContractProperty('abi') |
||||
} |
||||
|
||||
const copyContractProperty = (property) => { |
||||
let content = getContractProperty(property) |
||||
// if (!content) {
|
||||
// addTooltip('No content available for ' + property)
|
||||
// return
|
||||
// }
|
||||
|
||||
// try {
|
||||
// if (typeof content !== 'string') {
|
||||
// content = JSON.stringify(content, null, '\t')
|
||||
// }
|
||||
// } catch (e) {}
|
||||
|
||||
// copy(content)
|
||||
// addTooltip('Copied value to clipboard')
|
||||
} |
||||
|
||||
const getContractProperty = (property) => { |
||||
// if (!this.selectedContract) throw new Error('No contract compiled yet')
|
||||
// const contractProperties = this.data.contractsDetails[this.selectedContract]
|
||||
// return contractProperties[property] || null
|
||||
} |
||||
|
||||
const details = () => { |
||||
const help = { |
||||
Assembly: 'Assembly opcodes describing the contract including corresponding solidity source code', |
||||
Opcodes: 'Assembly opcodes describing the contract', |
||||
'Runtime Bytecode': 'Bytecode storing the state and being executed during normal contract call', |
||||
bytecode: 'Bytecode being executed during contract creation', |
||||
functionHashes: 'List of declared function and their corresponding hash', |
||||
gasEstimates: 'Gas estimation for each function call', |
||||
metadata: 'Contains all informations related to the compilation', |
||||
metadataHash: 'Hash representing all metadata information', |
||||
abi: 'ABI: describing all the functions (input/output params, scope, ...)', |
||||
name: 'Name of the compiled contract', |
||||
swarmLocation: 'Swarm url where all metadata information can be found (contract needs to be published first)', |
||||
web3Deploy: 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract' |
||||
} |
||||
if (!this.selectedContract) throw new Error('No contract compiled yet') |
||||
const contractProperties = this.data.contractsDetails[this.selectedContract] |
||||
const log = yo`<div class="${css.detailsJSON}"></div>` |
||||
Object.keys(contractProperties).map(propertyName => { |
||||
const copyDetails = yo`<span class="${css.copyDetails}">${copyToClipboard(() => contractProperties[propertyName])}</span>` |
||||
const questionMark = yo`<span class="${css.questionMark}"><i title="${help[propertyName]}" class="fas fa-question-circle" aria-hidden="true"></i></span>` |
||||
log.appendChild(yo`<div class=${css.log}>
|
||||
<div class="${css.key}">${propertyName} ${copyDetails} ${questionMark}</div> |
||||
${this.insertValue(contractProperties, propertyName)} |
||||
</div>`)
|
||||
}) |
||||
modalDialog(this.selectedContract, log, { label: '' }, { label: 'Close' }) |
||||
} |
||||
|
||||
const copyBytecode = () => { |
||||
copyContractProperty('bytecode') |
||||
} |
||||
|
||||
return ( |
||||
// define swarm logo
|
||||
<> |
||||
{ state.contractList.length ? |
||||
<section className="remixui_compilerSection pt-3"> |
||||
{/* Select Compiler Version */} |
||||
<div className="mb-3"> |
||||
<label className="remixui_compilerLabel form-check-label" htmlFor="compiledContracts">Contract</label> |
||||
<select onChange={(e) => selectContract(e.target.value)} data-id="compiledContracts" id="compiledContracts" className="custom-select"> |
||||
{state.contractList.map(({ name, file }) => <option value={name}>{name} ({file})</option>)} |
||||
</select> |
||||
</div> |
||||
<article className="mt-2 pb-0"> |
||||
<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 id="publishOnIpfs" className="btn btn-secondary btn-block" title="Publish on Ipfs" onClick={() => { handlePublishToStorage('ipfs') }}> |
||||
<span>Publish on Ipfs</span> |
||||
<img id="ipfsLogo" className="remixui_storageLogo} ml-2" src="assets/img/ipfs.webp" /> |
||||
</button> |
||||
<button data-id="compilation-details" className="btn btn-secondary btn-block" title="Display Contract Details" onClick={() => { details() }}> |
||||
Compilation Details |
||||
</button> |
||||
{/* Copy to Clipboard */} |
||||
<div className="remixui_contractHelperButtons"> |
||||
<div className="input-group"> |
||||
<div className="btn-group" role="group" aria-label="Copy to Clipboard"> |
||||
<button className="btn copyButton" title="Copy ABI to clipboard" onClick={() => { copyABI() }}> |
||||
<i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i> |
||||
<span>ABI</span> |
||||
</button> |
||||
<button className="btn remixui_copyButton" title="Copy Bytecode to clipboard" onClick={() => { copyBytecode() }}> |
||||
<i className="remixui_copyIcon far fa-copy" aria-hidden="true"></i> |
||||
<span>Bytecode</span> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</article> |
||||
</section> : <section className="remixui_container clearfix"><article className="px-2 mt-2 pb-0 d-flex"> |
||||
<span className="mt-2 mx-3 w-100 alert alert-warning" role="alert">No Contract Compiled Yet</span> |
||||
</article></section> |
||||
} |
||||
<PublishToStorage /> |
||||
</> |
||||
) |
||||
|
||||
// if (contractList.length) {
|
||||
// this.selectedContract = selectEl.value
|
||||
// } else {
|
||||
// delete this.selectedContract
|
||||
// }
|
||||
// return result
|
||||
|
||||
// return ()
|
||||
} |
||||
|
||||
export default ContractSelection |
@ -0,0 +1,234 @@ |
||||
.remixui_title { |
||||
font-size: 1.1em; |
||||
font-weight: bold; |
||||
margin-bottom: 1em; |
||||
} |
||||
.remixui_panicError { |
||||
color: red; |
||||
font-size: 20px; |
||||
} |
||||
.remixui_crow { |
||||
display: flex; |
||||
overflow: auto; |
||||
clear: both; |
||||
padding: .2em; |
||||
} |
||||
.remixui_checkboxText { |
||||
font-weight: normal; |
||||
} |
||||
.remixui_crow label { |
||||
cursor:pointer; |
||||
} |
||||
.remixui_crowNoFlex { |
||||
overflow: auto; |
||||
clear: both; |
||||
} |
||||
.remixui_info { |
||||
padding: 10px; |
||||
word-break: break-word; |
||||
} |
||||
.remixui_contract { |
||||
display: block; |
||||
margin: 3% 0; |
||||
} |
||||
.remixui_nightlyBuilds { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
} |
||||
.remixui_autocompileContainer { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.remixui_runs { |
||||
width: 40%; |
||||
} |
||||
.remixui_hideWarningsContainer { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.remixui_autocompile {} |
||||
.remixui_autocompileTitle { |
||||
font-weight: bold; |
||||
margin: 1% 0; |
||||
} |
||||
.remixui_autocompileText { |
||||
margin: 1% 0; |
||||
font-size: 12px; |
||||
overflow: hidden; |
||||
word-break: normal; |
||||
line-height: initial; |
||||
} |
||||
.remixui_warnCompilationSlow { |
||||
margin-left: 1%; |
||||
} |
||||
.remixui_compilerConfig { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
.remixui_compilerConfig label { |
||||
margin: 0; |
||||
} |
||||
.remixui_compilerSection { |
||||
padding: 12px 24px 16px; |
||||
} |
||||
.remixui_compilerLabel { |
||||
margin-bottom: 2px; |
||||
font-size: 11px; |
||||
line-height: 12px; |
||||
text-transform: uppercase; |
||||
} |
||||
.remixui_copyButton { |
||||
padding: 6px; |
||||
font-weight: bold; |
||||
font-size: 11px; |
||||
line-height: 15px; |
||||
} |
||||
.remixui_name { |
||||
display: flex; |
||||
} |
||||
.remixui_size { |
||||
display: flex; |
||||
} |
||||
.remixui_checkboxes { |
||||
display: flex; |
||||
width: 100%; |
||||
justify-content: space-between; |
||||
flex-wrap: wrap; |
||||
} |
||||
.remixui_compileButton { |
||||
width: 100%; |
||||
margin: 15px 0 10px 0; |
||||
font-size: 12px; |
||||
} |
||||
.remixui_container { |
||||
margin: 0; |
||||
margin-bottom: 2%; |
||||
} |
||||
.remixui_optimizeContainer { |
||||
display: flex; |
||||
} |
||||
.remixui_noContractAlert { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
.remixui_contractHelperButtons { |
||||
margin-top: 6px; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
float: right; |
||||
} |
||||
.remixui_copyToClipboard { |
||||
font-size: 1rem; |
||||
} |
||||
.remixui_copyIcon { |
||||
margin-right: 5px; |
||||
} |
||||
.remixui_log { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin-bottom: 5%; |
||||
overflow: visible; |
||||
} |
||||
.remixui_key { |
||||
margin-right: 5px; |
||||
text-transform: uppercase; |
||||
width: 100%; |
||||
} |
||||
.remixui_value { |
||||
display: flex; |
||||
width: 100%; |
||||
margin-top: 1.5%; |
||||
} |
||||
.remixui_questionMark { |
||||
margin-left: 2%; |
||||
cursor: pointer; |
||||
} |
||||
.remixui_questionMark:hover { |
||||
} |
||||
.remixui_detailsJSON { |
||||
padding: 8px 0; |
||||
border: none; |
||||
} |
||||
.remixui_icon { |
||||
margin-right: 0.3em; |
||||
} |
||||
.remixui_errorBlobs { |
||||
padding-left: 5px; |
||||
padding-right: 5px; |
||||
word-break: break-word; |
||||
} |
||||
.remixui_storageLogo { |
||||
width: 20px; |
||||
height: 20px; |
||||
} |
||||
.remixui_spinningIcon { |
||||
display: inline-block; |
||||
position: relative; |
||||
animation: spin 2s infinite linear; |
||||
-moz-animation: spin 2s infinite linear; |
||||
-o-animation: spin 2s infinite linear; |
||||
-webkit-animation: spin 2s infinite linear; |
||||
} |
||||
@keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-webkit-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-moz-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-o-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
@-ms-keyframes spin { |
||||
0% { transform: rotate(0deg); } |
||||
100% { transform: rotate(360deg); } |
||||
} |
||||
|
||||
.remixui_bouncingIcon { |
||||
display: inline-block; |
||||
position: relative; |
||||
-moz-animation: bounce 2s infinite linear; |
||||
-o-animation: bounce 2s infinite linear; |
||||
-webkit-animation: bounce 2s infinite linear; |
||||
animation: bounce 2s infinite linear; |
||||
} |
||||
|
||||
@-webkit-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@-moz-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@-o-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@-ms-keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
||||
@keyframes bounce { |
||||
0% { top: 0; } |
||||
50% { top: -0.2em; } |
||||
70% { top: -0.3em; } |
||||
100% { top: 0; } |
||||
} |
@ -0,0 +1,97 @@ |
||||
import React, { useState, useEffect } from 'react' // eslint-disable-line
|
||||
import { SolidityCompilerProps } from './types' |
||||
import { CompilerContainer } from './compiler-container' // eslint-disable-line
|
||||
import CompileTabLogic from './compileTabLogic' |
||||
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
|
||||
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||
|
||||
import './css/style.css' |
||||
|
||||
export const SolidityCompiler = (props: SolidityCompilerProps) => { |
||||
const { editor, config, fileProvider, fileManager, contentImport, queryParams, plugin } = props |
||||
const [state, setState] = useState({ |
||||
contractsDetails: {}, |
||||
eventHandlers: {}, |
||||
loading: false, |
||||
compileTabLogic: null, |
||||
compiler: null, |
||||
toasterMsg: '', |
||||
modal: { |
||||
hide: true, |
||||
title: '', |
||||
message: null, |
||||
okLabel: '', |
||||
okFn: () => {}, |
||||
cancelLabel: '', |
||||
cancelFn: () => {}, |
||||
handleHide: null |
||||
} |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
const miscApi = { clearAnnotations } |
||||
const compileTabLogic = new CompileTabLogic(queryParams, fileManager, editor, config, fileProvider, contentImport, miscApi) |
||||
const compiler = compileTabLogic.compiler |
||||
|
||||
compileTabLogic.init() |
||||
setState(prevState => { |
||||
return { ...prevState, compileTabLogic, compiler } |
||||
}) |
||||
}, []) |
||||
|
||||
const toast = (message: string) => { |
||||
setState(prevState => { |
||||
return { ...prevState, toasterMsg: message } |
||||
}) |
||||
} |
||||
|
||||
const clearAnnotations = () => { |
||||
plugin.call('editor', 'clearAnnotations') |
||||
} |
||||
|
||||
const modal = async (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => { |
||||
await setState(prevState => { |
||||
return { |
||||
...prevState, |
||||
modal: { |
||||
...prevState.modal, |
||||
message, |
||||
title, |
||||
okLabel, |
||||
okFn, |
||||
cancelLabel, |
||||
cancelFn |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
const handleHideModal = () => { |
||||
setState(prevState => { |
||||
return { ...prevState, modal: { ...state.modal, hide: true, message: null } } |
||||
}) |
||||
} |
||||
// this.onActivationInternal()
|
||||
return ( |
||||
<> |
||||
<div id="compileTabView"> |
||||
<CompilerContainer editor={editor} config={config} queryParams={queryParams} compileTabLogic={state.compileTabLogic} tooltip={toast} modal={modal} /> |
||||
{/* ${this._view.contractSelection} */} |
||||
<div className="remixui_errorBlobs p-4" data-id="compiledErrors"></div> |
||||
</div> |
||||
<Toaster message={state.toasterMsg} /> |
||||
<ModalDialog |
||||
id='workspacesModalDialog' |
||||
title={ state.modal.title } |
||||
message={ state.modal.message } |
||||
hide={ state.modal.hide } |
||||
okLabel={ state.modal.okLabel } |
||||
cancelLabel={ state.modal.cancelLabel } |
||||
handleHide={ handleHideModal }> |
||||
{ (typeof state.modal.message !== 'string') && state.modal.message } |
||||
</ModalDialog> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
export default SolidityCompiler |
@ -0,0 +1,24 @@ |
||||
|
||||
export interface SolidityCompilerProps { |
||||
editor: any, |
||||
config: any, |
||||
fileProvider: any, |
||||
fileManager: any, |
||||
contentImport: any, |
||||
plugin: any, |
||||
queryParams: any |
||||
} |
||||
|
||||
export interface CompilerContainerProps { |
||||
editor: any, |
||||
config: any, |
||||
queryParams: any, |
||||
compileTabLogic: any, |
||||
tooltip: (message: string) => void, |
||||
modal: (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => void |
||||
} |
||||
export interface ContractSelectionProps { |
||||
contractMap: { |
||||
file: string |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
Loading…
Reference in new issue