Merge pull request #989 from ethereum/file-exlorer

add support for large file hierarchies to file explorer
pull/1/head
yann300 7 years ago committed by GitHub
commit afee874f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/app.js
  2. 27
      src/app/files/basicReadOnlyExplorer.js
  3. 34
      src/app/files/browser-files.js
  4. 48
      src/app/files/file-explorer.js
  5. 2
      src/app/files/fileManager.js
  6. 67
      src/app/files/shared-folder.js

@ -454,7 +454,7 @@ function run () {
// insert ballot contract if there are no files available // insert ballot contract if there are no files available
if (!loadingFromGist) { if (!loadingFromGist) {
filesProviders['browser'].resolveDirectory('', (error, filesList) => { filesProviders['browser'].resolveDirectory('browser', (error, filesList) => {
if (error) console.error(error) if (error) console.error(error)
if (Object.keys(filesList).length === 0) { if (Object.keys(filesList).length === 0) {
if (!filesProviders['browser'].set(examples.ballot.name, examples.ballot.content)) { if (!filesProviders['browser'].set(examples.ballot.name, examples.ballot.content)) {

@ -81,7 +81,19 @@ class BasicReadOnlyExplorer {
// } // }
// //
resolveDirectory (path, callback /* (error, filesList) => { } */) { resolveDirectory (path, callback /* (error, filesList) => { } */) {
// path = '' + (path || '') 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) { function hashmapize (obj, path, val) {
var nodes = path.split('/') var nodes = path.split('/')
var i = 0 var i = 0
@ -96,19 +108,6 @@ class BasicReadOnlyExplorer {
obj[nodes[i]] = val obj[nodes[i]] = val
} }
var tree = {}
var self = this
// 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)
})
})
callback(null, tree)
} }
removePrefix (path) { removePrefix (path) {

@ -117,20 +117,10 @@ function Files (storage) {
// //
this.resolveDirectory = function (path, callback) { this.resolveDirectory = function (path, callback) {
var self = this var self = this
// path = '' + (path || '') if (path[0] === '/') path = path.substring(1)
function hashmapize (obj, path, val) { if (!path) return callback(null, { [self.type]: { } })
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
}
var filesList = {} var filesList = {}
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
@ -142,16 +132,26 @@ function Files (storage) {
Object.keys(readonly).forEach((path) => { Object.keys(readonly).forEach((path) => {
filesList[self.type + '/' + path] = true filesList[self.type + '/' + path] = true
}) })
var tree = {}
// This does not include '.remix.config', because it is filtered
// inside list().
Object.keys(filesList).forEach(function (path) { Object.keys(filesList).forEach(function (path) {
hashmapize(tree, path, { hashmapize(tree, path, {
'/readonly': self.isReadOnly(path), '/readonly': self.isReadOnly(path),
'/content': self.get(path) '/content': self.get(path)
}) })
}) })
callback(null, tree) 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
}
} }
this.removePrefix = function (path) { this.removePrefix = function (path) {

@ -79,8 +79,9 @@ function fileExplorer (appAPI, files) {
}) })
var fileEvents = files.event var fileEvents = files.event
var treeView = new Treeview({
extractData: function (value, tree, key) { self.treeView = new Treeview({
extractData: function extractData (value, tree, key) {
var newValue = {} var newValue = {}
// var isReadOnly = false // var isReadOnly = false
var isFile = false var isFile = false
@ -99,7 +100,7 @@ function fileExplorer (appAPI, files) {
})) : undefined })) : undefined
} }
}, },
formatSelf: function (key, data, li) { formatSelf: function formatSelf (key, data, li) {
var isRoot = data.path.indexOf('/') === -1 var isRoot = data.path.indexOf('/') === -1
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}"
@ -109,11 +110,33 @@ function fileExplorer (appAPI, files) {
onclick=${editModeOn} onclick=${editModeOn}
onkeydown=${editModeOff} onkeydown=${editModeOff}
onblur=${editModeOff} onblur=${editModeOff}
>${key}</label>` >${key.split('/').pop()}</label>`
} }
}) })
this.treeView = treeView self.treeView.event.register('nodeClick', function (path, childrenContainer) {
if (!childrenContainer) return
if (childrenContainer.style.display === 'none') {
childrenContainer.innerHTML = ''
return
}
files.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error)
if (!fileTree) return
var newTree = normalize(path, fileTree)
var tree = self.treeView.renderProperties(newTree, false)
childrenContainer.appendChild(tree)
})
})
function normalize (path, filesList) {
var prefix = path.split('/')[0]
var newList = {}
Object.keys(filesList).forEach(key => {
newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true }
})
return newList
}
var deleteButton = yo` var deleteButton = yo`
<span class=${css.remove} onclick=${deletePath}> <span class=${css.remove} onclick=${deletePath}>
@ -138,10 +161,9 @@ function fileExplorer (appAPI, files) {
var textUnderEdit = null var textUnderEdit = null
var textInRename = false var textInRename = false
var events = new EventManager() self.events = new EventManager()
this.events = events self.api = {}
var api = {} self.api.addFile = function addFile (file) {
api.addFile = function addFile (file) {
function loadFile () { function loadFile () {
var fileReader = new FileReader() var fileReader = new FileReader()
fileReader.onload = function (event) { fileReader.onload = function (event) {
@ -151,7 +173,7 @@ function fileExplorer (appAPI, files) {
} }
var success = files.set(name, event.target.result) var success = files.set(name, event.target.result)
if (!success) modalDialogCustom.alert('Failed to create file ' + name) if (!success) modalDialogCustom.alert('Failed to create file ' + name)
else events.trigger('focus', [name]) else self.events.trigger('focus', [name])
} }
fileReader.readAsText(file) fileReader.readAsText(file)
} }
@ -163,7 +185,6 @@ function fileExplorer (appAPI, files) {
modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() }) modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
} }
} }
this.api = api
function focus (event) { function focus (event) {
event.cancelBubble = true event.cancelBubble = true
@ -175,7 +196,7 @@ function fileExplorer (appAPI, files) {
var label = getLabelFrom(li) var label = getLabelFrom(li)
var filepath = label.dataset.path var filepath = label.dataset.path
var isFile = label.className.indexOf('file') === 0 var isFile = label.className.indexOf('file') === 0
if (isFile) events.trigger('focus', [filepath]) if (isFile) self.events.trigger('focus', [filepath])
} }
function unfocus (el) { function unfocus (el) {
@ -320,11 +341,13 @@ function fileExplorer (appAPI, files) {
} }
function fileRemoved (filepath) { function fileRemoved (filepath) {
// @TODO: only important if currently visible in TreeView
var li = getElement(filepath) var li = getElement(filepath)
if (li) li.parentElement.removeChild(li) if (li) li.parentElement.removeChild(li)
} }
function fileRenamed (oldName, newName, isFolder) { function fileRenamed (oldName, newName, isFolder) {
// @TODO: only important if currently visible in TreeView
var li = getElement(oldName) var li = getElement(oldName)
if (li) { if (li) {
oldName = oldName.split('/') oldName = oldName.split('/')
@ -346,6 +369,7 @@ function fileExplorer (appAPI, files) {
} }
function fileAdded (filepath) { function fileAdded (filepath) {
// @TODO: only important if currently visible in TreeView
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)

@ -122,7 +122,7 @@ class FileManager {
switchFile (file) { switchFile (file) {
var self = this var self = this
if (!file) { if (!file) {
self.opt.filesProviders['browser'].resolveDirectory('', (error, filesTree) => { self.opt.filesProviders['browser'].resolveDirectory('/', (error, filesTree) => {
if (error) console.error(error) if (error) console.error(error)
var fileList = Object.keys(flatten(filesTree)) var fileList = Object.keys(flatten(filesTree))
if (fileList.length) { if (fileList.length) {

@ -2,52 +2,6 @@
var EventManager = require('remix-lib').EventManager var EventManager = require('remix-lib').EventManager
var pathtool = require('path') var pathtool = require('path')
function buildList (self, path = '', callback) {
path = '' + (path || '')
self.remixd.dir(path, (error, filesList) => {
if (error) console.error(error)
var list = Object.keys(filesList)
var counter = list.length
var fileTree = {}
if (!counter) callback(null, fileTree)
for (var i = 0, name, len = counter; i < len; i++) {
name = list[i]
self.files[path] = path
if (filesList[name].isDirectory) {
setFolder(self, path, name, fileTree, finish)
} else {
setFileContent(self, path, name, fileTree, finish)
}
}
function finish (error) {
if (error) console.error(error)
counter--
if (!counter) callback(null, fileTree)
}
})
}
function setFolder (self, path, name, fileTree, done) {
buildList(self, name, (error, subFileTree) => {
if (error) console.error(error)
name = name.replace(path, '')
if (name[0] === '/') name = name.substring(1)
fileTree[name] = subFileTree
done(null)
})
}
function setFileContent (self, path, name, fileTree, done) {
self.remixd.read(name, (error, result) => {
if (error) console.error(error)
name = name.replace(path, '')
if (name[0] === '/') name = name.substring(1)
fileTree[name] = {
'/content': result.content,
'/readonly': result.readonly
}
done(null)
})
}
module.exports = class SharedFolder { module.exports = class SharedFolder {
constructor (remixd) { constructor (remixd) {
this.event = new EventManager() this.event = new EventManager()
@ -195,18 +149,19 @@ module.exports = class SharedFolder {
// } // }
// } // }
// //
resolveDirectory (path, callback) {
var self = this
path = '' + (path || '')
path = pathtool.join('./', path)
buildList(self, path, (error, fileTree) => {
if (error) return callback(error)
callback(null, { [self.type]: fileTree })
})
}
removePrefix (path) { removePrefix (path) {
return 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)
return path
}
resolveDirectory (path, callback) {
var self = this
if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } })
path = self.removePrefix('' + (path || ''))
self.remixd.dir(path, callback)
} }
} }

Loading…
Cancel
Save