Merge pull request #668 from ethereum/file-explorer

File explorer
pull/745/head
David Disu 4 years ago committed by GitHub
commit 03437f3295
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      apps/remix-ide-e2e/src/commands/addFile.ts
  2. 4
      apps/remix-ide-e2e/src/commands/getModalBody.ts
  3. 4
      apps/remix-ide-e2e/src/commands/openFile.ts
  4. 16
      apps/remix-ide-e2e/src/commands/removeFile.ts
  5. 13
      apps/remix-ide-e2e/src/commands/renamePath.ts
  6. 3
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  7. 20
      apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
  8. 25
      apps/remix-ide-e2e/src/tests/editor.test.ts
  9. 76
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  10. 11
      apps/remix-ide-e2e/src/tests/fileManager_api.test.ts
  11. 2
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  12. 33
      apps/remix-ide-e2e/src/tests/gist.test.ts
  13. 18
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  14. 2
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  15. 1
      apps/remix-ide-e2e/src/tests/runAndDeploy.ts
  16. 6
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  17. 5
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  18. 2
      apps/remix-ide-e2e/src/tests/usingWebWorker.test.ts
  19. 6
      apps/remix-ide-e2e/src/types/index.d.ts
  20. 7
      apps/remix-ide/src/app/files/remixDProvider.js
  21. 7
      apps/remix-ide/src/app/files/remixd-handle.js
  22. 117
      apps/remix-ide/src/app/panels/file-panel.js
  23. 56
      apps/remix-ide/src/app/panels/styles/file-panel-styles.css
  24. 2
      apps/remix-ide/src/app/panels/tab-proxy.js
  25. 2
      apps/remix-ide/src/app/tabs/test-tab.js
  26. 12
      apps/remix-ide/src/app/ui/modaldialog.js
  27. 2
      libs/remix-lib/src/eventManager.ts
  28. 4
      libs/remix-ui/file-explorer/.babelrc
  29. 19
      libs/remix-ui/file-explorer/.eslintrc
  30. 7
      libs/remix-ui/file-explorer/README.md
  31. 1
      libs/remix-ui/file-explorer/src/index.ts
  32. 28
      libs/remix-ui/file-explorer/src/lib/css/file-explorer-context-menu.css
  33. 55
      libs/remix-ui/file-explorer/src/lib/css/file-explorer.css
  34. 81
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  35. 97
      libs/remix-ui/file-explorer/src/lib/file-explorer-menu.tsx
  36. 947
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  37. 41
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  38. 16
      libs/remix-ui/file-explorer/tsconfig.json
  39. 13
      libs/remix-ui/file-explorer/tsconfig.lib.json
  40. 58
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  41. 15
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  42. 10
      libs/remix-ui/toaster/src/lib/toaster.tsx
  43. 257
      libs/remix-ui/tree-view/.eslintrc
  44. 2
      libs/remix-ui/tree-view/jest.config.js
  45. 4
      libs/remix-ui/tree-view/src/index.ts
  46. 2
      libs/remix-ui/tree-view/src/lib/remix-ui-tree-view.tsx
  47. 10
      libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx
  48. 17
      libs/remix-ui/tree-view/src/types/index.ts
  49. 26
      libs/remixd/src/services/remixdClient.ts
  50. 3
      nx.json
  51. 200
      package-lock.json
  52. 4
      package.json
  53. 3
      tsconfig.json
  54. 19
      workspace.json

@ -14,21 +14,17 @@ class AddFile extends EventEmitter {
}
function addFile (browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) {
browser.clickLaunchIcon('udapp').clickLaunchIcon('fileExplorers').click('.newFile')
.waitForElementVisible('#modal-dialog')
.perform((client, done) => {
browser.execute(function (fileName) {
if (fileName !== 'Untitled.sol') {
document.querySelector('#modal-dialog #prompt_text').setAttribute('value', fileName)
}
const elem = document.querySelector('#modal-footer-ok') as HTMLElement
elem.click()
}, [name], function (result) {
console.log(result)
done()
})
})
browser.clickLaunchIcon('udapp')
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"]') // focus on root directory
.click('.newFile')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
// .scrollAndClick('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items')
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', name)
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.pause(2000)
.waitForElementVisible(`li[data-id="treeViewLitreeViewItembrowser/${name}"]`)
.click(`li[data-id="treeViewLitreeViewItembrowser/${name}"]`)
.setEditorValue(content.content)
.pause(1000)
.perform(function () {

@ -3,8 +3,8 @@ import EventEmitter from "events"
class GetModalBody extends EventEmitter {
command (this: NightwatchBrowser, callback: (value: string, cb: VoidFunction) => void) {
this.api.waitForElementVisible('.modal-body')
.getText('.modal-body', (result) => {
this.api.waitForElementPresent('.modal-body')
.getText('#modal-dialog', (result) => {
console.log(result)
const value = typeof result.value === 'string' ? result.value : null

@ -16,8 +16,8 @@ class OpenFile extends EventEmitter {
// click on fileExplorer can toggle it. We go through settings to be sure FE is open
function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction) {
browser.clickLaunchIcon('settings').clickLaunchIcon('fileExplorers')
.waitForElementVisible('li[key="' + name + '"]')
.click('li[key="' + name + '"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem' + name + '"')
.click('li[data-id="treeViewLitreeViewItem' + name + '"')
.pause(2000)
.perform(() => {
done()

@ -34,13 +34,19 @@ function removeFile (browser: NightwatchBrowser, path: string, done: VoidFunctio
contextMenuClick(document.querySelector('[data-path="' + path + '"]'))
}, [path], function () {
browser
.waitForElementVisible('#menuitemdelete', 2000)
.waitForElementVisible('#menuitemdelete')
.click('#menuitemdelete')
.pause(500)
.waitForElementVisible('#modal-footer-ok', 2000)
.click('#modal-footer-ok')
.waitForElementNotPresent('[data-path="' + path + '"]')
.pause(2000)
.perform(() => {
if (path.indexOf('browser') !== -1) {
browser.waitForElementVisible('[data-id="browser-modal-footer-ok-react"]')
.click('[data-id="browser-modal-footer-ok-react"]')
.waitForElementNotPresent('[data-path="' + path + '"]')
} else if (path.indexOf('localhost') !== -1) {
browser.waitForElementVisible('[data-id="localhost-modal-footer-ok-react"]')
.click('[data-id="localhost-modal-footer-ok-react"]')
.waitForElementNotPresent('[data-path="' + path + '"]')
}
done()
})
})

@ -1,10 +1,10 @@
import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch'
class RenameFile extends EventEmitter {
class RenamePath extends EventEmitter {
command (this: NightwatchBrowser, path: string, newFileName: string, renamedPath: string) {
this.api.perform((done) => {
renameFile(this.api, path, newFileName, renamedPath, () => {
renamePath(this.api, path, newFileName, renamedPath, () => {
done()
this.emit('complete')
})
@ -13,7 +13,7 @@ class RenameFile extends EventEmitter {
}
}
function renameFile (browser: NightwatchBrowser, path: string, newFileName: string, renamedPath: string, done: VoidFunction) {
function renamePath (browser: NightwatchBrowser, path: string, newFileName: string, renamedPath: string, done: VoidFunction) {
browser.execute(function (path: string) {
function contextMenuClick (element) {
const evt = element.ownerDocument.createEvent('MouseEvents')
@ -41,10 +41,9 @@ function renameFile (browser: NightwatchBrowser, path: string, newFileName: stri
doneSetValue()
})
})
.click('body') // blur
.waitForElementVisible('#modal-footer-ok', 100000)
.pause(1000)
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"]') // focus on root directory
.pause(2000)
.click('#modal-footer-ok')
.waitForElementNotPresent('[data-path="' + path + '"]')
.waitForElementPresent('[data-path="' + renamedPath + '"]')
.perform(() => {
@ -53,4 +52,4 @@ function renameFile (browser: NightwatchBrowser, path: string, newFileName: stri
})
}
module.exports = RenameFile
module.exports = RenamePath

@ -109,7 +109,8 @@ module.exports = {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js')
.clickLaunchIcon('udapp')
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/externalImport.sol"')
.testContracts('withABIEncoderV2.sol', sources[2]['browser/withABIEncoderV2.sol'], ['test'])
.clickLaunchIcon('udapp')
.selectContract('test')

@ -20,10 +20,10 @@ module.exports = {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('li[key="browser/contracts"]')
.waitForElementVisible('li[key="browser/scripts"]')
.waitForElementVisible('li[key="browser/tests"]')
.waitForElementVisible('li[key="browser/README.txt"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/scripts"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/tests"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/README.txt"]')
},
'Loads Main View': function (browser: NightwatchBrowser) {
@ -61,17 +61,17 @@ module.exports = {
'Toggles File Explorer Browser': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.assert.visible('ul[key="browser"]')
.click('div[data-id="treeViewTogglebrowser"]')
.assert.hidden('ul[key="browser"]')
.click('div[data-id="treeViewTogglebrowser"]')
.assert.visible('ul[key="browser"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('[data-path="browser"]')
.waitForElementNotPresent('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('[data-path="browser"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/contracts"]')
},
'Switch Tabs using tabs icon': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.click('*[data-id="treeViewTogglebrowser/contracts"]')
.click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol')
.assert.containsText('div[title="browser/contracts/3_Ballot.sol"]', '3_Ballot.sol')
.click('span[class^=dropdownCaret]')

@ -14,7 +14,6 @@ module.exports = {
browser.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]')
.clickLaunchIcon('fileExplorers')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.click('*[data-id="treeViewLibrowser/contracts"]')
.openFile('browser/contracts/1_Storage.sol')
.waitForElementVisible('*[data-id="editorInput"]')
.checkElementStyle('*[data-id="editorInput"]', 'font-size', '12px')
@ -81,7 +80,8 @@ module.exports = {
'Should highlight source code': function (browser: NightwatchBrowser) {
// include all files here because switching between plugins in side-panel removes highlight
browser.addFile('sourcehighlight.js', sourcehighlightScript)
browser
.addFile('sourcehighlight.js', sourcehighlightScript)
.addFile('removeSourcehighlightScript.js', removeSourcehighlightScript)
.addFile('removeAllSourcehighlightScript.js', removeAllSourcehighlightScript)
.openFile('browser/sourcehighlight.js')
@ -96,27 +96,26 @@ module.exports = {
},
'Should remove 1 highlight from source code': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[key="browser/removeSourcehighlightScript.js"]')
.click('li[key="browser/removeSourcehighlightScript.js"]')
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/removeSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItembrowser/removeSourcehighlightScript.js"]')
.pause(2000)
.executeScript('remix.exeCurrent()')
.waitForElementVisible('li[key="browser/contracts"]')
.click('li[key="browser/contracts"]') // files don't appear, so we click twice to get the files
.click('li[key="browser/contracts"]')
.waitForElementVisible('li[key="browser/contracts/3_Ballot.sol"]')
.click('li[key="browser/contracts/3_Ballot.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('li[data-id="treeViewLitreeViewItembrowser/contracts"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.waitForElementNotPresent('.highlightLine32')
.checkElementStyle('.highlightLine40', 'background-color', 'rgb(8, 108, 181)')
.checkElementStyle('.highlightLine50', 'background-color', 'rgb(8, 108, 181)')
},
'Should remove all highlights from source code': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[key="browser/removeAllSourcehighlightScript.js"]')
.click('li[key="browser/removeAllSourcehighlightScript.js"]')
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/removeAllSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItembrowser/removeAllSourcehighlightScript.js"]')
.pause(2000)
.executeScript('remix.exeCurrent()')
.waitForElementVisible('li[key="browser/contracts/3_Ballot.sol"]')
.click('li[key="browser/contracts/3_Ballot.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItembrowser/contracts/3_Ballot.sol"]')
.pause(2000)
.waitForElementNotPresent('.highlightLine32')
.waitForElementNotPresent('.highlightLine40')

@ -21,70 +21,74 @@ module.exports = {
.clickLaunchIcon('fileExplorers')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', '5_New_contract.sol')
.modalFooterOKClick()
.waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]', 7000)
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', '5_New_contract.sol')
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_New_contract.sol"]', 7000)
},
'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]')
.renameFile('browser/5_New_contract.sol', '5_Renamed_Contract.sol', 'browser/5_Renamed_Contract.sol')
.waitForElementVisible('*[data-id="treeViewLibrowser/5_Renamed_Contract.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_New_contract.sol"]')
.renamePath('browser/5_New_contract.sol', '5_Renamed_Contract.sol', 'browser/5_Renamed_Contract.sol')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_Renamed_Contract.sol"]')
},
'Should delete file `5_Renamed_Contract.sol` from file explorer': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLibrowser/5_Renamed_Contract.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/5_Renamed_Contract.sol"]')
.rightClick('[data-path="browser/5_Renamed_Contract.sol"]')
.click('*[id="menuitemdelete"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.modalFooterOKClick()
.waitForElementNotPresent('*[data-id="treeViewLibrowser/5_Renamed_Contract.sol"')
.waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.pause(2000)
.click('.modal-ok')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItembrowser/5_Renamed_Contract.sol"')
},
'Should create a new folder': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLibrowser/README.txt"]')
.rightClick('[data-path="browser/README.txt"]')
.click('*[id="menuitemcreate folder"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', 'Browser_Tests')
.modalFooterOKClick()
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_Tests"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/README.txt"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_Tests"]')
},
'Should rename Browser_Tests folder to Browser_E2E_Tests': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_Tests"]')
.rightClick('[data-path="browser/Browser_Tests"]')
.click('*[id="menuitemrename"]')
.sendKeys('[data-path="browser/Browser_Tests"]', 'Browser_E2E_Tests')
.sendKeys('[data-path="browser/Browser_Tests"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_E2E_Tests"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_Tests"]')
.renamePath('browser/Browser_Tests', 'Browser_E2E_Tests', 'browser/Browser_E2E_Tests')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_E2E_Tests"]')
},
'Should delete Browser_E2E_Tests folder': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_E2E_Tests"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_E2E_Tests"]')
.rightClick('[data-path="browser/Browser_E2E_Tests"]')
.click('*[id="menuitemdelete"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.modalFooterOKClick()
.waitForElementNotPresent('*[data-id="treeViewLibrowser/Browser_E2E_Tests"]')
.waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.pause(2000)
.click('.modal-ok')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItembrowser/Browser_E2E_Tests"]')
},
'Should publish all explorer files to github gist': function (browser: NightwatchBrowser) {
const runtimeBrowser = browser.options.desiredCapabilities.browserName
browser
browser.refresh()
.pause(10000)
.waitForElementVisible('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="fileExplorerNewFilepublishToGist"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.modalFooterOKClick()
.waitForElementVisible('*[data-id="modalDialogContainer"]', 7000)
.modalFooterOKClick()
.waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.pause(2000)
.click('.modal-ok')
.pause(2000)
.waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.pause(2000)
.click('.modal-ok')
.pause(2000)
.perform((done) => {
if (runtimeBrowser === 'chrome') {
@ -101,9 +105,9 @@ module.exports = {
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3)
.waitForElementVisible('*[key="browser/editor.test.js"]')
.waitForElementVisible('*[key="browser/fileExplorer.test.js"]')
.waitForElementVisible('*[key="browser/generalSettings.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/editor.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/fileExplorer.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/generalSettings.test.js"]')
.end()
},

@ -63,7 +63,7 @@ module.exports = {
.addFile('renameFile.js', { content: executeRename })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/old_contract.sol"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/old_contract.sol"]')
},
'Should execute `mkdir` api from file manager external api': function (browser: NightwatchBrowser) {
@ -71,7 +71,7 @@ module.exports = {
.addFile('mkdirFile.js', { content: executeMkdir })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/Test_Folder"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItembrowser/Test_Folder"]')
},
'Should execute `readdir` api from file manager external api': function (browser: NightwatchBrowser) {
@ -87,15 +87,16 @@ module.exports = {
.addFile('removeFile.js', { content: executeRemove })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementNotPresent('[data-id="treeViewLibrowser/old_contract.sol"]')
.waitForElementNotPresent('[data-id="treeViewLitreeViewItembrowser/old_contract.sol"]')
},
'Should execute `remove` api from file manager external api on a folder': function (browser: NightwatchBrowser) {
// TODO: Fix remove root directory prefix for browser and localhost
'Should execute `remove` api from file manager external api on a folder': '' + function (browser: NightwatchBrowser) {
browser
.addFile('test_jsRemoveFolder.js', { content: executeRemoveOnFolder })
.executeScript('remix.exeCurrent()')
.pause(2000)
.waitForElementNotPresent('*[key="browser/tests"]')
.waitForElementNotPresent('[data-id="treeViewLitreeViewItembrowser/tests"]')
.end()
},

@ -20,7 +20,7 @@ module.exports = {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000)
.click('*[data-id="verticalIconsFileExplorerIcons"]')
.click('*[data-id="treeViewTogglebrowser/contracts"]')
.click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol')
.click('*[data-id="verticalIconsKindsolidity"]')
.pause(2000)

@ -13,7 +13,7 @@ module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'UploadToGists': function (browser: NightwatchBrowser) {
UploadToGists: function (browser: NightwatchBrowser) {
/*
- set the access token
- publish to gist
@ -24,18 +24,24 @@ module.exports = {
const runtimeBrowser = browser.options.desiredCapabilities.browserName
browser
.refresh()
.pause(10000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('fileExplorers')
.rightClick('[data-path="browser/README.txt"]')
.click('*[id="menuitemcreate folder"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', 'Browser_Tests')
.modalFooterOKClick()
.waitForElementVisible('*[data-id="treeViewLibrowser/Browser_Tests"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/Browser_Tests"]')
.addFile('File.sol', { content: '' })
.click('*[data-id="fileExplorerNewFilepublishToGist"]')
.modalFooterOKClick()
.getModalBody((value, done) => {
.waitForElementVisible('*[data-id="browserModalDialogContainer-react"]')
.pause(2000)
.click('.modal-ok')
.pause(10000)
.getText('[data-id="browserModalDialogModalBody-react"]', (result) => {
console.log(result)
const value = typeof result.value === 'string' ? result.value : null
const reg = /gist.github.com\/([^.]+)/
const id = value.match(reg)
@ -45,13 +51,12 @@ module.exports = {
} else {
const gistid = id[1]
browser
.modalFooterCancelClick()
.click('[data-id="browser-modal-footer-cancel-react"]')
.executeScript(`remix.loadgist('${gistid}')`)
.perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('browser/gists') } done() })
.waitForElementVisible(`li[key="browser/gists/${gistid}"]`)
.click(`li[key="browser/gists/${gistid}"]`)
.waitForElementVisible(`[data-id="treeViewLitreeViewItembrowser/gists/${gistid}"]`)
.click(`[data-id="treeViewLitreeViewItembrowser/gists/${gistid}"]`)
.openFile(`browser/gists/${gistid}/README.txt`)
.perform(done)
}
})
},

@ -16,10 +16,11 @@ module.exports = {
browser
.waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('fileExplorers')
.click('*[data-id="treeViewTogglebrowser/contracts"]')
.click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol')
.verifyContracts(['Ballot'])
.click('#publishOnIpfs')
.pause(8000)
.getModalBody((value, done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '')
if (value.indexOf('dweb:/ipfs') === -1) browser.assert.fail('ipfs deploy failed', '', '')
@ -31,6 +32,7 @@ module.exports = {
'Publish on Swarm': '' + function (browser: NightwatchBrowser) {
browser
.click('#publishOnSwarm')
.pause(8000)
.getModalBody((value, done) => {
if (value.indexOf('Metadata of "ballot" was successfully.') === -1) browser.assert.fail('swarm deploy failed', '', '')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed', '', '')
@ -43,24 +45,24 @@ module.exports = {
browser
.waitForElementVisible('#icon-panel')
.clickLaunchIcon('fileExplorers')
.click('*[data-id="treeViewLibrowser/contracts"]')
.click('*[data-id="treeViewLibrowser/contracts"]')
.openFile('browser/contracts/1_Storage.sol')
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="contractDropdownIpfsCheckbox"]')
.click('*[data-id="contractDropdownIpfsCheckbox"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.assert.containsText('*[data-id="modalDialogModalBody"]', 'Metadata of "storage" was published successfully.')
.pause(8000)
.getModalBody((value, done) => {
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed', '', '')
done()
})
.modalFooterOKClick()
},
'Should remember choice after page refresh': function (browser: NightwatchBrowser) {
browser
.refresh()
.waitForElementVisible('*[data-id="treeViewLibrowser/contracts"]')
.click('*[data-id="treeViewLibrowser/contracts"]')
.click('*[data-id="treeViewLibrowser/contracts"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.click('[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/1_Storage.sol')
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="contractDropdownIpfsCheckbox"]')

@ -121,7 +121,7 @@ function runTests (browser: NightwatchBrowser) {
.setEditorValue('contract test1 { function get () returns (uint) { return 10; }}')
.click('[data-path="localhost/folder1/contract_' + browserName + '.sol"]') // rename a file and check
.pause(1000)
.renameFile('localhost/folder1/contract_' + browserName + '.sol', 'renamed_contract_' + browserName + '.sol', 'localhost/folder1/renamed_contract_' + browserName + '.sol')
.renamePath('localhost/folder1/contract_' + browserName + '.sol', 'renamed_contract_' + browserName + '.sol', 'localhost/folder1/renamed_contract_' + browserName + '.sol')
.pause(1000)
.removeFile('localhost/folder1/contract_' + browserName + '_toremove.sol')
.perform(function (done) {

@ -128,6 +128,7 @@ module.exports = {
.waitForElementPresent('.transaction-status--submitted')
.pause(25000)
.switchBrowserTab(0)
.end()
},
'Should connect to Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) {

@ -48,6 +48,8 @@ module.exports = {
'Test Github Import - no branch specified': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.6.2+commit.bacdbe57.js') // open-zeppelin moved to pragma ^0.6.0 (master branch)
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"')
.addFile('Untitled6.sol', sources[5]['browser/Untitled6.sol'])
.clickLaunchIcon('fileExplorers')
.verifyContracts(['test10', 'ERC20', 'SafeMath'], {wait: 10000})
@ -55,6 +57,8 @@ module.exports = {
'Test Github Import - raw URL': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"')
.addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol'])
.clickLaunchIcon('fileExplorers')
.verifyContracts(['test11', 'ERC20', 'SafeMath'], {wait: 10000})
@ -63,6 +67,8 @@ module.exports = {
'Test switch to a github import from a solidity warning': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js')
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"')
.addFile('Untitled8.sol', sources[7]['browser/Untitled8.sol'])
.clickLaunchIcon('fileExplorers')
.clickLaunchIcon('solidity')

@ -144,7 +144,8 @@ module.exports = {
},
'Changing current path': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
browser
.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.addFile('myTests/simple_storage_test.sol', sources[0]['browser/tests/simple_storage_test.sol'])
.clickLaunchIcon('solidityUnitTesting')
.setValue('*[data-id="uiPathInput"]', 'browser/myTests')
@ -167,7 +168,7 @@ function runTests (browser: NightwatchBrowser) {
browser
.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.clickLaunchIcon('fileExplorers')
.click('*[data-id="treeViewTogglebrowser/contracts"]')
.click('*[data-id="treeViewLitreeViewItembrowser/contracts"]')
.openFile('browser/contracts/3_Ballot.sol')
.clickLaunchIcon('solidityUnitTesting')
.pause(500)

@ -28,7 +28,7 @@ module.exports = {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('fileExplorers')
.addFile('browser/basic.sol', sources[0]['browser/basic.sol'])
.addFile('basic.sol', sources[0]['browser/basic.sol'])
.clickLaunchIcon('solidity')
.execute(function() {
const elem = document.getElementById('nightlies') as HTMLInputElement

@ -28,7 +28,7 @@ declare module "nightwatch" {
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser,
openFile(name: string): NightwatchBrowser,
editorScroll(direction: 'up' | 'down', numberOfTimes: number): NightwatchBrowser,
renameFile(path: string, newFileName: string, renamedPath: string): NightwatchBrowser,
renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser,
rightClick(cssSelector: string): NightwatchBrowser,
waitForElementContainsText(id: string, value: string): NightwatchBrowser,
getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser,
@ -63,6 +63,10 @@ declare module "nightwatch" {
sendKeys: (selector: string, inputValue: string | string[], callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void) => NightwatchBrowser
}
export interface NightwatchAPI {
keys(keysToSend: string, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult<void>) => void): NightwatchAPI
}
export interface NightwatchContractContent {
content: string;
}

@ -115,6 +115,13 @@ module.exports = class RemixDProvider {
})
}
async createDir (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
const unprefixedpath = this.removePrefix(path)
return this._appManager.call('remixd', 'createDir', { path: unprefixedpath })
}
isReadOnly (path) {
return this._readOnlyMode || this._readOnlyFiles[path] === 1
}

@ -22,7 +22,7 @@ const profile = {
name: 'remixd',
displayName: 'RemixD',
url: 'ws://127.0.0.1:65520',
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list'],
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list', 'createDir'],
events: [],
description: 'Using Remixd daemon, allow to access file system',
kind: 'other',
@ -47,7 +47,6 @@ export class RemixdHandle extends WebsocketPlugin {
}
activate () {
this.fileSystemExplorer.show()
this.connectToLocalhost()
}
@ -83,7 +82,9 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled()
}
}, 3000)
this.locahostProvider.init(_ => this.fileSystemExplorer.ensureRoot())
this.locahostProvider.init(() => {
this.fileSystemExplorer.show()
})
this.call('manager', 'activatePlugin', 'git')
}
}

@ -1,13 +1,16 @@
import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { FileExplorer } from '@remix-ui/file-explorer' // eslint-disable-line
import './styles/file-panel-styles.css'
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
var FileExplorer = require('../files/file-explorer')
// var FileExplorer = require('../files/file-explorer')
var { RemixdHandle } = require('../files/remixd-handle.js')
var { GitHandle } = require('../files/git-handle.js')
var globalRegistry = require('../../global/registry')
var css = require('./styles/file-panel-styles')
var canUpload = window.File || window.FileReader || window.FileList || window.Blob
@ -44,63 +47,85 @@ const profile = {
module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) {
super(profile)
var self = this
self._components = {}
self._components.registry = globalRegistry
self._deps = {
fileProviders: self._components.registry.get('fileproviders').api,
fileManager: self._components.registry.get('filemanager').api,
config: self._components.registry.get('config').api
this._components = {}
this._components.registry = globalRegistry
this._deps = {
fileProviders: this._components.registry.get('fileproviders').api,
fileManager: this._components.registry.get('filemanager').api,
config: this._components.registry.get('config').api
}
function createProvider (key, menuItems) {
return new FileExplorer(self._components.registry, self._deps.fileProviders[key], menuItems, self)
this.hideRemixdExplorer = true
this.remixdExplorer = {
hide: () => {
this.hideRemixdExplorer = true
this.renderComponent()
},
show: () => {
this.hideRemixdExplorer = false
this.renderComponent()
}
var fileExplorer = createProvider('browser', ['createNewFile', 'publishToGist', canUpload ? 'uploadFile' : ''])
var fileSystemExplorer = createProvider('localhost')
self.remixdHandle = new RemixdHandle(fileSystemExplorer, self._deps.fileProviders.localhost, appManager)
self.gitHandle = new GitHandle()
const explorers = yo`
<div>
<div class="pl-2 ${css.treeview}" data-id="filePanelFileExplorerTree">${fileExplorer.init()}</div>
<div class="pl-2 filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
}
this.el = yo`
<div id="fileExplorerView">
</div>
`
function template () {
return yo`
<div class=${css.container}>
<div class="${css.fileexplorer}">
<div class="${css.fileExplorerTree}">
${explorers}
</div>
</div>
</div>
`
}
this.remixdHandle = new RemixdHandle(this.remixdExplorer, this._deps.fileProviders.localhost, appManager)
this.gitHandle = new GitHandle()
var event = new EventManager()
self.event = event
var element = template()
fileExplorer.ensureRoot()
self._deps.fileProviders.localhost.event.register('connecting', (event) => {
this.event = new EventManager()
this._deps.fileProviders.localhost.event.register('connecting', (event) => {
})
self._deps.fileProviders.localhost.event.register('connected', (event) => {
fileSystemExplorer.show()
this._deps.fileProviders.localhost.event.register('connected', (event) => {
this.remixdExplorer.show()
})
self._deps.fileProviders.localhost.event.register('errored', (event) => {
fileSystemExplorer.hide()
this._deps.fileProviders.localhost.event.register('errored', (event) => {
this.remixdExplorer.hide()
})
self._deps.fileProviders.localhost.event.register('closed', (event) => {
fileSystemExplorer.hide()
this._deps.fileProviders.localhost.event.register('closed', (event) => {
this.remixdExplorer.hide()
})
self.render = function render () { return element }
this.renderComponent()
}
render () {
return this.el
}
renderComponent () {
ReactDOM.render(
<div className='remixui_container'>
<div className='remixui_fileexplorer'>
<div className='remixui_fileExplorerTree'>
<div>
<div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>
<FileExplorer
name='browser'
registry={this._components.registry}
filesProvider={this._deps.fileProviders.browser}
menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']}
plugin={this}
/>
</div>
<div className='pl-2 filesystemexplorer remixui_treeview'>
{ !this.hideRemixdExplorer &&
<FileExplorer
name='localhost'
registry={this._components.registry}
filesProvider={this._deps.fileProviders.localhost}
menuItems={['createNewFile', 'createNewFolder']}
plugin={this}
/>
}
</div>
</div>
</div>
</div>
</div>
, this.el)
}
}

@ -0,0 +1,56 @@
.remixui_container {
display : flex;
flex-direction : row;
width : 100%;
height : 100%;
box-sizing : border-box;
}
.remixui_fileexplorer {
display : flex;
flex-direction : column;
position : relative;
width : 100%;
padding-left : 6px;
padding-top : 6px;
}
.remixui_fileExplorerTree {
cursor : default;
}
.remixui_gist {
padding : 10px;
}
.remixui_gist i {
cursor : pointer;
}
.remixui_gist i:hover {
color : orange;
}
.remixui_connectToLocalhost {
padding : 10px;
}
.remixui_connectToLocalhost i {
cursor : pointer;
}
.remixui_connectToLocalhost i:hover {
color : var(--secondary)
}
.remixui_uploadFile {
padding : 10px;
}
.remixui_uploadFile label:hover {
color : var(--secondary)
}
.remixui_uploadFile label {
cursor : pointer;
}
.remixui_treeview {
overflow-y : auto;
}
.remixui_dialog {
display: flex;
flex-direction: column;
}
.remixui_dialogParagraph {
margin-bottom: 2em;
word-break: break-word;
}

@ -194,7 +194,7 @@ export class TabProxy extends Plugin {
}
this._view.filetabs.addTab({
id: name,
id: name.split(' ').join(''),
title,
icon,
tooltip: name

@ -52,7 +52,7 @@ module.exports = class TestTab extends ViewPlugin {
listenToEvents () {
this.filePanel.event.register('newTestFileCreated', file => {
var testList = this.view.querySelector("[class^='testList']")
var testList = this._view.el.querySelector("[class^='testList']")
var test = this.createSingleTest(file)
testList.appendChild(test)
this.data.allTests.push(file)

@ -6,10 +6,10 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
let agreed = true
let footerIsActive = false
opts = opts || {}
var container = document.querySelector('.modal')
var container = document.getElementById('modal-dialog')
if (!container) {
document.querySelector('body').appendChild(html(opts))
container = document.querySelector('.modal')
container = document.getElementById('modal-dialog')
incomingModal = false
} else incomingModal = true
@ -24,8 +24,8 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
cancelDiv.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : 'Cancel'
cancelDiv.style.display = cancelDiv.innerHTML === '' ? 'none' : 'inline-block'
var modal = document.querySelector('.modal-body')
var modalTitle = document.querySelector('.modal-header h6')
var modal = document.getElementById('modal-body-id')
var modalTitle = document.getElementById('modal-title-h6')
modalTitle.innerHTML = ''
if (title) modalTitle.innerText = title
@ -134,12 +134,12 @@ function html (opts) {
<div id="modal-background" class="modal-dialog" role="document">
<div class="modal-content ${css.modalContent} ${opts.class}">
<div class="modal-header">
<h6 class="modal-title" data-id="modalDialogModalTitle"></h6>
<h6 id="modal-title-h6" class="modal-title" data-id="modalDialogModalTitle"></h6>
<span class="modal-close">
<i id="modal-close" title="Close" class="fas fa-times" aria-hidden="true"></i>
</span>
</div>
<div class="modal-body ${css.modalBody}" data-id="modalDialogModalBody"> - </div>
<div id="modal-body-id" class="modal-body ${css.modalBody}" data-id="modalDialogModalBody"> - </div>
<div class="modal-footer" data-id="modalDialogModalFooter" autofocus>
<span id="modal-footer-ok" class="${css.modalFooterOk} modal-ok btn btn-sm btn-light" tabindex='5'>OK</span>
<span id="modal-footer-cancel" class="${css.modalFooterCancel} modal-cancel btn btn-sm btn-light" tabindex='10' data-dismiss="modal">Cancel</span>

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

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-file-explorer
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-file-explorer` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/file-explorer'

@ -0,0 +1,28 @@
.remixui_contextContainer
{
display: block;
position: fixed;
border-radius: 2px;
z-index: 1000;
box-shadow: 0 0 4px var(--dark);
}
.remixui_contextContainer:focus {
outline: none;
}
.remixui_liitem
{
padding: 2px;
padding-left: 6px;
cursor: pointer;
color: var(--text-dark);
background-color: var(--light);
}
.remixui_liitem:hover
{
background-color: var(--secondary);
}
#remixui_menuitems
{
list-style: none;
margin: 0px;
}

@ -0,0 +1,55 @@
.remixui_label {
margin-top : 4px;
}
.remixui_leaf {
overflow : hidden;
text-overflow : ellipsis;
width : 90%;
margin-bottom : 0px;
}
.remixui_fileexplorer {
box-sizing : border-box;
}
input[type="file"] {
display: none;
}
.remixui_folder,
.remixui_file {
font-size : 14px;
cursor : pointer;
}
.remixui_file {
padding : 4px;
}
.remixui_newFile {
padding-right : 10px;
}
.remixui_newFile i {
cursor : pointer;
}
.remixui_newFile:hover {
transform : scale(1.3);
}
.remixui_menu {
margin-left : 20px;
}
.remixui_items {
display : inline
}
.remixui_remove {
margin-left : auto;
padding-left : 5px;
padding-right : 5px;
}
.remixui_activeMode {
display : flex;
width : 100%;
margin-right : 10px;
padding-right : 19px;
}
.remixui_activeMode > div {
min-width : 10px;
}
ul {
padding : 0;
}

@ -0,0 +1,81 @@
import React, { useRef, useEffect } from 'react' // eslint-disable-line
import { FileExplorerContextMenuProps } from './types'
import './css/file-explorer-context-menu.css'
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, publishToGist, runScript, pageX, pageY, path, type, ...otherProps } = props
const contextMenuRef = useRef(null)
useEffect(() => {
contextMenuRef.current.focus()
}, [])
useEffect(() => {
const menuItemsContainer = contextMenuRef.current
const boundary = menuItemsContainer.getBoundingClientRect()
if (boundary.bottom > (window.innerHeight || document.documentElement.clientHeight)) {
menuItemsContainer.style.position = 'absolute'
menuItemsContainer.style.bottom = '10px'
menuItemsContainer.style.top = null
}
}, [pageX, pageY])
const menu = () => {
return actions.filter(item => {
if (item.type.findIndex(name => name === type) !== -1) return true
else if (item.path.findIndex(key => key === path) !== -1) return true
else if (item.extension.findIndex(ext => path.endsWith(ext)) !== -1) return true
else if (item.pattern.filter(value => path.match(new RegExp(value))).length > 0) return true
else return false
}).map((item, index) => {
return <li
id={`menuitem${item.name.toLowerCase()}`}
key={index}
className='remixui_liitem'
onClick={(e) => {
e.stopPropagation()
switch (item.name) {
case 'New File':
createNewFile(path)
break
case 'New Folder':
createNewFolder(path)
break
case 'Rename':
renamePath(path, type)
break
case 'Delete':
deletePath(path)
break
case 'Push changes to gist':
publishToGist()
break
case 'Run':
runScript(path)
break
default:
break
}
hideContextMenu()
}}>{item.name}</li>
})
}
return (
<div
id="menuItemsContainer"
className="p-1 remixui_contextContainer bg-light shadow border"
style={{ left: pageX, top: pageY }}
ref={contextMenuRef}
onBlur={hideContextMenu}
tabIndex={500}
{...otherProps}
>
<ul id='remixui_menuitems'>{menu()}</ul>
</div>
)
}
export default FileExplorerContextMenu

@ -0,0 +1,97 @@
import React, { useState, useEffect } from 'react' //eslint-disable-line
import { FileExplorerMenuProps } from './types'
export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
const [state, setState] = useState({
menuItems: [
{
action: 'createNewFile',
title: 'Create New File',
icon: 'far fa-file'
},
{
action: 'createNewFolder',
title: 'Create New Folder',
icon: 'far fa-folder'
},
{
action: 'publishToGist',
title: 'Publish all [browser] explorer files to a github gist',
icon: 'fab fa-github'
},
{
action: 'uploadFile',
title: 'Load a local file into Remix\'s browser folder',
icon: 'fa fa-upload'
},
{
action: 'updateGist',
title: 'Update the current [gist] explorer',
icon: 'fab fa-github'
}
].filter(item => props.menuItems && props.menuItems.find((name) => { return name === item.action })),
actions: {}
})
useEffect(() => {
const actions = {
updateGist: () => {}
}
setState(prevState => {
return { ...prevState, actions }
})
}, [])
return (
<>
<span className='remixui_label' title={props.title} data-path={props.title} style={{ fontWeight: 'bold' }}>{ props.title }</span>
<span className="remixui_menu">{
state.menuItems.map(({ action, title, icon }, index) => {
if (action === 'uploadFile') {
return (
<label
id={action}
data-id={'fileExplorerUploadFile' + action }
className={icon + ' mb-0 remixui_newFile'}
title={title}
key={index}
>
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onChange={(e) => {
e.stopPropagation()
props.uploadFile(e.target)
}}
multiple />
</label>
)
} else {
return (
<span
id={action}
data-id={'fileExplorerNewFile' + action}
onClick={(e) => {
e.stopPropagation()
if (action === 'createNewFile') {
props.createNewFile()
} else if (action === 'createNewFolder') {
props.createNewFolder()
} else if (action === 'publishToGist') {
props.publishToGist()
} else {
state.actions[action]()
}
}}
className={'newFile ' + icon + ' remixui_newFile'}
title={title}
key={index}
>
</span>
)
}
})}
</span>
</>
)
}
export default FileExplorerMenu

@ -0,0 +1,947 @@
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
// import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' // eslint-disable-line
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import * as async from 'async'
import Gists from 'gists'
import { FileExplorerMenu } from './file-explorer-menu' // eslint-disable-line
import { FileExplorerContextMenu } from './file-explorer-context-menu' // eslint-disable-line
import { FileExplorerProps, File } from './types'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
import './css/file-explorer.css'
const queryParams = new QueryParams()
export const FileExplorer = (props: FileExplorerProps) => {
const { filesProvider, name, registry, plugin } = props
const [state, setState] = useState({
focusElement: [{
key: name,
type: 'folder'
}],
focusPath: null,
files: [],
fileManager: null,
accessToken: null,
ctrlKey: false,
newFileName: '',
actions: [],
focusContext: {
element: null,
x: null,
y: null
},
focusEdit: {
element: null,
type: '',
isNew: false,
lastEdit: ''
},
expandPath: [],
modalOptions: {
hide: true,
title: '',
message: '',
ok: {
label: 'Ok',
fn: null
},
cancel: {
label: 'Cancel',
fn: null
},
handleHide: null
},
toasterMsg: ''
})
const editRef = useRef(null)
useEffect(() => {
if (state.focusEdit.element) {
setTimeout(() => {
if (editRef && editRef.current) {
editRef.current.focus()
}
}, 150)
}
}, [state.focusEdit.element])
useEffect(() => {
(async () => {
const fileManager = registry.get('filemanager').api
const config = registry.get('config').api
const accessToken = config.get('settings/gist-access-token')
const files = await fetchDirectoryContent(name)
const actions = [{
name: 'New File',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'New Folder',
type: ['folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'Rename',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'Delete',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
}, {
name: 'Push changes to gist',
type: [],
path: [],
extension: [],
pattern: ['^browser/gists/([0-9]|[a-z])*$']
}, {
name: 'Run',
type: [],
path: [],
extension: ['.js'],
pattern: []
}]
setState(prevState => {
return { ...prevState, fileManager, accessToken, files, actions }
})
})()
}, [])
useEffect(() => {
if (state.fileManager) {
filesProvider.event.register('fileExternallyChanged', fileExternallyChanged)
filesProvider.event.register('fileRenamedError', fileRenamedError)
}
}, [state.fileManager])
useEffect(() => {
const { expandPath } = state
const expandFn = async () => {
let files = state.files
for (let i = 0; i < expandPath.length; i++) {
files = await resolveDirectory(expandPath[i], files)
await setState(prevState => {
return { ...prevState, files }
})
}
}
if (expandPath && expandPath.length > 0) {
expandFn()
}
}, [state.expandPath])
useEffect(() => {
// unregister event to update state in callback
if (filesProvider.event.registered.fileAdded) filesProvider.event.unregister('fileAdded', fileAdded)
if (filesProvider.event.registered.folderAdded) filesProvider.event.unregister('folderAdded', folderAdded)
if (filesProvider.event.registered.fileRemoved) filesProvider.event.unregister('fileRemoved', fileRemoved)
if (filesProvider.event.registered.fileRenamed) filesProvider.event.unregister('fileRenamed', fileRenamed)
filesProvider.event.register('fileAdded', fileAdded)
filesProvider.event.register('folderAdded', folderAdded)
filesProvider.event.register('fileRemoved', fileRemoved)
filesProvider.event.register('fileRenamed', fileRenamed)
}, [state.files])
const resolveDirectory = async (folderPath, dir: File[], isChild = false): Promise<File[]> => {
if (!isChild && (state.focusEdit.element === 'browser/blank') && state.focusEdit.isNew && (dir.findIndex(({ path }) => path === 'browser/blank') === -1)) {
dir = state.focusEdit.type === 'file' ? [...dir, {
path: state.focusEdit.element,
name: '',
isDirectory: false
}] : [{
path: state.focusEdit.element,
name: '',
isDirectory: true
}, ...dir]
}
dir = await Promise.all(dir.map(async (file) => {
if (file.path === folderPath) {
if ((extractParentFromKey(state.focusEdit.element) === folderPath) && state.focusEdit.isNew) {
file.child = state.focusEdit.type === 'file' ? [...await fetchDirectoryContent(folderPath), {
path: state.focusEdit.element,
name: '',
isDirectory: false
}] : [{
path: state.focusEdit.element,
name: '',
isDirectory: true
}, ...await fetchDirectoryContent(folderPath)]
} else {
file.child = await fetchDirectoryContent(folderPath)
}
return file
} else if (file.child) {
file.child = await resolveDirectory(folderPath, file.child, true)
return file
} else {
return file
}
}))
return dir
}
const fetchDirectoryContent = async (folderPath: string): Promise<File[]> => {
return new Promise((resolve) => {
filesProvider.resolveDirectory(folderPath, (error, fileTree) => {
if (error) console.error(error)
const files = normalize(folderPath, fileTree)
resolve(files)
})
})
}
const normalize = (path, filesList): File[] => {
const folders = []
const files = []
const prefix = path.split('/')[0]
Object.keys(filesList || {}).forEach(key => {
const path = prefix + '/' + key
if (filesList[key].isDirectory) {
folders.push({
path,
name: extractNameFromKey(path),
isDirectory: filesList[key].isDirectory
})
} else {
files.push({
path,
name: extractNameFromKey(path),
isDirectory: filesList[key].isDirectory
})
}
})
return [...folders, ...files]
}
const extractNameFromKey = (key: string):string => {
const keyPath = key.split('/')
return keyPath[keyPath.length - 1]
}
const extractParentFromKey = (key: string):string => {
if (!key) return
const keyPath = key.split('/')
keyPath.pop()
return keyPath.join('/')
}
const createNewFile = (newFilePath: string) => {
const fileManager = state.fileManager
helper.createNonClashingName(newFilePath, filesProvider, async (error, newName) => {
if (error) {
modal('Create File Failed', error, {
label: 'Close',
fn: async () => {}
}, null)
} else {
const createFile = await fileManager.writeFile(newName, '')
if (!createFile) {
toast('Failed to create file ' + newName)
}
}
})
}
const createNewFolder = async (newFolderPath: string) => {
const fileManager = state.fileManager
const dirName = newFolderPath + '/'
try {
const exists = await fileManager.exists(dirName)
if (exists) return
await fileManager.mkdir(dirName)
// addFolder(parentFolder, newFolderPath)
} catch (e) {
console.log('error: ', e)
toast('Failed to create folder: ' + newFolderPath)
}
}
const deletePath = async (path: string) => {
if (filesProvider.isReadOnly(path)) {
return toast('cannot delete file. ' + name + ' is a read only explorer')
}
const isDir = state.fileManager.isDirectory(path)
modal('Delete file', `Are you sure you want to delete ${path} ${isDir ? 'folder' : 'file'}?`, {
label: 'Ok',
fn: async () => {
try {
const fileManager = state.fileManager
await fileManager.remove(path)
} catch (e) {
toast(`Failed to remove file ${path}.`)
}
}
}, {
label: 'Cancel',
fn: () => {}
})
}
const renamePath = async (oldPath: string, newPath: string) => {
try {
const fileManager = state.fileManager
const exists = await fileManager.exists(newPath)
if (exists) {
modal('Rename File Failed', 'File name already exists', {
label: 'Close',
fn: () => {}
}, null)
} else {
await fileManager.rename(oldPath, newPath)
}
} catch (error) {
modal('Rename File Failed', 'Unexpected error while renaming: ' + error, {
label: 'Close',
fn: async () => {}
}, null)
}
}
const removePath = (path: string, files: File[]): File[] => {
return files.map(file => {
if (file.path === path) {
return null
} else if (file.child) {
const childFiles = removePath(path, file.child)
file.child = childFiles.filter(file => file)
return file
} else {
return file
}
})
}
const fileAdded = async (filePath: string) => {
const pathArr = filePath.split('/')
const expandPath = pathArr.map((path, index) => {
return [...pathArr.slice(0, index)].join('/')
}).filter(path => path && (path !== props.name))
const files = await fetchDirectoryContent(props.name)
setState(prevState => {
const uniquePaths = [...new Set([...prevState.expandPath, ...expandPath])]
return { ...prevState, files, expandPath: uniquePaths, focusElement: [{ key: filePath, type: 'file' }] }
})
if (filePath.includes('_test.sol')) {
plugin.event.trigger('newTestFileCreated', [filePath])
}
}
const folderAdded = async (folderPath: string) => {
const pathArr = folderPath.split('/')
const expandPath = pathArr.map((path, index) => {
return [...pathArr.slice(0, index)].join('/')
}).filter(path => path && (path !== props.name))
const files = await fetchDirectoryContent(props.name)
setState(prevState => {
const uniquePaths = [...new Set([...prevState.expandPath, ...expandPath])]
return { ...prevState, files, expandPath: uniquePaths, focusElement: [{ key: folderPath, type: 'folder' }] }
})
}
const fileExternallyChanged = (path: string, file: { content: string }) => {
const config = registry.get('config').api
if (config.get('currentFile') === path && registry.editor.currentContent() && registry.editor.currentContent() !== file.content) {
if (filesProvider.isReadOnly(path)) return registry.editor.setText(file.content)
modal(path + ' changed', 'This file has been changed outside of Remix IDE.', {
label: 'Replace by the new content',
fn: () => {
registry.editor.setText(file.content)
}
}, {
label: 'Keep the content displayed in Remix',
fn: () => {}
})
}
}
const fileRemoved = (filePath) => {
const files = removePath(filePath, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles }
})
}
const fileRenamed = async () => {
const files = await fetchDirectoryContent(props.name)
setState(prevState => {
return { ...prevState, files, expandPath: [...prevState.expandPath] }
})
}
// register to event of the file provider
// files.event.register('fileRenamed', fileRenamed)
const fileRenamedError = (error: string) => {
modal('File Renamed Failed', error, {
label: 'Close',
fn: () => {}
}, null)
}
const uploadFile = (target) => {
// TODO The file explorer is merely a view on the current state of
// 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) => {
const files = filesProvider
const loadFile = (name: string): void => {
const fileReader = new FileReader()
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
modal('File Upload Failed', 'Special characters are not allowed', {
label: 'Close',
fn: async () => {}
}, null)
return
}
const success = await files.set(name, event.target.result)
if (!success) {
modal('File Upload Failed', 'Failed to create file ' + name, {
label: 'Close',
fn: async () => {}
}, null)
}
}
fileReader.readAsText(file)
}
const name = files.type + '/' + file.name
files.exists(name, (error, exist) => {
if (error) console.log(error)
if (!exist) {
loadFile(name)
} else {
modal('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, {
label: 'Ok',
fn: () => {
loadFile(name)
}
}, {
label: 'Cancel',
fn: () => {}
})
}
})
})
}
const publishToGist = () => {
modal('Create a public gist', 'Are you sure you want to publish all your files in browser directory anonymously as a public gist on github.com? Note: this will not include directories.', {
label: 'Ok',
fn: toGist
}, {
label: 'Cancel',
fn: () => {}
})
}
const toGist = (id?: string) => {
const proccedResult = function (error, data) {
if (error) {
modal('Publish to gist Failed', 'Failed to manage gist: ' + error, {
label: 'Close',
fn: async () => {}
}, null)
} else {
if (data.html_url) {
modal('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, {
label: 'Ok',
fn: () => {
window.open(data.html_url, '_blank')
}
}, {
label: 'Cancel',
fn: () => {}
})
} else {
modal('Publish to gist Failed', data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t'), {
label: 'Close',
fn: async () => {}
}, null)
}
}
}
/**
* This function is to get the original content of given gist
* @params id is the gist id to fetch
*/
const getOriginalFiles = async (id) => {
if (!id) {
return []
}
const url = `https://api.github.com/gists/${id}`
const res = await fetch(url)
const data = await res.json()
return data.files || []
}
// If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
const folder = id ? 'browser/gists/' + id : 'browser/'
packageFiles(filesProvider, folder, async (error, packaged) => {
if (error) {
console.log(error)
modal('Publish to gist Failed', 'Failed to create gist: ' + error.message, {
label: 'Close',
fn: async () => {}
}, null)
} else {
// check for token
if (!state.accessToken) {
modal('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', {
label: 'Close',
fn: async () => {}
}, null)
} else {
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
const gists = new Gists({ token: state.accessToken })
if (id) {
const originalFileList = await getOriginalFiles(id)
// Telling the GIST API to remove files
const updatedFileList = Object.keys(packaged)
const allItems = Object.keys(originalFileList)
.filter(fileName => updatedFileList.indexOf(fileName) === -1)
.reduce((acc, deleteFileName) => ({
...acc,
[deleteFileName]: null
}), originalFileList)
// adding new files
updatedFileList.forEach((file) => {
const _items = file.split('/')
const _fileName = _items[_items.length - 1]
allItems[_fileName] = packaged[file]
})
toast('Saving gist (' + id + ') ...')
gists.edit({
description: description,
public: true,
files: allItems,
id: id
}, (error, result) => {
proccedResult(error, result)
if (!error) {
for (const key in allItems) {
if (allItems[key] === null) delete allItems[key]
}
}
})
} else {
// id is not existing, need to create a new gist
toast('Creating a new gist ...')
gists.create({
description: description,
public: true,
files: packaged
}, (error, result) => {
proccedResult(error, result)
})
}
}
}
})
}
const runScript = async (path: string) => {
filesProvider.get(path, (error, content: string) => {
if (error) return console.log(error)
plugin.call('scriptRunner', 'execute', content)
})
}
const handleHideModal = () => {
setState(prevState => {
return { ...prevState, modalOptions: { ...state.modalOptions, hide: true } }
})
}
const modal = (title: string, message: string, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => {
setState(prevState => {
return {
...prevState,
modalOptions: {
...prevState.modalOptions,
hide: false,
message,
title,
ok,
cancel,
handleHide: handleHideModal
}
}
})
}
const toast = (message: string) => {
setState(prevState => {
return { ...prevState, toasterMsg: message }
})
}
const handleClickFile = (path: string) => {
state.fileManager.open(path)
setState(prevState => {
return { ...prevState, focusElement: [{ key: path, type: 'file' }] }
})
}
const handleClickFolder = async (path: string) => {
if (state.ctrlKey) {
if (state.focusElement.findIndex(item => item.key === path) !== -1) {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement.filter(item => item.key !== path)] }
})
} else {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement, { key: path, type: 'folder' }] }
})
}
} else {
let expandPath = []
if (!state.expandPath.includes(path)) {
expandPath = [...new Set([...state.expandPath, path])]
} else {
expandPath = [...new Set(state.expandPath.filter(key => key && (typeof key === 'string') && !key.startsWith(path)))]
}
setState(prevState => {
return { ...prevState, focusElement: [{ key: path, type: 'folder' }], expandPath }
})
}
}
const handleContextMenuFile = (pageX: number, pageY: number, path: string, content: string) => {
if (!content) return
setState(prevState => {
return { ...prevState, focusContext: { element: path, x: pageX, y: pageY }, focusEdit: { ...prevState.focusEdit, lastEdit: content } }
})
}
const handleContextMenuFolder = (pageX: number, pageY: number, path: string, content: string) => {
if (!content) return
setState(prevState => {
return { ...prevState, focusContext: { element: path, x: pageX, y: pageY }, focusEdit: { ...prevState.focusEdit, lastEdit: content } }
})
}
const hideContextMenu = () => {
setState(prevState => {
return { ...prevState, focusContext: { element: null, x: 0, y: 0 } }
})
}
const editModeOn = (path: string, type: string, isNew: boolean = false) => {
if (filesProvider.isReadOnly(path)) return
setState(prevState => {
return { ...prevState, focusEdit: { ...prevState.focusEdit, element: path, isNew, type } }
})
}
const editModeOff = async (content: string) => {
const parentFolder = extractParentFromKey(state.focusEdit.element)
if (!content || (content.trim() === '')) {
if (state.focusEdit.isNew) {
const files = removePath(state.focusEdit.element, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
} else {
editRef.current.textContent = state.focusEdit.lastEdit
setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
}
} else {
if (state.focusEdit.lastEdit === content) {
return setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
}
if (helper.checkSpecialChars(content)) {
modal('Validation Error', 'Special characters are not allowed', {
label: 'Ok',
fn: () => {}
}, null)
} else {
if (state.focusEdit.isNew) {
state.focusEdit.type === 'file' ? createNewFile(parentFolder + '/' + content) : createNewFolder(parentFolder + '/' + content)
const files = removePath(state.focusEdit.element, state.files)
const updatedFiles = files.filter(file => file)
setState(prevState => {
return { ...prevState, files: updatedFiles }
})
} else {
const oldPath: string = state.focusEdit.element
const oldName = extractNameFromKey(oldPath)
const newPath = oldPath.replace(oldName, content)
editRef.current.textContent = extractNameFromKey(oldPath)
renamePath(oldPath, newPath)
}
setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
})
}
}
}
const handleNewFileInput = async (parentFolder?: string) => {
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name
const expandPath = [...new Set([...state.expandPath, parentFolder])]
setState(prevState => {
return { ...prevState, expandPath }
})
editModeOn(parentFolder + '/blank', 'file', true)
}
const handleNewFolderInput = async (parentFolder?: string) => {
if (!parentFolder) parentFolder = state.focusElement[0] ? state.focusElement[0].type === 'folder' ? state.focusElement[0].key : extractParentFromKey(state.focusElement[0].key) : name
else if ((parentFolder.indexOf('.sol') !== -1) || (parentFolder.indexOf('.js') !== -1)) parentFolder = extractParentFromKey(parentFolder)
const expandPath = [...new Set([...state.expandPath, parentFolder])]
setState(prevState => {
return { ...prevState, expandPath }
})
editModeOn(parentFolder + '/blank', 'folder', true)
}
const handleEditInput = (event) => {
if (event.which === 13) {
event.preventDefault()
editModeOff(editRef.current.innerText)
}
}
const label = (file: File) => {
return (
<div
className='remixui_items d-inline-block w-100'
ref={state.focusEdit.element === file.path ? editRef : null}
suppressContentEditableWarning={true}
contentEditable={state.focusEdit.element === file.path}
onKeyDown={handleEditInput}
onBlur={(e) => {
e.stopPropagation()
editModeOff(editRef.current.innerText)
}}
>
<span
title={file.path}
className={'remixui_label ' + (file.isDirectory ? 'folder' : 'remixui_leaf')}
data-path={file.path}
>
{ file.name }
</span>
</div>
)
}
const renderFiles = (file: File, index: number) => {
if (file.isDirectory) {
return (
<div key={index}>
<TreeViewItem
id={`treeViewItem${file.path}`}
iconX='pr-3 fa fa-folder'
iconY='pr-3 fa fa-folder-open'
key={`${file.path + index}`}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
if (state.focusEdit.element !== file.path) handleClickFolder(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFolder(e.pageX, e.pageY, file.path, e.target.textContent)
}}
labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
controlBehaviour={ state.ctrlKey }
expand={state.expandPath.includes(file.path)}
>
{
file.child ? <TreeView id={`treeView${file.path}`} key={index}>{
file.child.map((file, index) => {
return renderFiles(file, index)
})
}
</TreeView> : <TreeView id={`treeView${file.path}`} key={index} />
}
</TreeViewItem>
{ ((state.focusContext.element === file.path) && (state.focusEdit.element !== file.path)) &&
<FileExplorerContextMenu
actions={state.actions}
hideContextMenu={hideContextMenu}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
deletePath={deletePath}
renamePath={editModeOn}
extractParentFromKey={extractParentFromKey}
publishToGist={publishToGist}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
path={file.path}
type='folder'
/>
}
</div>
)
} else {
return (
<div key={index}>
<TreeViewItem
id={`treeViewItem${file.path}`}
key={index}
label={label(file)}
onClick={(e) => {
e.stopPropagation()
if (state.focusEdit.element !== file.path) handleClickFile(file.path)
}}
onContextMenu={(e) => {
e.preventDefault()
e.stopPropagation()
handleContextMenuFile(e.pageX, e.pageY, file.path, e.target.textContent)
}}
icon='far fa-file'
labelClass={ state.focusEdit.element === file.path ? 'bg-light' : state.focusElement.findIndex(item => item.key === file.path) !== -1 ? 'bg-secondary' : '' }
/>
{ ((state.focusContext.element === file.path) && (state.focusEdit.element !== file.path)) &&
<FileExplorerContextMenu
actions={state.actions}
hideContextMenu={hideContextMenu}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
deletePath={deletePath}
renamePath={editModeOn}
runScript={runScript}
pageX={state.focusContext.x}
pageY={state.focusContext.y}
path={file.path}
type='file'
/>
}
</div>
)
}
}
return (
<div>
<TreeView id='treeView'>
<TreeViewItem id="treeViewItem"
label={
<FileExplorerMenu
title={name}
menuItems={props.menuItems}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
publishToGist={publishToGist}
uploadFile={uploadFile}
fileManager={state.fileManager}
/>
}
expand={true}>
<div className='pb-2'>
<TreeView id='treeViewMenu'>
{
state.files.map((file, index) => {
return renderFiles(file, index)
})
}
</TreeView>
</div>
</TreeViewItem>
</TreeView>
{
props.name && <ModalDialog
id={ props.name }
title={ state.modalOptions.title }
message={ state.modalOptions.message }
hide={ state.modalOptions.hide }
ok={ state.modalOptions.ok }
cancel={ state.modalOptions.cancel }
handleHide={ handleHideModal }
/>
}
<Toaster message={state.toasterMsg} />
</div>
)
}
export default FileExplorer
function packageFiles (filesProvider, directory, callback) {
const ret = {}
filesProvider.resolveDirectory(directory, (error, files) => {
if (error) callback(error)
else {
async.eachSeries(Object.keys(files), (path, cb) => {
if (filesProvider.isDirectory(path)) {
cb()
} else {
filesProvider.get(path, (error, content) => {
if (error) return cb(error)
if (/^\s+$/.test(content) || !content.length) {
content = '// this line is added to create a gist. Empty file is not allowed.'
}
ret[path] = { content }
cb()
})
}
}, (error) => {
callback(error, ret)
})
}
})
}

@ -0,0 +1,41 @@
/* eslint-disable-next-line */
export interface FileExplorerProps {
name: string,
registry: any,
filesProvider: any,
menuItems?: string[],
plugin: any
}
export interface File {
path: string,
name: string,
isDirectory: boolean,
child?: File[]
}
export interface FileExplorerMenuProps {
title: string,
menuItems: string[],
fileManager: any,
createNewFile: (folder?: string) => void,
createNewFolder: (parentFolder?: string) => void,
publishToGist: () => void,
uploadFile: (target: EventTarget & HTMLInputElement) => void
}
export interface FileExplorerContextMenuProps {
actions: { name: string, type: string[], path: string[], extension: string[], pattern: string[] }[],
createNewFile: (folder?: string) => void,
createNewFolder: (parentFolder?: string) => void,
deletePath: (path: string) => void,
renamePath: (path: string, type: string) => void,
hideContextMenu: () => void,
extractParentFromKey?: (key: string) => string,
publishToGist?: () => void,
runScript?: (path: string) => void,
pageX: number,
pageY: number,
path: string,
type: string
}

@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -9,14 +9,12 @@ export const ModalDialog = (props: ModalDialogProps) => {
})
const modal = useRef(null)
const handleHide = () => {
props.hide()
props.handleHide()
}
useEffect(
() => {
useEffect(() => {
modal.current.focus()
}, []
)
}, [props.hide])
const modalKeyEvent = (keyCode) => {
if (keyCode === 27) { // Esc
@ -41,74 +39,72 @@ export const ModalDialog = (props: ModalDialogProps) => {
}
handleHide()
}
return (<>
return (
<div
id="modal-dialog"
data-id="modalDialogContainer"
data-id={`${props.id}ModalDialogContainer-react`}
data-backdrop="static"
data-keyboard="false"
tabIndex={-1}
className="modal d-block"
className='modal'
style={{ display: props.hide ? 'none' : 'block' }}
role="dialog"
>
<div id="modal-background" className="modal-dialog" role="document">
<div className="modal-dialog" role="document">
<div
tabIndex={1}
onBlur={(e) => {
e.stopPropagation()
handleHide()
}}
ref={modal}
className={'modal-content remixModalContent ' + (props.opts ? props.opts.class ? props.opts.class : '' : '')}
tabIndex={-1}
className={'modal-content remixModalContent ' + (props.modalClass ? props.modalClass : '')}
onKeyDown={({ keyCode }) => { modalKeyEvent(keyCode) }}
>
<div className="modal-header">
<h6 className="modal-title" data-id="modalDialogModalTitle">
<h6 className="modal-title" data-id={`${props.id}ModalDialogModalTitle-react`}>
{props.title && props.title}
</h6>
{!props.opts.hideClose &&
{!props.showCancelIcon &&
<span className="modal-close" onClick={() => handleHide()}>
<i id="modal-close" title="Close" className="fas fa-times" aria-hidden="true"></i>
<i title="Close" className="fas fa-times" aria-hidden="true"></i>
</span>
}
</div>
<div className="modal-body text-break remixModalBody" data-id="modalDialogModalBody">
{props.content &&
props.content
}
<div className="modal-body text-break remixModalBody" data-id={`${props.id}ModalDialogModalBody-react`}>
{ props.children ? props.children : props.message }
</div>
<div className="modal-footer" data-id="modalDialogModalFooter">
<div className="modal-footer" data-id={`${props.id}ModalDialogModalFooter-react`}>
{/* todo add autofocus ^^ */}
{props.ok &&
{ props.ok &&
<span
id="modal-footer-ok"
data-id={`${props.id}-modal-footer-ok-react`}
className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')}
onClick={() => {
if (props.ok && props.ok.fn) props.ok.fn()
if (props.ok.fn) props.ok.fn()
handleHide()
}}
tabIndex={1}
>
{props.ok && props.ok.label ? props.ok.label : 'OK'}
{ props.ok.label ? props.ok.label : 'OK' }
</span>
}
{ props.cancel &&
<span
id="modal-footer-cancel"
data-id={`${props.id}-modal-footer-cancel-react`}
className={'modal-cancel btn btn-sm ' + (state.toggleBtn ? 'btn-light' : 'btn-dark')}
data-dismiss="modal"
onClick={() => {
if (props.cancel && props.cancel.fn) props.cancel.fn()
if (props.cancel.fn) props.cancel.fn()
handleHide()
}}
tabIndex={2}
>
{props.cancel && props.cancel.label ? props.cancel.label : 'Cancel'}
{ props.cancel.label ? props.cancel.label : 'Cancel' }
</span>
}
</div>
</div>
</div>
</div>
</>)
)
}
export default ModalDialog

@ -1,9 +1,12 @@
export interface ModalDialogProps {
id?: string
title?: string,
content?: JSX.Element,
ok?: {label:string, fn: () => void},
cancel?: {label:string, fn: () => void},
focusSelector?: string,
opts?: {class: string, hideClose?: boolean},
hide: () => void
message?: string,
ok?: { label: string, fn: () => void },
cancel: { label: string, fn: () => void },
modalClass?: string,
showCancelIcon?: boolean,
hide: boolean,
handleHide: (hideState?: boolean) => void,
children?: React.ReactNode
}

@ -89,7 +89,15 @@ export const Toaster = (props: ToasterProps) => {
return (
<>
{/* <ModalDialog /> */}
<ModalDialog
message={props.message}
cancel={{
label: 'Close',
fn: () => {}
}}
hide={!state.showModal}
handleHide={hideFullMessage}
/>
{ !state.hide &&
<div data-shared="tooltipPopup" className={`remixui_tooltip alert alert-info p-2 ${state.hiding ? 'remixui_animateTop' : 'remixui_animateBottom'}`} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<span className="px-2">

@ -1,248 +1,19 @@
{
"rules": {
"array-callback-return": "warn",
"dot-location": ["warn", "property"],
"eqeqeq": ["warn", "smart"],
"new-parens": "warn",
"no-caller": "warn",
"no-cond-assign": ["warn", "except-parens"],
"no-const-assign": "warn",
"no-control-regex": "warn",
"no-delete-var": "warn",
"no-dupe-args": "warn",
"no-dupe-keys": "warn",
"no-duplicate-case": "warn",
"no-empty-character-class": "warn",
"no-empty-pattern": "warn",
"no-eval": "warn",
"no-ex-assign": "warn",
"no-extend-native": "warn",
"no-extra-bind": "warn",
"no-extra-label": "warn",
"no-fallthrough": "warn",
"no-func-assign": "warn",
"no-implied-eval": "warn",
"no-invalid-regexp": "warn",
"no-iterator": "warn",
"no-label-var": "warn",
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }],
"no-lone-blocks": "warn",
"no-loop-func": "warn",
"no-mixed-operators": [
"warn",
{
"groups": [
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["&&", "||"],
["in", "instanceof"]
],
"allowSamePrecedence": false
}
],
"no-multi-str": "warn",
"no-native-reassign": "warn",
"no-negated-in-lhs": "warn",
"no-new-func": "warn",
"no-new-object": "warn",
"no-new-symbol": "warn",
"no-new-wrappers": "warn",
"no-obj-calls": "warn",
"no-octal": "warn",
"no-octal-escape": "warn",
"no-redeclare": "warn",
"no-regex-spaces": "warn",
"no-restricted-syntax": ["warn", "WithStatement"],
"no-script-url": "warn",
"no-self-assign": "warn",
"no-self-compare": "warn",
"no-sequences": "warn",
"no-shadow-restricted-names": "warn",
"no-sparse-arrays": "warn",
"no-template-curly-in-string": "warn",
"no-this-before-super": "warn",
"no-throw-literal": "warn",
"no-restricted-globals": [
"error",
"addEventListener",
"blur",
"close",
"closed",
"confirm",
"defaultStatus",
"defaultstatus",
"event",
"external",
"find",
"focus",
"frameElement",
"frames",
"history",
"innerHeight",
"innerWidth",
"length",
"location",
"locationbar",
"menubar",
"moveBy",
"moveTo",
"name",
"onblur",
"onerror",
"onfocus",
"onload",
"onresize",
"onunload",
"open",
"opener",
"opera",
"outerHeight",
"outerWidth",
"pageXOffset",
"pageYOffset",
"parent",
"print",
"removeEventListener",
"resizeBy",
"resizeTo",
"screen",
"screenLeft",
"screenTop",
"screenX",
"screenY",
"scroll",
"scrollbars",
"scrollBy",
"scrollTo",
"scrollX",
"scrollY",
"self",
"status",
"statusbar",
"stop",
"toolbar",
"top"
],
"no-unexpected-multiline": "warn",
"no-unreachable": "warn",
"no-unused-expressions": [
"error",
{
"allowShortCircuit": true,
"allowTernary": true,
"allowTaggedTemplates": true
}
],
"no-unused-labels": "warn",
"no-useless-computed-key": "warn",
"no-useless-concat": "warn",
"no-useless-escape": "warn",
"no-useless-rename": [
"warn",
{
"ignoreDestructuring": false,
"ignoreImport": false,
"ignoreExport": false
}
],
"no-with": "warn",
"no-whitespace-before-property": "warn",
"react-hooks/exhaustive-deps": "warn",
"require-yield": "warn",
"rest-spread-spacing": ["warn", "never"],
"strict": ["warn", "never"],
"unicode-bom": ["warn", "never"],
"use-isnan": "warn",
"valid-typeof": "warn",
"no-restricted-properties": [
"error",
{
"object": "require",
"property": "ensure",
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
},
{
"object": "System",
"property": "import",
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
}
],
"getter-return": "warn",
"import/first": "error",
"import/no-amd": "error",
"import/no-webpack-loader-syntax": "error",
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }],
"react/jsx-no-comment-textnodes": "warn",
"react/jsx-no-duplicate-props": "warn",
"react/jsx-no-target-blank": "warn",
"react/jsx-no-undef": "error",
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }],
"react/jsx-uses-react": "warn",
"react/jsx-uses-vars": "warn",
"react/no-danger-with-children": "warn",
"react/no-direct-mutation-state": "warn",
"react/no-is-mounted": "warn",
"react/no-typos": "error",
"react/react-in-jsx-scope": "error",
"react/require-render-return": "error",
"react/style-prop-object": "warn",
"react/jsx-no-useless-fragment": "warn",
"jsx-a11y/accessible-emoji": "warn",
"jsx-a11y/alt-text": "warn",
"jsx-a11y/anchor-has-content": "warn",
"jsx-a11y/anchor-is-valid": [
"warn",
{ "aspects": ["noHref", "invalidHref"] }
],
"jsx-a11y/aria-activedescendant-has-tabindex": "warn",
"jsx-a11y/aria-props": "warn",
"jsx-a11y/aria-proptypes": "warn",
"jsx-a11y/aria-role": "warn",
"jsx-a11y/aria-unsupported-elements": "warn",
"jsx-a11y/heading-has-content": "warn",
"jsx-a11y/iframe-has-title": "warn",
"jsx-a11y/img-redundant-alt": "warn",
"jsx-a11y/no-access-key": "warn",
"jsx-a11y/no-distracting-elements": "warn",
"jsx-a11y/no-redundant-roles": "warn",
"jsx-a11y/role-has-required-aria-props": "warn",
"jsx-a11y/role-supports-aria-props": "warn",
"jsx-a11y/scope": "warn",
"react-hooks/rules-of-hooks": "error",
"default-case": "off",
"no-dupe-class-members": "off",
"no-undef": "off",
"@typescript-eslint/consistent-type-assertions": "warn",
"no-array-constructor": "off",
"@typescript-eslint/no-array-constructor": "warn",
"@typescript-eslint/no-namespace": "error",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": [
"warn",
{
"functions": false,
"classes": false,
"variables": false,
"typedefs": false
}
],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{ "args": "none", "ignoreRestSiblings": true }
],
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "warn"
},
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"jest": true,
"node": true
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"settings": { "react": { "version": "detect" } },
"plugins": ["import", "jsx-a11y", "react", "react-hooks"],
"extends": ["../../../.eslintrc"],
"ignorePatterns": ["!**/*"]
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -9,4 +9,4 @@ module.exports = {
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
coverageDirectory: '../../../coverage/libs/remix-ui/tree-view'
};
}

@ -1,2 +1,2 @@
export * from './lib/tree-view-item/tree-view-item';
export * from './lib/remix-ui-tree-view';
export * from './lib/tree-view-item/tree-view-item'
export * from './lib/remix-ui-tree-view'

@ -1,4 +1,4 @@
import React from 'react'
import React from 'react' // eslint-disable-line
import { TreeViewProps } from '../types'
import './remix-ui-tree-view.css'

@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect } from 'react' // eslint-disable-line
import { TreeViewItemProps } from '../../types'
import './tree-view-item.css'
export const TreeViewItem = (props: TreeViewItemProps) => {
const { id, children, label, expand, ...otherProps } = props
const { id, children, label, labelClass, expand, iconX = 'fas fa-caret-right', iconY = 'fas fa-caret-down', icon, controlBehaviour = false, innerRef, ...otherProps } = props
const [isExpanded, setIsExpanded] = useState(false)
useEffect(() => {
@ -12,9 +12,9 @@ export const TreeViewItem = (props: TreeViewItemProps) => {
}, [expand])
return (
<li key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}>
<div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className='d-flex flex-row align-items-center' onClick={() => setIsExpanded(!isExpanded)}>
<div className={isExpanded ? 'px-1 fas fa-caret-down caret caret_tv' : 'px-1 fas fa-caret-right caret caret_tv'} style={{ visibility: children ? 'visible' : 'hidden' }}></div>
<li ref={innerRef} key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}>
<div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className={`d-flex flex-row align-items-center ${labelClass}`} onClick={() => !controlBehaviour && setIsExpanded(!isExpanded)}>
{ children ? <div className={isExpanded ? `px-1 ${iconY} caret caret_tv` : `px-1 ${iconX} caret caret_tv`} style={{ visibility: children ? 'visible' : 'hidden' }}></div> : icon ? <div className={`pr-3 pl-1 ${icon} caret caret_tv`}></div> : null }
<span className='w-100 pl-1'>
{ label }
</span>

@ -1,13 +1,22 @@
export interface TreeViewProps {
children?: React.ReactNode,
id: string
id?: string
}
export interface TreeViewItemProps {
children?: React.ReactNode,
id: string,
id?: string,
label: string | number | React.ReactNode,
expand?: boolean,
onClick?: VoidFunction,
className?: string
onClick?: (...args: any) => void,
onInput?: (...args: any) => void,
className?: string,
iconX?: string,
iconY?: string,
icon?: string,
labelClass?: string,
controlBehaviour?: boolean
innerRef?: any,
onContextMenu?: (...args: any) => void,
onBlur?: (...args: any) => void
}

@ -7,7 +7,7 @@ import * as fs from 'fs-extra'
import * as isbinaryfile from 'isbinaryfile'
export class RemixdClient extends PluginClient {
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'list', 'isDirectory']
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'list', 'isDirectory', 'createDir']
trackDownStreamUpdate: TrackDownStreamUpdate = {}
websocket: WS
currentSharedFolder: string
@ -127,6 +127,30 @@ export class RemixdClient extends PluginClient {
}
}
createDir (args: SharedFolderArgs): Promise<void> {
try {
return new Promise((resolve, reject) => {
if (this.readOnly) reject(new Error('Cannot create folder: read-only mode selected'))
const path = utils.absolutePath(args.path, this.currentSharedFolder)
const exists = fs.existsSync(path)
if (exists && !isRealPath(path)) reject(new Error(''))
this.trackDownStreamUpdate[path] = path
fs.mkdirp(path).then(() => {
let splitPath = args.path.split('/')
splitPath = splitPath.filter(dir => dir)
const dir = '/' + splitPath.join('/')
this.emit('folderAdded', dir)
resolve()
}).catch((e: Error) => reject(e))
})
} catch (error) {
throw new Error(error)
}
}
rename (args: SharedFolderArgs): Promise<boolean> {
try {
return new Promise((resolve, reject) => {

@ -86,6 +86,9 @@
},
"remix-ui-toaster": {
"tags": []
},
"remix-ui-file-explorer": {
"tags": []
}
}
}

200
package-lock.json generated

@ -7597,6 +7597,15 @@
"csstype": "^2.2.0"
}
},
"@types/react-beautiful-dnd": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz",
"integrity": "sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-dom": {
"version": "16.9.4",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz",
@ -13509,6 +13518,14 @@
"insert-css": "^0.2.0"
}
},
"css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"requires": {
"tiny-invariant": "^1.0.6"
}
},
"css-color-names": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
@ -20226,6 +20243,14 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"homedir-polyfill": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
@ -25592,6 +25617,11 @@
}
}
},
"memoize-one": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
"integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
},
"memory-fs": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
@ -28220,7 +28250,6 @@
},
"dezalgo": {
"version": "1.0.3",
"resolved": false,
"integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
"requires": {
"asap": "^2.0.0",
@ -28849,7 +28878,6 @@
},
"normalize-git-url": {
"version": "3.0.2",
"resolved": false,
"integrity": "sha1-jl8Uvgva7bc+ByADEKpBbCc1D8Q="
},
"normalize-package-data": {
@ -28887,7 +28915,6 @@
},
"npm-install-checks": {
"version": "3.0.0",
"resolved": false,
"integrity": "sha1-1K7N/VGlPjcjt7L5Oy7ijjB7wNc=",
"requires": {
"semver": "^2.3.0 || 3.x || 4 || 5"
@ -29234,7 +29261,6 @@
},
"realize-package-specifier": {
"version": "3.0.3",
"resolved": false,
"integrity": "sha1-0N74gpUrjeP2frpekRmWYScfQfQ=",
"requires": {
"dezalgo": "^1.0.1",
@ -29771,7 +29797,6 @@
"dependencies": {
"unique-slug": {
"version": "2.0.0",
"resolved": false,
"integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
"requires": {
"imurmurhash": "^0.1.4"
@ -30530,19 +30555,19 @@
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"code-point-at": {
"version": "1.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"cross-spawn": {
"version": "5.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"dev": true,
"requires": {
@ -30553,13 +30578,13 @@
},
"decamelize": {
"version": "1.2.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"execa": {
"version": "0.7.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"dev": true,
"requires": {
@ -30574,7 +30599,7 @@
},
"find-up": {
"version": "2.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
@ -30583,25 +30608,25 @@
},
"get-caller-file": {
"version": "1.0.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
"dev": true
},
"get-stream": {
"version": "3.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
},
"invert-kv": {
"version": "1.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
@ -30610,19 +30635,19 @@
},
"is-stream": {
"version": "1.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"lcid": {
"version": "1.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
"integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
"dev": true,
"requires": {
@ -30631,7 +30656,7 @@
},
"locate-path": {
"version": "2.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
"dev": true,
"requires": {
@ -30641,7 +30666,7 @@
},
"lru-cache": {
"version": "4.1.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
"integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
"dev": true,
"requires": {
@ -30651,7 +30676,7 @@
},
"mem": {
"version": "1.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
"dev": true,
"requires": {
@ -30660,19 +30685,19 @@
},
"mimic-fn": {
"version": "1.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz",
"integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=",
"dev": true
},
"minimist": {
"version": "0.0.8",
"resolved": false,
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
@ -30681,7 +30706,7 @@
},
"npm-run-path": {
"version": "2.0.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
"dev": true,
"requires": {
@ -30690,13 +30715,13 @@
},
"number-is-nan": {
"version": "1.0.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"os-locale": {
"version": "2.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
"integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
"dev": true,
"requires": {
@ -30707,19 +30732,19 @@
},
"p-finally": {
"version": "1.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
},
"p-limit": {
"version": "1.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz",
"integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=",
"dev": true
},
"p-locate": {
"version": "2.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
"dev": true,
"requires": {
@ -30728,43 +30753,43 @@
},
"path-exists": {
"version": "3.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"path-key": {
"version": "2.0.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
},
"pseudomap": {
"version": "1.0.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"require-directory": {
"version": "2.1.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "1.0.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"dev": true
},
"set-blocking": {
"version": "2.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"shebang-command": {
"version": "1.2.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"dev": true,
"requires": {
@ -30773,19 +30798,19 @@
},
"shebang-regex": {
"version": "1.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"string-width": {
"version": "1.0.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@ -30796,7 +30821,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@ -30805,13 +30830,13 @@
},
"strip-eof": {
"version": "1.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
"which": {
"version": "1.3.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
"integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
"dev": true,
"requires": {
@ -30820,13 +30845,13 @@
},
"which-module": {
"version": "2.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
@ -30836,19 +30861,19 @@
},
"y18n": {
"version": "3.2.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true
},
"yallist": {
"version": "2.1.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"yargs": {
"version": "10.0.3",
"resolved": false,
"resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz",
"integrity": "sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==",
"dev": true,
"requires": {
@ -30868,13 +30893,13 @@
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"cliui": {
"version": "3.2.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"dev": true,
"requires": {
@ -30885,7 +30910,7 @@
"dependencies": {
"string-width": {
"version": "1.0.2",
"resolved": false,
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@ -30898,7 +30923,7 @@
},
"string-width": {
"version": "2.1.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
@ -30908,13 +30933,13 @@
"dependencies": {
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
@ -30927,7 +30952,7 @@
},
"yargs-parser": {
"version": "8.0.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.0.0.tgz",
"integrity": "sha1-IdR2Mw5agieaS4gTRb8GYQLiGcY=",
"dev": true,
"requires": {
@ -30936,7 +30961,7 @@
"dependencies": {
"camelcase": {
"version": "4.1.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
}
@ -35432,6 +35457,11 @@
"integrity": "sha1-W4h48ROlgheEjGSCAmxz4bpXcn8=",
"dev": true
},
"raf-schd": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz",
"integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ=="
},
"ramda": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
@ -35539,6 +35569,20 @@
"prop-types": "^15.6.2"
}
},
"react-beautiful-dnd": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz",
"integrity": "sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==",
"requires": {
"@babel/runtime": "^7.8.4",
"css-box-model": "^1.2.0",
"memoize-one": "^5.1.1",
"raf-schd": "^4.0.2",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"use-memo-one": "^1.1.1"
}
},
"react-bootstrap": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.3.0.tgz",
@ -35616,6 +35660,28 @@
"warning": "^4.0.3"
}
},
"react-redux": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz",
"integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==",
"requires": {
"@babel/runtime": "^7.12.1",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"react-transition-group": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
@ -35900,6 +35966,15 @@
"esprima": "~4.0.0"
}
},
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
},
"regenerate": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz",
@ -39142,8 +39217,7 @@
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"dev": true
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"symbol-tree": {
"version": "3.2.4",
@ -39731,6 +39805,11 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true
},
"tiny-invariant": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@ -40748,6 +40827,11 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
"use-memo-one": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz",
"integrity": "sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ=="
},
"utf8": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",

@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remixd,remix-ui-modal-dialog,remix-ui-toaster",
"lint:libs": "nx run-many --target=lint --projects=remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs",
@ -153,6 +153,7 @@
"merge": "^1.2.0",
"npm-install-version": "^6.0.2",
"react": "16.13.1",
"react-beautiful-dnd": "^13.0.0",
"react-bootstrap": "^1.3.0",
"react-dom": "16.13.1",
"signale": "^1.4.0",
@ -190,6 +191,7 @@
"@types/nightwatch": "^1.1.6",
"@types/node": "~8.9.4",
"@types/react": "16.9.17",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "16.9.4",
"@types/react-router-dom": "5.1.3",
"@types/ws": "^7.2.4",

@ -33,7 +33,8 @@
"@remix-ui/clipboard": ["libs/remix-ui/clipboard/src/index.ts"],
"@remix-project/remix-solidity-ts": ["libs/remix-solidity/src/index.ts"],
"@remix-ui/modal-dialog": ["libs/remix-ui/modal-dialog/src/index.ts"],
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"]
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"],
"@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -617,6 +617,25 @@
}
}
}
},
"remix-ui-file-explorer": {
"root": "libs/remix-ui/file-explorer",
"sourceRoot": "libs/remix-ui/file-explorer/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/file-explorer/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/file-explorer/**/*"
]
}
}
}
}
},
"cli": {

Loading…
Cancel
Save