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. 90
      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. 261
      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. 4
      libs/remix-ui/tree-view/src/lib/remix-ui-tree-view.tsx
  47. 12
      libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx
  48. 19
      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) { function addFile (browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) {
browser.clickLaunchIcon('udapp').clickLaunchIcon('fileExplorers').click('.newFile') browser.clickLaunchIcon('udapp')
.waitForElementVisible('#modal-dialog') .clickLaunchIcon('fileExplorers')
.perform((client, done) => { .click('li[data-id="treeViewLitreeViewItembrowser/README.txt"]') // focus on root directory
browser.execute(function (fileName) { .click('.newFile')
if (fileName !== 'Untitled.sol') { .waitForElementVisible('*[data-id="treeViewLitreeViewItembrowser/blank"]')
document.querySelector('#modal-dialog #prompt_text').setAttribute('value', fileName) // .scrollAndClick('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items')
} .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', name)
const elem = document.querySelector('#modal-footer-ok') as HTMLElement .sendKeys('*[data-id="treeViewLitreeViewItembrowser/blank"] .remixui_items', browser.Keys.ENTER)
.pause(2000)
elem.click() .waitForElementVisible(`li[data-id="treeViewLitreeViewItembrowser/${name}"]`)
}, [name], function (result) { .click(`li[data-id="treeViewLitreeViewItembrowser/${name}"]`)
console.log(result)
done()
})
})
.setEditorValue(content.content) .setEditorValue(content.content)
.pause(1000) .pause(1000)
.perform(function () { .perform(function () {

@ -3,8 +3,8 @@ import EventEmitter from "events"
class GetModalBody extends EventEmitter { class GetModalBody extends EventEmitter {
command (this: NightwatchBrowser, callback: (value: string, cb: VoidFunction) => void) { command (this: NightwatchBrowser, callback: (value: string, cb: VoidFunction) => void) {
this.api.waitForElementVisible('.modal-body') this.api.waitForElementPresent('.modal-body')
.getText('.modal-body', (result) => { .getText('#modal-dialog', (result) => {
console.log(result) console.log(result)
const value = typeof result.value === 'string' ? result.value : null 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 // click on fileExplorer can toggle it. We go through settings to be sure FE is open
function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction) { function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction) {
browser.clickLaunchIcon('settings').clickLaunchIcon('fileExplorers') browser.clickLaunchIcon('settings').clickLaunchIcon('fileExplorers')
.waitForElementVisible('li[key="' + name + '"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItem' + name + '"')
.click('li[key="' + name + '"]') .click('li[data-id="treeViewLitreeViewItem' + name + '"')
.pause(2000) .pause(2000)
.perform(() => { .perform(() => {
done() done()

@ -34,13 +34,19 @@ function removeFile (browser: NightwatchBrowser, path: string, done: VoidFunctio
contextMenuClick(document.querySelector('[data-path="' + path + '"]')) contextMenuClick(document.querySelector('[data-path="' + path + '"]'))
}, [path], function () { }, [path], function () {
browser browser
.waitForElementVisible('#menuitemdelete', 2000) .waitForElementVisible('#menuitemdelete')
.click('#menuitemdelete') .click('#menuitemdelete')
.pause(500) .pause(2000)
.waitForElementVisible('#modal-footer-ok', 2000)
.click('#modal-footer-ok')
.waitForElementNotPresent('[data-path="' + path + '"]')
.perform(() => { .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() done()
}) })
}) })

@ -1,10 +1,10 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch' import { NightwatchBrowser } from 'nightwatch'
class RenameFile extends EventEmitter { class RenamePath extends EventEmitter {
command (this: NightwatchBrowser, path: string, newFileName: string, renamedPath: string) { command (this: NightwatchBrowser, path: string, newFileName: string, renamedPath: string) {
this.api.perform((done) => { this.api.perform((done) => {
renameFile(this.api, path, newFileName, renamedPath, () => { renamePath(this.api, path, newFileName, renamedPath, () => {
done() done()
this.emit('complete') 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) { browser.execute(function (path: string) {
function contextMenuClick (element) { function contextMenuClick (element) {
const evt = element.ownerDocument.createEvent('MouseEvents') const evt = element.ownerDocument.createEvent('MouseEvents')
@ -41,10 +41,9 @@ function renameFile (browser: NightwatchBrowser, path: string, newFileName: stri
doneSetValue() doneSetValue()
}) })
}) })
.click('body') // blur .pause(1000)
.waitForElementVisible('#modal-footer-ok', 100000) .click('li[data-id="treeViewLitreeViewItembrowser/README.txt"]') // focus on root directory
.pause(2000) .pause(2000)
.click('#modal-footer-ok')
.waitForElementNotPresent('[data-path="' + path + '"]') .waitForElementNotPresent('[data-path="' + path + '"]')
.waitForElementPresent('[data-path="' + renamedPath + '"]') .waitForElementPresent('[data-path="' + renamedPath + '"]')
.perform(() => { .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 browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js') .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']) .testContracts('withABIEncoderV2.sol', sources[2]['browser/withABIEncoderV2.sol'], ['test'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectContract('test') .selectContract('test')

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

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

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

@ -63,7 +63,7 @@ module.exports = {
.addFile('renameFile.js', { content: executeRename }) .addFile('renameFile.js', { content: executeRename })
.executeScript(`remix.exeCurrent()`) .executeScript(`remix.exeCurrent()`)
.pause(2000) .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) { 'Should execute `mkdir` api from file manager external api': function (browser: NightwatchBrowser) {
@ -71,7 +71,7 @@ module.exports = {
.addFile('mkdirFile.js', { content: executeMkdir }) .addFile('mkdirFile.js', { content: executeMkdir })
.executeScript(`remix.exeCurrent()`) .executeScript(`remix.exeCurrent()`)
.pause(2000) .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) { 'Should execute `readdir` api from file manager external api': function (browser: NightwatchBrowser) {
@ -87,15 +87,16 @@ module.exports = {
.addFile('removeFile.js', { content: executeRemove }) .addFile('removeFile.js', { content: executeRemove })
.executeScript(`remix.exeCurrent()`) .executeScript(`remix.exeCurrent()`)
.pause(2000) .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 browser
.addFile('test_jsRemoveFolder.js', { content: executeRemoveOnFolder }) .addFile('test_jsRemoveFolder.js', { content: executeRemoveOnFolder })
.executeScript('remix.exeCurrent()') .executeScript('remix.exeCurrent()')
.pause(2000) .pause(2000)
.waitForElementNotPresent('*[key="browser/tests"]') .waitForElementNotPresent('[data-id="treeViewLitreeViewItembrowser/tests"]')
.end() .end()
}, },

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

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

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

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

@ -128,6 +128,7 @@ module.exports = {
.waitForElementPresent('.transaction-status--submitted') .waitForElementPresent('.transaction-status--submitted')
.pause(25000) .pause(25000)
.switchBrowserTab(0) .switchBrowserTab(0)
.end()
}, },
'Should connect to Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) { '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) { 'Test Github Import - no branch specified': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.6.2+commit.bacdbe57.js') // open-zeppelin moved to pragma ^0.6.0 (master branch) .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']) .addFile('Untitled6.sol', sources[5]['browser/Untitled6.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.verifyContracts(['test10', 'ERC20', 'SafeMath'], {wait: 10000}) .verifyContracts(['test10', 'ERC20', 'SafeMath'], {wait: 10000})
@ -55,6 +57,8 @@ module.exports = {
'Test Github Import - raw URL': function (browser: NightwatchBrowser) { 'Test Github Import - raw URL': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('fileExplorers')
.click('li[data-id="treeViewLitreeViewItembrowser/README.txt"')
.addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol']) .addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.verifyContracts(['test11', 'ERC20', 'SafeMath'], {wait: 10000}) .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) { 'Test switch to a github import from a solidity warning': function (browser: NightwatchBrowser) {
browser browser
.setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js') .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']) .addFile('Untitled8.sol', sources[7]['browser/Untitled8.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')

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

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

@ -28,7 +28,7 @@ declare module "nightwatch" {
checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser, checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser,
openFile(name: string): NightwatchBrowser, openFile(name: string): NightwatchBrowser,
editorScroll(direction: 'up' | 'down', numberOfTimes: number): 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, rightClick(cssSelector: string): NightwatchBrowser,
waitForElementContainsText(id: string, value: string): NightwatchBrowser, waitForElementContainsText(id: string, value: string): NightwatchBrowser,
getModalBody(callback: (value: string, cb: VoidFunction) => void): 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 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 { export interface NightwatchContractContent {
content: string; 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) { isReadOnly (path) {
return this._readOnlyMode || this._readOnlyFiles[path] === 1 return this._readOnlyMode || this._readOnlyFiles[path] === 1
} }

@ -22,7 +22,7 @@ const profile = {
name: 'remixd', name: 'remixd',
displayName: 'RemixD', displayName: 'RemixD',
url: 'ws://127.0.0.1:65520', 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: [], events: [],
description: 'Using Remixd daemon, allow to access file system', description: 'Using Remixd daemon, allow to access file system',
kind: 'other', kind: 'other',
@ -47,7 +47,6 @@ export class RemixdHandle extends WebsocketPlugin {
} }
activate () { activate () {
this.fileSystemExplorer.show()
this.connectToLocalhost() this.connectToLocalhost()
} }
@ -83,7 +82,9 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled() this.canceled()
} }
}, 3000) }, 3000)
this.locahostProvider.init(_ => this.fileSystemExplorer.ensureRoot()) this.locahostProvider.init(() => {
this.fileSystemExplorer.show()
})
this.call('manager', 'activatePlugin', 'git') this.call('manager', 'activatePlugin', 'git')
} }
} }

@ -1,13 +1,16 @@
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' 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 yo = require('yo-yo')
var EventManager = require('../../lib/events') 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 { RemixdHandle } = require('../files/remixd-handle.js')
var { GitHandle } = require('../files/git-handle.js') var { GitHandle } = require('../files/git-handle.js')
var globalRegistry = require('../../global/registry') var globalRegistry = require('../../global/registry')
var css = require('./styles/file-panel-styles')
var canUpload = window.File || window.FileReader || window.FileList || window.Blob var canUpload = window.File || window.FileReader || window.FileList || window.Blob
@ -44,63 +47,85 @@ const profile = {
module.exports = class Filepanel extends ViewPlugin { module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) { constructor (appManager) {
super(profile) super(profile)
var self = this this._components = {}
self._components = {} this._components.registry = globalRegistry
self._components.registry = globalRegistry this._deps = {
self._deps = { fileProviders: this._components.registry.get('fileproviders').api,
fileProviders: self._components.registry.get('fileproviders').api, fileManager: this._components.registry.get('filemanager').api,
fileManager: self._components.registry.get('filemanager').api, config: this._components.registry.get('config').api
config: self._components.registry.get('config').api
} }
this.hideRemixdExplorer = true
function createProvider (key, menuItems) { this.remixdExplorer = {
return new FileExplorer(self._components.registry, self._deps.fileProviders[key], menuItems, self) hide: () => {
this.hideRemixdExplorer = true
this.renderComponent()
},
show: () => {
this.hideRemixdExplorer = false
this.renderComponent()
}
} }
this.el = yo`
var fileExplorer = createProvider('browser', ['createNewFile', 'publishToGist', canUpload ? 'uploadFile' : '']) <div id="fileExplorerView">
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>
</div> </div>
` `
function template () { this.remixdHandle = new RemixdHandle(this.remixdExplorer, this._deps.fileProviders.localhost, appManager)
return yo` this.gitHandle = new GitHandle()
<div class=${css.container}>
<div class="${css.fileexplorer}">
<div class="${css.fileExplorerTree}">
${explorers}
</div>
</div>
</div>
`
}
var event = new EventManager() this.event = new EventManager()
self.event = event this._deps.fileProviders.localhost.event.register('connecting', (event) => {
var element = template()
fileExplorer.ensureRoot()
self._deps.fileProviders.localhost.event.register('connecting', (event) => {
}) })
self._deps.fileProviders.localhost.event.register('connected', (event) => { this._deps.fileProviders.localhost.event.register('connected', (event) => {
fileSystemExplorer.show() this.remixdExplorer.show()
}) })
self._deps.fileProviders.localhost.event.register('errored', (event) => { this._deps.fileProviders.localhost.event.register('errored', (event) => {
fileSystemExplorer.hide() this.remixdExplorer.hide()
}) })
self._deps.fileProviders.localhost.event.register('closed', (event) => { this._deps.fileProviders.localhost.event.register('closed', (event) => {
fileSystemExplorer.hide() 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({ this._view.filetabs.addTab({
id: name, id: name.split(' ').join(''),
title, title,
icon, icon,
tooltip: name tooltip: name

@ -52,7 +52,7 @@ module.exports = class TestTab extends ViewPlugin {
listenToEvents () { listenToEvents () {
this.filePanel.event.register('newTestFileCreated', file => { 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) var test = this.createSingleTest(file)
testList.appendChild(test) testList.appendChild(test)
this.data.allTests.push(file) this.data.allTests.push(file)

@ -6,10 +6,10 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
let agreed = true let agreed = true
let footerIsActive = false let footerIsActive = false
opts = opts || {} opts = opts || {}
var container = document.querySelector('.modal') var container = document.getElementById('modal-dialog')
if (!container) { if (!container) {
document.querySelector('body').appendChild(html(opts)) document.querySelector('body').appendChild(html(opts))
container = document.querySelector('.modal') container = document.getElementById('modal-dialog')
incomingModal = false incomingModal = false
} else incomingModal = true } 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.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : 'Cancel'
cancelDiv.style.display = cancelDiv.innerHTML === '' ? 'none' : 'inline-block' cancelDiv.style.display = cancelDiv.innerHTML === '' ? 'none' : 'inline-block'
var modal = document.querySelector('.modal-body') var modal = document.getElementById('modal-body-id')
var modalTitle = document.querySelector('.modal-header h6') var modalTitle = document.getElementById('modal-title-h6')
modalTitle.innerHTML = '' modalTitle.innerHTML = ''
if (title) modalTitle.innerText = title if (title) modalTitle.innerText = title
@ -134,12 +134,12 @@ function html (opts) {
<div id="modal-background" class="modal-dialog" role="document"> <div id="modal-background" class="modal-dialog" role="document">
<div class="modal-content ${css.modalContent} ${opts.class}"> <div class="modal-content ${css.modalContent} ${opts.class}">
<div class="modal-header"> <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"> <span class="modal-close">
<i id="modal-close" title="Close" class="fas fa-times" aria-hidden="true"></i> <i id="modal-close" title="Close" class="fas fa-times" aria-hidden="true"></i>
</span> </span>
</div> </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> <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-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> <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 obj = this.anonymous
} }
for (const reg in this.registered[eventName]) { 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) 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 modal = useRef(null)
const handleHide = () => { const handleHide = () => {
props.hide() props.handleHide()
} }
useEffect( useEffect(() => {
() => { modal.current.focus()
modal.current.focus() }, [props.hide])
}, []
)
const modalKeyEvent = (keyCode) => { const modalKeyEvent = (keyCode) => {
if (keyCode === 27) { // Esc if (keyCode === 27) { // Esc
@ -41,74 +39,72 @@ export const ModalDialog = (props: ModalDialogProps) => {
} }
handleHide() handleHide()
} }
return (<>
return (
<div <div
id="modal-dialog" data-id={`${props.id}ModalDialogContainer-react`}
data-id="modalDialogContainer"
data-backdrop="static" data-backdrop="static"
data-keyboard="false" data-keyboard="false"
tabIndex={-1} className='modal'
className="modal d-block" style={{ display: props.hide ? 'none' : 'block' }}
role="dialog" role="dialog"
> >
<div id="modal-background" className="modal-dialog" role="document"> <div className="modal-dialog" role="document">
<div <div
tabIndex={1}
onBlur={(e) => { onBlur={(e) => {
e.stopPropagation() e.stopPropagation()
handleHide() handleHide()
}} }}
ref={modal} 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) }} onKeyDown={({ keyCode }) => { modalKeyEvent(keyCode) }}
> >
<div className="modal-header"> <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} {props.title && props.title}
</h6> </h6>
{!props.opts.hideClose && {!props.showCancelIcon &&
<span className="modal-close" onClick={() => handleHide()}> <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> </span>
} }
</div> </div>
<div className="modal-body text-break remixModalBody" data-id="modalDialogModalBody"> <div className="modal-body text-break remixModalBody" data-id={`${props.id}ModalDialogModalBody-react`}>
{props.content && { props.children ? props.children : props.message }
props.content
}
</div> </div>
<div className="modal-footer" data-id="modalDialogModalFooter"> <div className="modal-footer" data-id={`${props.id}ModalDialogModalFooter-react`}>
{/* todo add autofocus ^^ */} {/* todo add autofocus ^^ */}
{props.ok && { props.ok &&
<span <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')} className={'modal-ok btn btn-sm ' + (state.toggleBtn ? 'btn-dark' : 'btn-light')}
onClick={() => { onClick={() => {
if (props.ok && props.ok.fn) props.ok.fn() if (props.ok.fn) props.ok.fn()
handleHide() handleHide()
}} }}
tabIndex={1} >
> { props.ok.label ? props.ok.label : 'OK' }
{props.ok && props.ok.label ? props.ok.label : 'OK'} </span>
</span> }
{ props.cancel &&
<span
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.fn) props.cancel.fn()
handleHide()
}}
>
{ props.cancel.label ? props.cancel.label : 'Cancel' }
</span>
} }
<span
id="modal-footer-cancel"
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()
handleHide()
}}
tabIndex={2}
>
{props.cancel && props.cancel.label ? props.cancel.label : 'Cancel'}
</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</>) )
} }
export default ModalDialog export default ModalDialog

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

@ -89,7 +89,15 @@ export const Toaster = (props: ToasterProps) => {
return ( return (
<> <>
{/* <ModalDialog /> */} <ModalDialog
message={props.message}
cancel={{
label: 'Close',
fn: () => {}
}}
hide={!state.showModal}
handleHide={hideFullMessage}
/>
{ !state.hide && { !state.hide &&
<div data-shared="tooltipPopup" className={`remixui_tooltip alert alert-info p-2 ${state.hiding ? 'remixui_animateTop' : 'remixui_animateBottom'}`} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}> <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"> <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": { "env": {
"browser": true, "browser": true,
"commonjs": true, "es6": true
"es6": true, },
"jest": true, "extends": "../../../.eslintrc",
"node": true "globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
}, },
"settings": { "react": { "version": "detect" } }, "parserOptions": {
"plugins": ["import", "jsx-a11y", "react", "react-hooks"], "ecmaVersion": 11,
"extends": ["../../../.eslintrc"], "sourceType": "module"
"ignorePatterns": ["!**/*"] },
} "rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

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

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

@ -1,11 +1,11 @@
import React from 'react' import React from 'react' // eslint-disable-line
import { TreeViewProps } from '../types' import { TreeViewProps } from '../types'
import './remix-ui-tree-view.css' import './remix-ui-tree-view.css'
export const TreeView = (props: TreeViewProps) => { export const TreeView = (props: TreeViewProps) => {
const { children, id, ...otherProps } = props const { children, id, ...otherProps } = props
return ( return (
<ul data-id={`treeViewUl${id}`} className="ul_tv ml-0 px-2" { ...otherProps }> <ul data-id={`treeViewUl${id}`} className="ul_tv ml-0 px-2" { ...otherProps }>
{ children } { children }

@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react' // eslint-disable-line
import { TreeViewItemProps } from '../../types' import { TreeViewItemProps } from '../../types'
import './tree-view-item.css' import './tree-view-item.css'
export const TreeViewItem = (props: TreeViewItemProps) => { 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) const [isExpanded, setIsExpanded] = useState(false)
useEffect(() => { useEffect(() => {
@ -12,14 +12,14 @@ export const TreeViewItem = (props: TreeViewItemProps) => {
}, [expand]) }, [expand])
return ( return (
<li key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}> <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' onClick={() => setIsExpanded(!isExpanded)}> <div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className={`d-flex flex-row align-items-center ${labelClass}`} onClick={() => !controlBehaviour && 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> { 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'> <span className='w-100 pl-1'>
{ label } { label }
</span> </span>
</div> </div>
{ isExpanded ? children : null } { isExpanded ? children : null }
</li> </li>
) )
} }

@ -1,13 +1,22 @@
export interface TreeViewProps { export interface TreeViewProps {
children?: React.ReactNode, children?: React.ReactNode,
id: string id?: string
} }
export interface TreeViewItemProps { export interface TreeViewItemProps {
children?: React.ReactNode, children?: React.ReactNode,
id: string, id?: string,
label: string | number | React.ReactNode, label: string | number | React.ReactNode,
expand?: boolean, expand?: boolean,
onClick?: VoidFunction, onClick?: (...args: any) => void,
className?: string 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' import * as isbinaryfile from 'isbinaryfile'
export class RemixdClient extends PluginClient { 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 = {} trackDownStreamUpdate: TrackDownStreamUpdate = {}
websocket: WS websocket: WS
currentSharedFolder: string 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> { rename (args: SharedFolderArgs): Promise<boolean> {
try { try {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

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

200
package-lock.json generated

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

@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "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", "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", "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", "publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs",
@ -153,6 +153,7 @@
"merge": "^1.2.0", "merge": "^1.2.0",
"npm-install-version": "^6.0.2", "npm-install-version": "^6.0.2",
"react": "16.13.1", "react": "16.13.1",
"react-beautiful-dnd": "^13.0.0",
"react-bootstrap": "^1.3.0", "react-bootstrap": "^1.3.0",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"signale": "^1.4.0", "signale": "^1.4.0",
@ -190,6 +191,7 @@
"@types/nightwatch": "^1.1.6", "@types/nightwatch": "^1.1.6",
"@types/node": "~8.9.4", "@types/node": "~8.9.4",
"@types/react": "16.9.17", "@types/react": "16.9.17",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "16.9.4", "@types/react-dom": "16.9.4",
"@types/react-router-dom": "5.1.3", "@types/react-router-dom": "5.1.3",
"@types/ws": "^7.2.4", "@types/ws": "^7.2.4",

@ -33,7 +33,8 @@
"@remix-ui/clipboard": ["libs/remix-ui/clipboard/src/index.ts"], "@remix-ui/clipboard": ["libs/remix-ui/clipboard/src/index.ts"],
"@remix-project/remix-solidity-ts": ["libs/remix-solidity/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/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"] "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": { "cli": {

Loading…
Cancel
Save