Merge pull request #1016 from ethereum/file-exlorer

Fix file explorer update events
pull/3094/head
yann300 7 years ago committed by GitHub
commit 6486120b2d
  1. 2
      .travis.yml
  2. 9
      ci/browser_tests.sh
  3. 1
      contracts/contract1.sol
  4. 1
      contracts/contract2.sol
  5. 1
      contracts/folder1/contract1.sol
  6. 1
      contracts/folder1/contract2.sol
  7. 1
      contracts/folder1/contract_chrome.sol
  8. 1
      contracts/folder1/contract_firefox.sol
  9. 3
      package.json
  10. 6
      src/app.js
  11. 63
      src/app/files/basicReadOnlyExplorer.js
  12. 36
      src/app/files/browser-files.js
  13. 367
      src/app/files/file-explorer.js
  14. 53
      src/app/files/fileManager.js
  15. 15
      src/app/files/shared-folder.js
  16. 36
      src/app/panels/file-panel.js
  17. 2
      src/app/panels/styles/file-panel-styles.js

@ -2,7 +2,7 @@ language: node_js
node_js: node_js:
- "7" - "7"
script: 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 - ./ci/browser_tests.sh
deploy: deploy:
- provider: script - provider: script

@ -4,14 +4,7 @@ set -e
setupRemixd () { setupRemixd () {
mkdir remixdSharedfolder mkdir remixdSharedfolder
cd remixdSharedfolder cd contracts
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 ..
echo 'sharing folder: ' echo 'sharing folder: '
echo $PWD echo $PWD
node ../node_modules/remixd/src/main.js -s $PWD & node ../node_modules/remixd/src/main.js -s $PWD &

@ -0,0 +1 @@
contract test1 { function get () returns (uint) { return 8; }}

@ -0,0 +1 @@
contract test2 { function get () returns (uint) { return 9; }}

@ -0,0 +1 @@
contract test1 { function get () returns (uint) { return 10; }}

@ -0,0 +1 @@
contract test2 { function get () returns (uint) { return 11; }}

@ -0,0 +1 @@
contract test2 { function get () returns (uint) { return 11; }}

@ -0,0 +1 @@
contract test2 { function get () returns (uint) { return 11; }}

@ -139,7 +139,8 @@
] ]
}, },
"scripts": { "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 ..", "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 ..", "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 ..", "linkremixsolidity": "cd node_modules && rm -rf remix-solidity && ln -s ../remix/remix-solidity remix-solidity && cd ..",

@ -497,10 +497,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
switchFile: function (path) { switchFile: function (path) {
fileManager.switchFile(path) fileManager.switchFile(path)
}, },
event: this.event, event: fileManager.event,
currentFile: function () { config: config,
return config.get('currentFile')
},
currentContent: function () { currentContent: function () {
return editor.get(config.get('currentFile')) return editor.get(config.get('currentFile'))
}, },

@ -5,7 +5,9 @@ class BasicReadOnlyExplorer {
constructor (type) { constructor (type) {
this.event = new EventManager() this.event = new EventManager()
this.files = {} this.files = {}
this.paths = {}
this.normalizedNames = {} // contains the raw url associated with the displayed path this.normalizedNames = {} // contains the raw url associated with the displayed path
this.paths[type] = {}
this.type = type this.type = type
this.readonly = true this.readonly = true
} }
@ -36,19 +38,32 @@ class BasicReadOnlyExplorer {
} }
set (path, content, cb) { set (path, content, cb) {
this.addReadOnly(path, content) var unprefixedPath = this.removePrefix(path)
this.addReadOnly(unprefixedPath, content)
if (cb) cb() if (cb) cb()
return true return true
} }
addReadOnly (path, content, rawPath) { addReadOnly (path, content, rawPath) {
var unprefixedPath = this.removePrefix(path)
try { // lazy try to format JSON try { // lazy try to format JSON
content = JSON.stringify(JSON.parse(content), null, '\t') content = JSON.stringify(JSON.parse(content), null, '\t')
} catch (e) {} } 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.normalizedNames[rawPath] = path
this.event.trigger('fileAdded', [this.type + '/' + unprefixedPath, true]) this.event.trigger('fileAdded', [path, true])
return true return true
} }
@ -68,46 +83,12 @@ class BasicReadOnlyExplorer {
return this.files return this.files
} }
// resolveDirectory (path, callback) {
// 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': '<executable>', '/mode': 0755 }
// }
// }
//
resolveDirectory (path, callback /* (error, filesList) => { } */) {
var self = this var self = this
if (path[0] === '/') path = path.substring(1) if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } }) if (!path) return callback(null, { [self.type]: { } })
var tree = {} // we just return the json tree populated by `addReadOnly`
// This does not include '.remix.config', because it is filtered callback(null, this.paths[path])
// 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
}
} }
removePrefix (path) { removePrefix (path) {

@ -103,55 +103,29 @@ function Files (storage) {
return false 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': '<executable>', '/mode': 0755 }
// }
// }
//
this.resolveDirectory = function (path, callback) { this.resolveDirectory = function (path, callback) {
var self = this var self = this
if (path[0] === '/') path = path.substring(1) if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } }) if (!path) return callback(null, { [self.type]: { } })
path = self.removePrefix(path)
var filesList = {} var filesList = {}
var tree = {} var tree = {}
// add r/w filesList to the list // add r/w filesList to the list
storage.keys().forEach((path) => { storage.keys().forEach((path) => {
// NOTE: as a temporary measure do not show the config file // NOTE: as a temporary measure do not show the config file
if (path !== '.remix.config') { if (path !== '.remix.config') {
filesList[self.type + '/' + path] = false filesList[path] = false
} }
}) })
// add r/o files to the list // add r/o files to the list
Object.keys(readonly).forEach((path) => { Object.keys(readonly).forEach((path) => {
filesList[self.type + '/' + path] = true filesList[path] = true
}) })
Object.keys(filesList).forEach(function (path) { Object.keys(filesList).forEach(function (path) {
hashmapize(tree, path, { tree[path] = { isDirectory: false }
'/readonly': self.isReadOnly(path),
'/content': self.get(path)
})
}) })
return callback(null, tree[path] || {}) return callback(null, tree)
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
}
} }
this.removePrefix = function (path) { this.removePrefix = function (path) {

@ -1,4 +1,3 @@
/* global FileReader */
var yo = require('yo-yo') var yo = require('yo-yo')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var Treeview = require('remix-debugger').ui.TreeView var Treeview = require('remix-debugger').ui.TreeView
@ -55,14 +54,21 @@ module.exports = fileExplorer
function fileExplorer (appAPI, files) { function fileExplorer (appAPI, files) {
var self = this var self = this
this.events = new EventManager()
// file provider backend
this.files = files 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 () { function remixdDialog () {
return yo`<div>This file has been changed outside of Remix IDE.</div>` return yo`<div>This file has been changed outside of Remix IDE.</div>`
} }
this.files.event.register('fileExternallyChanged', (path, content) => { this.files.event.register('fileExternallyChanged', (path, file) => {
if (appAPI.currentFile() === path && appAPI.currentContent() !== content) { if (appAPI.config.get('currentFile') === path && appAPI.currentContent() !== file.content) {
modalDialog(path + ' changed', remixdDialog(), modalDialog(path + ' changed', remixdDialog(),
{ {
label: 'Keep the content displayed in Remix', label: 'Keep the content displayed in Remix',
@ -71,15 +77,58 @@ function fileExplorer (appAPI, files) {
{ {
label: 'Replace by the new content', label: 'Replace by the new content',
fn: () => { 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({ self.treeView = new Treeview({
extractData: function extractData (value, tree, key) { extractData: function extractData (value, tree, key) {
var newValue = {} var newValue = {}
@ -93,20 +142,18 @@ function fileExplorer (appAPI, files) {
return { return {
path: (tree || {}).path ? tree.path + '/' + key : key, path: (tree || {}).path ? tree.path + '/' + key : key,
children: isFile ? undefined children: isFile ? undefined
: value instanceof Array ? value.map((item, index) => ({ : value instanceof Array ? value.map((item, index) => ({
key: index, value: item key: index, value: item
})) : value instanceof Object ? Object.keys(value).map(subkey => ({ })) : value instanceof Object ? Object.keys(value).map(subkey => ({
key: subkey, value: value[subkey] key: subkey, value: value[subkey]
})) : undefined })) : undefined
} }
}, },
formatSelf: function formatSelf (key, data, li) { formatSelf: function formatSelf (key, data, li) {
var isRoot = data.path.indexOf('/') === -1 var isRoot = data.path === self.files.type
return yo`<label class="${data.children ? css.folder : css.file}" return yo`<label class="${data.children ? css.folder : css.file}"
data-path="${data.path}" data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}" style="${isRoot ? 'font-weight:bold;' : ''}"
onload=${function (el) { adaptEnvironment(el, focus, hover, li) }}
onunload=${function (el) { unadaptEnvironment(el, focus, hover, li) }}
onclick=${editModeOn} onclick=${editModeOn}
onkeydown=${editModeOff} onkeydown=${editModeOff}
onblur=${editModeOff} onblur=${editModeOff}
@ -114,18 +161,30 @@ function fileExplorer (appAPI, files) {
} }
}) })
self.treeView.event.register('leafClick', function (key, data, label) {
if (self.focusElement) {
self.focusElement.classList.remove(css.hasFocus)
self.focusElement = null
self.focusPath = null
}
self.focusElement = self.treeView.labelAt(key)
if (self.focusElement) {
self.focusElement.classList.add(css.hasFocus)
self.focusPath = key
self.events.trigger('focus', [key])
appAPI.config.set('currentFile', key)
}
})
self.treeView.event.register('nodeClick', function (path, childrenContainer) { self.treeView.event.register('nodeClick', function (path, childrenContainer) {
if (!childrenContainer) return if (!childrenContainer) return
if (childrenContainer.style.display === 'none') { if (childrenContainer.style.display === 'none') return
childrenContainer.innerHTML = ''
return
}
files.resolveDirectory(path, (error, fileTree) => { files.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error) if (error) console.error(error)
if (!fileTree) return if (!fileTree) return
var newTree = normalize(path, fileTree) var newTree = normalize(path, fileTree)
var tree = self.treeView.renderProperties(newTree, false) self.treeView.updateNodeFromJSON(path, newTree, true)
childrenContainer.appendChild(tree)
}) })
}) })
@ -138,111 +197,22 @@ function fileExplorer (appAPI, files) {
return newList return newList
} }
var deleteButton = yo` // register to main app, trigger when the current file in the editor changed
<span class=${css.remove} onclick=${deletePath}>
<i class="fa fa-trash" aria-hidden="true"></i>
</span>
`
appAPI.event.register('currentFileChanged', (newFile, explorer) => { appAPI.event.register('currentFileChanged', (newFile, explorer) => {
if (explorer === files) { if (self.focusElement && explorer.type !== files.type && self.focusPath !== newFile) {
fileFocus(newFile) self.focusElement.classList.remove(css.hasFocus)
} else { self.focusElement = null
unfocus(focusElement) self.focusPath = null
} }
}) })
fileEvents.register('fileRemoved', fileRemoved)
fileEvents.register('fileRenamed', fileRenamed)
fileEvents.register('fileRenamedError', fileRenamedError)
fileEvents.register('fileAdded', fileAdded)
var filepath = null
var focusElement = null
var textUnderEdit = null var textUnderEdit = null
var textInRename = false var textInRename = false
self.events = new EventManager()
self.api = {}
self.api.addFile = function addFile (file) {
function loadFile () {
var fileReader = new FileReader()
fileReader.onload = function (event) {
if (helper.checkSpecialChars(file.name)) {
modalDialogCustom.alert('Special characters are not allowed')
return
}
var success = files.set(name, event.target.result)
if (!success) modalDialogCustom.alert('Failed to create file ' + name)
else self.events.trigger('focus', [name])
}
fileReader.readAsText(file)
}
var name = files.type + '/' + file.name
if (!files.exists(name)) {
loadFile()
} else {
modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
}
}
function focus (event) {
event.cancelBubble = true
var li = this
if (focusElement === li) return
unfocus(focusElement)
focusElement = li
focusElement.classList.toggle(css.hasFocus)
var label = getLabelFrom(li)
var filepath = label.dataset.path
var isFile = label.className.indexOf('file') === 0
if (isFile) self.events.trigger('focus', [filepath])
}
function unfocus (el) {
if (focusElement) focusElement.classList.toggle(css.hasFocus)
focusElement = null
}
function hover (event) {
var path = this.querySelector('label').dataset.path
if (path === self.files.type) return // can't delete the root node
if (event.type === 'mouseout') {
var exitedTo = event.toElement || event.relatedTarget
if (this.contains(exitedTo)) return
this.style.backgroundColor = ''
this.style.paddingRight = '19px'
return this.removeChild(deleteButton)
}
this.style.backgroundColor = styles.leftPanel.backgroundColor_FileExplorer
this.style.paddingRight = '0px'
this.appendChild(deleteButton)
}
function getElement (path) {
var label = self.element.querySelector(`label[data-path="${path}"]`)
if (label) return getLiFrom(label)
}
function deletePath (event) {
event.cancelBubble = true
var span = this
var li = span.parentElement.parentElement
var label = getLabelFrom(li)
var path = label.dataset.path
var isFolder = !!~label.className.indexOf('folder')
if (isFolder) path += '/'
modalDialogCustom.confirm(null, `Do you really want to delete "${path}" ?`, () => {
li.parentElement.removeChild(li)
removeSubtree(files, path, isFolder)
})
}
function editModeOn (event) { function editModeOn (event) {
if (self.files.readonly) return if (self.files.readonly) return
var label = this var label = this
var li = getLiFrom(label) var li = label.parentElement.parentElement.parentElement
var classes = li.className var classes = li.className
if (~classes.indexOf('hasFocus') && !label.getAttribute('contenteditable') && label.getAttribute('data-path') !== self.files.type) { if (~classes.indexOf('hasFocus') && !label.getAttribute('contenteditable') && label.getAttribute('data-path') !== self.files.type) {
textUnderEdit = label.innerText textUnderEdit = label.innerText
@ -273,185 +243,38 @@ function fileExplorer (appAPI, files) {
} }
} }
function cancelRename () {
label.innerText = textUnderEdit
}
if (event.which === 13) event.preventDefault() if (event.which === 13) event.preventDefault()
if (!textInRename && (event.type === 'blur' || event.which === 27 || event.which === 13) && label.getAttribute('contenteditable')) { if (!textInRename && (event.type === 'blur' || event.which === 27 || event.which === 13) && label.getAttribute('contenteditable')) {
textInRename = true textInRename = true
var isFolder = label.className.indexOf('folder') !== -1 var isFolder = label.className.indexOf('folder') !== -1
var save = textUnderEdit !== label.innerText var save = textUnderEdit !== label.innerText
if (save) { if (save) {
modalDialogCustom.confirm(null, 'Do you want to rename?', () => { rename() }, () => { cancelRename() }) modalDialogCustom.confirm(null, 'Do you want to rename?', () => { rename() }, () => { label.innerText = textUnderEdit })
} }
label.removeAttribute('contenteditable') label.removeAttribute('contenteditable')
label.classList.remove(css.rename) label.classList.remove(css.rename)
textInRename = false textInRename = false
} }
} }
function renameSubtree (label, dontcheck) {
var oldPath = label.dataset.path
var newPath = oldPath
newPath = newPath.split('/')
newPath[newPath.length - 1] = label.innerText
newPath = newPath.join('/')
if (!dontcheck) {
var allPaths = Object.keys(files.list())
for (var i = 0, len = allPaths.length, path, err; i < len; i++) {
path = allPaths[i]
if (files.isReadOnly(path)) {
err = 'path contains readonly elements'
break
} else if (path.indexOf(newPath) === 0) {
err = 'new path is conflicting with another existing path'
break
}
}
}
if (err) {
modalDialogCustom.alert(`could not rename - ${err}`)
label.innerText = textUnderEdit
} else {
textUnderEdit = label.innerText
updateAllLabels([getElement(oldPath)], oldPath, newPath)
}
}
function updateAllLabels (lis, oldPath, newPath) {
lis.forEach(function (li) {
var label = getLabelFrom(li)
var path = label.dataset.path
var newName = path.replace(oldPath, newPath)
label.dataset.path = newName
var ul = li.lastChild
if (ul.tagName === 'UL') {
updateAllLabels([...ul.children], oldPath, newPath)
}
})
}
function fileFocus (path) {
if (filepath === path) return
filepath = path
var el = getElement(filepath)
expandPathTo(el)
setTimeout(function focusNode () { el.click() }, 0)
}
function fileRemoved (filepath) {
// @TODO: only important if currently visible in TreeView
var li = getElement(filepath)
if (li) li.parentElement.removeChild(li)
}
function fileRenamed (oldName, newName, isFolder) {
// @TODO: only important if currently visible in TreeView
var li = getElement(oldName)
if (li) {
oldName = oldName.split('/')
newName = newName.split('/')
var index = oldName.reduce(function (idx, key, i) {
return oldName[i] !== newName[i] ? i : idx
}, undefined)
var newKey = newName[index]
var oldPath = oldName.slice(0, index + 1).join('/')
li = getElement(oldPath)
var label = getLabelFrom(li)
label.innerText = newKey
renameSubtree(label, true)
}
}
function fileRenamedError (error) {
modalDialogCustom.alert(error)
}
function fileAdded (filepath) {
// @TODO: only important if currently visible in TreeView
self.files.resolveDirectory('./', (error, files) => {
if (error) console.error(error)
var element = self.treeView.render(files)
element.className = css.fileexplorer
self.element.parentElement.replaceChild(element, self.element)
self.element = element
})
}
}
/*
HELPER FUNCTIONS
*/
function adaptEnvironment (label, focus, hover) {
var li = getLiFrom(label) // @TODO: maybe this gets refactored?
li.style.position = 'relative'
var span = li.firstChild
// add focus
li.addEventListener('click', focus)
// add hover
span.classList.add(css.activeMode)
span.addEventListener('mouseover', hover)
span.addEventListener('mouseout', hover)
}
function unadaptEnvironment (label, focus, hover) {
var li = getLiFrom(label) // @TODO: maybe this gets refactored?
var span = li.firstChild
li.style.position = undefined
// remove focus
li.removeEventListener('click', focus)
// remove hover
span.classList.remove(css.activeMode)
span.removeEventListener('mouseover', hover)
span.removeEventListener('mouseout', hover)
}
function getLiFrom (label) {
return label.parentElement.parentElement.parentElement
} }
function getLabelFrom (li) { fileExplorer.prototype.init = function () {
return li.children[0].children[1].children[0] this.container = yo`<div></div>`
} return this.container
function removeSubtree (files, path, isFolder) {
var parts = path.split('/')
var isFile = parts[parts.length - 1].length
var removePaths = isFile ? [path] : Object.keys(files.list()).filter(keep)
function keep (p) { return ~p.indexOf(path) }
removePaths.forEach(function (path) {
[...window.files.querySelectorAll('.file .name')].forEach(function (span) {
if (span.innerText === path) {
var li = span.parentElement
li.parentElement.removeChild(li) // delete tab
}
})
files.remove(path)
})
if (isFolder) files.remove(path)
}
function expandPathTo (li) {
while ((li = li.parentElement.parentElement) && li.tagName === 'LI') {
var caret = li.firstChild.firstChild
if (caret.classList.contains('fa-caret-right')) caret.click() // expand
}
} }
fileExplorer.prototype.init = function () { fileExplorer.prototype.ensureRoot = function (cb) {
var self = this var self = this
if (self.element && cb) return cb()
self.files.resolveDirectory('/', (error, files) => { self.files.resolveDirectory('/', (error, files) => {
if (error) console.error(error) if (error) console.error(error)
var element = self.treeView.render(files) var element = self.treeView.render(files, false)
element.className = css.fileexplorer element.className = css.fileexplorer
element.events = self.events element.events = self.events
element.api = self.api element.api = self.api
setTimeout(function () { self.container.appendChild(element)
self.element.parentElement.replaceChild(element, self.element) self.element = element
self.element = element if (cb) cb()
}, 0)
}) })
self.element = yo`<div></div>`
return self.element
} }

@ -47,6 +47,7 @@ class FileManager {
self.switchFile(Object.keys(self.tabbedFiles)[0]) self.switchFile(Object.keys(self.tabbedFiles)[0])
} else { } else {
opt.editor.displayEmptyReadOnlySession() opt.editor.displayEmptyReadOnlySession()
self.opt.config.set('currentFile', '')
} }
return false return false
}) })
@ -98,7 +99,6 @@ class FileManager {
// Display files that have already been selected // Display files that have already been selected
refreshTabs (newfile) { refreshTabs (newfile) {
var self = this
if (newfile) { if (newfile) {
this.tabbedFiles[newfile] = newfile this.tabbedFiles[newfile] = newfile
} }
@ -109,29 +109,32 @@ class FileManager {
for (var file in this.tabbedFiles) { for (var file in this.tabbedFiles) {
$filesEl.append(yo`<li class="file"><span class="name">${file}</span><span class="remove"><i class="fa fa-close"></i></span></li>`) $filesEl.append(yo`<li class="file"><span class="name">${file}</span><span class="remove"><i class="fa fa-close"></i></span></li>`)
} }
var currentFileOpen = !!this.opt.config.get('currentFile')
if (currentFileOpen) { var active = $('#files .file').filter(function () {
var active = $('#files .file').filter(function () { return $(this).find('.name').text() === self.opt.config.get('currentFile') }) return $(this).find('.name').text() === newfile
active.addClass('active') })
} if (active.length) active.addClass('active')
$('#input').toggle(currentFileOpen) else this.switchFile()
$('#output').toggle(currentFileOpen) // $('#input').toggle(active)
$('#output').toggle(active)
} }
switchFile (file) { switchFile (file) {
var self = this var self = this
if (!file) { if (file) return _switchFile(file)
self.opt.filesProviders['browser'].resolveDirectory('/', (error, filesTree) => { else {
var browserProvider = self.opt.filesProviders['browser']
browserProvider.resolveDirectory('browser', (error, filesTree) => {
if (error) console.error(error) if (error) console.error(error)
var fileList = Object.keys(flatten(filesTree)) var fileList = Object.keys(filesTree)
if (fileList.length) { if (fileList.length) {
file = fileList[0] _switchFile(browserProvider.type + '/' + fileList[0])
if (file) _switchFile(file) } else {
self.event.trigger('currentFileChanged', [])
} }
}) })
} else _switchFile(file) }
function _switchFile () { function _switchFile (file) {
self.saveCurrentFile() self.saveCurrentFile()
self.opt.config.set('currentFile', file) self.opt.config.set('currentFile', file)
self.refreshTabs(file) self.refreshTabs(file)
@ -179,23 +182,3 @@ class FileManager {
} }
module.exports = FileManager module.exports = FileManager
function flatten (tree) {
var flat = {}
var names = Object.keys(tree || {})
if (!names.length) return
else {
names.forEach(name => {
if ('/content' in tree[name]) flat[name] = false
else {
var subflat = flatten(tree[name])
if (!subflat) {
// empty folder
} else {
Object.keys(subflat).forEach(path => { flat[name + '/' + path] = false })
}
}
})
return flat
}
}

@ -137,19 +137,6 @@ module.exports = class SharedFolder {
return true return true
} }
//
// 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': '<executable>', '/mode': 0755 }
// }
// }
//
removePrefix (path) { removePrefix (path) {
path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path path = path.indexOf(this.type) === 0 ? path.replace(this.type, '') : path
if (path[0] === '/') return path.substring(1) if (path[0] === '/') return path.substring(1)
@ -160,7 +147,7 @@ module.exports = class SharedFolder {
var self = this var self = this
if (path[0] === '/') path = path.substring(1) if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } }) if (!path) return callback(null, { [self.type]: { } })
path = self.removePrefix('' + (path || '')) path = self.removePrefix(path)
self.remixd.dir(path, callback) self.remixd.dir(path, callback)
} }
} }

@ -1,3 +1,4 @@
/* global FileReader */
var async = require('async') var async = require('async')
var $ = require('jquery') var $ = require('jquery')
var yo = require('yo-yo') var yo = require('yo-yo')
@ -73,7 +74,7 @@ function filepanel (appAPI, filesProvider) {
</div> </div>
<div class=${css.treeviews}> <div class=${css.treeviews}>
<div class=${css.treeview}>${fileExplorer.init()}</div> <div class=${css.treeview}>${fileExplorer.init()}</div>
<div class="filesystemexplorer ${css.treeview}"></div> <div class="filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
<div class="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div> <div class="swarmexplorer ${css.treeview}">${swarmExplorer.init()}</div>
<div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div> <div class="githubexplorer ${css.treeview}">${githubExplorer.init()}</div>
<div class="gistexplorer ${css.treeview}">${gistExplorer.init()}</div> <div class="gistexplorer ${css.treeview}">${gistExplorer.init()}</div>
@ -87,7 +88,7 @@ function filepanel (appAPI, filesProvider) {
var event = new EventManager() var event = new EventManager()
self.event = event self.event = event
var element = template() var element = template()
fileExplorer.ensureRoot()
var containerFileSystem = element.querySelector('.filesystemexplorer') var containerFileSystem = element.querySelector('.filesystemexplorer')
var websocketconn = element.querySelector('.websocketconn') var websocketconn = element.querySelector('.websocketconn')
filesProvider['localhost'].remixd.event.register('connecting', (event) => { filesProvider['localhost'].remixd.event.register('connecting', (event) => {
@ -143,7 +144,30 @@ function filepanel (appAPI, filesProvider) {
// the files module. Please ask the user here if they want to overwrite // the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will // a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module. // pick that up via the 'fileAdded' event from the files module.
;[...this.files].forEach(fileExplorer.api.addFile)
;[...this.files].forEach((file) => {
var files = fileExplorer.files
function loadFile () {
var fileReader = new FileReader()
fileReader.onload = function (event) {
if (helper.checkSpecialChars(file.name)) {
modalDialogCustom.alert('Special characters are not allowed')
return
}
var success = files.set(name, event.target.result)
if (!success) modalDialogCustom.alert('Failed to create file ' + name)
else self.events.trigger('focus', [name])
}
fileReader.readAsText(file)
}
var name = files.type + '/' + file.name
if (!files.exists(name)) {
loadFile()
} else {
modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
}
})
} }
// ----------------- resizeable ui --------------- // ----------------- resizeable ui ---------------
@ -200,7 +224,6 @@ function filepanel (appAPI, filesProvider) {
* @param {String} txHash - hash of the transaction * @param {String} txHash - hash of the transaction
*/ */
function connectToLocalhost () { function connectToLocalhost () {
var container = document.querySelector('.filesystemexplorer')
if (filesProvider['localhost'].isConnected()) { if (filesProvider['localhost'].isConnected()) {
filesProvider['localhost'].close((error) => { filesProvider['localhost'].close((error) => {
if (error) console.log(error) if (error) console.log(error)
@ -213,10 +236,7 @@ function filepanel (appAPI, filesProvider) {
if (error) { if (error) {
console.log(error) console.log(error)
} else { } else {
if (fileSystemExplorer.element && container.children.length > 0) { fileSystemExplorer.ensureRoot()
container.removeChild(fileSystemExplorer.element)
}
container.appendChild(fileSystemExplorer.init())
} }
}) })
}}) }})

@ -77,7 +77,7 @@ var css = csjs`
} }
.dragbar { .dragbar {
position : absolute; position : absolute;
top : 37px; top : 29px;
width : 0.5em; width : 0.5em;
right : 0; right : 0;
bottom : 0; bottom : 0;

Loading…
Cancel
Save