Merge branch 'master' into show-icon

pull/2067/head
David Disu 3 years ago committed by GitHub
commit a32430bcb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .circleci/config.yml
  2. 2
      apps/remix-ide-e2e/src/helpers/init.ts
  3. 39
      apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts
  4. 4
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  5. 9
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  6. 2
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  7. 17
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  8. 4
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  9. 7
      apps/remix-ide/src/app.js
  10. 4
      apps/remix-ide/src/app/editor/editor.js
  11. 59
      apps/remix-ide/src/app/files/dgitProvider.js
  12. 235
      apps/remix-ide/src/app/files/fileManager.ts
  13. 223
      apps/remix-ide/src/app/files/fileProvider.js
  14. 24
      apps/remix-ide/src/app/panels/tab-proxy.js
  15. 22
      apps/remix-ide/src/app/plugins/storage.ts
  16. 107
      apps/remix-ide/src/assets/js/init.js
  17. 1
      apps/remix-ide/src/assets/js/lightning-fs.min.js
  18. 139
      apps/remix-ide/src/assets/js/migrate.js
  19. 57
      apps/remix-ide/src/index.html
  20. 1
      apps/remix-ide/src/lib/helper.js
  21. 57
      apps/remix-ide/src/production.index.html
  22. 2
      apps/remix-ide/src/remixAppManager.js
  23. 2
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  24. 1
      libs/remix-debug/src/debugger/stepManager.ts
  25. 2
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  26. 10
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  27. 17
      libs/remix-ui/solidity-unit-testing/src/lib/logic/testTabLogic.ts
  28. 2
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  29. 1
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  30. 1
      libs/remix-ui/workspace/src/lib/actions/events.ts
  31. 16
      libs/remix-ui/workspace/src/lib/actions/index.ts
  32. 60
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  33. 12
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  34. 669
      package-lock.json
  35. 1
      package.json
  36. 5
      workspace.json

@ -20,7 +20,6 @@ jobs:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org" - COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI" - COMMIT_AUTHOR: "Circle CI"
working_directory: ~/remix-project working_directory: ~/remix-project
steps: steps:
- checkout - checkout

@ -13,7 +13,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.fullscreenWindow(() => { .fullscreenWindow(() => {
if (preloadPlugins) { if (preloadPlugins) {
initModules(browser, () => { initModules(browser, () => {
browser.clickLaunchIcon('solidity') browser.pause(2000).clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]') .waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]') .click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked') .verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')

@ -0,0 +1,39 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080?e2e_testmigration=true', false)
},
'Should have README file with TEST README as content': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.openFile('TEST_README.txt')
.getEditorValue((content) => {
browser.assert.equal(content, 'TEST README')
})
},
'Should have a workspace_test': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="workspacesSelect"] option[value="workspace_test"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_contracts"]')
},
'Should have a sol file with test data': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="treeViewLitreeViewItemtest_contracts"]')
.openFile('test_contracts/1_Storage.sol')
.getEditorValue((content) => {
browser.assert.equal(content, 'testing')
})
},
'Should have a artifacts file with JSON test data': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="treeViewLitreeViewItemtest_contracts/artifacts"]')
.openFile('test_contracts/artifacts/Storage_metadata.json')
.getEditorValue((content) => {
const metadata = JSON.parse(content)
browser.assert.equal(metadata.test, 'data')
})
}
}

@ -235,7 +235,9 @@ module.exports = {
}, },
'Should write to file #group2': async function (browser: NightwatchBrowser) { 'Should write to file #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:writeFile', null, { event: 'fileSaved', args: ['README.txt'] }, ['README.txt', 'test']) await clickAndCheckLog(browser, 'fileManager:writeFile', null, { event: 'fileSaved', args: ['README.txt'] }, ['README.txt', 'test'])
await clickAndCheckLog(browser, 'fileManager:readFile', 'test', null, 'README.txt') browser.pause(4000, async () => {
await clickAndCheckLog(browser, 'fileManager:getFile', 'test', null, 'README.txt')
})
}, },
'Should set file #group2': async function (browser: NightwatchBrowser) { 'Should set file #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:setFile', null, { event: 'fileAdded', args: ['new.sol'] }, ['new.sol', 'test']) await clickAndCheckLog(browser, 'fileManager:setFile', null, { event: 'fileAdded', args: ['new.sol'] }, ['new.sol', 'test'])

@ -11,7 +11,7 @@ module.exports = {
return sources return sources
}, },
'Test Recorder': function (browser: NightwatchBrowser) { 'Run Scenario': function (browser: NightwatchBrowser) {
let addressRef let addressRef
browser.addFile('scenario.json', { content: records }) browser.addFile('scenario.json', { content: records })
.pause(5000) .pause(5000)
@ -34,7 +34,10 @@ module.exports = {
.perform(() => done()) .perform(() => done())
}) })
.click('*[data-id="deployAndRunClearInstances"]') .click('*[data-id="deployAndRunClearInstances"]')
.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder'])
},
'Save scenario': function (browser: NightwatchBrowser) {
browser.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract('12') .createContract('12')
.clickInstance(0) .clickInstance(0)
@ -45,7 +48,7 @@ module.exports = {
const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modalOk.click() modalOk.click()
}) }).pause(1000)
.getEditorValue(function (result) { .getEditorValue(function (result) {
const parsed = JSON.parse(result) const parsed = JSON.parse(result)
browser.assert.equal(JSON.stringify(parsed.transactions[0].record.parameters), JSON.stringify(scenario.transactions[0].record.parameters)) browser.assert.equal(JSON.stringify(parsed.transactions[0].record.parameters), JSON.stringify(scenario.transactions[0].record.parameters))

@ -18,7 +18,7 @@ module.exports = {
'Test Success Import #group1': function (browser: NightwatchBrowser) { 'Test Success Import #group1': function (browser: NightwatchBrowser) {
browser.addFile('Untitled1.sol', sources[1]['Untitled1.sol']) browser.addFile('Untitled1.sol', sources[1]['Untitled1.sol'])
.addFile('Untitled2.sol', sources[1]['Untitled2.sol']) .addFile('Untitled2.sol', sources[1]['Untitled2.sol']).pause(4000)
.openFile('Untitled1.sol') .openFile('Untitled1.sol')
.verifyContracts(['test6', 'test4', 'test5']) .verifyContracts(['test6', 'test4', 'test5'])
.pause(1000) .pause(1000)

@ -263,7 +263,7 @@ module.exports = {
.removeFile('tests/ballotFailedLog_test.sol', 'workspace_new') .removeFile('tests/ballotFailedLog_test.sol', 'workspace_new')
}, },
'Debug tests using debugger #group5': function (browser: NightwatchBrowser) { 'Debug tests using debugger #group7': function (browser: NightwatchBrowser) {
browser browser
.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]') .waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol']) .addFile('tests/ballotFailedDebug_test.sol', sources[0]['tests/ballotFailedDebug_test.sol'])
@ -288,9 +288,10 @@ module.exports = {
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW)) .setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'vote(proposal)', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'vote(proposal)', 60000)
.pause(1000) .pause(5000)
.checkVariableDebug('soliditylocals', locals) .checkVariableDebug('soliditylocals', locals)
.clickLaunchIcon('solidityUnitTesting') .pause(5000)
.clickLaunchIcon('solidityUnitTesting').pause(2000)
.scrollAndClick('#Check_winning_proposal_passed') .scrollAndClick('#Check_winning_proposal_passed')
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000) .waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000)
@ -301,8 +302,8 @@ module.exports = {
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000)
// remix_test.sol should be opened in editor // remix_test.sol should be opened in editor
.getEditorValue((content) => browser.assert.ok(content.indexOf('library Assert {') !== -1)) .getEditorValue((content) => browser.assert.ok(content.indexOf('library Assert {') !== -1))
.pause(1000) .pause(5000)
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting').pause(2000)
.scrollAndClick('#Check_winning_proposal_again') .scrollAndClick('#Check_winning_proposal_again')
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000) .waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000)
@ -311,9 +312,9 @@ module.exports = {
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW)) .setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.waitForElementContainsText('*[data-id="functionPanel"]', 'equal(a, b, message)', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'equal(a, b, message)', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000)
.pause(1000) .pause(5000)
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting').pause(5000)
.scrollAndClick('#Check_winnin_proposal_with_return_value') .scrollAndClick('#Check_winnin_proposal_with_return_value').pause(5000)
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000) .waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinninProposalWithReturnValue()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinninProposalWithReturnValue()', 60000)
// eslint-disable-next-line dot-notation // eslint-disable-next-line dot-notation

@ -19,9 +19,9 @@ module.exports = {
browser browser
.pause(5000) .pause(5000)
.refresh() .refresh()
.pause(10000) .waitForElementVisible('#editorView', 30000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Ballot {') !== -1, 'content doesn\'t include Ballot contract') browser.assert.ok(content.indexOf('contract Ballot {') !== -1, 'content includes Ballot contract')
}) })
}, },

@ -19,6 +19,7 @@ import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, Fetch
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 { StoragePlugin } from './app/plugins/storage'
import { Layout } from './app/panels/layout' import { Layout } from './app/panels/layout'
import { NotificationPlugin } from './app/plugins/notification' import { NotificationPlugin } from './app/plugins/notification'
import { Blockchain } from './blockchain/blockchain.js' import { Blockchain } from './blockchain/blockchain.js'
@ -143,6 +144,9 @@ class AppComponent {
// ----------------- dGit provider --------------------------------- // ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider() const dGitProvider = new DGitProvider()
// ----------------- Storage plugin ---------------------------------
const storagePlugin = new StoragePlugin()
// ----------------- import content service ------------------------ // ----------------- import content service ------------------------
const contentImport = new CompilerImports() const contentImport = new CompilerImports()
@ -215,6 +219,7 @@ class AppComponent {
web3Provider, web3Provider,
fetchAndCompile, fetchAndCompile,
dGitProvider, dGitProvider,
storagePlugin,
hardhatProvider, hardhatProvider,
this.walkthroughService this.walkthroughService
]) ])
@ -327,7 +332,7 @@ class AppComponent {
await this.appManager.activatePlugin(['settings', 'config']) await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough']) await this.appManager.activatePlugin(['walkthrough','storage'])
this.appManager.on( this.appManager.on(
'filePanel', 'filePanel',

@ -149,11 +149,11 @@ class Editor extends Plugin {
if (this.saveTimeout) { if (this.saveTimeout) {
window.clearTimeout(this.saveTimeout) window.clearTimeout(this.saveTimeout)
} }
this.triggerEvent('contentChanged', [])
this.saveTimeout = window.setTimeout(() => { this.saveTimeout = window.setTimeout(() => {
this.triggerEvent('contentChanged', [])
this.triggerEvent('requiringToSaveCurrentfile', []) this.triggerEvent('requiringToSaveCurrentfile', [])
}, 5000) }, 500)
} }
_switchSession (path) { _switchSession (path) {

@ -51,8 +51,8 @@ class DGitProvider extends Plugin {
async getGitConfig () { async getGitConfig () {
const workspace = await this.call('filePanel', 'getCurrentWorkspace') const workspace = await this.call('filePanel', 'getCurrentWorkspace')
return { return {
fs: window.remixFileSystem, fs: window.remixFileSystemCallback,
dir: workspace.absolutePath dir: addSlash(workspace.absolutePath)
} }
} }
@ -91,7 +91,9 @@ class DGitProvider extends Plugin {
...await this.getGitConfig(), ...await this.getGitConfig(),
...cmd ...cmd
}) })
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
} }
async rm (cmd) { async rm (cmd) {
@ -99,7 +101,9 @@ class DGitProvider extends Plugin {
...await this.getGitConfig(), ...await this.getGitConfig(),
...cmd ...cmd
}) })
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
} }
async checkout (cmd) { async checkout (cmd) {
@ -107,7 +111,9 @@ class DGitProvider extends Plugin {
...await this.getGitConfig(), ...await this.getGitConfig(),
...cmd ...cmd
}) })
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
} }
async log (cmd) { async log (cmd) {
@ -133,7 +139,9 @@ class DGitProvider extends Plugin {
...await this.getGitConfig(), ...await this.getGitConfig(),
...cmd ...cmd
}) })
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
return status return status
} }
@ -241,7 +249,9 @@ class DGitProvider extends Plugin {
} }
const result = await git.clone(cmd) const result = await git.clone(cmd)
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
return result return result
} }
@ -274,7 +284,9 @@ class DGitProvider extends Plugin {
...await this.getGitConfig() ...await this.getGitConfig()
} }
const result = await git.pull(cmd) const result = await git.pull(cmd)
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
return result return result
} }
@ -291,7 +303,9 @@ class DGitProvider extends Plugin {
...await this.getGitConfig() ...await this.getGitConfig()
} }
const result = await git.fetch(cmd) const result = await git.fetch(cmd)
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
return result return result
} }
@ -301,7 +315,7 @@ class DGitProvider extends Plugin {
const files = await this.getDirectory('/') const files = await this.getDirectory('/')
this.filesToSend = [] this.filesToSend = []
for (const file of files) { for (const file of files) {
const c = window.remixFileSystem.readFileSync(`${workspace.absolutePath}/${file}`) const c = await window.remixFileSystem.readFile(`${workspace.absolutePath}/${file}`)
const ob = { const ob = {
path: file, path: file,
content: c content: c
@ -321,10 +335,10 @@ class DGitProvider extends Plugin {
this.filesToSend = [] this.filesToSend = []
const data = new FormData() const data = new FormData()
files.forEach(async (file) => { for (const file of files) {
const c = window.remixFileSystem.readFileSync(`${workspace.absolutePath}/${file}`) const c = await window.remixFileSystem.readFile(`${workspace.absolutePath}/${file}`)
data.append('file', new Blob([c]), `base/${file}`) data.append('file', new Blob([c]), `base/${file}`)
}) }
// get last commit data // get last commit data
let ob let ob
try { try {
@ -430,10 +444,10 @@ class DGitProvider extends Plugin {
} }
const dir = path.dirname(file.path) const dir = path.dirname(file.path)
try { try {
this.createDirectories(`${workspace.absolutePath}/${dir}`) await this.createDirectories(`${workspace.absolutePath}/${dir}`)
} catch (e) { throw new Error(e) } } catch (e) { throw new Error(e) }
try { try {
window.remixFileSystem.writeFileSync(`${workspace.absolutePath}/${file.path}`, Buffer.concat(content) || new Uint8Array()) await window.remixFileSystem.writeFile(`${workspace.absolutePath}/${file.path}`, Buffer.concat(content) || new Uint8Array())
} catch (e) { throw new Error(e) } } catch (e) { throw new Error(e) }
} }
} catch (e) { } catch (e) {
@ -469,7 +483,9 @@ class DGitProvider extends Plugin {
} else { } else {
result = await this.importIPFSFiles(this.remixIPFS, cid, workspace) || await this.importIPFSFiles(this.ipfsconfig, cid, workspace) || await this.importIPFSFiles(this.globalIPFSConfig, cid, workspace) result = await this.importIPFSFiles(this.remixIPFS, cid, workspace) || await this.importIPFSFiles(this.ipfsconfig, cid, workspace) || await this.importIPFSFiles(this.globalIPFSConfig, cid, workspace)
} }
await this.call('fileManager', 'refresh') setTimeout(async () => {
await this.call('fileManager', 'refresh')
}, 1000)
if (!result) throw new Error(`Cannot pull files from IPFS at ${cid}`) if (!result) throw new Error(`Cannot pull files from IPFS at ${cid}`)
} }
@ -497,7 +513,7 @@ class DGitProvider extends Plugin {
const files = await this.getDirectory('/') const files = await this.getDirectory('/')
this.filesToSend = [] this.filesToSend = []
for (const file of files) { for (const file of files) {
const c = window.remixFileSystem.readFileSync(`${workspace.absolutePath}/${file}`) const c = await window.remixFileSystem.readFile(`${workspace.absolutePath}/${file}`)
zip.file(file, c) zip.file(file, c)
} }
await zip.generateAsync({ await zip.generateAsync({
@ -517,8 +533,8 @@ class DGitProvider extends Plugin {
if (i > 0) previouspath = '/' + directories.slice(0, i).join('/') if (i > 0) previouspath = '/' + directories.slice(0, i).join('/')
const finalPath = previouspath + '/' + directories[i] const finalPath = previouspath + '/' + directories[i]
try { try {
if (!window.remixFileSystem.existsSync(finalPath)) { if (!await window.remixFileSystem.exists(finalPath)) {
window.remixFileSystem.mkdirSync(finalPath) await window.remixFileSystem.mkdir(finalPath)
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e)
@ -549,6 +565,11 @@ class DGitProvider extends Plugin {
} }
} }
const addSlash = (file) => {
if (!file.startsWith('/'))file = '/' + file
return file
}
const normalize = (filesList) => { const normalize = (filesList) => {
const folders = [] const folders = []
const files = [] const files = []

@ -1,12 +1,11 @@
'use strict' 'use strict'
import async from 'async'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry' import Registry from '../state/registry'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types' import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types'
import { fileChangedToastMsg } from '@remix-ui/helper' import { fileChangedToastMsg } from '@remix-ui/helper'
const helper = require('../../lib/helper.js') import helper from '../../lib/helper.js'
/* /*
attach to files event (removed renamed) attach to files event (removed renamed)
@ -47,7 +46,7 @@ class FileManager extends Plugin {
getFolder: (path: any) => Promise<unknown> getFolder: (path: any) => Promise<unknown>
setFile: (path: any, data: any) => Promise<unknown> setFile: (path: any, data: any) => Promise<unknown>
switchFile: (path: any) => Promise<void> switchFile: (path: any) => Promise<void>
constructor (editor, appManager) { constructor(editor, appManager) {
super(profile) super(profile)
this.mode = 'browser' this.mode = 'browser'
this.openedFiles = {} // list all opened files this.openedFiles = {} // list all opened files
@ -59,19 +58,19 @@ class FileManager extends Plugin {
this.init() this.init()
} }
getOpenedFiles () { getOpenedFiles() {
return this.openedFiles return this.openedFiles
} }
setMode (mode) { setMode(mode) {
this.mode = mode this.mode = mode
} }
limitPluginScope (path) { limitPluginScope(path) {
return path.replace(/^\/browser\//, '').replace(/^browser\//, '') // forbids plugin to access the root filesystem return path.replace(/^\/browser\//, '').replace(/^browser\//, '') // forbids plugin to access the root filesystem
} }
normalize (path) { normalize(path) {
return path.replace(/^\/+/, '') return path.replace(/^\/+/, '')
} }
@ -80,7 +79,7 @@ class FileManager extends Plugin {
* @param {string} path path of the file/directory * @param {string} path path of the file/directory
* @param {string} message message to display if path doesn't exist. * @param {string} message message to display if path doesn't exist.
*/ */
async _handleExists (path: string, message?:string) { async _handleExists(path: string, message?: string) {
const exists = await this.exists(path) const exists = await this.exists(path)
if (!exists) { if (!exists) {
@ -93,7 +92,7 @@ class FileManager extends Plugin {
* @param {string} path path of the file/directory * @param {string} path path of the file/directory
* @param {string} message message to display if path is not a file. * @param {string} message message to display if path is not a file.
*/ */
async _handleIsFile (path, message) { async _handleIsFile(path, message) {
const isFile = await this.isFile(path) const isFile = await this.isFile(path)
if (!isFile) { if (!isFile) {
@ -106,7 +105,7 @@ class FileManager extends Plugin {
* @param {string} path path of the file/directory * @param {string} path path of the file/directory
* @param {string} message message to display if path is not a directory. * @param {string} message message to display if path is not a directory.
*/ */
async _handleIsDir (path: string, message?: string) { async _handleIsDir(path: string, message?: string) {
const isDir = await this.isDirectory(path) const isDir = await this.isDirectory(path)
if (!isDir) { if (!isDir) {
@ -115,7 +114,7 @@ class FileManager extends Plugin {
} }
/** The current opened file */ /** The current opened file */
file () { file() {
try { try {
const file = this.currentFile() const file = this.currentFile()
@ -131,12 +130,12 @@ class FileManager extends Plugin {
* @param {string} path path of the directory or file * @param {string} path path of the directory or file
* @returns {boolean} true if the path exists * @returns {boolean} true if the path exists
*/ */
exists (path) { async exists(path) {
try { try {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
const provider = this.fileProviderOf(path) const provider = this.fileProviderOf(path)
const result = provider.exists(path) const result = await provider.exists(path)
return result return result
} catch (e) { } catch (e) {
@ -147,7 +146,7 @@ class FileManager extends Plugin {
/* /*
* refresh the file explorer * refresh the file explorer
*/ */
refresh () { refresh() {
const provider = this.fileProviderOf('/') const provider = this.fileProviderOf('/')
// emit rootFolderChanged so that File Explorer reloads the file tree // emit rootFolderChanged so that File Explorer reloads the file tree
provider.event.emit('rootFolderChanged', provider.workspace || '/') provider.event.emit('rootFolderChanged', provider.workspace || '/')
@ -159,10 +158,9 @@ class FileManager extends Plugin {
* @param {string} path path of the directory or file * @param {string} path path of the directory or file
* @returns {boolean} true if path is a file. * @returns {boolean} true if path is a file.
*/ */
isFile (path) { async isFile(path) {
const provider = this.fileProviderOf(path) const provider = this.fileProviderOf(path)
const result = provider.isFile(path) const result = await provider.isFile(path)
return result return result
} }
@ -171,7 +169,7 @@ class FileManager extends Plugin {
* @param {string} path path of the directory * @param {string} path path of the directory
* @returns {boolean} true if path is a directory. * @returns {boolean} true if path is a directory.
*/ */
async isDirectory (path) { async isDirectory(path) {
const provider = this.fileProviderOf(path) const provider = this.fileProviderOf(path)
const result = await provider.isDirectory(path) const result = await provider.isDirectory(path)
@ -183,7 +181,7 @@ class FileManager extends Plugin {
* @param {string} path path of the file * @param {string} path path of the file
* @returns {void} * @returns {void}
*/ */
async open (path) { async open(path) {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
path = this.getPathFromUrl(path).file path = this.getPathFromUrl(path).file
@ -198,7 +196,7 @@ class FileManager extends Plugin {
* @param {string} data content to write on the file * @param {string} data content to write on the file
* @returns {void} * @returns {void}
*/ */
async writeFile (path, data) { async writeFile(path, data) {
try { try {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
@ -220,7 +218,7 @@ class FileManager extends Plugin {
* @param {string} path path of the file * @param {string} path path of the file
* @returns {string} content of the file * @returns {string} content of the file
*/ */
async readFile (path) { async readFile(path) {
try { try {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
@ -238,7 +236,7 @@ class FileManager extends Plugin {
* @param {string} dest path of the destrination file * @param {string} dest path of the destrination file
* @returns {void} * @returns {void}
*/ */
async copyFile (src, dest, customName) { async copyFile(src, dest, customName) {
try { try {
src = this.normalize(src) src = this.normalize(src)
dest = this.normalize(dest) dest = this.normalize(dest)
@ -264,7 +262,7 @@ class FileManager extends Plugin {
* @param {string} dest path of the destination dir * @param {string} dest path of the destination dir
* @returns {void} * @returns {void}
*/ */
async copyDir (src, dest) { async copyDir(src, dest) {
try { try {
src = this.normalize(src) src = this.normalize(src)
dest = this.normalize(dest) dest = this.normalize(dest)
@ -280,7 +278,7 @@ class FileManager extends Plugin {
} }
} }
async inDepthCopy (src, dest, count = 0) { async inDepthCopy(src, dest, count = 0) {
const content = await this.readdir(src) const content = await this.readdir(src)
let 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) copiedFolderPath = await helper.createNonClashingDirNameAsync(copiedFolderPath, this)
@ -302,7 +300,7 @@ class FileManager extends Plugin {
* @param {string} newPath new path of the file/directory * @param {string} newPath new path of the file/directory
* @returns {void} * @returns {void}
*/ */
async rename (oldPath, newPath) { async rename(oldPath, newPath) {
try { try {
oldPath = this.normalize(oldPath) oldPath = this.normalize(oldPath)
newPath = this.normalize(newPath) newPath = this.normalize(newPath)
@ -342,7 +340,7 @@ class FileManager extends Plugin {
* @param {string} path path of the new directory * @param {string} path path of the new directory
* @returns {void} * @returns {void}
*/ */
async mkdir (path) { async mkdir(path) {
try { try {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
@ -350,8 +348,7 @@ class FileManager extends Plugin {
throw createError({ code: 'EEXIST', message: `Cannot create directory ${path}` }) throw createError({ code: 'EEXIST', message: `Cannot create directory ${path}` })
} }
const provider = this.fileProviderOf(path) const provider = this.fileProviderOf(path)
return await provider.createDir(path)
return provider.createDir(path)
} catch (e) { } catch (e) {
throw new Error(e) throw new Error(e)
} }
@ -362,7 +359,7 @@ class FileManager extends Plugin {
* @param {string} path path of the directory * @param {string} path path of the directory
* @returns {string[]} list of the file/directory name in this directory * @returns {string[]} list of the file/directory name in this directory
*/ */
async readdir (path) { async readdir(path) {
try { try {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
@ -387,7 +384,7 @@ class FileManager extends Plugin {
* @param {string} path path of the directory/file to remove * @param {string} path path of the directory/file to remove
* @returns {void} * @returns {void}
*/ */
async remove (path) { async remove(path) {
try { try {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
@ -399,7 +396,7 @@ class FileManager extends Plugin {
} }
} }
init () { init() {
this._deps = { this._deps = {
config: this._components.registry.get('config').api, config: this._components.registry.get('config').api,
browserExplorer: this._components.registry.get('fileproviders/browser').api, browserExplorer: this._components.registry.get('fileproviders/browser').api,
@ -427,15 +424,15 @@ class FileManager extends Plugin {
this.switchFile = this.open this.switchFile = this.open
} }
fileAddedEvent (path) { fileAddedEvent(path) {
this.emit('fileAdded', path) this.emit('fileAdded', path)
} }
fileChangedEvent (path) { fileChangedEvent(path) {
this.emit('fileChanged', path) this.emit('fileChanged', path)
} }
fileRenamedEvent (oldName, newName, isFolder) { fileRenamedEvent(oldName, newName, isFolder) {
if (!isFolder) { if (!isFolder) {
this._deps.config.set('currentFile', '') this._deps.config.set('currentFile', '')
this.editor.discard(oldName) this.editor.discard(oldName)
@ -445,9 +442,9 @@ class FileManager extends Plugin {
} }
this.openFile(newName) this.openFile(newName)
} else { } else {
for (var k in this.openedFiles) { for (const k in this.openedFiles) {
if (k.indexOf(oldName + '/') === 0) { if (k.indexOf(oldName + '/') === 0) {
var newAbsolutePath = k.replace(oldName, newName) const newAbsolutePath = k.replace(oldName, newName)
this.openedFiles[newAbsolutePath] = newAbsolutePath this.openedFiles[newAbsolutePath] = newAbsolutePath
delete this.openedFiles[k] delete this.openedFiles[k]
if (this._deps.config.get('currentFile') === k) { if (this._deps.config.get('currentFile') === k) {
@ -461,19 +458,19 @@ class FileManager extends Plugin {
this.events.emit('fileRenamed', oldName, newName, isFolder) this.events.emit('fileRenamed', oldName, newName, isFolder)
} }
currentFileProvider () { currentFileProvider() {
var path = this.currentPath() const path = this.currentPath()
if (path) { if (path) {
return this.fileProviderOf(path) return this.fileProviderOf(path)
} }
return null return null
} }
currentFile () { currentFile() {
return this.editor.current() return this.editor.current()
} }
async closeAllFiles () { async closeAllFiles() {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('filesAllClosed') this.emit('filesAllClosed')
this.events.emit('filesAllClosed') this.events.emit('filesAllClosed')
@ -482,7 +479,7 @@ class FileManager extends Plugin {
} }
} }
async closeFile (name) { async closeFile(name) {
delete this.openedFiles[name] delete this.openedFiles[name]
if (!Object.keys(this.openedFiles).length) { if (!Object.keys(this.openedFiles).length) {
this._deps.config.set('currentFile', '') this._deps.config.set('currentFile', '')
@ -495,18 +492,18 @@ class FileManager extends Plugin {
this.events.emit('fileClosed', name) this.events.emit('fileClosed', name)
} }
currentPath () { currentPath() {
var currentFile = this._deps.config.get('currentFile') const currentFile = this._deps.config.get('currentFile')
return this.extractPathOf(currentFile) return this.extractPathOf(currentFile)
} }
extractPathOf (file) { extractPathOf(file) {
var reg = /(.*)(\/).*/ const reg = /(.*)(\/).*/
var path = reg.exec(file) const path = reg.exec(file)
return path ? path[1] : '/' return path ? path[1] : '/'
} }
getFileContent (path) { getFileContent(path) {
const provider = this.fileProviderOf(path) const provider = this.fileProviderOf(path)
if (!provider) throw createError({ code: 'ENOENT', message: `${path} not available` }) if (!provider) throw createError({ code: 'ENOENT', message: `${path} not available` })
@ -520,19 +517,19 @@ class FileManager extends Plugin {
}) })
} }
async setFileContent (path, content) { async setFileContent(path, content) {
if (this.currentRequest) { if (this.currentRequest) {
const canCall = await this.askUserPermission('writeFile', '') const canCall = await this.askUserPermission('writeFile', '')
const required = this.appManager.isRequired(this.currentRequest.from) const required = this.appManager.isRequired(this.currentRequest.from)
if (canCall && !required) { if (canCall && !required) {
// inform the user about modification after permission is granted and even if permission was saved before // inform the user about modification after permission is granted and even if permission was saved before
this.call('notification','toast', fileChangedToastMsg(this.currentRequest.from, path)) this.call('notification', 'toast', fileChangedToastMsg(this.currentRequest.from, path))
} }
} }
return await this._setFileInternal(path, content) return await this._setFileInternal(path, content)
} }
_setFileInternal (path, content) { _setFileInternal(path, content) {
const provider = this.fileProviderOf(path) const provider = this.fileProviderOf(path)
if (!provider) throw createError({ code: 'ENOENT', message: `${path} not available` }) if (!provider) throw createError({ code: 'ENOENT', message: `${path} not available` })
// TODO : Add permission // TODO : Add permission
@ -547,19 +544,6 @@ class FileManager extends Plugin {
}) })
} }
_saveAsCopy (path, content) {
const fileProvider = this.fileProviderOf(path)
if (fileProvider) {
helper.createNonClashingNameWithPrefix(path, fileProvider, '', (error, copyName) => {
if (error) {
copyName = path + '.' + this.currentRequest.from
}
this._setFileInternal(copyName, content)
this.openFile(copyName)
})
}
}
/** /**
* Try to resolve the given file path (the actual path in the file system) * Try to resolve the given file path (the actual path in the file system)
* e.g if it's specified a github link, npm library, or any external content, * e.g if it's specified a github link, npm library, or any external content,
@ -567,7 +551,7 @@ class FileManager extends Plugin {
* @param {string} file url we are trying to resolve * @param {string} file url we are trying to resolve
* @returns {{ string, provider }} file path resolved and its provider. * @returns {{ string, provider }} file path resolved and its provider.
*/ */
getPathFromUrl (file) { getPathFromUrl(file) {
const provider = this.fileProviderOf(file) const provider = this.fileProviderOf(file)
if (!provider) throw new Error(`no provider for ${file}`) if (!provider) throw new Error(`no provider for ${file}`)
return { return {
@ -581,7 +565,7 @@ class FileManager extends Plugin {
* @param {string} file path we are trying to resolve * @param {string} file path we are trying to resolve
* @returns {{ string, provider }} file url resolved and its provider. * @returns {{ string, provider }} file url resolved and its provider.
*/ */
getUrlFromPath (file) { getUrlFromPath(file) {
const provider = this.fileProviderOf(file) const provider = this.fileProviderOf(file)
if (!provider) throw new Error(`no provider for ${file}`) if (!provider) throw new Error(`no provider for ${file}`)
return { return {
@ -590,15 +574,15 @@ class FileManager extends Plugin {
} }
} }
removeTabsOf (provider) { removeTabsOf(provider) {
for (var tab in this.openedFiles) { for (const tab in this.openedFiles) {
if (this.fileProviderOf(tab).type === provider.type) { if (this.fileProviderOf(tab).type === provider.type) {
this.fileRemovedEvent(tab) this.fileRemovedEvent(tab)
} }
} }
} }
fileRemovedEvent (path) { fileRemovedEvent(path) {
if (path === this._deps.config.get('currentFile')) { if (path === this._deps.config.get('currentFile')) {
this._deps.config.set('currentFile', '') this._deps.config.set('currentFile', '')
} }
@ -610,32 +594,35 @@ class FileManager extends Plugin {
this.openFile() this.openFile()
} }
unselectCurrentFile () { async unselectCurrentFile() {
this.saveCurrentFile() await this.saveCurrentFile()
this._deps.config.set('currentFile', '') this._deps.config.set('currentFile', '')
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected') this.emit('noFileSelected')
this.events.emit('noFileSelected') this.events.emit('noFileSelected')
} }
async openFile (file?: string) { async openFile(file?: string) {
if (!file) { if (!file) {
this.emit('noFileSelected') this.emit('noFileSelected')
this.events.emit('noFileSelected') this.events.emit('noFileSelected')
} else { } else {
file = this.normalize(file) file = this.normalize(file)
this.saveCurrentFile() await this.saveCurrentFile()
const resolved = this.getPathFromUrl(file) const resolved = this.getPathFromUrl(file)
file = resolved.file file = resolved.file
const provider = resolved.provider const provider = resolved.provider
this._deps.config.set('currentFile', file) this._deps.config.set('currentFile', file)
this.openedFiles[file] = file this.openedFiles[file] = file
await (() => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
provider.get(file, (error, content) => { provider.get(file, (error, content) => {
if (error) { if (error) {
console.log(error) console.log(error)
reject(error) reject(error)
} else {
if (provider.isReadOnly(file)) {
this.editor.openReadOnly(file, content)
} else { } else {
if (provider.isReadOnly(file)) { if (provider.isReadOnly(file)) {
this.editor.openReadOnly(file, content) this.editor.openReadOnly(file, content)
@ -647,9 +634,13 @@ class FileManager extends Plugin {
this.events.emit('currentFileChanged', file) this.events.emit('currentFileChanged', file)
resolve(true) resolve(true)
} }
}) // TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file)
this.events.emit('currentFileChanged', file)
resolve(true)
}
}) })
})() })
} }
} }
@ -659,7 +650,7 @@ class FileManager extends Plugin {
* *
*/ */
async getProviderOf (file) { async getProviderOf(file) {
const cancall = await this.askUserPermission('getProviderByName') const cancall = await this.askUserPermission('getProviderByName')
if (cancall) { if (cancall) {
return file ? this.fileProviderOf(file) : this.currentFileProvider() return file ? this.fileProviderOf(file) : this.currentFileProvider()
@ -672,18 +663,18 @@ class FileManager extends Plugin {
* *
*/ */
async getProviderByName (name) { async getProviderByName(name) {
const cancall = await this.askUserPermission('getProviderByName') const cancall = await this.askUserPermission('getProviderByName')
if (cancall) { if (cancall) {
return this.getProvider(name) return this.getProvider(name)
} }
} }
getProvider (name) { getProvider(name) {
return this._deps.filesProviders[name] return this._deps.filesProviders[name]
} }
fileProviderOf (file) { fileProviderOf(file) {
if (file.startsWith('localhost') || this.mode === 'localhost') { if (file.startsWith('localhost') || this.mode === 'localhost') {
return this._deps.filesProviders.localhost return this._deps.filesProviders.localhost
} }
@ -694,7 +685,7 @@ class FileManager extends Plugin {
} }
// returns the list of directories inside path // returns the list of directories inside path
dirList (path) { dirList(path) {
const dirPaths = [] const dirPaths = []
const collectList = (path) => { const collectList = (path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -713,18 +704,18 @@ class FileManager extends Plugin {
return collectList(path) return collectList(path)
} }
isRemixDActive () { isRemixDActive() {
return this.appManager.isActive('remixd') return this.appManager.isActive('remixd')
} }
saveCurrentFile () { async saveCurrentFile() {
var currentFile = this._deps.config.get('currentFile') const currentFile = this._deps.config.get('currentFile')
if (currentFile && this.editor.current()) { if (currentFile && this.editor.current()) {
var input = this.editor.get(currentFile) const input = this.editor.get(currentFile)
if ((input !== null) && (input !== undefined)) { if ((input !== null) && (input !== undefined)) {
var provider = this.fileProviderOf(currentFile) const provider = this.fileProviderOf(currentFile)
if (provider) { if (provider) {
provider.set(currentFile, input) await provider.set(currentFile, input)
this.emit('fileSaved', currentFile) this.emit('fileSaved', currentFile)
} else { } else {
console.log('cannot save ' + currentFile + '. Does not belong to any explorer') console.log('cannot save ' + currentFile + '. Does not belong to any explorer')
@ -733,64 +724,64 @@ class FileManager extends Plugin {
} }
} }
syncEditor (path) { async syncEditor(path) {
var currentFile = this._deps.config.get('currentFile') const currentFile = this._deps.config.get('currentFile')
if (path !== currentFile) return if (path !== currentFile) return
var provider = this.fileProviderOf(currentFile) const provider = this.fileProviderOf(currentFile)
if (provider) { if (provider) {
provider.get(currentFile, (error, content) => { try{
if (error) console.log(error) const content = await provider.get(currentFile)
this.editor.setText(content) this.editor.setText(content)
}) }catch(error){
console.log(error)
}
} else { } else {
console.log('cannot save ' + currentFile + '. Does not belong to any explorer') console.log('cannot save ' + currentFile + '. Does not belong to any explorer')
} }
} }
setBatchFiles (filesSet, fileProvider, override, callback) { async setBatchFiles(filesSet, fileProvider, override, callback) {
const self = this const self = this
if (!fileProvider) fileProvider = 'browser' if (!fileProvider) fileProvider = 'workspace'
if (override === undefined) override = false if (override === undefined) override = false
for (const file of Object.keys(filesSet)) {
async.each(Object.keys(filesSet), (file, callback) => {
if (override) { if (override) {
try { try {
self._deps.filesProviders[fileProvider].set(file, filesSet[file].content) await self._deps.filesProviders[fileProvider].set(file, filesSet[file].content)
} catch (e) { } catch (e) {
return callback(e.message || e) callback(e.message || e)
} }
self.syncEditor(fileProvider + file) await self.syncEditor(fileProvider + file)
return callback() } else {
} try{
const name = await helper.createNonClashingNameAsync(file, self._deps.filesProviders[fileProvider])
helper.createNonClashingName(file, self._deps.filesProviders[fileProvider], if (helper.checkSpecialChars(name)) {
(error, name) => {
if (error) {
this.call('notification', 'alert', {
id: 'fileManagerAlert',
message: 'Unexpected error loading file ' + file + ': ' + error
})
} else if (helper.checkSpecialChars(name)) {
this.call('notification', 'alert', { this.call('notification', 'alert', {
id: 'fileManagerAlert', id: 'fileManagerAlert',
message: 'Special characters are not allowed in file names.' message: 'Special characters are not allowed in file names.'
}) })
} else { } else {
try { try {
self._deps.filesProviders[fileProvider].set(name, filesSet[file].content) await self._deps.filesProviders[fileProvider].set(name, filesSet[file].content)
} catch (e) { } catch (e) {
return callback(e.message || e) return callback(e.message || e)
} }
self.syncEditor(fileProvider + name) self.syncEditor(fileProvider + name)
} }
callback() }catch(error){
}) if (error) {
}, (error) => { this.call('notification', 'alert', {
if (callback) callback(error) id: 'fileManagerAlert',
}) message: 'Unexpected error loading file ' + file + ': ' + error
})
}
}
}
}
callback()
} }
currentWorkspace () { currentWorkspace() {
if (this.mode !== 'localhost') { if (this.mode !== 'localhost') {
const file = this.currentFile() || '' const file = this.currentFile() || ''
const provider = this.fileProviderOf(file) const provider = this.fileProviderOf(file)

@ -47,7 +47,7 @@ class FileProvider {
return this.externalFolders.includes(path) return this.externalFolders.includes(path)
} }
discardChanges (path, toastCb, modalCb) { async discardChanges (path, toastCb, modalCb) {
this.remove(path) this.remove(path)
const compilerImport = new CompilerImports() const compilerImport = new CompilerImports()
this.providerExternalsStorage.keys().map(value => { this.providerExternalsStorage.keys().map(value => {
@ -56,11 +56,11 @@ class FileProvider {
this.getNormalizedName(value), this.getNormalizedName(value),
true, true,
(loadingMsg) => { toastCb(loadingMsg) }, (loadingMsg) => { toastCb(loadingMsg) },
(error, content, cleanUrl, type, url) => { async (error, content, cleanUrl, type, url) => {
if (error) { if (error) {
modalCb(error) modalCb(error)
} else { } else {
this.addExternal(type + '/' + cleanUrl, content, url) await this.addExternal(type + '/' + cleanUrl, content, url)
} }
} }
) )
@ -71,48 +71,48 @@ class FileProvider {
async exists (path) { async exists (path) {
// todo check the type (directory/file) as well #2386 // todo check the type (directory/file) as well #2386
// currently it is not possible to have a file and folder with same path // currently it is not possible to have a file and folder with same path
const ret = this._exists(path) const ret = await this._exists(path)
return ret return ret
} }
_exists (path) { async _exists (path) {
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
return path === this.type ? true : window.remixFileSystem.existsSync(unprefixedpath) return path === this.type ? true : await window.remixFileSystem.exists(unprefixedpath)
} }
init (cb) { init (cb) {
cb() cb()
} }
get (path, cb) { async get (path, cb) {
cb = cb || function () { /* do nothing. */ } cb = cb || function () { /* do nothing. */ }
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
var exists = window.remixFileSystem.existsSync(unprefixedpath) try {
if (!exists) return cb(null, null) const content = await window.remixFileSystem.readFile(unprefixedpath, 'utf8')
window.remixFileSystem.readFile(unprefixedpath, 'utf8', (err, content) => { if (cb) cb(null, content)
cb(err, content) return content
}) } catch (err) {
if (cb) cb(err, null)
throw new Error(err)
}
} }
set (path, content, cb) { async set (path, content, cb) {
cb = cb || function () { /* do nothing. */ } cb = cb || function () { /* do nothing. */ }
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
var exists = window.remixFileSystem.existsSync(unprefixedpath) const exists = await window.remixFileSystem.exists(unprefixedpath)
if (exists && window.remixFileSystem.readFileSync(unprefixedpath, 'utf8') === content) { if (exists && await window.remixFileSystem.readFile(unprefixedpath, 'utf8') === content) {
cb() if (cb) cb()
return true return null
}
if (!exists && unprefixedpath.indexOf('/') !== -1) {
// the last element is the filename and we should remove it
this.createDir(path.substr(0, path.lastIndexOf('/')))
} }
await this.createDir(path.substr(0, path.lastIndexOf('/')))
try { try {
window.remixFileSystem.writeFileSync(unprefixedpath, content) await window.remixFileSystem.writeFile(unprefixedpath, content, 'utf8')
} catch (e) { } catch (e) {
cb(e) if (cb) cb(e)
return false return false
} }
if (!exists) { if (!exists) {
@ -120,84 +120,88 @@ class FileProvider {
} else { } else {
this.event.emit('fileChanged', this._normalizePath(unprefixedpath)) this.event.emit('fileChanged', this._normalizePath(unprefixedpath))
} }
cb() if (cb) cb()
return true return true
} }
createDir (path, cb) { async createDir (path, cb) {
const unprefixedpath = this.removePrefix(path) const unprefixedpath = this.removePrefix(path)
const paths = unprefixedpath.split('/') const paths = unprefixedpath.split('/')
if (paths.length && paths[0] === '') paths.shift() if (paths.length && paths[0] === '') paths.shift()
let currentCheck = '' let currentCheck = ''
paths.forEach((value) => { for (const value of paths) {
currentCheck = currentCheck + '/' + value currentCheck = currentCheck + '/' + value
if (!window.remixFileSystem.existsSync(currentCheck)) { if (!await window.remixFileSystem.exists(currentCheck)) {
window.remixFileSystem.mkdirSync(currentCheck) try {
this.event.emit('folderAdded', this._normalizePath(currentCheck)) await window.remixFileSystem.mkdir(currentCheck)
} catch (error) {
console.log(error)
}
} }
}) }
currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + '/' + value
this.event.emit('folderAdded', this._normalizePath(currentCheck))
}
if (cb) cb() if (cb) cb()
} }
// this will not add a folder as readonly but keep the original url to be able to restore it later // this will not add a folder as readonly but keep the original url to be able to restore it later
addExternal (path, content, url) { async addExternal (path, content, url) {
if (url) this.addNormalizedName(path, url) if (url) this.addNormalizedName(path, url)
return this.set(path, content) return await this.set(path, content)
} }
isReadOnly (path) { isReadOnly (path) {
return false return false
} }
isDirectory (path) { async isDirectory (path) {
const unprefixedpath = this.removePrefix(path) const unprefixedpath = this.removePrefix(path)
return path === this.type ? true : (await window.remixFileSystem.stat(unprefixedpath)).isDirectory()
return path === this.type ? true : window.remixFileSystem.statSync(unprefixedpath).isDirectory()
} }
isFile (path) { async isFile (path) {
path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here path = this.getPathFromUrl(path) || path // ensure we actually use the normalized path from here
path = this.removePrefix(path) path = this.removePrefix(path)
return window.remixFileSystem.statSync(path).isFile() return (await window.remixFileSystem.stat(path)).isFile()
} }
/** /**
* Removes the folder recursively * Removes the folder recursively
* @param {*} path is the folder to be removed * @param {*} path is the folder to be removed
*/ */
remove (path) { async remove (path) {
return new Promise((resolve, reject) => { path = this.removePrefix(path)
path = this.removePrefix(path) if (await window.remixFileSystem.exists(path)) {
if (window.remixFileSystem.existsSync(path)) { const stat = await window.remixFileSystem.stat(path)
const stat = window.remixFileSystem.statSync(path) try {
try { if (!stat.isDirectory()) {
if (!stat.isDirectory()) { return (this.removeFile(path))
resolve(this.removeFile(path)) } else {
} else { const items = await window.remixFileSystem.readdir(path)
const items = window.remixFileSystem.readdirSync(path) if (items.length !== 0) {
if (items.length !== 0) { for (const item of items) {
items.forEach((item, index) => { const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}` if ((await window.remixFileSystem.stat(curPath)).isDirectory()) { // delete folder
if (window.remixFileSystem.statSync(curPath).isDirectory()) { // delete folder await this.remove(curPath)
this.remove(curPath) } else { // delete file
} else { // delete file await this.removeFile(curPath)
this.removeFile(curPath) }
}
})
if (window.remixFileSystem.readdirSync(path).length === 0) window.remixFileSystem.rmdirSync(path, console.log)
} else {
// folder is empty
window.remixFileSystem.rmdirSync(path, console.log)
} }
this.event.emit('fileRemoved', this._normalizePath(path)) await window.remixFileSystem.rmdir(path)
} else {
// folder is empty
await window.remixFileSystem.rmdir(path)
} }
} catch (e) { this.event.emit('fileRemoved', this._normalizePath(path))
console.log(e)
return resolve(false)
} }
} catch (e) {
console.log(e)
return false
} }
return resolve(true) }
})
} }
/** /**
@ -206,36 +210,35 @@ class FileProvider {
* @param {Function} visitFile is a function called for each visited files * @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders * @param {Function} visitFolder is a function called for each visited folders
*/ */
_copyFolderToJsonInternal (path, visitFile, visitFolder) { async _copyFolderToJsonInternal (path, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ } visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ } visitFolder = visitFolder || function () { /* do nothing. */ }
return new Promise((resolve, reject) => {
const json = {} const json = {}
path = this.removePrefix(path) path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path)) { if (await window.remixFileSystem.exists(path)) {
try { try {
const items = window.remixFileSystem.readdirSync(path) const items = await window.remixFileSystem.readdir(path)
visitFolder({ path }) visitFolder({ path })
if (items.length !== 0) { if (items.length !== 0) {
items.forEach(async (item, index) => { for (const item of items) {
const file = {} const file = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}` const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if (window.remixFileSystem.statSync(curPath).isDirectory()) { if ((await window.remixFileSystem.stat(curPath)).isDirectory()) {
file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder) file.children = await this._copyFolderToJsonInternal(curPath, visitFile, visitFolder)
} else { } else {
file.content = window.remixFileSystem.readFileSync(curPath, 'utf8') file.content = await window.remixFileSystem.readFile(curPath, 'utf8')
visitFile({ path: curPath, content: file.content }) visitFile({ path: curPath, content: file.content })
} }
json[curPath] = file json[curPath] = file
})
} }
} catch (e) {
console.log(e)
return reject(e)
} }
} catch (e) {
console.log(e)
throw new Error(e)
} }
return resolve(json) }
}) return json
} }
/** /**
@ -244,26 +247,26 @@ class FileProvider {
* @param {Function} visitFile is a function called for each visited files * @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders * @param {Function} visitFolder is a function called for each visited folders
*/ */
copyFolderToJson (path, visitFile, visitFolder) { async copyFolderToJson (path, visitFile, visitFolder) {
visitFile = visitFile || function () { /* do nothing. */ } visitFile = visitFile || function () { /* do nothing. */ }
visitFolder = visitFolder || function () { /* do nothing. */ } visitFolder = visitFolder || function () { /* do nothing. */ }
return this._copyFolderToJsonInternal(path, visitFile, visitFolder) return await this._copyFolderToJsonInternal(path, visitFile, visitFolder)
} }
removeFile (path) { async removeFile (path) {
path = this.removePrefix(path) path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path) && !window.remixFileSystem.statSync(path).isDirectory()) { if (await window.remixFileSystem.exists(path) && !(await window.remixFileSystem.stat(path)).isDirectory()) {
window.remixFileSystem.unlinkSync(path, console.log) await window.remixFileSystem.unlink(path)
this.event.emit('fileRemoved', this._normalizePath(path)) this.event.emit('fileRemoved', this._normalizePath(path))
return true return true
} else return false } else return false
} }
rename (oldPath, newPath, isFolder) { async rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath) var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath) var unprefixednewPath = this.removePrefix(newPath)
if (this._exists(unprefixedoldPath)) { if (await this._exists(unprefixedoldPath)) {
window.remixFileSystem.renameSync(unprefixedoldPath, unprefixednewPath) await window.remixFileSystem.rename(unprefixedoldPath, unprefixednewPath)
this.event.emit('fileRenamed', this.event.emit('fileRenamed',
this._normalizePath(unprefixedoldPath), this._normalizePath(unprefixedoldPath),
this._normalizePath(unprefixednewPath), this._normalizePath(unprefixednewPath),
@ -274,24 +277,26 @@ class FileProvider {
return false return false
} }
resolveDirectory (path, callback) { async resolveDirectory (path, cb) {
path = this.removePrefix(path) path = this.removePrefix(path)
if (path.indexOf('/') !== 0) path = '/' + path if (path.indexOf('/') !== 0) path = '/' + path
try {
window.remixFileSystem.readdir(path, (error, files) => { const files = await window.remixFileSystem.readdir(path)
var ret = {} const ret = {}
if (files) { if (files) {
files.forEach(element => { for (let element of files) {
path = path.replace(/^\/|\/$/g, '') // remove first and last slash path = path.replace(/^\/|\/$/g, '') // remove first and last slash
element = element.replace(/^\/|\/$/g, '') // remove first and last slash element = element.replace(/^\/|\/$/g, '') // remove first and last slash
const absPath = (path === '/' ? '' : path) + '/' + element const absPath = (path === '/' ? '' : path) + '/' + element
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() } ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: (await window.remixFileSystem.stat(absPath)).isDirectory() }
// ^ ret does not accept path starting with '/' // ^ ret does not accept path starting with '/'
}) }
} }
callback(error, ret) if (cb) cb(null, ret)
}) return ret
} catch (error) {
if (cb) cb(error, null)
}
} }
removePrefix (path) { removePrefix (path) {

@ -70,13 +70,13 @@ export class TabProxy extends Plugin {
this.tabsApi.activateTab(workspacePath) this.tabsApi.activateTab(workspacePath)
return return
} }
this.addTab(workspacePath, '', () => { this.addTab(workspacePath, '', async () => {
this.fileManager.open(file) await this.fileManager.open(file)
this.event.emit('openFile', file) this.event.emit('openFile', file)
this.emit('openFile', file) this.emit('openFile', file)
}, },
() => { async () => {
this.fileManager.closeFile(file) await this.fileManager.closeFile(file)
this.event.emit('closeFile', file) this.event.emit('closeFile', file)
this.emit('closeFile', file) this.emit('closeFile', file)
}) })
@ -88,13 +88,13 @@ export class TabProxy extends Plugin {
this.tabsApi.activateTab(path) this.tabsApi.activateTab(path)
return return
} }
this.addTab(path, '', () => { this.addTab(path, '', async () => {
this.fileManager.open(file) await this.fileManager.open(file)
this.event.emit('openFile', file) this.event.emit('openFile', file)
this.emit('openFile', file) this.emit('openFile', file)
}, },
() => { async () => {
this.fileManager.closeFile(file) await this.fileManager.closeFile(file)
this.event.emit('closeFile', file) this.event.emit('closeFile', file)
this.emit('closeFile', file) this.emit('closeFile', file)
}) })
@ -197,12 +197,12 @@ export class TabProxy extends Plugin {
} }
renameTab (oldName, newName) { renameTab (oldName, newName) {
this.addTab(newName, '', () => { this.addTab(newName, '', async () => {
this.fileManager.open(newName) await this.fileManager.open(newName)
this.event.emit('openFile', newName) this.event.emit('openFile', newName)
}, },
() => { async () => {
this.fileManager.closeFile(newName) await this.fileManager.closeFile(newName)
this.event.emit('closeFile', newName) this.event.emit('closeFile', newName)
this.emit('closeFile', newName) this.emit('closeFile', newName)
}) })

@ -0,0 +1,22 @@
import { Plugin } from '@remixproject/engine';
const profile = {
name: 'storage',
displayName: 'Storage',
description: 'Storage',
methods: ['getStorage']
};
export class StoragePlugin extends Plugin {
constructor() {
super(profile);
}
async getStorage() {
if ('storage' in navigator && 'estimate' in navigator.storage) {
return navigator.storage.estimate()
} else {
throw new Error("Can't get storage quota");
}
}
}

@ -0,0 +1,107 @@
/* eslint-disable prefer-promise-reject-errors */
function urlParams () {
var qs = window.location.hash.substr(1)
if (window.location.search.length > 0) {
// use legacy query params instead of hash
window.location.hash = window.location.search.substr(1)
window.location.search = ''
}
var params = {}
var parts = qs.split('&')
for (var x in parts) {
var keyValue = parts[x].split('=')
if (keyValue[0] !== '') {
params[keyValue[0]] = keyValue[1]
}
}
return params
}
const defaultVersion = '0.8.0'
const versionToLoad = urlParams().appVersion ? urlParams().appVersion : defaultVersion
const assets = {
'0.8.0': ['https://use.fontawesome.com/releases/v5.8.1/css/all.css', 'assets/css/pygment_trac.css'],
'0.7.7': ['assets/css/font-awesome.min.css', 'assets/css/pygment_trac.css']
}
const versions = {
'0.7.7': 'assets/js/0.7.7/app.js', // commit 7b5c7ae3de935e0ccc32eadfd83bf7349478491e
'0.8.0': 'main.js'
}
for (const k in assets[versionToLoad]) {
const app = document.createElement('link')
app.setAttribute('rel', 'stylesheet')
app.setAttribute('href', assets[versionToLoad][k])
if (assets[versionToLoad][k] === 'https://use.fontawesome.com/releases/v5.8.1/css/all.css') {
app.setAttribute('integrity', 'sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf')
app.setAttribute('crossorigin', 'anonymous')
}
document.head.appendChild(app)
}
window.onload = () => {
// eslint-disable-next-line no-undef
class RemixFileSystem extends LightningFS {
constructor (...t) {
super(...t)
this.addSlash = (file) => {
if (!file.startsWith('/')) file = '/' + file
return file
}
this.base = this.promises
this.promises = {
...this.promises,
exists: async (path) => {
return new Promise((resolve, reject) => {
this.base.stat(this.addSlash(path)).then(() => resolve(true)).catch(() => resolve(false))
})
},
rmdir: async (path) => {
return this.base.rmdir(this.addSlash(path))
},
readdir: async (path) => {
return this.base.readdir(this.addSlash(path))
},
unlink: async (path) => {
return this.base.unlink(this.addSlash(path))
},
mkdir: async (path) => {
return this.base.mkdir(this.addSlash(path))
},
readFile: async (path, options) => {
return this.base.readFile(this.addSlash(path), options)
},
rename: async (from, to) => {
return this.base.rename(this.addSlash(from), this.addSlash(to))
},
writeFile: async (path, content, options) => {
return this.base.writeFile(this.addSlash(path), content, options)
},
stat: async (path) => {
return this.base.stat(this.addSlash(path))
}
}
}
}
function loadApp () {
const app = document.createElement('script')
app.setAttribute('src', versions[versionToLoad])
document.body.appendChild(app)
}
window.remixFileSystemCallback = new RemixFileSystem()
window.remixFileSystemCallback.init('RemixFileSystem').then(() => {
window.remixFileSystem = window.remixFileSystemCallback.promises
// check if .workspaces is present in indexeddb
window.remixFileSystem.stat('.workspaces').then((dir) => {
if (dir.isDirectory()) loadApp()
}).catch(() => {
// no indexeddb .workspaces -> run migration
// eslint-disable-next-line no-undef
migrateFilesFromLocalStorage(loadApp)
})
})
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,139 @@
// eslint-disable-next-line no-unused-vars
async function migrateFilesFromLocalStorage (cb) {
let testmigration = false // migration loads test data into localstorage with browserfs
// indexeddb will be empty by this point, so there is no danger but do a check for the origin to load test data so it runs only locally
testmigration = window.location.hash.includes('e2e_testmigration=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:'
// eslint-disable-next-line no-undef
BrowserFS.install(window)
// eslint-disable-next-line no-undef
BrowserFS.configure({
fs: 'LocalStorage'
}, async function (e) {
if (e) console.log(e)
const browserFS = window.require('fs')
/**
* copy the folder recursively (internal use)
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
async function _copyFolderToJsonInternal (path, visitFile, visitFolder, fs) {
visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { })
return new Promise((resolve, reject) => {
const json = {}
if (fs.existsSync(path)) {
try {
const items = fs.readdirSync(path)
visitFolder({ path })
if (items.length !== 0) {
items.forEach(async (item, index) => {
const file = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if (fs.statSync(curPath).isDirectory()) {
file.children = await _copyFolderToJsonInternal(curPath, visitFile, visitFolder, fs)
} else {
file.content = fs.readFileSync(curPath, 'utf8')
visitFile({ path: curPath, content: file.content })
}
json[curPath] = file
})
}
} catch (e) {
console.log(e)
return reject(e)
}
}
return resolve(json)
})
}
/**
* copy the folder recursively
* @param {string} path is the folder to be copied over
* @param {Function} visitFile is a function called for each visited files
* @param {Function} visitFolder is a function called for each visited folders
*/
async function copyFolderToJson (path, visitFile, visitFolder, fs) {
visitFile = visitFile || (() => { })
visitFolder = visitFolder || (() => { })
return _copyFolderToJsonInternal(path, visitFile, visitFolder, fs)
}
const populateWorkspace = async (json, fs) => {
for (const item in json) {
const isFolder = json[item].content === undefined
if (isFolder) {
await createDir(item, fs)
await populateWorkspace(json[item].children, fs)
} else {
try {
await fs.writeFile(item, json[item].content, 'utf8')
} catch (error) {
console.log(error)
}
}
}
}
const createDir = async (path, fs) => {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
currentCheck = currentCheck + (currentCheck ? '/' : '') + value
if (!await fs.exists(currentCheck)) {
try {
await fs.mkdir(currentCheck)
} catch (error) {
console.log(error)
}
}
}
}
//
if (testmigration) await populateWorkspace(testData, browserFS)
const files = await copyFolderToJson('/', null, null, browserFS)
await populateWorkspace(files, window.remixFileSystem)
// eslint-disable-next-line no-undef
if (cb) cb()
})
}
/* eslint-disable no-template-curly-in-string */
const testData = {
'.workspaces': {
children: {
'.workspaces/default_workspace': {
children: {
'.workspaces/default_workspace/README.txt': {
content: 'TEST README'
}
}
},
'.workspaces/workspace_test': {
children: {
'.workspaces/workspace_test/TEST_README.txt': {
content: 'TEST README'
},
'.workspaces/workspace_test/test_contracts': {
children: {
'.workspaces/workspace_test/test_contracts/1_Storage.sol': {
content: 'testing'
},
'.workspaces/workspace_test/test_contracts/artifacts': {
children: {
'.workspaces/workspace_test/test_contracts/artifacts/Storage_metadata.json': {
content: '{ "test": "data" }'
}
}
}
}
}
}
}
}
}
}

@ -28,6 +28,8 @@
<link rel="icon" type="x-icon" href="assets/img/icon.png"> <link rel="icon" type="x-icon" href="assets/img/icon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/4.1.0/introjs.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/4.1.0/introjs.min.css">
<script src="assets/js/browserfs.min.js"></script> <script src="assets/js/browserfs.min.js"></script>
<script src="assets/js/migrate.js"></script>
<script src="assets/js/lightning-fs.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!-- Matomo --> <!-- Matomo -->
<script type="text/javascript"> <script type="text/javascript">
@ -57,60 +59,7 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script> <script type="text/javascript" src="assets/js/init.js"></script>
function urlParams () {
var qs = window.location.hash.substr(1)
if (window.location.search.length > 0) {
// use legacy query params instead of hash
window.location.hash = window.location.search.substr(1)
window.location.search = ''
}
var params = {}
var parts = qs.split('&')
for (var x in parts) {
var keyValue = parts[x].split('=')
if (keyValue[0] !== '') {
params[keyValue[0]] = keyValue[1]
}
}
return params
}
const defaultVersion = '0.8.0'
let versionToLoad = urlParams().appVersion ? urlParams().appVersion : defaultVersion
let assets = {
'0.8.0': ['https://use.fontawesome.com/releases/v5.8.1/css/all.css', 'assets/css/pygment_trac.css'],
'0.7.7': ['assets/css/font-awesome.min.css', 'assets/css/pygment_trac.css']
}
let versions = {
'0.7.7': 'assets/js/0.7.7/app.js', // commit 7b5c7ae3de935e0ccc32eadfd83bf7349478491e
'0.8.0': 'main.js'
}
for (let k in assets[versionToLoad]) {
let app = document.createElement('link')
app.setAttribute('rel', 'stylesheet')
app.setAttribute('href', assets[versionToLoad][k])
if (assets[versionToLoad][k] === 'https://use.fontawesome.com/releases/v5.8.1/css/all.css') {
app.setAttribute('integrity', 'sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf')
app.setAttribute('crossorigin', 'anonymous')
}
document.head.appendChild(app)
}
window.onload = () => {
BrowserFS.install(window)
BrowserFS.configure({
fs: "LocalStorage"
}, function(e) {
if (e) console.log(e)
let app = document.createElement('script')
app.setAttribute('src', versions[versionToLoad])
document.body.appendChild(app)
window.remixFileSystem = require('fs')
})
}
</script>
<script src="runtime.js" type="module"></script> <script src="runtime.js" type="module"></script>
<script src="polyfills.js" type="module"></script> <script src="polyfills.js" type="module"></script>
<script src="vendor.js" type="module"></script> <script src="vendor.js" type="module"></script>

@ -64,7 +64,6 @@ module.exports = {
do { do {
const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext) const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext)
if (isDuplicate) counter = (counter | 0) + 1 if (isDuplicate) counter = (counter | 0) + 1
else exist = false else exist = false
} while (exist) } while (exist)

@ -28,6 +28,8 @@
<link rel="icon" type="x-icon" href="assets/img/icon.png"> <link rel="icon" type="x-icon" href="assets/img/icon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/4.1.0/introjs.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intro.js/4.1.0/introjs.min.css">
<script src="assets/js/browserfs.min.js"></script> <script src="assets/js/browserfs.min.js"></script>
<script src="assets/js/migrate.js"></script>
<script src="assets/js/lightning-fs.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!-- Matomo --> <!-- Matomo -->
<script type="text/javascript"> <script type="text/javascript">
@ -57,60 +59,7 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script> <script type="text/javascript" src="assets/js/init.js"></script>
function urlParams () {
var qs = window.location.hash.substr(1)
if (window.location.search.length > 0) {
// use legacy query params instead of hash
window.location.hash = window.location.search.substr(1)
window.location.search = ''
}
var params = {}
var parts = qs.split('&')
for (var x in parts) {
var keyValue = parts[x].split('=')
if (keyValue[0] !== '') {
params[keyValue[0]] = keyValue[1]
}
}
return params
}
const defaultVersion = '0.8.0'
let versionToLoad = urlParams().appVersion ? urlParams().appVersion : defaultVersion
let assets = {
'0.8.0': ['https://use.fontawesome.com/releases/v5.8.1/css/all.css', 'assets/css/pygment_trac.css'],
'0.7.7': ['assets/css/font-awesome.min.css', 'assets/css/pygment_trac.css']
}
let versions = {
'0.7.7': 'assets/js/0.7.7/app.js', // commit 7b5c7ae3de935e0ccc32eadfd83bf7349478491e
'0.8.0': 'main.js'
}
for (let k in assets[versionToLoad]) {
let app = document.createElement('link')
app.setAttribute('rel', 'stylesheet')
app.setAttribute('href', assets[versionToLoad][k])
if (assets[versionToLoad][k] === 'https://use.fontawesome.com/releases/v5.8.1/css/all.css') {
app.setAttribute('integrity', 'sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf')
app.setAttribute('crossorigin', 'anonymous')
}
document.head.appendChild(app)
}
window.onload = () => {
BrowserFS.install(window)
BrowserFS.configure({
fs: "LocalStorage"
}, function(e) {
if (e) console.log(e)
let app = document.createElement('script')
app.setAttribute('src', versions[versionToLoad])
document.body.appendChild(app)
window.remixFileSystem = require('fs')
})
}
</script>
<script src="polyfills.js" type="module"></script> <script src="polyfills.js" type="module"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/intro.min.js"></script> <script type="text/javascript" src="assets/js/intro.min.js"></script>

@ -7,7 +7,7 @@ const _paq = window._paq = window._paq || []
const requiredModules = [ // services + layout views + system views const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough'] 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage']
const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd) const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)

@ -103,7 +103,7 @@ export class CompilerImports extends Plugin {
try { try {
const provider = await this.call('fileManager', 'getProviderOf', null) const provider = await this.call('fileManager', 'getProviderOf', null)
const path = targetPath || type + '/' + cleanUrl const path = targetPath || type + '/' + cleanUrl
if (provider) provider.addExternal('.deps/' + path, content, url) if (provider) await provider.addExternal('.deps/' + path, content, url)
} catch (err) { } catch (err) {
console.error(err) console.error(err)
} }

@ -82,7 +82,6 @@ export class DebuggerStepManager {
} }
const jumpOutDisabled = (step === this.traceManager.findStepOut(step)) const jumpOutDisabled = (step === this.traceManager.findStepOut(step))
this.event.trigger('stepChanged', [step, stepState, jumpOutDisabled]) this.event.trigger('stepChanged', [step, stepState, jumpOutDisabled])
}) })
} }

@ -230,6 +230,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
debugWithGeneratedSources: state.opt.debugWithGeneratedSources debugWithGeneratedSources: state.opt.debugWithGeneratedSources
}) })
setTimeout(async() => {
try { try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => { await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt) listenToEvents(debuggerInstance, currentReceipt)
@ -257,6 +258,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
}) })
} }
}, 300)
} }
const debug = (txHash, web3?) => { const debug = (txHash, web3?) => {

@ -636,13 +636,13 @@ export const runTransactions = (
) )
} }
const saveScenario = (newPath: string, provider, promptCb, cb) => { const saveScenario = async (newPath: string, provider, promptCb, cb) => {
const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2) const txJSON = JSON.stringify(plugin.recorder.getAll(), null, 2)
promptCb(() => { promptCb(async () => {
try { try {
if (!provider.set(newPath, txJSON)) return cb('Failed to create file ' + newPath) await provider.set(newPath, txJSON)
plugin.fileManager.open(newPath) await plugin.fileManager.open(newPath)
} catch (error) { } catch (error) {
if (error) return cb('Failed to create file. ' + newPath + ' ' + error) if (error) return cb('Failed to create file. ' + newPath + ' ' + error)
} }
@ -651,7 +651,7 @@ const saveScenario = (newPath: string, provider, promptCb, cb) => {
export const storeScenario = async (prompt: (msg: string, defaultValue: string) => JSX.Element) => { export const storeScenario = async (prompt: (msg: string, defaultValue: string) => JSX.Element) => {
const path = plugin.fileManager.currentPath() const path = plugin.fileManager.currentPath()
const fileProvider = plugin.fileManager.fileProviderOf(path) const fileProvider = await plugin.fileManager.fileProviderOf(path)
if (!fileProvider) return displayNotification('Alert', 'Invalid File Provider', 'OK', null) if (!fileProvider) return displayNotification('Alert', 'Invalid File Provider', 'OK', null)
const newPath = await createNonClashingNameAsync(path + '/' + plugin.REACT_API.recorder.pathToScenario, plugin.fileManager) const newPath = await createNonClashingNameAsync(path + '/' + plugin.REACT_API.recorder.pathToScenario, plugin.fileManager)

@ -38,20 +38,23 @@ export class TestTabLogic {
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
generateTestFile (errorCb:any) { async generateTestFile (errorCb:any) {
let fileName = this.fileManager.currentFile() let fileName = this.fileManager.currentFile()
const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol' const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol'
if (!hasCurrent) fileName = this.currentPath + '/newFile.sol' if (!hasCurrent) fileName = this.currentPath + '/newFile.sol'
const fileProvider = this.fileManager.fileProviderOf(this.currentPath) const fileProvider = await this.fileManager.fileProviderOf(this.currentPath)
if (!fileProvider) return if (!fileProvider) return
const splittedFileName = fileName.split('/') const splittedFileName = fileName.split('/')
const fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1] const fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1]
this.helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', (error: Error, newFile: string) => { this.helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', async (error: Error, newFile: string) => {
if (error) return errorCb('Failed to create file. ' + newFile + ' ' + error) if (error) return errorCb('Failed to create file. ' + newFile + ' ' + error)
const isFileCreated = fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName)) try{
if (!isFileCreated) return errorCb('Failed to create test file ' + newFile) await fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName))
this.fileManager.open(newFile) await this.fileManager.open(newFile)
this.fileManager.syncEditor(newFile) await this.fileManager.syncEditor(newFile)
}catch(e){
return errorCb('Failed to create test file ' + newFile)
}
}) })
} }

@ -680,7 +680,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
title="Generate sample test file." title="Generate sample test file."
disabled={disableGenerateButton} disabled={disableGenerateButton}
onClick={async () => { onClick={async () => {
testTabLogic.generateTestFile((err:any) => { if (err) setToasterMsg(err)}) // eslint-disable-line @typescript-eslint/no-explicit-any await testTabLogic.generateTestFile((err:any) => { if (err) setToasterMsg(err)}) // eslint-disable-line @typescript-eslint/no-explicit-any
await updateForNewCurrent() await updateForNewCurrent()
}} }}
> >

@ -142,7 +142,6 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
if (cb) cb() if (cb) cb()
return return
} }
provider.get(file, (error, content) => { provider.get(file, (error, content) => {
console.log({ content }) console.log({ content })
if (error) { if (error) {

@ -154,7 +154,6 @@ const folderAdded = async (folderPath: string) => {
const promise = new Promise((resolve) => { const promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree) => { provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error) if (error) console.error(error)
resolve(fileTree) resolve(fileTree)
}) })
}) })

@ -42,7 +42,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
plugin.setWorkspace({ name: 'code-sample', isLocalhost: false }) plugin.setWorkspace({ name: 'code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('code-sample')) dispatch(setCurrentWorkspace('code-sample'))
const filePath = await loadWorkspacePreset('code-template') const filePath = await loadWorkspacePreset('code-template')
plugin.on('editor', 'editorMounted', () => plugin.fileManager.openFile(filePath)) plugin.on('editor', 'editorMounted', async () => await plugin.fileManager.openFile(filePath))
} else { } else {
if (workspaces.length === 0) { if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace', 'default-template') await createWorkspaceTemplate('default_workspace', 'default-template')
@ -229,7 +229,7 @@ export const copyFile = async (src: string, dest: string) => {
const fileManager = plugin.fileManager const fileManager = plugin.fileManager
try { try {
fileManager.copyFile(src, dest) await fileManager.copyFile(src, dest)
} catch (error) { } catch (error) {
dispatch(displayPopUp('Oops! An error ocurred while performing copyFile operation.' + error)) dispatch(displayPopUp('Oops! An error ocurred while performing copyFile operation.' + error))
} }
@ -239,7 +239,7 @@ export const copyFolder = async (src: string, dest: string) => {
const fileManager = plugin.fileManager const fileManager = plugin.fileManager
try { try {
fileManager.copyDir(src, dest) await fileManager.copyDir(src, dest)
} catch (error) { } catch (error) {
dispatch(displayPopUp('Oops! An error ocurred while performing copyDir operation.' + error)) dispatch(displayPopUp('Oops! An error ocurred while performing copyDir operation.' + error))
} }
@ -257,11 +257,11 @@ export const runScript = async (path: string) => {
} }
export const emitContextMenuEvent = async (cmd: customAction) => { export const emitContextMenuEvent = async (cmd: customAction) => {
plugin.call(cmd.id, cmd.name, cmd) await plugin.call(cmd.id, cmd.name, cmd)
} }
export const handleClickFile = async (path: string, type: 'file' | 'folder' | 'gist') => { export const handleClickFile = async (path: string, type: 'file' | 'folder' | 'gist') => {
plugin.fileManager.open(path) await plugin.fileManager.open(path)
dispatch(focusElement([{ key: path, type }])) dispatch(focusElement([{ key: path, type }]))
} }
@ -269,10 +269,10 @@ export const handleExpandPath = (paths: string[]) => {
dispatch(setExpandPath(paths)) dispatch(setExpandPath(paths))
} }
const packageGistFiles = (directory) => { const packageGistFiles = async (directory) => {
const workspaceProvider = plugin.fileProviders.workspace
const isFile = await workspaceProvider.isFile(directory)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const workspaceProvider = plugin.fileProviders.workspace
const isFile = workspaceProvider.isFile(directory)
const ret = {} const ret = {}
if (isFile) { if (isFile) {

@ -46,9 +46,9 @@ export const createWorkspace = async (workspaceName: string, isEmpty = false, cb
dispatch(createWorkspaceRequest(promise)) dispatch(createWorkspaceRequest(promise))
promise.then(async () => { promise.then(async () => {
dispatch(createWorkspaceSuccess(workspaceName)) dispatch(createWorkspaceSuccess(workspaceName))
plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) await plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
plugin.setWorkspaces(await getWorkspaces()) await plugin.setWorkspaces(await getWorkspaces())
plugin.workspaceCreated(workspaceName) await plugin.workspaceCreated(workspaceName)
if (!isEmpty) await loadWorkspacePreset('default-template') if (!isEmpty) await loadWorkspacePreset('default-template')
cb && cb(null, workspaceName) cb && cb(null, workspaceName)
}).catch((error) => { }).catch((error) => {
@ -90,14 +90,14 @@ export const loadWorkspacePreset = async (template: 'gist-template' | 'code-temp
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol' path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code) content = atob(params.code)
workspaceProvider.set(path, content) await workspaceProvider.set(path, content)
} }
if (params.url) { if (params.url) {
const data = await plugin.call('contentImport', 'resolve', params.url) const data = await plugin.call('contentImport', 'resolve', params.url)
path = data.cleanUrl path = data.cleanUrl
content = data.content content = data.content
workspaceProvider.set(path, content) await workspaceProvider.set(path, content)
} }
return path return path
} catch (e) { } catch (e) {
@ -152,7 +152,7 @@ export const workspaceExists = async (name: string) => {
const browserProvider = plugin.fileProviders.browser const browserProvider = plugin.fileProviders.browser
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
return browserProvider.exists(workspacePath) return await browserProvider.exists(workspacePath)
} }
export const fetchWorkspaceDirectory = async (path: string) => { export const fetchWorkspaceDirectory = async (path: string) => {
@ -178,8 +178,8 @@ export const fetchWorkspaceDirectory = async (path: string) => {
export const renameWorkspace = async (oldName: string, workspaceName: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => { export const renameWorkspace = async (oldName: string, workspaceName: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
await renameWorkspaceFromProvider(oldName, workspaceName) await renameWorkspaceFromProvider(oldName, workspaceName)
await dispatch(setRenameWorkspace(oldName, workspaceName)) await dispatch(setRenameWorkspace(oldName, workspaceName))
plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) await plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
plugin.workspaceRenamed(oldName, workspaceName) await plugin.workspaceRenamed(oldName, workspaceName)
cb && cb(null, workspaceName) cb && cb(null, workspaceName)
} }
@ -190,9 +190,9 @@ export const renameWorkspaceFromProvider = async (oldName: string, workspaceName
const browserProvider = plugin.fileProviders.browser const browserProvider = plugin.fileProviders.browser
const workspaceProvider = plugin.fileProviders.workspace const workspaceProvider = plugin.fileProviders.workspace
const workspacesPath = workspaceProvider.workspacesPath const workspacesPath = workspaceProvider.workspacesPath
browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true) await browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true)
workspaceProvider.setWorkspace(workspaceName) await workspaceProvider.setWorkspace(workspaceName)
plugin.setWorkspaces(await getWorkspaces()) await plugin.setWorkspaces(await getWorkspaces())
} }
export const deleteWorkspace = async (workspaceName: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => { export const deleteWorkspace = async (workspaceName: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
@ -206,8 +206,8 @@ const deleteWorkspaceFromProvider = async (workspaceName: string) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath const workspacesPath = plugin.fileProviders.workspace.workspacesPath
await plugin.fileManager.closeAllFiles() await plugin.fileManager.closeAllFiles()
plugin.fileProviders.browser.remove(workspacesPath + '/' + workspaceName) await plugin.fileProviders.browser.remove(workspacesPath + '/' + workspaceName)
plugin.setWorkspaces(await getWorkspaces()) await plugin.setWorkspaces(await getWorkspaces())
} }
export const switchToWorkspace = async (name: string) => { export const switchToWorkspace = async (name: string) => {
@ -219,15 +219,15 @@ export const switchToWorkspace = async (name: string) => {
dispatch(setMode('localhost')) dispatch(setMode('localhost'))
plugin.emit('setWorkspace', { name: null, isLocalhost: true }) plugin.emit('setWorkspace', { name: null, isLocalhost: true })
} else if (name === NO_WORKSPACE) { } else if (name === NO_WORKSPACE) {
plugin.fileProviders.workspace.clearWorkspace() await plugin.fileProviders.workspace.clearWorkspace()
plugin.setWorkspace({ name: null, isLocalhost: false }) await plugin.setWorkspace({ name: null, isLocalhost: false })
dispatch(setCurrentWorkspace(null)) dispatch(setCurrentWorkspace(null))
} else { } else {
const isActive = await plugin.call('manager', 'isActive', 'remixd') const isActive = await plugin.call('manager', 'isActive', 'remixd')
if (isActive) await plugin.call('manager', 'deactivatePlugin', 'remixd') if (isActive) await plugin.call('manager', 'deactivatePlugin', 'remixd')
await plugin.fileProviders.workspace.setWorkspace(name) await plugin.fileProviders.workspace.setWorkspace(name)
plugin.setWorkspace({ name, isLocalhost: false }) await plugin.setWorkspace({ name, isLocalhost: false })
dispatch(setMode('browser')) dispatch(setMode('browser'))
dispatch(setCurrentWorkspace(name)) dispatch(setCurrentWorkspace(name))
dispatch(setReadOnlyMode(false)) dispatch(setReadOnlyMode(false))
@ -239,7 +239,7 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
// the files module. Please ask the user here if they want to overwrite // the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will // a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module. // pick that up via the 'fileAdded' event from the files module.
[...target.files].forEach((file) => { [...target.files].forEach(async (file) => {
const workspaceProvider = plugin.fileProviders.workspace const workspaceProvider = plugin.fileProviders.workspace
const loadFile = (name: string): void => { const loadFile = (name: string): void => {
const fileReader = new FileReader() const fileReader = new FileReader()
@ -248,11 +248,12 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
if (checkSpecialChars(file.name)) { if (checkSpecialChars(file.name)) {
return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => {})) return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => {}))
} }
const success = await workspaceProvider.set(name, event.target.result) try {
await workspaceProvider.set(name, event.target.result)
if (!success) { } catch (error) {
return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => {})) return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => {}))
} }
const config = plugin.registry.get('config').api const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api const editor = plugin.registry.get('editor').api
@ -265,18 +266,13 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
} }
const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}` const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}`
workspaceProvider.exists(name).then(exist => { if (!await workspaceProvider.exists(name)) {
if (!exist) { loadFile(name)
} else {
dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => {
loadFile(name) loadFile(name)
} else { }, () => {}))
dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => { }
loadFile(name)
}, () => {}))
}
}).catch(error => {
cb && cb(error)
if (error) console.log(error)
})
}) })
} }
@ -295,7 +291,7 @@ export const getWorkspaces = async (): Promise<string[]> | undefined => {
}) })
}) })
plugin.setWorkspaces(workspaces) await plugin.setWorkspaces(workspaces)
return workspaces return workspaces
} catch (e) {} } catch (e) {}
} }

@ -656,14 +656,22 @@ const fetchDirectoryContent = (state: BrowserState, payload: { fileTree, path: s
if (state.mode === 'browser') { if (state.mode === 'browser') {
if (payload.path === state.browser.currentWorkspace) { if (payload.path === state.browser.currentWorkspace) {
let files = normalize(payload.fileTree, payload.path, payload.type) let files = normalize(payload.fileTree, payload.path, payload.type)
files = _.merge(files, state.browser.files[state.browser.currentWorkspace]) files = _.merge(files, state.browser.files[state.browser.currentWorkspace])
if (deletePath) delete files[deletePath] if (deletePath) delete files[deletePath]
return { [state.browser.currentWorkspace]: files } return { [state.browser.currentWorkspace]: files }
} else { } else {
let files = state.browser.files let files = state.browser.files
const _path = splitPath(state, payload.path) const _path = splitPath(state, payload.path)
const prevFiles = _.get(files, _path) let prevFiles = _.get(files, _path)
if (!prevFiles) {
const object = {}; let o = object
for (const pa of _path) {
o = o[pa] = {}
}
files = _.defaultsDeep(files, object)
prevFiles = _.get(files, _path)
}
if (prevFiles) { if (prevFiles) {
prevFiles.child = _.merge(normalize(payload.fileTree, payload.path, payload.type), prevFiles.child) prevFiles.child = _.merge(normalize(payload.fileTree, payload.path, payload.type), prevFiles.child)

669
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -95,6 +95,7 @@
"nightwatch_local_url": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/url.test.js --env=chrome", "nightwatch_local_url": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/url.test.js --env=chrome",
"nightwatch_local_verticalIconscontextmenu": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.js --env=chrome", "nightwatch_local_verticalIconscontextmenu": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.js --env=chrome",
"nightwatch_local_pluginApi": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/plugin_api_*.js --env=chrome", "nightwatch_local_pluginApi": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/plugin_api_*.js --env=chrome",
"nightwatch_local_migrate_filesystem": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/migrateFileSystem.test.js --env=chrome",
"onchange": "onchange apps/remix-ide/build/app.js -- npm-run-all lint", "onchange": "onchange apps/remix-ide/build/app.js -- npm-run-all lint",
"remixd": "nx build remixd && chmod +x dist/libs/remixd/src/bin/remixd.js && dist/libs/remixd/src/bin/remixd.js -s ./apps/remix-ide/contracts --remix-ide http://127.0.0.1:8080", "remixd": "nx build remixd && chmod +x dist/libs/remixd/src/bin/remixd.js && dist/libs/remixd/src/bin/remixd.js -s ./apps/remix-ide/contracts --remix-ide http://127.0.0.1:8080",
"selenium": "selenium-standalone start", "selenium": "selenium-standalone start",

@ -1196,11 +1196,6 @@
"linter": "eslint" "linter": "eslint"
} }
}, },
"@nrwl/cypress": {
"cypress-project": {
"linter": "eslint"
}
},
"@nrwl/react": { "@nrwl/react": {
"application": { "application": {
"style": "css", "style": "css",

Loading…
Cancel
Save