diff --git a/.travis.yml b/.travis.yml index dc03c584f3..142210d258 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js node_js: - "7" script: - - npm run lint && npm run test && npm run downloadsolc && npm run make-mock-compiler && npm run build + - npm run lint && npm run test && npm run downloadsolc && npm run make-mock-compiler && npm run setupremix && npm run build - ./ci/browser_tests.sh deploy: - provider: script diff --git a/ci/browser_tests.sh b/ci/browser_tests.sh index 5b4c27ce40..93da0c55cc 100755 --- a/ci/browser_tests.sh +++ b/ci/browser_tests.sh @@ -4,14 +4,7 @@ set -e setupRemixd () { mkdir remixdSharedfolder - cd remixdSharedfolder - echo "contract test1 { function get () returns (uint) { return 8; }}" > contract1.sol - echo "contract test2 { function get () returns (uint) { return 9; }}" > contract2.sol - mkdir folder1 - cd folder1 - echo "contract test1 { function get () returns (uint) { return 10; }}" > contract1.sol - echo "contract test2 { function get () returns (uint) { return 11; }}" > contract2.sol - cd .. + cd contracts echo 'sharing folder: ' echo $PWD node ../node_modules/remixd/src/main.js -s $PWD & diff --git a/contracts/contract1.sol b/contracts/contract1.sol new file mode 100644 index 0000000000..4abefc018b --- /dev/null +++ b/contracts/contract1.sol @@ -0,0 +1 @@ +contract test1 { function get () returns (uint) { return 8; }} \ No newline at end of file diff --git a/contracts/contract2.sol b/contracts/contract2.sol new file mode 100644 index 0000000000..96b59660ba --- /dev/null +++ b/contracts/contract2.sol @@ -0,0 +1 @@ +contract test2 { function get () returns (uint) { return 9; }} \ No newline at end of file diff --git a/contracts/folder1/contract1.sol b/contracts/folder1/contract1.sol new file mode 100644 index 0000000000..11122d3008 --- /dev/null +++ b/contracts/folder1/contract1.sol @@ -0,0 +1 @@ +contract test1 { function get () returns (uint) { return 10; }} \ No newline at end of file diff --git a/contracts/folder1/contract2.sol b/contracts/folder1/contract2.sol new file mode 100644 index 0000000000..04f9b2eb26 --- /dev/null +++ b/contracts/folder1/contract2.sol @@ -0,0 +1 @@ +contract test2 { function get () returns (uint) { return 11; }} \ No newline at end of file diff --git a/contracts/folder1/contract_chrome.sol b/contracts/folder1/contract_chrome.sol new file mode 100644 index 0000000000..04f9b2eb26 --- /dev/null +++ b/contracts/folder1/contract_chrome.sol @@ -0,0 +1 @@ +contract test2 { function get () returns (uint) { return 11; }} \ No newline at end of file diff --git a/contracts/folder1/contract_firefox.sol b/contracts/folder1/contract_firefox.sol new file mode 100644 index 0000000000..04f9b2eb26 --- /dev/null +++ b/contracts/folder1/contract_firefox.sol @@ -0,0 +1 @@ +contract test2 { function get () returns (uint) { return 11; }} \ No newline at end of file diff --git a/package.json b/package.json index 967adf8eec..42060a51a2 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,8 @@ ] }, "scripts": { - "pullremix": "mkdir remix; git clone https://github.com/ethereum/remix; cd ..", + "setupremix": "npm run pullremix && npm run linkremixcore && npm run linkremixlib && npm run linkremixsolidity && npm run linkremixdebugger;", + "pullremix": "git clone https://github.com/ethereum/remix", "linkremixcore": "cd node_modules && rm -rf remix-core && ln -s ../remix/remix-core remix-core && cd ..", "linkremixlib": "cd node_modules && rm -rf remix-lib && ln -s ../remix/remix-lib remix-lib && cd ..", "linkremixsolidity": "cd node_modules && rm -rf remix-solidity && ln -s ../remix/remix-solidity remix-solidity && cd ..", diff --git a/src/app.js b/src/app.js index 9db3e79058..ab8d2f04c3 100644 --- a/src/app.js +++ b/src/app.js @@ -497,10 +497,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org switchFile: function (path) { fileManager.switchFile(path) }, - event: this.event, - currentFile: function () { - return config.get('currentFile') - }, + event: fileManager.event, + config: config, currentContent: function () { return editor.get(config.get('currentFile')) }, diff --git a/src/app/files/basicReadOnlyExplorer.js b/src/app/files/basicReadOnlyExplorer.js index fa3006f53a..a9178702ab 100644 --- a/src/app/files/basicReadOnlyExplorer.js +++ b/src/app/files/basicReadOnlyExplorer.js @@ -5,7 +5,9 @@ class BasicReadOnlyExplorer { constructor (type) { this.event = new EventManager() this.files = {} + this.paths = {} this.normalizedNames = {} // contains the raw url associated with the displayed path + this.paths[type] = {} this.type = type this.readonly = true } @@ -36,19 +38,32 @@ class BasicReadOnlyExplorer { } set (path, content, cb) { - this.addReadOnly(path, content) + var unprefixedPath = this.removePrefix(path) + this.addReadOnly(unprefixedPath, content) if (cb) cb() return true } addReadOnly (path, content, rawPath) { - var unprefixedPath = this.removePrefix(path) try { // lazy try to format JSON content = JSON.stringify(JSON.parse(content), null, '\t') } catch (e) {} - this.files[this.type + '/' + unprefixedPath] = content + // splitting off the path in a tree structure, the json tree is used in `resolveDirectory` + var split = path + var folder = false + while (split.lastIndexOf('/') !== -1) { + var subitem = split.substring(split.lastIndexOf('/')) + split = split.substring(0, split.lastIndexOf('/')) + if (!this.paths[this.type + '/' + split]) { + this.paths[this.type + '/' + split] = {} + } + this.paths[this.type + '/' + split][split + subitem] = { isDirectory: folder } + folder = true + } + this.paths[this.type][split] = { isDirectory: folder } + this.files[path] = content this.normalizedNames[rawPath] = path - this.event.trigger('fileAdded', [this.type + '/' + unprefixedPath, true]) + this.event.trigger('fileAdded', [path, true]) return true } @@ -68,46 +83,12 @@ class BasicReadOnlyExplorer { return this.files } - // - // Tree model for files - // { - // 'a': { }, // empty directory 'a' - // 'b': { - // 'c': {}, // empty directory 'b/c' - // 'd': { '/readonly': true, '/content': 'Hello World' } // files 'b/c/d' - // 'e': { '/readonly': false, '/path': 'b/c/d' } // symlink to 'b/c/d' - // 'f': { '/readonly': false, '/content': '', '/mode': 0755 } - // } - // } - // - resolveDirectory (path, callback /* (error, filesList) => { } */) { + resolveDirectory (path, callback) { var self = this if (path[0] === '/') path = path.substring(1) if (!path) return callback(null, { [self.type]: { } }) - var tree = {} - // This does not include '.remix.config', because it is filtered - // inside list(). - Object.keys(this.list()).forEach(function (path) { - hashmapize(tree, path, { - '/readonly': self.isReadOnly(path), - '/content': self.get(path) - }) - }) - return callback(null, tree[path] || {}) - function hashmapize (obj, path, val) { - var nodes = path.split('/') - var i = 0 - - for (; i < nodes.length - 1; i++) { - var node = nodes[i] - if (obj[node] === undefined) { - obj[node] = {} - } - obj = obj[node] - } - - obj[nodes[i]] = val - } + // we just return the json tree populated by `addReadOnly` + callback(null, this.paths[path]) } removePrefix (path) { diff --git a/src/app/files/browser-files.js b/src/app/files/browser-files.js index ac0504e4aa..ce645a083c 100644 --- a/src/app/files/browser-files.js +++ b/src/app/files/browser-files.js @@ -103,55 +103,29 @@ function Files (storage) { return false } - // - // Tree model for files - // { - // 'a': { }, // empty directory 'a' - // 'b': { - // 'c': {}, // empty directory 'b/c' - // 'd': { '/readonly': true, '/content': 'Hello World' } // files 'b/c/d' - // 'e': { '/readonly': false, '/path': 'b/c/d' } // symlink to 'b/c/d' - // 'f': { '/readonly': false, '/content': '', '/mode': 0755 } - // } - // } - // this.resolveDirectory = function (path, callback) { var self = this if (path[0] === '/') path = path.substring(1) if (!path) return callback(null, { [self.type]: { } }) + path = self.removePrefix(path) var filesList = {} var tree = {} // add r/w filesList to the list storage.keys().forEach((path) => { // NOTE: as a temporary measure do not show the config file if (path !== '.remix.config') { - filesList[self.type + '/' + path] = false + filesList[path] = false } }) // add r/o files to the list Object.keys(readonly).forEach((path) => { - filesList[self.type + '/' + path] = true + filesList[path] = true }) Object.keys(filesList).forEach(function (path) { - hashmapize(tree, path, { - '/readonly': self.isReadOnly(path), - '/content': self.get(path) - }) + tree[path] = { isDirectory: false } }) - return callback(null, tree[path] || {}) - function hashmapize (obj, path, val) { - var nodes = path.split('/') - var i = 0 - for (; i < nodes.length - 1; i++) { - var node = nodes[i] - if (obj[node] === undefined) { - obj[node] = {} - } - obj = obj[node] - } - obj[nodes[i]] = val - } + return callback(null, tree) } this.removePrefix = function (path) { diff --git a/src/app/files/file-explorer.js b/src/app/files/file-explorer.js index 15baaf437d..5c6bb27d07 100755 --- a/src/app/files/file-explorer.js +++ b/src/app/files/file-explorer.js @@ -1,4 +1,3 @@ -/* global FileReader */ var yo = require('yo-yo') var csjs = require('csjs-inject') var Treeview = require('remix-debugger').ui.TreeView @@ -55,14 +54,21 @@ module.exports = fileExplorer function fileExplorer (appAPI, files) { var self = this + this.events = new EventManager() + // file provider backend this.files = files + // element currently focused on + this.focusElement = null + // path currently focused on + this.focusPath = null + // warn if file changed outside of Remix function remixdDialog () { return yo`
This file has been changed outside of Remix IDE.
` } - this.files.event.register('fileExternallyChanged', (path, content) => { - if (appAPI.currentFile() === path && appAPI.currentContent() !== content) { + this.files.event.register('fileExternallyChanged', (path, file) => { + if (appAPI.config.get('currentFile') === path && appAPI.currentContent() !== file.content) { modalDialog(path + ' changed', remixdDialog(), { label: 'Keep the content displayed in Remix', @@ -71,15 +77,58 @@ function fileExplorer (appAPI, files) { { label: 'Replace by the new content', fn: () => { - appAPI.setText(content) + appAPI.setText(file.content) } } ) } }) - var fileEvents = files.event + // register to event of the file provider + files.event.register('fileRemoved', fileRemoved) + files.event.register('fileRenamed', fileRenamed) + files.event.register('fileRenamedError', fileRenamedError) + files.event.register('fileAdded', fileAdded) + function fileRenamedError (error) { + modalDialogCustom.alert(error) + } + + function fileAdded (filepath) { + self.ensureRoot(() => { + var folderpath = filepath.split('/').slice(0, -1).join('/') + + var currentTree = self.treeView.nodeAt(folderpath) + if (currentTree && self.treeView.isExpanded(folderpath)) { + self.files.resolveDirectory(folderpath, (error, fileTree) => { + if (error) console.error(error) + if (!fileTree) return + fileTree = normalize(folderpath, fileTree) + self.treeView.updateNodeFromJSON(folderpath, fileTree, true) + self.focusElement = self.treeView.labelAt(self.focusPath) + // TODO: here we update the selected file (it applicable) + // cause we are refreshing the interface of the whole directory when there's a new file. + if (self.focusElement && !self.focusElement.classList.contains(css.hasFocus)) { + self.focusElement.classList.add(css.hasFocus) + } + }) + } + }) + } + + function fileRemoved (filepath) { + var label = this.treeView.labelAt(filepath) + if (label && label.parentElement) { + label.parentElement.removeChild(label) + } + } + + function fileRenamed (oldName, newName, isFolder) { + fileRemoved(oldName) + fileAdded(newName) + } + + // make interface and register to nodeClick, leafClick self.treeView = new Treeview({ extractData: function extractData (value, tree, key) { var newValue = {} @@ -93,20 +142,18 @@ function fileExplorer (appAPI, files) { return { path: (tree || {}).path ? tree.path + '/' + key : key, children: isFile ? undefined - : value instanceof Array ? value.map((item, index) => ({ - key: index, value: item - })) : value instanceof Object ? Object.keys(value).map(subkey => ({ - key: subkey, value: value[subkey] - })) : undefined + : value instanceof Array ? value.map((item, index) => ({ + key: index, value: item + })) : value instanceof Object ? Object.keys(value).map(subkey => ({ + key: subkey, value: value[subkey] + })) : undefined } }, formatSelf: function formatSelf (key, data, li) { - var isRoot = data.path.indexOf('/') === -1 + var isRoot = data.path === self.files.type return yo`