Merge branch 'master' of github.com:ethereum/remix-project

pull/1861/head
Joseph Izang 3 years ago
commit 648604282e
  1. 9
      apps/remix-ide/src/app.js
  2. 44
      apps/remix-ide/src/app/plugins/modal.tsx
  3. 53
      apps/remix-ide/src/app/plugins/test.ts
  4. 2
      libs/remix-solidity/src/lib/eventManager.ts
  5. 1
      libs/remix-tests/package.json
  6. 23
      libs/remix-tests/src/compiler.ts
  7. 21
      libs/remix-tests/src/runTestSources.ts
  8. 3
      libs/remix-ui/app/src/index.ts
  9. 30
      libs/remix-ui/app/src/lib/remix-app/actions/modals.ts
  10. 0
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.css
  11. 0
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  12. 16
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogViewPlugin.tsx
  13. 16
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx
  14. 45
      libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx
  15. 56
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  16. 30
      libs/remix-ui/app/src/lib/remix-app/components/modals/origin-warning.tsx
  17. 0
      libs/remix-ui/app/src/lib/remix-app/components/splashscreen.tsx
  18. 23
      libs/remix-ui/app/src/lib/remix-app/context/context.tsx
  19. 64
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  20. 28
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  21. 51
      libs/remix-ui/app/src/lib/remix-app/modals/matomo.tsx
  22. 46
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  23. 31
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  24. 17
      libs/remix-ui/app/src/lib/remix-app/state/modals.ts
  25. 7
      libs/remix-ui/app/src/lib/remix-app/types/index.ts
  26. 2
      libs/remix-ui/modal-dialog/src/lib/types/index.ts

@ -12,6 +12,7 @@ import { VerticalIcons } from './app/components/vertical-icons'
import { LandingPage } from './app/ui/landing-page/landing-page' import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel' import { MainPanel } from './app/components/main-panel'
import { FramingService } from './framingService' import { FramingService } from './framingService'
import { ModalPluginTester } from './app/plugins/test'
import { WalkthroughService } from './walkthroughService' import { WalkthroughService } from './walkthroughService'
@ -20,6 +21,7 @@ import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, Fetch
import migrateFileSystem from './migrateFileSystem' import migrateFileSystem from './migrateFileSystem'
import Registry from './app/state/registry' import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config' import { ConfigPlugin } from './app/plugins/config'
import { ModalPlugin } from './app/plugins/modal'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -158,9 +160,12 @@ class AppComponent {
) )
const contextualListener = new EditorContextListener() const contextualListener = new EditorContextListener()
self.modal = new ModalPlugin()
const configPlugin = new ConfigPlugin() const configPlugin = new ConfigPlugin()
self.engine.register([ self.engine.register([
self.modal,
configPlugin, configPlugin,
blockchain, blockchain,
contentImport, contentImport,
@ -238,7 +243,9 @@ class AppComponent {
contentImport contentImport
) )
const testplugin = new ModalPluginTester()
self.engine.register([ self.engine.register([
testplugin,
compileTab, compileTab,
run, run,
debug, debug,
@ -266,6 +273,7 @@ class AppComponent {
console.log('couldn\'t register iframe plugins', e.message) console.log('couldn\'t register iframe plugins', e.message)
} }
await self.appManager.activatePlugin(['modal'])
await self.appManager.activatePlugin(['editor']) await self.appManager.activatePlugin(['editor'])
await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter']) await self.appManager.activatePlugin(['theme', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await self.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs']) await self.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
@ -275,6 +283,7 @@ class AppComponent {
await self.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport']) await self.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport'])
await self.appManager.activatePlugin(['settings']) await self.appManager.activatePlugin(['settings'])
await self.appManager.activatePlugin(['walkthrough']) await self.appManager.activatePlugin(['walkthrough'])
await self.appManager.activatePlugin(['testerplugin'])
self.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => { self.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => {
await self.appManager.registerContextMenuItems() await self.appManager.registerContextMenuItems()

@ -0,0 +1,44 @@
import { Plugin } from '@remixproject/engine'
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils'
import { AppModal } from '@remix-ui/app'
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface'
import { dispatchModalInterface } from 'libs/remix-ui/app/src/lib/remix-app/context/context'
interface IModalApi {
events: StatusEvents,
methods: {
modal: (args: AppModal) => void
alert: (args: AlertModal) => void
toast: (message: string) => void
}
}
const profile:LibraryProfile<IModalApi> = {
name: 'modal',
displayName: 'Modal',
description: 'Modal',
methods: ['modal', 'alert', 'toast']
}
export class ModalPlugin extends Plugin implements MethodApi<IModalApi> {
dispatcher: dispatchModalInterface
constructor () {
super(profile)
}
setDispatcher (dispatcher: dispatchModalInterface) {
this.dispatcher = dispatcher
}
async modal (args: AppModal) {
this.dispatcher.modal(args)
}
async alert (args: AlertModal) {
this.dispatcher.alert(args)
}
async toast (message: string) {
this.dispatcher.toast(message)
}
}

@ -0,0 +1,53 @@
import { Plugin } from '@remixproject/engine'
import { Profile } from '@remixproject/plugin-utils'
import { AlertModal } from 'libs/remix-ui/app/src/lib/remix-app/interface'
import { ModalTypes } from 'libs/remix-ui/app/src/lib/remix-app/types'
import { AppModal } from '../../../../../libs/remix-ui/app/src'
const profile:Profile = {
name: 'testerplugin',
displayName: 'testerplugin',
description: 'testerplugin',
methods: []
}
export class ModalPluginTester extends Plugin {
constructor () {
super(profile)
}
handleMessage (message: any): void {
console.log(message)
}
onActivation (): void {
// just a modal
let mod:AppModal = {
id: 'modal1',
title: 'test',
message: 'test',
okFn: this.handleMessage,
okLabel: 'yes',
cancelFn: null,
cancelLabel: 'no'
}
// this.call('modal', 'modal', mod)
// modal with callback
mod = { ...mod, message: 'gist url', modalType: ModalTypes.prompt, defaultValue: 'prompting' }
// this.call('modal', 'modal', mod)
// modal with password
mod = { ...mod, message: 'enter password to give me eth', modalType: ModalTypes.password, defaultValue: 'pass' }
// this.call('modal', 'modal', mod)
const al:AlertModal = {
id: 'myalert',
message: 'alert message'
}
// this.call('modal', 'alert', al)
// set toaster
// this.call('modal', 'toast', 'toast message')
}
}

@ -21,7 +21,7 @@ export default class EventManager {
obj = this.anonymous obj = this.anonymous
} }
for (const reg in this.registered[eventName]) { for (const reg in this.registered[eventName]) {
if (this.registered[eventName][reg].obj === obj && this.registered[eventName][reg].func === func) { if (this.registered[eventName][reg].obj === obj && this.registered[eventName][reg].func.toString() === func.toString()) {
this.registered[eventName].splice(reg, 1) this.registered[eventName].splice(reg, 1)
} }
} }

@ -49,6 +49,7 @@
"color-support": "^1.1.3", "color-support": "^1.1.3",
"colors": "^1.1.2", "colors": "^1.1.2",
"commander": "^2.13.0", "commander": "^2.13.0",
"deep-equal": "^1.0.1",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2", "ethers": "^5.4.2",
"ethjs-util": "^0.1.6", "ethjs-util": "^0.1.6",

@ -170,8 +170,7 @@ export function compileFileOrFiles (filename: string, isDirectory: boolean, opts
* @param opts Options * @param opts Options
* @param cb Callback * @param cb Callback
*/ */
export function compileContractSources (sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb): void { export function compileContractSources (sources: SrcIfc, compiler: any, opts: any, cb): void {
let compiler
const filepath = opts.testFilePath || '' const filepath = opts.testFilePath || ''
const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
@ -184,24 +183,14 @@ export function compileContractSources (sources: SrcIfc, compilerConfig: Compile
} }
async.waterfall([ async.waterfall([
function loadCompiler (next) {
const { currentCompilerUrl, evmVersion, optimize, runs, usingWorker } = compilerConfig
compiler = new RemixCompiler(importFileCb)
compiler.set('evmVersion', evmVersion)
compiler.set('optimize', optimize)
compiler.set('runs', runs)
compiler.loadVersion(usingWorker, currentCompilerUrl)
// @ts-ignore
compiler.event.register('compilerLoaded', this, (version) => {
next()
})
},
function doCompilation (next) { function doCompilation (next) {
// @ts-ignore const compilationFinishedCb = (success, data, source) => {
compiler.event.register('compilationFinished', this, (success, data, source) => {
if (opts && opts.event) opts.event.emit('compilationFinished', success, data, source) if (opts && opts.event) opts.event.emit('compilationFinished', success, data, source)
next(null, data) next(null, data)
}) }
compiler.event.unregister('compilationFinished', compilationFinishedCb)
// @ts-ignore
compiler.event.register('compilationFinished', compilationFinishedCb)
compiler.compile(sources, filepath) compiler.compile(sources, filepath)
} }
], function (err: Error | null | undefined, result: any) { ], function (err: Error | null | undefined, result: any) {

@ -1,4 +1,6 @@
import async, { ErrorCallback } from 'async' import async, { ErrorCallback } from 'async'
import deepequal from 'deep-equal'
import { Compiler as RemixCompiler } from '@remix-project/remix-solidity'
import { compileContractSources, writeTestAccountsContract } from './compiler' import { compileContractSources, writeTestAccountsContract } from './compiler'
import { deployAll } from './deployer' import { deployAll } from './deployer'
import { runTest } from './testRunner' import { runTest } from './testRunner'
@ -16,6 +18,8 @@ export class UnitTestRunner {
accountsLibCode accountsLibCode
testsAccounts: string[] | null testsAccounts: string[] | null
web3 web3
compiler
compilerConfig
constructor () { constructor () {
this.event = new EventEmitter() this.event = new EventEmitter()
@ -53,7 +57,22 @@ export class UnitTestRunner {
async.waterfall([ async.waterfall([
(next) => { (next) => {
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts: this.testsAccounts, testFilePath: opts.testFilePath, event: this.event }, next) if (!deepequal(this.compilerConfig, compilerConfig)) {
this.compilerConfig = compilerConfig
const { currentCompilerUrl, evmVersion, optimize, runs, usingWorker } = compilerConfig
this.compiler = new RemixCompiler(importFileCb)
this.compiler.set('evmVersion', evmVersion)
this.compiler.set('optimize', optimize)
this.compiler.set('runs', runs)
this.compiler.loadVersion(usingWorker, currentCompilerUrl)
// @ts-ignore
this.compiler.event.register('compilerLoaded', this, (version) => {
next()
})
} else next()
},
(next) => {
compileContractSources(contractSources, this.compiler, { accounts: this.testsAccounts, testFilePath: opts.testFilePath, event: this.event }, next)
}, },
(compilationResult: compilationInterface, asts: ASTInterface, next) => { (compilationResult: compilationInterface, asts: ASTInterface, next) => {
for (const filename in asts) { for (const filename in asts) {

@ -1 +1,4 @@
export { default as RemixApp } from './lib/remix-app/remix-app' export { default as RemixApp } from './lib/remix-app/remix-app'
export { dispatchModalContext } from './lib/remix-app/context/context'
export { ModalProvider } from './lib/remix-app/context/provider'
export { AppModal } from './lib/remix-app/interface/index'

@ -0,0 +1,30 @@
import { AppModal } from '../interface'
type ActionMap<M extends { [index: string]: any }> = {
[Key in keyof M]: M[Key] extends undefined
? {
type: Key;
}
: {
type: Key;
payload: M[Key];
}
}
export const enum modalActionTypes {
setModal = 'SET_MODAL',
setToast = 'SET_TOAST',
handleHideModal = 'HANDLE_HIDE_MODAL',
handleToaster = 'HANDLE_HIDE_TOAST'
}
type ModalPayload = {
[modalActionTypes.setModal]: AppModal
[modalActionTypes.handleHideModal]: any
[modalActionTypes.setToast]: string
[modalActionTypes.handleToaster]: any
}
export type ModalAction = ActionMap<ModalPayload>[keyof ActionMap<
ModalPayload
>]

@ -0,0 +1,16 @@
import React, { useContext, useEffect } from 'react'
import { AppContext } from '../../context/context'
import { useDialogDispatchers } from '../../context/provider'
const DialogViewPlugin = () => {
const { modal, alert, toast } = useDialogDispatchers()
const app = useContext(AppContext)
useEffect(() => {
console.log(modal, app)
app.modal.setDispatcher({ modal, alert, toast })
}, [])
return <></>
}
export default DialogViewPlugin

@ -0,0 +1,16 @@
import React from 'react'
import { useDialogDispatchers, useDialogs } from '../../context/provider'
import { Toaster } from '@remix-ui/toaster'
import ModalWrapper from './modal-wrapper'
const AppDialogs = () => {
const { handleHideModal, handleToaster } = useDialogDispatchers()
const { focusModal, focusToaster } = useDialogs()
return (
<>
<ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper>
<Toaster message={focusToaster} handleHide={handleToaster} />
</>)
}
export default AppDialogs

@ -0,0 +1,45 @@
import React, { useContext, useEffect, useState } from 'react'
import { AppContext } from '../../context/context'
import { useDialogDispatchers } from '../../context/provider'
const _paq = window._paq = window._paq || []
const MatomoDialog = (props) => {
const { settings, showMatamo, appManager } = useContext(AppContext)
const { modal } = useDialogDispatchers()
const [visible, setVisible] = useState<boolean>(props.hide)
const message = () => {
return (<><p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p>
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p>
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p>
<p>We do not collect nor store any personally identifiable information (PII).</p>
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p>
<p>You can change your choice in the Settings panel anytime.</p></>)
}
useEffect(() => {
if (visible && showMatamo) {
modal({ id: 'matomoModal', title: 'Help us to improve Remix IDE', message: message(), okLabel: 'Accept', okFn: handleModalOkClick, cancelLabel: 'Decline', cancelFn: declineModal })
}
}, [visible])
const declineModal = async () => {
settings.updateMatomoAnalyticsChoice(false)
_paq.push(['optUserOut'])
appManager.call('walkthrough', 'start')
setVisible(false)
}
const handleModalOkClick = async () => {
_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;'
settings.updateMatomoAnalyticsChoice(true)
appManager.call('walkthrough', 'start')
setVisible(false)
}
return (<></>)
}
export default MatomoDialog

@ -0,0 +1,56 @@
import React, { useEffect, useRef, useState } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog'
import { ModalDialogProps } from 'libs/remix-ui/modal-dialog/src/lib/types'
import { ModalTypes } from '../../types'
interface ModalWrapperProps extends ModalDialogProps {
modalType?: ModalTypes
defaultValue?: string
}
const ModalWrapper = (props: ModalWrapperProps) => {
const [state, setState] = useState<ModalDialogProps>()
const ref = useRef()
const onFinishPrompt = async () => {
if (ref.current === undefined) {
props.okFn()
} else {
// @ts-ignore: Object is possibly 'null'.
props.okFn(ref.current.value)
}
}
const createModalMessage = (defaultValue: string) => {
return (
<>
{props.message}
<input type={props.modalType === ModalTypes.password ? 'password' : 'text'} defaultValue={defaultValue} data-id="modalDialogCustomPromp" ref={ref} className="form-control" /></>
)
}
useEffect(() => {
if (props.modalType) {
switch (props.modalType) {
case ModalTypes.prompt:
case ModalTypes.password:
setState({
...props,
okFn: onFinishPrompt,
message: createModalMessage(props.defaultValue)
})
break
default:
setState({ ...props })
break
}
} else {
setState({ ...props })
}
}, [props])
return (
<ModalDialog id={props.id} {...state} handleHide={props.handleHide} />
)
}
export default ModalWrapper

@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog' import { ModalDialog } from '@remix-ui/modal-dialog'
import { useDialogDispatchers } from '../../context/provider'
const AlertModal = () => { const OriginWarning = () => {
const [visible, setVisible] = useState<boolean>(true) const { alert } = useDialogDispatchers()
const [content, setContent] = useState<string>('') const [content, setContent] = useState<string>(null)
useEffect(() => { useEffect(() => {
// check the origin and warn message // check the origin and warn message
@ -20,24 +21,15 @@ const AlertModal = () => {
This instance of Remix you are visiting WILL NOT BE UPDATED.\n This instance of Remix you are visiting WILL NOT BE UPDATED.\n
Please make a backup of your contracts and start using http://remix.ethereum.org`) Please make a backup of your contracts and start using http://remix.ethereum.org`)
} }
setVisible(content !== '')
}, []) }, [])
const closeModal = async () => { useEffect(() => {
setVisible(false) if (content) {
} alert({ id: 'warningOriging', title: null, message: content })
const handleModalOkClick = async () => {
setVisible(false)
} }
return (<ModalDialog }, [content])
handleHide={closeModal}
id="appAlert" return (<></>)
hide={!visible}
title="Alert"
okLabel="Ok"
okFn={ handleModalOkClick }
cancelLabel=""
cancelFn={closeModal}>{content}</ModalDialog>)
} }
export default AlertModal export default OriginWarning

@ -1,5 +1,24 @@
import React from 'react' import React from 'react'
import { AlertModal, AppModal } from '../interface'
import { ModalInitialState } from '../state/modals'
import { ModalTypes } from '../types'
const AppContext = React.createContext(null) export const AppContext = React.createContext<any>(null)
export default AppContext export interface dispatchModalInterface {
modal: (data: AppModal) => void
toast: (message: string) => void
alert: (data: AlertModal) => void
handleHideModal: () => void,
handleToaster: () => void
}
export const dispatchModalContext = React.createContext<dispatchModalInterface>({
modal: (data: AppModal) => { },
toast: (message: string) => {},
alert: (data: AlertModal) => {},
handleHideModal: () => {},
handleToaster: () => {}
})
export const modalContext = React.createContext(ModalInitialState)

@ -0,0 +1,64 @@
import React, { useReducer } from 'react'
import { modalActionTypes } from '../actions/modals'
import { AlertModal, AppModal } from '../interface'
import { modalReducer } from '../reducer/modals'
import { ModalInitialState } from '../state/modals'
import { ModalTypes } from '../types'
import { AppContext, dispatchModalContext, modalContext } from './context'
export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => {
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState)
const modal = (data: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue } = data
dispatch({
type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue }
})
}
const alert = (data: AlertModal) => {
modal({ id: data.id, title: data.title || 'Alert', message: data.message || data.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} })
}
const handleHideModal = () => {
dispatch({
type: modalActionTypes.handleHideModal,
payload: null
})
}
const toast = (message: string) => {
dispatch({
type: modalActionTypes.setToast,
payload: message
})
}
const handleToaster = () => {
dispatch({
type: modalActionTypes.handleToaster,
payload: null
})
}
return (<dispatchModalContext.Provider value={{ modal, toast, alert, handleHideModal, handleToaster }}>
<modalContext.Provider value={{ modals, toasters, focusModal, focusToaster }}>
{children}
</modalContext.Provider>
</dispatchModalContext.Provider>)
}
export const AppProvider = ({ children = [], value = {} } = {}) => {
return <AppContext.Provider value={value}>
<ModalProvider>{children}</ModalProvider>
</AppContext.Provider>
}
export const useDialogs = () => {
return React.useContext(modalContext)
}
export const useDialogDispatchers = () => {
return React.useContext(dispatchModalContext)
}

@ -0,0 +1,28 @@
import { ModalTypes } from '../types'
export interface AppModal {
id: string
hide?: boolean
title: string
// eslint-disable-next-line no-undef
message: string | JSX.Element
okLabel: string
okFn: (value?:any) => void
cancelLabel: string
cancelFn: () => void,
modalType?: ModalTypes,
defaultValue?: string
}
export interface AlertModal {
id: string
title?: string,
message: string | JSX.Element,
}
export interface ModalState {
modals: AppModal[],
toasters: string[],
focusModal: AppModal,
focusToaster: string
}

@ -1,51 +0,0 @@
import React, { useContext, useEffect, useState } from 'react'
import { ModalDialog } from '@remix-ui/modal-dialog'
import AppContext from '../context/context'
const _paq = window._paq = window._paq || []
const MatomoDialog = (props) => {
const { settings, showMatamo, appManager } = useContext(AppContext)
const [visible, setVisible] = useState<boolean>(props.hide)
useEffect(() => {
if (showMatamo) {
setVisible(true)
} else {
setVisible(false)
}
}, [])
const declineModal = async () => {
settings.updateMatomoAnalyticsChoice(false)
_paq.push(['optUserOut'])
appManager.call('walkthrough', 'start')
setVisible(false)
}
const hideModal = async () => {
setVisible(false)
}
const handleModalOkClick = async () => {
_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;'
settings.updateMatomoAnalyticsChoice(true)
appManager.call('walkthrough', 'start')
setVisible(false)
}
return (<ModalDialog
handleHide={hideModal}
id="matomoDialog"
hide={!visible}
title="Help us to improve Remix IDE"
okLabel="Accept"
okFn={ handleModalOkClick }
cancelLabel="Decline"
cancelFn={declineModal}>
<p>An Opt-in version of <a href="https://matomo.org" target="_blank" rel="noreferrer">Matomo</a>, an open source data analytics platform is being used to improve Remix IDE.</p>
<p>We realize that our users have sensitive information in their code and that their privacy - your privacy - must be protected.</p>
<p>All data collected through Matomo is stored on our own server - no data is ever given to third parties. Our analytics reports are public: <a href="https://matomo.ethereum.org/index.php?module=MultiSites&action=index&idSite=23&period=day&date=yesterday" target="_blank" rel="noreferrer">take a look</a>.</p>
<p>We do not collect nor store any personally identifiable information (PII).</p>
<p>For more info, see: <a href="https://medium.com/p/66ef69e14931/" target="_blank" rel="noreferrer">Matomo Analyitcs on Remix iDE</a>.</p>
<p>You can change your choice in the Settings panel anytime.</p>
</ModalDialog>)
}
export default MatomoDialog

@ -0,0 +1,46 @@
import { modalActionTypes, ModalAction } from '../actions/modals'
import { ModalInitialState } from '../state/modals'
import { AppModal, ModalState } from '../interface'
export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => {
switch (action.type) {
case modalActionTypes.setModal: {
let modalList:AppModal[] = state.modals
modalList.push(action.payload)
if (state.modals.length === 1 && state.focusModal.hide === true) { // if it's the first one show it
const focusModal: AppModal = {
id: modalList[0].id,
hide: false,
title: modalList[0].title,
message: modalList[0].message,
okLabel: modalList[0].okLabel,
okFn: modalList[0].okFn,
cancelLabel: modalList[0].cancelLabel,
cancelFn: modalList[0].cancelFn,
modalType: modalList[0].modalType,
defaultValue: modalList[0].defaultValue
}
modalList = modalList.slice()
modalList.shift()
return { ...state, modals: modalList, focusModal: focusModal }
}
return { ...state, modals: modalList }
}
case modalActionTypes.handleHideModal:
state.focusModal = { ...state.focusModal, hide: true, message: null }
return { ...state }
case modalActionTypes.setToast:
state.toasters.push(action.payload)
if (state.toasters.length > 0) {
const focus = state.toasters[0]
state.toasters.shift()
return { ...state, focusToaster: focus }
}
return { ...state }
case modalActionTypes.handleToaster:
return { ...state, focusToaster: '' }
}
}

@ -1,10 +1,13 @@
import React, { useContext, useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import './style/remix-app.css' import './style/remix-app.css'
import RemixSplashScreen from './modals/splashscreen' import RemixSplashScreen from './components/splashscreen'
import MatomoDialog from './modals/matomo' import MatomoDialog from './components/modals/matomo'
import AlertModal from './modals/alert' import OriginWarning from './components/modals/origin-warning'
import AppContext from './context/context' import DragBar from './components/dragbar/dragbar'
import DragBar from './dragbar/dragbar' import { AppProvider } from './context/provider'
import AppDialogs from './components/modals/dialogs'
import DialogViewPlugin from './components/modals/dialogViewPlugin'
interface IRemixAppUi { interface IRemixAppUi {
app: any app: any
} }
@ -68,10 +71,17 @@ const RemixApp = (props: IRemixAppUi) => {
hiddenPanel: <div ref={hiddenPanelRef}></div> hiddenPanel: <div ref={hiddenPanelRef}></div>
} }
const value = {
settings: props.app.settings,
showMatamo: props.app.showMatamo,
appManager: props.app.appManager,
modal: props.app.modal
}
return ( return (
<AppContext.Provider value={{ settings: props.app.settings, showMatamo: props.app.showMatamo, appManager: props.app.appManager }}> <AppProvider value={value}>
<RemixSplashScreen hide={appReady}></RemixSplashScreen> <RemixSplashScreen hide={appReady}></RemixSplashScreen>
<AlertModal></AlertModal> <OriginWarning></OriginWarning>
<MatomoDialog hide={!appReady}></MatomoDialog> <MatomoDialog hide={!appReady}></MatomoDialog>
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE"> <div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE">
@ -82,8 +92,9 @@ const RemixApp = (props: IRemixAppUi) => {
</div> </div>
{components.hiddenPanel} {components.hiddenPanel}
</AppContext.Provider> <AppDialogs></AppDialogs>
<DialogViewPlugin></DialogViewPlugin>
</AppProvider>
) )
} }

@ -0,0 +1,17 @@
import { ModalState } from '../interface'
export const ModalInitialState: ModalState = {
modals: [],
toasters: [],
focusModal: {
id: '',
hide: true,
title: '',
message: '',
okLabel: '',
okFn: () => { },
cancelLabel: '',
cancelFn: () => { }
},
focusToaster: ''
}

@ -0,0 +1,7 @@
export const enum ModalTypes {
alert = 'alert',
confirm = 'confirm',
prompt = 'prompt',
password = 'password',
default = 'default',
}

@ -4,7 +4,7 @@ export interface ModalDialogProps {
title?: string, title?: string,
message?: string | JSX.Element, message?: string | JSX.Element,
okLabel?: string, okLabel?: string,
okFn?: () => void, okFn?: (value?:any) => void,
cancelLabel?: string, cancelLabel?: string,
cancelFn?: () => void, cancelFn?: () => void,
modalClass?: string, modalClass?: string,

Loading…
Cancel
Save