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: "Circle CI"
working_directory: ~/remix-project
steps:
- checkout

@ -13,7 +13,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.fullscreenWindow(() => {
if (preloadPlugins) {
initModules(browser, () => {
browser.clickLaunchIcon('solidity')
browser.pause(2000).clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.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) {
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) {
await clickAndCheckLog(browser, 'fileManager:setFile', null, { event: 'fileAdded', args: ['new.sol'] }, ['new.sol', 'test'])

@ -11,7 +11,7 @@ module.exports = {
return sources
},
'Test Recorder': function (browser: NightwatchBrowser) {
'Run Scenario': function (browser: NightwatchBrowser) {
let addressRef
browser.addFile('scenario.json', { content: records })
.pause(5000)
@ -34,7 +34,10 @@ module.exports = {
.perform(() => done())
})
.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')
.createContract('12')
.clickInstance(0)
@ -45,7 +48,7 @@ module.exports = {
const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modalOk.click()
})
}).pause(1000)
.getEditorValue(function (result) {
const parsed = JSON.parse(result)
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) {
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')
.verifyContracts(['test6', 'test4', 'test5'])
.pause(1000)

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

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

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

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

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

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

@ -70,13 +70,13 @@ export class TabProxy extends Plugin {
this.tabsApi.activateTab(workspacePath)
return
}
this.addTab(workspacePath, '', () => {
this.fileManager.open(file)
this.addTab(workspacePath, '', async () => {
await this.fileManager.open(file)
this.event.emit('openFile', file)
this.emit('openFile', file)
},
() => {
this.fileManager.closeFile(file)
async () => {
await this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
this.emit('closeFile', file)
})
@ -88,13 +88,13 @@ export class TabProxy extends Plugin {
this.tabsApi.activateTab(path)
return
}
this.addTab(path, '', () => {
this.fileManager.open(file)
this.addTab(path, '', async () => {
await this.fileManager.open(file)
this.event.emit('openFile', file)
this.emit('openFile', file)
},
() => {
this.fileManager.closeFile(file)
async () => {
await this.fileManager.closeFile(file)
this.event.emit('closeFile', file)
this.emit('closeFile', file)
})
@ -197,12 +197,12 @@ export class TabProxy extends Plugin {
}
renameTab (oldName, newName) {
this.addTab(newName, '', () => {
this.fileManager.open(newName)
this.addTab(newName, '', async () => {
await this.fileManager.open(newName)
this.event.emit('openFile', newName)
},
() => {
this.fileManager.closeFile(newName)
async () => {
await this.fileManager.closeFile(newName)
this.event.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="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/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">
<!-- Matomo -->
<script type="text/javascript">
@ -57,60 +59,7 @@
</head>
<body>
<div id="root"></div>
<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 type="text/javascript" src="assets/js/init.js"></script>
<script src="runtime.js" type="module"></script>
<script src="polyfills.js" type="module"></script>
<script src="vendor.js" type="module"></script>

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

@ -28,6 +28,8 @@
<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">
<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">
<!-- Matomo -->
<script type="text/javascript">
@ -57,60 +59,7 @@
</head>
<body>
<div id="root"></div>
<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 type="text/javascript" src="assets/js/init.js"></script>
<script src="polyfills.js" type="module"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></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
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'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)

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

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

@ -230,6 +230,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
debugWithGeneratedSources: state.opt.debugWithGeneratedSources
})
setTimeout(async() => {
try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt)
@ -257,6 +258,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
})
}
}, 300)
}
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)
promptCb(() => {
promptCb(async () => {
try {
if (!provider.set(newPath, txJSON)) return cb('Failed to create file ' + newPath)
plugin.fileManager.open(newPath)
await provider.set(newPath, txJSON)
await plugin.fileManager.open(newPath)
} catch (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) => {
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)
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
generateTestFile (errorCb:any) {
async generateTestFile (errorCb:any) {
let fileName = this.fileManager.currentFile()
const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === '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
const splittedFileName = fileName.split('/')
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)
const isFileCreated = fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName))
if (!isFileCreated) return errorCb('Failed to create test file ' + newFile)
this.fileManager.open(newFile)
this.fileManager.syncEditor(newFile)
try{
await fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName))
await this.fileManager.open(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."
disabled={disableGenerateButton}
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()
}}
>

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

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

@ -42,7 +42,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
plugin.setWorkspace({ name: 'code-sample', isLocalhost: false })
dispatch(setCurrentWorkspace('code-sample'))
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 {
if (workspaces.length === 0) {
await createWorkspaceTemplate('default_workspace', 'default-template')
@ -229,7 +229,7 @@ export const copyFile = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
try {
fileManager.copyFile(src, dest)
await fileManager.copyFile(src, dest)
} catch (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
try {
fileManager.copyDir(src, dest)
await fileManager.copyDir(src, dest)
} catch (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) => {
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') => {
plugin.fileManager.open(path)
await plugin.fileManager.open(path)
dispatch(focusElement([{ key: path, type }]))
}
@ -269,10 +269,10 @@ export const handleExpandPath = (paths: string[]) => {
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) => {
const workspaceProvider = plugin.fileProviders.workspace
const isFile = workspaceProvider.isFile(directory)
const ret = {}
if (isFile) {

@ -46,9 +46,9 @@ export const createWorkspace = async (workspaceName: string, isEmpty = false, cb
dispatch(createWorkspaceRequest(promise))
promise.then(async () => {
dispatch(createWorkspaceSuccess(workspaceName))
plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
plugin.setWorkspaces(await getWorkspaces())
plugin.workspaceCreated(workspaceName)
await plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
await plugin.setWorkspaces(await getWorkspaces())
await plugin.workspaceCreated(workspaceName)
if (!isEmpty) await loadWorkspacePreset('default-template')
cb && cb(null, workspaceName)
}).catch((error) => {
@ -90,14 +90,14 @@ export const loadWorkspacePreset = async (template: 'gist-template' | 'code-temp
path = 'contract-' + hash.replace('0x', '').substring(0, 10) + '.sol'
content = atob(params.code)
workspaceProvider.set(path, content)
await workspaceProvider.set(path, content)
}
if (params.url) {
const data = await plugin.call('contentImport', 'resolve', params.url)
path = data.cleanUrl
content = data.content
workspaceProvider.set(path, content)
await workspaceProvider.set(path, content)
}
return path
} catch (e) {
@ -152,7 +152,7 @@ export const workspaceExists = async (name: string) => {
const browserProvider = plugin.fileProviders.browser
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
return browserProvider.exists(workspacePath)
return await browserProvider.exists(workspacePath)
}
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) => {
await renameWorkspaceFromProvider(oldName, workspaceName)
await dispatch(setRenameWorkspace(oldName, workspaceName))
plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
plugin.workspaceRenamed(oldName, workspaceName)
await plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
await plugin.workspaceRenamed(oldName, workspaceName)
cb && cb(null, workspaceName)
}
@ -190,9 +190,9 @@ export const renameWorkspaceFromProvider = async (oldName: string, workspaceName
const browserProvider = plugin.fileProviders.browser
const workspaceProvider = plugin.fileProviders.workspace
const workspacesPath = workspaceProvider.workspacesPath
browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true)
workspaceProvider.setWorkspace(workspaceName)
plugin.setWorkspaces(await getWorkspaces())
await browserProvider.rename('browser/' + workspacesPath + '/' + oldName, 'browser/' + workspacesPath + '/' + workspaceName, true)
await workspaceProvider.setWorkspace(workspaceName)
await plugin.setWorkspaces(await getWorkspaces())
}
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
await plugin.fileManager.closeAllFiles()
plugin.fileProviders.browser.remove(workspacesPath + '/' + workspaceName)
plugin.setWorkspaces(await getWorkspaces())
await plugin.fileProviders.browser.remove(workspacesPath + '/' + workspaceName)
await plugin.setWorkspaces(await getWorkspaces())
}
export const switchToWorkspace = async (name: string) => {
@ -219,15 +219,15 @@ export const switchToWorkspace = async (name: string) => {
dispatch(setMode('localhost'))
plugin.emit('setWorkspace', { name: null, isLocalhost: true })
} else if (name === NO_WORKSPACE) {
plugin.fileProviders.workspace.clearWorkspace()
plugin.setWorkspace({ name: null, isLocalhost: false })
await plugin.fileProviders.workspace.clearWorkspace()
await plugin.setWorkspace({ name: null, isLocalhost: false })
dispatch(setCurrentWorkspace(null))
} else {
const isActive = await plugin.call('manager', 'isActive', 'remixd')
if (isActive) await plugin.call('manager', 'deactivatePlugin', 'remixd')
await plugin.fileProviders.workspace.setWorkspace(name)
plugin.setWorkspace({ name, isLocalhost: false })
await plugin.setWorkspace({ name, isLocalhost: false })
dispatch(setMode('browser'))
dispatch(setCurrentWorkspace(name))
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
// a file and then just use `files.add`. The file explorer will
// 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 loadFile = (name: string): void => {
const fileReader = new FileReader()
@ -248,11 +248,12 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
if (checkSpecialChars(file.name)) {
return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => {}))
}
const success = await workspaceProvider.set(name, event.target.result)
if (!success) {
try {
await workspaceProvider.set(name, event.target.result)
} catch (error) {
return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => {}))
}
const config = plugin.registry.get('config').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}`
workspaceProvider.exists(name).then(exist => {
if (!exist) {
if (!await workspaceProvider.exists(name)) {
loadFile(name)
} else {
dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => {
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
} catch (e) {}
}

@ -656,14 +656,22 @@ const fetchDirectoryContent = (state: BrowserState, payload: { fileTree, path: s
if (state.mode === 'browser') {
if (payload.path === state.browser.currentWorkspace) {
let files = normalize(payload.fileTree, payload.path, payload.type)
files = _.merge(files, state.browser.files[state.browser.currentWorkspace])
if (deletePath) delete files[deletePath]
return { [state.browser.currentWorkspace]: files }
} else {
let files = state.browser.files
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) {
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_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_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",
"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",

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

Loading…
Cancel
Save