Merge branch 'master' of https://github.com/ethereum/remix-project into cherry
commit
fbc18a2f23
@ -0,0 +1,121 @@ |
||||
'use strict' |
||||
|
||||
/** |
||||
* returns the fork name for the @argument networkId and @argument blockNumber |
||||
* |
||||
* @param {Object} networkId - network Id (1 for VM, 3 for Ropsten, 4 for Rinkeby, 5 for Goerli) |
||||
* @param {Object} blockNumber - block number |
||||
* @return {String} - fork name (Berlin, Istanbul, ...) |
||||
*/ |
||||
export function forkAt (networkId, blockNumber) { |
||||
if (forks[networkId]) { |
||||
let currentForkName = forks[networkId][0].name |
||||
for (const fork of forks[networkId]) { |
||||
if (blockNumber >= fork.number) { |
||||
currentForkName = fork.name |
||||
} |
||||
} |
||||
return currentForkName |
||||
} |
||||
return 'berlin' |
||||
} |
||||
|
||||
// see https://github.com/ethereum/go-ethereum/blob/master/params/config.go
|
||||
const forks = { |
||||
1: [ |
||||
{ |
||||
number: 4370000, |
||||
name: 'byzantium' |
||||
}, |
||||
{ |
||||
number: 7280000, |
||||
name: 'constantinople' |
||||
}, |
||||
{ |
||||
number: 7280000, |
||||
name: 'petersburg' |
||||
}, |
||||
{ |
||||
number: 9069000, |
||||
name: 'istanbul' |
||||
}, |
||||
{ |
||||
number: 9200000, |
||||
name: 'muirglacier' |
||||
}, |
||||
{ |
||||
number: 12244000, |
||||
name: 'berlin' |
||||
} |
||||
], |
||||
3: [ |
||||
{ |
||||
number: 1700000, |
||||
name: 'byzantium' |
||||
}, |
||||
{ |
||||
number: 4230000, |
||||
name: 'constantinople' |
||||
}, |
||||
{ |
||||
number: 4939394, |
||||
name: 'petersburg' |
||||
}, |
||||
{ |
||||
number: 6485846, |
||||
name: 'istanbul' |
||||
}, |
||||
{ |
||||
number: 7117117, |
||||
name: 'muirglacier' |
||||
}, |
||||
{ |
||||
number: 9812189, |
||||
name: 'berlin' |
||||
}, |
||||
{ |
||||
number: 10499401, |
||||
name: 'london' |
||||
} |
||||
], |
||||
4: [ |
||||
{ |
||||
number: 1035301, |
||||
name: 'byzantium' |
||||
}, |
||||
{ |
||||
number: 3660663, |
||||
name: 'constantinople' |
||||
}, |
||||
{ |
||||
number: 4321234, |
||||
name: 'petersburg' |
||||
}, |
||||
{ |
||||
number: 5435345, |
||||
name: 'istanbul' |
||||
}, |
||||
{ |
||||
number: 8290928, |
||||
name: 'berlin' |
||||
}, |
||||
{ |
||||
number: 8897988, |
||||
name: 'london' |
||||
} |
||||
], |
||||
5: [ |
||||
{ |
||||
number: 1561651, |
||||
name: 'istanbul' |
||||
}, |
||||
{ |
||||
number: 4460644, |
||||
name: 'berlin' |
||||
}, |
||||
{ |
||||
number: 5062605, |
||||
name: 'london' |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,19 @@ |
||||
{ |
||||
"env": { |
||||
"browser": true, |
||||
"es6": true |
||||
}, |
||||
"extends": "../../../.eslintrc", |
||||
"globals": { |
||||
"Atomics": "readonly", |
||||
"SharedArrayBuffer": "readonly" |
||||
}, |
||||
"parserOptions": { |
||||
"ecmaVersion": 11, |
||||
"sourceType": "module" |
||||
}, |
||||
"rules": { |
||||
"no-unused-vars": "off", |
||||
"@typescript-eslint/no-unused-vars": "error" |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
# remix-ui-settings |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-settings` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-ui-settings' |
@ -0,0 +1,12 @@ |
||||
export const generateContractMetadataText = 'Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.' |
||||
export const textSecondary = 'text-secondary' |
||||
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 gitAccessTokenTitle = 'Github Access Token' |
||||
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 gitAccessTokenLink = 'https://github.com/settings/tokens' |
||||
export const ethereunVMText = 'Always use Ethereum VM at load' |
||||
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 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 ' |
@ -0,0 +1,166 @@ |
||||
import React, { useState, useReducer, useEffect, useCallback } from 'react' // 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 './remix-ui-settings.css' |
||||
import { etherumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast } from './settingsAction' |
||||
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer' |
||||
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
|
||||
|
||||
/* eslint-disable-next-line */ |
||||
export interface RemixUiSettingsProps { |
||||
config: any, |
||||
editor: any, |
||||
_deps: any |
||||
} |
||||
|
||||
export const RemixUiSettings = (props: RemixUiSettingsProps) => { |
||||
const [, dispatch] = useReducer(settingReducer, initialState) |
||||
const [state, dispatchToast] = useReducer(toastReducer, toastInitialState) |
||||
const [tokenValue, setTokenValue] = useState('') |
||||
const [themeName, setThemeName] = useState('') |
||||
|
||||
useEffect(() => { |
||||
const token = props.config.get('settings/gist-access-token') |
||||
if (token === undefined) { |
||||
props.config.set('settings/generate-contract-metadata', true) |
||||
dispatch({ type: 'contractMetadata', payload: { name: 'contractMetadata', isChecked: true, textClass: textDark } }) |
||||
} |
||||
if (token) { |
||||
setTokenValue(token) |
||||
} |
||||
}, [themeName, state.message]) |
||||
|
||||
const onchangeGenerateContractMetadata = (event) => { |
||||
generateContractMetadat(props, event, dispatch) |
||||
} |
||||
|
||||
const onchangeOption = (event) => { |
||||
etherumVM(props, event, dispatch) |
||||
} |
||||
|
||||
const textWrapEvent = (event) => { |
||||
textWrapEventAction(props, event, dispatch) |
||||
} |
||||
|
||||
const onchangePersonal = event => { |
||||
personal(props, event, dispatch) |
||||
} |
||||
|
||||
const onchangeMatomoAnalytics = event => { |
||||
useMatomoAnalytics(props, event, dispatch) |
||||
} |
||||
|
||||
const onswitchTheme = (event, name) => { |
||||
props._deps.themeModule.switchTheme(name) |
||||
setThemeName(name) |
||||
} |
||||
|
||||
const getTextClass = (key) => { |
||||
if (props.config.get(key)) { |
||||
return textDark |
||||
} else { |
||||
return textSecondary |
||||
} |
||||
} |
||||
|
||||
const generalConfig = () => ( |
||||
<div className="$border-top"> |
||||
<div className="card-body pt-3 pb-2"> |
||||
<h6 className="card-title">General settings</h6> |
||||
<div className="mt-2 custom-control custom-checkbox mb-1"> |
||||
<input onChange={onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" className="custom-control-input" name="contractMetadata" checked = { props.config.get('settings/generate-contract-metadata') }/> |
||||
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/generate-contract-metadata')}`} data-id="settingsTabGenerateContractMetadataLabel" htmlFor="generatecontractmetadata">{generateContractMetadataText}</label> |
||||
</div> |
||||
<div className="fmt-2 custom-control custom-checkbox mb-1"> |
||||
<input onChange={onchangeOption} className="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox" name="ethereumVM" checked={ props.config.get('settings/always-use-vm') }/> |
||||
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/always-use-vm')}`} htmlFor="alwaysUseVM">{ethereunVMText}</label> |
||||
</div> |
||||
<div className="mt-2 custom-control custom-checkbox mb-1"> |
||||
<input id="editorWrap" className="custom-control-input" type="checkbox" onChange={textWrapEvent} checked = { props.config.get('settings/text-wrap')}/> |
||||
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/text-wrap')}`} htmlFor="editorWrap">{wordWrapText}</label> |
||||
</div> |
||||
<div className="custom-control custom-checkbox mb-1"> |
||||
<input onChange={onchangePersonal} id="personal" type="checkbox" className="custom-control-input" checked = { props.config.get('settings/personal-mode')}/> |
||||
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/personal-mode')}`} htmlFor="personal"> |
||||
<i className="fas fa-exclamation-triangle text-warning" aria-hidden="true"></i> <span> </span> |
||||
<span> </span>{enablePersonalModeText} {warnText} |
||||
</label> |
||||
</div> |
||||
<div className="custom-control custom-checkbox mb-1"> |
||||
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={ props.config.get('settings/matomo-analytics')}/> |
||||
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/matomo-analytics')}`} htmlFor="settingsMatomoAnalytics"> |
||||
<span>{matomoAnalytics}</span> |
||||
<a href="https://medium.com/p/66ef69e14931/" target="_blank"> Analytics in Remix IDE</a> <span>&</span> <a target="_blank" href="https://matomo.org/free-software">Matomo</a> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
|
||||
const saveToken = () => { |
||||
saveTokenToast(props, dispatchToast, tokenValue) |
||||
} |
||||
|
||||
const removeToken = () => { |
||||
setTokenValue('') |
||||
removeTokenToast(props, dispatchToast) |
||||
} |
||||
|
||||
const handleSaveTokenState = useCallback( |
||||
(event) => { |
||||
setTokenValue(event.target.value) |
||||
}, |
||||
[tokenValue] |
||||
) |
||||
|
||||
const gistToken = () => ( |
||||
<div className="border-top"> |
||||
<div className="card-body pt-3 pb-2"> |
||||
<h6 className="card-title">{ gitAccessTokenTitle }</h6> |
||||
<p className="mb-1">{ gitAccessTokenText }</p> |
||||
<p className="">{ gitAccessTokenText2 }</p> |
||||
<p className="mb-1"><a className="text-primary" target="_blank" href="https://github.com/settings/tokens">{ gitAccessTokenLink }</a></p> |
||||
<div className=""><label>TOKEN:</label> |
||||
<div className="text-secondary mb-0 h6"> |
||||
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={handleSaveTokenState} value={ tokenValue } /> |
||||
<div className="d-flex justify-content-end pt-2"> |
||||
<CopyToClipboard content={tokenValue} 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> |
||||
<button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onClick={() => removeToken()}>Remove</button> |
||||
</div> |
||||
</div></div> |
||||
</div> |
||||
</div> |
||||
) |
||||
|
||||
const themes = () => { |
||||
const themes = props._deps.themeModule.getThemes() |
||||
if (themes) { |
||||
return themes.map((aTheme, index) => ( |
||||
<div className="radio custom-control custom-radio mb-1 form-check" key={index}> |
||||
<input type="radio" onChange={event => { onswitchTheme(event, aTheme.name) }} className="align-middle custom-control-input" name='theme' id={aTheme.name} data-id={`settingsTabTheme${aTheme.name}`} checked = {props._deps.themeModule.active === aTheme.name ? true : null}/> |
||||
<label className="form-check-label custom-control-label" data-id={`settingsTabThemeLabel${aTheme.name}`} htmlFor={aTheme.name}>{aTheme.name} ({aTheme.quality})</label> |
||||
</div> |
||||
) |
||||
) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
{state.message ? <Toaster message= {state.message}/> : null} |
||||
{generalConfig()} |
||||
{gistToken()} |
||||
<div className="border-top"> |
||||
<div className="card-body pt-3 pb-2"> |
||||
<h6 className="card-title">Themes</h6> |
||||
<div className="card-text themes-container"> |
||||
{themes()} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
@ -0,0 +1,52 @@ |
||||
import { textDark, textSecondary } from './constants' |
||||
|
||||
declare global { |
||||
interface Window { |
||||
_paq: any |
||||
} |
||||
} |
||||
|
||||
const _paq = window._paq = window._paq || [] //eslint-disable-line
|
||||
|
||||
export const generateContractMetadat = (element, event, dispatch) => { |
||||
element.config.set('settings/generate-contract-metadata', event.target.checked) |
||||
dispatch({ type: 'contractMetadata', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } }) |
||||
} |
||||
|
||||
export const etherumVM = (element, event, dispatch) => { |
||||
element.config.set('settings/always-use-vm', event.target.checked) |
||||
dispatch({ type: 'ethereumVM', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } }) |
||||
} |
||||
|
||||
export const textWrapEventAction = (element, event, dispatch) => { |
||||
element.config.set('settings/text-wrap', event.target.checked) |
||||
element.editor.resize(event.target.checked) |
||||
dispatch({ type: 'textWrap', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } }) |
||||
} |
||||
|
||||
export const personal = (element, event, dispatch) => { |
||||
element.config.set('settings/personal-mode', event.target.checked) |
||||
dispatch({ type: 'personal', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } }) |
||||
} |
||||
|
||||
export const useMatomoAnalytics = (element, event, dispatch) => { |
||||
element.config.set('settings/matomo-analytics', event.target.checked) |
||||
dispatch({ type: 'useMatomoAnalytics', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } }) |
||||
if (event.target.checked) { |
||||
_paq.push(['forgetUserOptOut']) |
||||
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
|
||||
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' |
||||
} else { |
||||
_paq.push(['optUserOut']) |
||||
} |
||||
} |
||||
|
||||
export const saveTokenToast = (props, dispatch, tokenValue) => { |
||||
props.config.set('settings/gist-access-token', tokenValue) |
||||
dispatch({ type: 'save', payload: { message: 'Access token has been saved' } }) |
||||
} |
||||
|
||||
export const removeTokenToast = (props, dispatch) => { |
||||
props.config.set('settings/gist-access-token', '') |
||||
dispatch({ type: 'removed', payload: { message: 'Access token removed' } }) |
||||
} |
@ -0,0 +1,103 @@ |
||||
import { textSecondary } from './constants' |
||||
|
||||
export const initialState = { |
||||
elementState: [ |
||||
{ |
||||
name: 'contractMetadata', |
||||
isChecked: false, |
||||
textClass: textSecondary |
||||
}, |
||||
{ |
||||
name: 'ethereumVM', |
||||
isChecked: false, |
||||
textClass: textSecondary |
||||
}, |
||||
{ |
||||
name: 'textWrap', |
||||
isChecked: false, |
||||
textClass: textSecondary |
||||
}, |
||||
{ |
||||
name: 'personal', |
||||
isChecked: false, |
||||
textClass: textSecondary |
||||
}, |
||||
{ |
||||
name: 'useMatomoAnalytics', |
||||
isChecked: false, |
||||
textClass: textSecondary |
||||
} |
||||
] |
||||
} |
||||
|
||||
export const settingReducer = (state, action) => { |
||||
switch (action.type) { |
||||
case 'contractMetadata': |
||||
state.elementState.map(element => { |
||||
if (element.name === 'contractMetadata') { |
||||
element.isChecked = action.payload.isChecked |
||||
element.textClass = action.payload.textClass |
||||
} |
||||
}) |
||||
return { |
||||
...state |
||||
} |
||||
case 'ethereumVM': |
||||
state.elementState.map(element => { |
||||
if (element.name === 'ethereumVM') { |
||||
element.isChecked = action.payload.isChecked |
||||
element.textClass = action.payload.textClass |
||||
} |
||||
}) |
||||
return { |
||||
...state |
||||
} |
||||
case 'textWrap': |
||||
state.elementState.map(element => { |
||||
if (element.name === 'textWrap') { |
||||
element.isChecked = action.payload.isChecked |
||||
element.textClass = action.payload.textClass |
||||
} |
||||
}) |
||||
return { |
||||
...state |
||||
} |
||||
case 'personal': |
||||
state.elementState.map(element => { |
||||
if (element.name === 'personal') { |
||||
element.isChecked = action.payload.isChecked |
||||
element.textClass = action.payload.textClass |
||||
} |
||||
}) |
||||
return { |
||||
...state |
||||
} |
||||
case 'useMatomoAnalytics': |
||||
state.elementState.map(element => { |
||||
if (element.name === 'useMatomoAnalytics') { |
||||
element.isChecked = action.payload.isChecked |
||||
element.textClass = action.payload.textClass |
||||
} |
||||
}) |
||||
return { |
||||
...state |
||||
} |
||||
default: |
||||
return initialState |
||||
} |
||||
} |
||||
|
||||
export const toastInitialState = { |
||||
message: '' |
||||
} |
||||
|
||||
export const toastReducer = (state, action) => { |
||||
switch (action.type) { |
||||
case 'save' : |
||||
return { ...state, message: action.payload.message } |
||||
case 'removed' : |
||||
return { ...state, message: action.payload.message } |
||||
default : |
||||
return { ...state, message: '' } |
||||
} |
||||
} |
@ -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