Merge pull request #1883 from ethereum/remixdreact

remixd handle in react
remixdreact
David Disu 3 years ago committed by GitHub
commit a48d8c2d3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      apps/remix-ide-e2e/src/tests/importFromGithub.test.ts
  2. 4
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  3. 2
      apps/remix-ide/src/app/panels/file-panel.js
  4. 139
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  5. 1
      apps/remix-ide/tsconfig.json
  6. 1
      libs/remix-ui/app/src/index.ts
  7. 1
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogViewPlugin.tsx
  8. 4
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  9. 1
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  10. 6
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  11. 14
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx

@ -31,8 +31,6 @@ module.exports = {
'Display Error Message For Invalid GitHub URL Modal': function (browser: NightwatchBrowser) { 'Display Error Message For Invalid GitHub URL Modal': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings')
.clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]') .scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]') .waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
.execute(() => { .execute(() => {
@ -48,8 +46,6 @@ module.exports = {
'Import From Github For Valid URL': function (browser: NightwatchBrowser) { 'Import From Github For Valid URL': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings')
.clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]') .scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]') .waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]')
.clearValue('*[data-id="homeTabModalDialogCustomPromptText"]') .clearValue('*[data-id="homeTabModalDialogCustomPromptText"]')

@ -125,9 +125,9 @@ function startRemixd (browser: NightwatchBrowser) {
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.clickLaunchIcon('pluginManager') .clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager *[data-id="pluginManagerComponentActivateButtonremixd"]') .scrollAndClick('#pluginManager *[data-id="pluginManagerComponentActivateButtonremixd"]')
.waitForElementVisible('#modal-footer-ok', 2000) .waitForElementVisible('*[data-id="remixdConnect-modal-footer-ok-react"]', 2000)
.pause(2000) .pause(2000)
.click('#modal-footer-ok') .click('*[data-id="remixdConnect-modal-footer-ok-react"]')
// .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]') // .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]')
} }

@ -5,7 +5,7 @@ import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line
import Registry from '../state/registry' import Registry from '../state/registry'
const { RemixdHandle } = require('../files/remixd-handle.js') import { RemixdHandle } from '../plugins/remixd-handle'
const { GitHandle } = require('../files/git-handle.js') const { GitHandle } = require('../files/git-handle.js')
const { HardhatHandle } = require('../files/hardhat-handle.js') const { HardhatHandle } = require('../files/hardhat-handle.js')
const { SlitherHandle } = require('../files/slither-handle.js') const { SlitherHandle } = require('../files/slither-handle.js')

@ -1,24 +1,13 @@
/* eslint-disable no-unused-vars */
import React, { useRef, useState, useEffect } from 'react' // eslint-disable-line
import isElectron from 'is-electron' import isElectron from 'is-electron'
import { WebsocketPlugin } from '@remixproject/engine-web' import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { version as remixdVersion } from '../../../../../libs/remixd/package.json' import { version as remixdVersion } from '../../../../../libs/remixd/package.json'
var yo = require('yo-yo') import { PluginManager } from '@remixproject/engine'
var modalDialog = require('../ui/modaldialog') import { AppModal, AlertModal } from '@remix-ui/app'
var modalDialogCustom = require('../ui/modal-dialog-custom') import { CopyToClipboard } from '@remix-ui/clipboard'
var copyToClipboard = require('../ui/copy-to-clipboard')
var csjs = require('csjs-inject')
var css = csjs`
.dialog {
display: flex;
flex-direction: column;
}
.dialogParagraph {
margin-bottom: 2em;
word-break: break-word;
}
`
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
const profile = { const profile = {
@ -32,29 +21,37 @@ const profile = {
version: packageJson.version version: packageJson.version
} }
enum State {
ok,
cancel,
new
}
export class RemixdHandle extends WebsocketPlugin { export class RemixdHandle extends WebsocketPlugin {
localhostProvider: any
appManager: PluginManager
state: State
constructor (localhostProvider, appManager) { constructor (localhostProvider, appManager) {
super(profile) super(profile)
this.localhostProvider = localhostProvider this.localhostProvider = localhostProvider
this.appManager = appManager this.appManager = appManager
} }
deactivate () { async deactivate () {
if (super.socket) super.deactivate() if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342 // this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
if (this.appManager.actives.includes('hardhat')) this.appManager.deactivatePlugin('hardhat') if (this.appManager.isActive('hardhat')) this.appManager.deactivatePlugin('hardhat')
if (this.appManager.actives.includes('slither')) this.appManager.deactivatePlugin('slither') if (this.appManager.isActive('slither')) this.appManager.deactivatePlugin('slither')
this.localhostProvider.close((error) => { this.localhostProvider.close((error) => {
if (error) console.log(error) if (error) console.log(error)
}) })
} }
activate () { async activate () {
this.connectToLocalhost() await this.connectToLocalhost()
} }
async canceled () { async canceled () {
// await this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
await this.appManager.deactivatePlugin('remixd') await this.appManager.deactivatePlugin('remixd')
} }
@ -65,23 +62,25 @@ export class RemixdHandle extends WebsocketPlugin {
* @param {String} txHash - hash of the transaction * @param {String} txHash - hash of the transaction
*/ */
async connectToLocalhost () { async connectToLocalhost () {
const connection = (error) => { const connection = (error?:any) => {
if (error) { if (error) {
console.log(error) console.log(error)
modalDialogCustom.alert( const alert:AlertModal = {
'Cannot connect to the remixd daemon. ' + id: 'connectionAlert',
'Please make sure you have the remixd running in the background.' message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.'
) }
this.call('modal', 'alert', alert)
this.canceled() this.canceled()
} else { } else {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
clearInterval(intervalId) clearInterval(intervalId)
console.log(error) console.log(error)
modalDialogCustom.alert( const alert:AlertModal = {
'Connection to remixd terminated. ' + id: 'connectionAlert',
'Please make sure remixd is still running in the background.' message: 'Connection to remixd terminated.Please make sure remixd is still running in the background.'
) }
this.call('modal', 'alert', alert)
this.canceled() this.canceled()
} }
}, 3000) }, 3000)
@ -96,34 +95,38 @@ export class RemixdHandle extends WebsocketPlugin {
this.deactivate() this.deactivate()
} else if (!isElectron()) { } else if (!isElectron()) {
// warn the user only if he/she is in the browser context // warn the user only if he/she is in the browser context
modalDialog( this.state = State.new
'Connect to localhost', const mod:AppModal = {
remixdDialog(), id: 'remixdConnect',
{ title: 'Connect to localhost',
label: 'Connect', message: remixdDialog(),
fn: () => { okFn: () => {
try { this.state = State.ok
this.localhostProvider.preInit() try {
super.activate() this.localhostProvider.preInit()
setTimeout(() => { super.activate()
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed setTimeout(() => {
connection(new Error('Connection with daemon failed.')) if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
} else { connection(new Error('Connection with daemon failed.'))
connection() } else {
} connection()
}, 3000) }
} catch (error) { }, 3000)
connection(error) } catch (error) {
} connection(error)
} }
}, },
{ cancelFn: async () => {
label: 'Cancel', this.state = State.cancel
fn: () => { await this.canceled()
this.canceled() },
} okLabel: 'Connect',
cancelLabel: 'Cancel',
hideFn: async () => {
if (this.state === State.new) await this.canceled()
} }
) }
await this.call('modal', 'modal', mod)
} else { } else {
try { try {
super.activate() super.activate()
@ -137,31 +140,31 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog () { function remixdDialog () {
const commandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>' const commandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return yo` return (<>
<div class=${css.dialog}> <div className=''>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.<br/><br/> Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.<br/><br/>
Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a>. Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a>.
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
If you are just looking for the remixd command, here it is: If you are just looking for the remixd command, here it is:
<br><br><b>${commandText}</b> <br></br><br></br><b>${commandText}</b>
<span class="">${copyToClipboard(() => commandText)}</span> <CopyToClipboard data-id='remixdCopyCommand' content={commandText}></CopyToClipboard>
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
When connected, a session will be started between <em>${window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>. When connected, a session will be started between <em>${window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>.
The shared folder will be in the "File Explorers" workspace named "localhost". The shared folder will be in the "File Explorers" workspace named "localhost".
<br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a> <br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
This feature is still in Alpha. We recommend to keep a backup of the shared folder. This feature is still in Alpha. We recommend to keep a backup of the shared folder.
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
<h6 class="text-danger"> <h6 className="text-danger">
Before using, make sure remixd version is latest i.e. <b>${remixdVersion}</b> Before using, make sure remixd version is latest i.e. <b>${remixdVersion}</b>
<br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a> <br></br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a>
</h6> </h6>
</div> </div>
</div> </div>
` </>)
} }

@ -7,6 +7,7 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"types": ["node", "jest"], "types": ["node", "jest"],
"module": "es6", "module": "es6",
"resolveJsonModule": true
}, },
"files": [ "files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",

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

@ -7,7 +7,6 @@ const DialogViewPlugin = () => {
const app = useContext(AppContext) const app = useContext(AppContext)
useEffect(() => { useEffect(() => {
console.log(modal, app)
app.modal.setDispatcher({ modal, alert, toast }) app.modal.setDispatcher({ modal, alert, toast })
}, []) }, [])
return <></> return <></>

@ -10,10 +10,10 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState)
const modal = (data: AppModal) => { const modal = (data: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue } = data const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn } = data
dispatch({ dispatch({
type: modalActionTypes.setModal, type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue } payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn }
}) })
} }

@ -12,6 +12,7 @@ export interface AppModal {
cancelFn: () => void, cancelFn: () => void,
modalType?: ModalTypes, modalType?: ModalTypes,
defaultValue?: string defaultValue?: string
hideFn?: () => void
} }
export interface AlertModal { export interface AlertModal {

@ -18,7 +18,8 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
cancelLabel: modalList[0].cancelLabel, cancelLabel: modalList[0].cancelLabel,
cancelFn: modalList[0].cancelFn, cancelFn: modalList[0].cancelFn,
modalType: modalList[0].modalType, modalType: modalList[0].modalType,
defaultValue: modalList[0].defaultValue defaultValue: modalList[0].defaultValue,
hideFn: modalList[0].hideFn
} }
modalList = modalList.slice() modalList = modalList.slice()
@ -28,6 +29,9 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
return { ...state, modals: modalList } return { ...state, modals: modalList }
} }
case modalActionTypes.handleHideModal: case modalActionTypes.handleHideModal:
if (state.focusModal.hideFn) {
state.focusModal.hideFn()
}
state.focusModal = { ...state.focusModal, hide: true, message: null } state.focusModal = { ...state.focusModal, hide: true, message: null }
return { ...state } return { ...state }

@ -12,12 +12,15 @@ export const ModalDialog = (props: ModalDialogProps) => {
const [state, setState] = useState({ const [state, setState] = useState({
toggleBtn: true toggleBtn: true
}) })
const calledHideFunctionOnce = useRef<boolean>()
const modal = useRef(null) const modal = useRef(null)
const handleHide = () => { const handleHide = () => {
props.handleHide() if (!calledHideFunctionOnce.current) { props.handleHide() }
calledHideFunctionOnce.current = true
} }
useEffect(() => { useEffect(() => {
calledHideFunctionOnce.current = props.hide
modal.current.focus() modal.current.focus()
}, [props.hide]) }, [props.hide])
@ -32,12 +35,9 @@ export const ModalDialog = (props: ModalDialogProps) => {
} }
if (modal.current) { if (modal.current) {
modal.current.addEventListener('blur', handleBlur) modal.current.addEventListener('blur', handleBlur)
}
return () => { return () => {
if (modal.current) { modal.current.removeEventListener('blur', handleBlur)
modal.current.removeEventListener('blur', handleBlur)
}
}
} }
}, [modal.current]) }, [modal.current])

Loading…
Cancel
Save