diff --git a/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts b/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts index ec4af936d0..26a49d9beb 100644 --- a/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts +++ b/apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts @@ -147,8 +147,8 @@ const executeReadFile = ` const executeCopyFile = ` const run = async () => { - await remix.call('fileManager', 'copyFile', 'contracts/3_Ballot.sol', '/', 'new_contract.sol') - const result = await remix.call('fileManager', 'readFile', 'new_contract.sol') + await remix.call('fileManager', 'copyFile', 'contracts/3_Ballot.sol', '/', 'copy_contract.sol') + const result = await remix.call('fileManager', 'readFile', 'copy_contract.sol') console.log(result) } diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 8e05410e85..de0f01d541 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -29,6 +29,7 @@ const { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConvert const QueryParams = require('./lib/query-params') const Storage = remixLib.Storage const RemixDProvider = require('./app/files/remixDProvider') +const HardhatProvider = require('./app/tabs/hardhat-provider') const Config = require('./config') const modalDialogCustom = require('./app/ui/modal-dialog-custom') const modalDialog = require('./app/ui/modaldialog') @@ -277,6 +278,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org const networkModule = new NetworkModule(blockchain) // ----------------- represent the current selected web3 provider ---- const web3Provider = new Web3ProviderModule(blockchain) + const hardhatProvider = new HardhatProvider(blockchain) // ----------------- convert offset to line/column service ----------- const offsetToLineColumnConverter = new OffsetToLineColumnConverter() registry.put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' }) @@ -313,7 +315,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org terminal, web3Provider, fetchAndCompile, - dGitProvider + dGitProvider, + hardhatProvider ]) // LAYOUT & SYSTEM VIEWS diff --git a/apps/remix-ide/src/app/files/fileManager.js b/apps/remix-ide/src/app/files/fileManager.js index 1375a28ca8..9342c21405 100644 --- a/apps/remix-ide/src/app/files/fileManager.js +++ b/apps/remix-ide/src/app/files/fileManager.js @@ -232,9 +232,10 @@ class FileManager extends Plugin { await this._handleExists(dest, `Cannot paste content into ${dest}. Path does not exist.`) await this._handleIsDir(dest, `Cannot paste content into ${dest}. Path is not directory.`) const content = await this.readFile(src) - const copiedFileName = customName ? '/' + customName : '/' + `Copy_${helper.extractNameFromKey(src)}` + let copiedFilePath = dest + (customName ? '/' + customName : '/' + `Copy_${helper.extractNameFromKey(src)}`) + copiedFilePath = await helper.createNonClashingNameAsync(copiedFilePath, this) - await this.writeFile(dest + copiedFileName, content) + await this.writeFile(copiedFilePath, content) } catch (e) { throw new Error(e) } @@ -262,7 +263,8 @@ class FileManager extends Plugin { async inDepthCopy (src, dest, count = 0) { const content = await this.readdir(src) - const copiedFolderPath = count === 0 ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src) + let copiedFolderPath = count === 0 ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src) + copiedFolderPath = await helper.createNonClashingDirNameAsync(copiedFolderPath, this) await this.mkdir(copiedFolderPath) diff --git a/apps/remix-ide/src/app/files/remixd-handle.js b/apps/remix-ide/src/app/files/remixd-handle.js index a2d94eb3ea..679a27ce9a 100644 --- a/apps/remix-ide/src/app/files/remixd-handle.js +++ b/apps/remix-ide/src/app/files/remixd-handle.js @@ -4,6 +4,7 @@ import * as packageJson from '../../../../../package.json' var yo = require('yo-yo') var modalDialog = require('../ui/modaldialog') var modalDialogCustom = require('../ui/modal-dialog-custom') +var copyToClipboard = require('../ui/copy-to-clipboard') var csjs = require('csjs-inject') @@ -130,22 +131,27 @@ export class RemixdHandle extends WebsocketPlugin { } function remixdDialog () { + const commandText = 'remixd -s absolute-path-to-the-shared-folder --remix-ide your-remix-ide-URL-instance' return yo`
-
Interact with your file system from Remix.
See the Remixd tutorial for more info. +
+ Access your file system from Remix IDE. Remixd the NPM module needs to be running in the background to use the Remixd plugin. For more info please check the Remixd tutorial. +
+
If you are just looking for the remixd command here it is: +

remixd -s absolute-path-to-the-shared-folder --remix-ide your-remix-ide-URL-instance + ${copyToClipboard(() => commandText)}
-
If you have looked at the Remixd docs and just need remixd command,
here it is: -
remixd -s absolute-path-to-the-shared-folder --remix-ide your-remix-ide-URL-instance +
A connection will start a session between ${window.location.origin} and your local file system ws://127.0.0.1:65520 +
To see that a connection has been made, check that there is a localhost section in the Files Explorer
-
Connection will start a session between ${window.location.origin} and your local file system ws://127.0.0.1:65520 - so please make sure your system is secured enough (port 65520 neither opened nor forwarded). +
Please make sure your system is secured enough (port 65520 should not be opened nor forwarded). + This feature is still in Alpha, so we recommend you to keep a copy of the shared folder.
Before using, make sure you have the latest remixd version.
Read here how to update it
-
This feature is still in Alpha, so we recommend you to keep a copy of the shared folder.
` } diff --git a/apps/remix-ide/src/app/tabs/hardhat-provider.js b/apps/remix-ide/src/app/tabs/hardhat-provider.js new file mode 100644 index 0000000000..037117fa85 --- /dev/null +++ b/apps/remix-ide/src/app/tabs/hardhat-provider.js @@ -0,0 +1,71 @@ +import * as packageJson from '../../../../../package.json' +import { Plugin } from '@remixproject/engine' +import Web3 from 'web3' +const yo = require('yo-yo') +const modalDialogCustom = require('../ui/modal-dialog-custom') + +const profile = { + name: 'hardhat-provider', + displayName: 'Hardhat Provider', + kind: 'provider', + description: 'Hardhat provider', + methods: ['sendAsync'], + version: packageJson.version +} + +export default class HardhatProvider extends Plugin { + constructor (blockchain) { + super(profile) + this.provider = null + this.blockchain = blockchain + } + + onDeactivation () { + this.provider = null + } + + hardhatProviderDialogBody () { + return yo` +
+ Note: To run Hardhat network node on your system, go to hardhat project folder and run command: +
npx hardhat node
+
+ For more info, visit: Hardhat Documentation +

+ Hardhat JSON-RPC Endpoint +
+ ` + } + + sendAsync (data) { + return new Promise((resolve, reject) => { + if (!this.provider) { + modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => { + this.provider = new Web3.providers.HttpProvider(target) + this.sendAsyncInternal(data, resolve, reject) + }, () => { + this.sendAsyncInternal(data, resolve, reject) + }) + } else { + this.sendAsyncInternal(data, resolve, reject) + } + }) + } + + sendAsyncInternal (data, resolve, reject) { + if (this.provider) { + this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, (error, message) => { + if (error) { + this.provider = null + return reject(error) + } + resolve(message) + }) + } else { + const result = data.method === 'net_listening' ? 'canceled' : [] + resolve({ jsonrpc: '2.0', result: result, id: data.id }) + } + } +} + +module.exports = HardhatProvider diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index 41588e3708..dbefacda87 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -145,7 +145,6 @@ export class ExecutionContext { if (context === 'web3') { confirmCb(cb) } - if (this.customNetWorks[context]) { var network = this.customNetWorks[context] this.setProviderFromEndpoint(network.provider, network.name, (error) => { @@ -189,14 +188,16 @@ export class ExecutionContext { const oldProvider = web3.currentProvider web3.setProvider(endpoint) - web3.eth.net.isListening((err, isConnected) => { - if (!err && isConnected) { + if (!err && isConnected === true) { this.executionContext = context this._updateBlockGasLimit() this.event.trigger('contextChanged', [context]) this.event.trigger('web3EndpointChanged') cb() + } else if (isConnected === 'canceled') { + web3.setProvider(oldProvider) + cb() } else { web3.setProvider(oldProvider) cb('Not possible to connect to the Web3 provider. Make sure the provider is running, a connection is open (via IPC or RPC) or that the provider plugin is properly configured.') diff --git a/apps/remix-ide/src/lib/helper.js b/apps/remix-ide/src/lib/helper.js index b910dccfb3..0486c557d0 100644 --- a/apps/remix-ide/src/lib/helper.js +++ b/apps/remix-ide/src/lib/helper.js @@ -71,6 +71,20 @@ module.exports = { return name + counter + prefix + '.' + ext }, + async createNonClashingDirNameAsync (name, fileManager) { + if (!name) name = 'Undefined' + let counter = '' + let exist = true + + do { + const isDuplicate = await fileManager.exists(name + counter) + + if (isDuplicate) counter = (counter | 0) + 1 + else exist = false + } while (exist) + + return name + counter + }, checkSpecialChars (name) { return name.match(/[:*?"<>\\'|]/) != null }, diff --git a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx index a176909b94..a17f2c3faa 100644 --- a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx +++ b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx @@ -763,10 +763,6 @@ export const FileExplorer = (props: FileExplorerProps) => { state.copyElement.map(({ key, type }) => { type === 'file' ? copyFile(key, dest) : copyFolder(key, dest) }) - setState(prevState => { - return { ...prevState, copyElement: [] } - }) - setCanPaste(false) } const label = (file: File) => {