add .resolveDirectory + refactor remove .list & .listAsTree

pull/3094/head
serapath 7 years ago
parent cd3b84972c
commit 6967f501be
  1. 2
      package.json
  2. 13
      src/app.js
  3. 6
      src/app/compiler/compiler.js
  4. 5
      src/app/files/basicReadOnlyExplorer.js
  5. 132
      src/app/files/browser-files.js
  6. 38
      src/app/files/file-explorer.js
  7. 64
      src/app/files/fileManager.js
  8. 243
      src/app/files/shared-folder.js
  9. 2
      src/app/panels/file-panel.js

@ -44,7 +44,7 @@
"remix-debugger": "latest",
"remix-lib": "latest",
"remix-solidity": "latest",
"remixd": "^0.1.2",
"remixd": "git+https://github.com/ethereum/remixd.git",
"rimraf": "^2.6.1",
"selenium-standalone": "^6.0.1",
"solc": "https://github.com/ethereum/solc-js",

@ -453,10 +453,15 @@ function run () {
})
// insert ballot contract if there are no files available
if (!loadingFromGist && Object.keys(filesProviders['browser'].list()).length === 0) {
if (!filesProviders['browser'].set(examples.ballot.name, examples.ballot.content)) {
modalDialogCustom.alert('Failed to store example contract in browser. Remix will not work properly. Please ensure Remix has access to LocalStorage. Safari in Private mode is known not to work.')
}
if (!loadingFromGist) {
filesProviders['browser'].resolveDirectory('', (error, filesList) => {
if (error) console.error(error)
if (Object.keys(filesList).length === 0) {
if (!filesProviders['browser'].set(examples.ballot.name, examples.ballot.content)) {
modalDialogCustom.alert('Failed to store example contract in browser. Remix will not work properly. Please ensure Remix has access to LocalStorage. Safari in Private mode is known not to work.')
}
}
})
}
window.syncStorage = chromeCloudStorageSync

@ -49,7 +49,9 @@ function Compiler (handleImportCall) {
self.lastCompilationResult = null
self.event.trigger('compilationFinished', [false, {'error': { formattedMessage: error, severity: 'error' }}, files])
} else {
compileJSON(input, optimize ? 1 : 0)
setTimeout(function () {
compileJSON(input, optimize ? 1 : 0)
}, 0)
}
})
}
@ -233,7 +235,7 @@ function Compiler (handleImportCall) {
// Set a safe fallback until the new one is loaded
setCompileJSON(function (source, optimize) {
compilationFinished({error: 'Compiler not yet loaded.'})
compilationFinished({ error: { formattedMessage: 'Compiler not yet loaded.' } })
})
var newScript = document.createElement('script')

@ -80,7 +80,8 @@ class BasicReadOnlyExplorer {
// }
// }
//
listAsTree () {
resolveDirectory (path, callback /* (error, filesList) => { } */) {
// path = '' + (path || '')
function hashmapize (obj, path, val) {
var nodes = path.split('/')
var i = 0
@ -107,7 +108,7 @@ class BasicReadOnlyExplorer {
'/content': self.get(path)
})
})
return tree
setTimeout(_ => callback(null, tree), 0)
}
removePrefix (path) {

@ -7,23 +7,17 @@ function Files (storage) {
this.event = event
var readonly = {}
this.type = 'browser'
this.filesTree = null
this.exists = function (path) {
var unprefixedpath = this.removePrefix(path)
// NOTE: ignore the config file
if (path === '.remix.config') {
return false
}
if (path === '.remix.config') return false
return this.isReadOnly(unprefixedpath) || storage.exists(unprefixedpath)
}
this.init = function (cb) {
listAsTree(this, this.list(), (error, tree) => {
this.filesTree = tree
if (cb) cb(error)
})
this.resolveDirectory('', (error, filesTree) => cb && cb(error))
}
this.get = function (path, cb) {
@ -113,29 +107,6 @@ function Files (storage) {
return false
}
this.list = function () {
var files = {}
// add r/w files to the list
storage.keys().forEach((path) => {
// NOTE: as a temporary measure do not show the config file
if (path !== '.remix.config') {
files[this.type + '/' + path] = false
}
})
// add r/o files to the list
Object.keys(readonly).forEach((path) => {
files[this.type + '/' + path] = true
})
return files
}
this.removePrefix = function (path) {
return path.indexOf(this.type + '/') === 0 ? path.replace(this.type + '/', '') : path
}
//
// Tree model for files
// {
@ -148,23 +119,49 @@ function Files (storage) {
// }
// }
//
this.listAsTree = function (path, level) {
var nodes = path ? path.split('/') : []
var tree = this.filesTree
try {
while (nodes.length) {
var key = nodes.shift()
if (key) tree = tree[key]
this.resolveDirectory = function (path, callback) {
var self = this
// path = '' + (path || '')
setTimeout(function () {
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
}
} catch (e) {
tree = {}
}
if (level) {
var leveltree = {}
build(tree, level, leveltree)
tree = leveltree
}
return tree
var filesList = {}
// 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
}
})
// add r/o files to the list
Object.keys(readonly).forEach((path) => {
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) {
hashmapize(tree, path, {
'/readonly': self.isReadOnly(path),
'/content': self.get(path)
})
})
callback(null, tree)
}, 0)
}
this.removePrefix = function (path) {
return path.indexOf(this.type + '/') === 0 ? path.replace(this.type + '/', '') : path
}
// rename .browser-solidity.json to .remix.config
@ -176,44 +173,3 @@ function Files (storage) {
}
module.exports = Files
function build (tree, level, leveltree) {
if (!level) return
Object.keys(tree).forEach(key => {
var value = tree[key]
var more = value === Object(value)
if (more) {
leveltree[key] = {}
build(value, level - 1, leveltree[key])
} else leveltree[key] = value
})
}
function listAsTree (self, filesList, callback) {
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
}
var tree = {}
// This does not include '.remix.config', because it is filtered
// inside list().
Object.keys(filesList).forEach(function (path) {
hashmapize(tree, path, {
'/readonly': self.isReadOnly(path),
'/content': self.get(path)
})
})
callback(null, tree)
}

@ -346,17 +346,21 @@ function fileExplorer (appAPI, files) {
}
function fileAdded (filepath) {
var el = treeView.render(files.listAsTree())
el.className = css.fileexplorer
self.element.parentElement.replaceChild(el, self.element)
self.element = el
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, li) {
function adaptEnvironment (label, focus, hover) {
var li = getLiFrom(label) // @TODO: maybe this gets refactored?
li.style.position = 'relative'
var span = li.firstChild
// add focus
@ -367,7 +371,8 @@ function adaptEnvironment (label, focus, hover, li) {
span.addEventListener('mouseout', hover)
}
function unadaptEnvironment (label, focus, hover, li) {
function unadaptEnvironment (label, focus, hover) {
var li = getLiFrom(label) // @TODO: maybe this gets refactored?
var span = li.firstChild
li.style.position = undefined
// remove focus
@ -411,11 +416,18 @@ function expandPathTo (li) {
}
fileExplorer.prototype.init = function () {
var files = this.files.listAsTree()
var element = this.treeView.render(files)
element.className = css.fileexplorer
element.events = this.events
element.api = this.api
this.element = element
return element
var self = this
self.files.resolveDirectory('/', (error, files) => {
if (error) console.error(error)
var element = self.treeView.render(files)
element.className = css.fileexplorer
element.events = self.events
element.api = self.api
setTimeout(function () {
self.element.parentElement.replaceChild(element, self.element)
self.element = element
}, 0)
})
self.element = yo`<div></div>`
return self.element
}

@ -120,28 +120,34 @@ class FileManager {
}
switchFile (file) {
var self = this
if (!file) {
var fileList = Object.keys(this.opt.filesProviders['browser'].list())
if (fileList.length) {
file = fileList[0]
}
}
if (!file) return
this.saveCurrentFile()
this.opt.config.set('currentFile', file)
this.refreshTabs(file)
this.fileProviderOf(file).get(file, (error, content) => {
if (error) {
console.log(error)
} else {
if (this.fileProviderOf(file).isReadOnly(file)) {
this.opt.editor.openReadOnly(file, content)
self.opt.filesProviders['browser'].resolveDirectory('', (error, filesList) => {
if (error) console.error(error)
var fileList = Object.keys(flatten(filesList))
if (fileList.length) {
file = fileList[0]
if (file) _switchFile(file)
}
})
} else _switchFile(file)
function _switchFile () {
self.saveCurrentFile()
self.opt.config.set('currentFile', file)
self.refreshTabs(file)
self.fileProviderOf(file).get(file, (error, content) => {
if (error) {
console.log(error)
} else {
this.opt.editor.open(file, content)
if (self.fileProviderOf(file).isReadOnly(file)) {
self.opt.editor.openReadOnly(file, content)
} else {
self.opt.editor.open(file, content)
}
self.event.trigger('currentFileChanged', [file, self.fileProviderOf(file)])
}
this.event.trigger('currentFileChanged', [file, this.fileProviderOf(file)])
}
})
})
}
}
fileProviderOf (file) {
@ -173,3 +179,23 @@ class 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
}
}

@ -1,19 +1,62 @@
'use strict'
var async = require('async')
var EventManager = require('remix-lib').EventManager
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]
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)
})
}
class SharedFolder {
module.exports = class SharedFolder {
constructor (remixd) {
this.event = new EventManager()
this.remixd = remixd
this.files = null
this.filesContent = {}
this.filesTree = null
this._remixd = remixd
this.remixd = remixapi(remixd, this)
this.type = 'localhost'
this.error = {
'EEXIST': 'File already exists'
}
this.remixd.event.register('notified', (data) => {
this.error = { 'EEXIST': 'File already exists' }
this._isReady = true
remixd.event.register('notified', (data) => {
if (data.scope === 'sharedfolder') {
if (data.name === 'created') {
this.init(() => {
@ -24,7 +67,7 @@ class SharedFolder {
this.event.trigger('fileRemoved', [this.type + '/' + data.value.path])
})
} else if (data.name === 'changed') {
this.remixd.call('sharedfolder', 'get', {path: data.value}, (error, content) => {
this._remixd.call('sharedfolder', 'get', {path: data.value}, (error, content) => {
if (error) {
console.log(error)
} else {
@ -38,38 +81,43 @@ class SharedFolder {
})
}
isConnected () {
return this._isReady
}
close (cb) {
this.remixd.close()
this.files = null
this.filesTree = null
this.remixd.exit()
this._isReady = false
cb()
}
init (cb) {
this.remixd.call('sharedfolder', 'list', {}, (error, filesList) => {
if (error) {
cb(error)
} else {
this.files = {}
for (var k in filesList) {
this.files[this.type + '/' + k] = filesList[k]
}
listAsTree(this, this.files, (error, tree) => {
this.filesTree = tree
cb(error)
})
}
})
this._isReady = true
cb()
}
// @TODO: refactor all `this._remixd.call(....)` uses into `this.remixd[api](...)`
// where `api = ...`:
// this.remixd.read(path, (error, content) => {})
// this.remixd.write(path, content, (error, result) => {})
// this.remixd.rename(path1, path2, (error, result) => {})
// this.remixd.remove(path, (error, result) => {})
// this.remixd.dir(path, (error, filesList) => {})
//
// this.remixd.exists(path, (error, isValid) => {})
exists (path) {
// @TODO: add new remixd.exists() method
// we remove the this.files = null at the beggining
// modify the exists() (cause it is using the this.files) to use remixd
// yes for the exists I think you might need another remixd function
if (!this.files) return false
return this.files[path] !== undefined
}
get (path, cb) {
var unprefixedpath = this.removePrefix(path)
this.remixd.call('sharedfolder', 'get', {path: unprefixedpath}, (error, content) => {
this._remixd.call('sharedfolder', 'get', {path: unprefixedpath}, (error, content) => {
if (!error) {
this.filesContent[path] = content
cb(error, content)
@ -83,7 +131,7 @@ class SharedFolder {
set (path, content, cb) {
var unprefixedpath = this.removePrefix(path)
this.remixd.call('sharedfolder', 'set', {path: unprefixedpath, content: content}, (error, result) => {
this._remixd.call('sharedfolder', 'set', {path: unprefixedpath, content: content}, (error, result) => {
if (cb) cb(error, result)
var path = this.type + '/' + unprefixedpath
this.filesContent[path]
@ -103,7 +151,7 @@ class SharedFolder {
remove (path) {
var unprefixedpath = this.removePrefix(path)
this.remixd.call('sharedfolder', 'remove', {path: unprefixedpath}, (error, result) => {
this._remixd.call('sharedfolder', 'remove', {path: unprefixedpath}, (error, result) => {
if (error) console.log(error)
var path = this.type + '/' + unprefixedpath
delete this.filesContent[path]
@ -116,7 +164,7 @@ class SharedFolder {
rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath)
this.remixd.call('sharedfolder', 'rename', {oldPath: unprefixedoldPath, newPath: unprefixednewPath}, (error, result) => {
this._remixd.call('sharedfolder', 'rename', {oldPath: unprefixedoldPath, newPath: unprefixednewPath}, (error, result) => {
if (error) {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
@ -134,27 +182,26 @@ class SharedFolder {
return true
}
list () {
return this.files
}
listAsTree (path, level) {
var nodes = path ? path.split('/') : []
var tree = this.filesTree
try {
while (nodes.length) {
var key = nodes.shift()
if (key) tree = tree[key]
}
} catch (e) {
tree = {}
}
if (level) {
var leveltree = {}
build(tree, level, leveltree)
tree = leveltree
}
return tree
//
// 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) {
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) {
@ -162,67 +209,33 @@ class SharedFolder {
}
}
//
// 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 }
// }
// }
//
function build (tree, level, leveltree) {
if (!level) return
Object.keys(tree).forEach(key => {
var value = tree[key]
var more = value === Object(value)
if (more) {
leveltree[key] = {}
build(value, level - 1, leveltree[key])
} else leveltree[key] = value
})
}
function listAsTree (self, filesList, callback) {
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
function remixapi (remixd, self) {
const read = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'get', { path }, (error, content) => callback(error, content))
}
var tree = {}
// This does not include '.remix.config', because it is filtered
// inside list().
async.eachSeries(Object.keys(filesList), function (path, cb) {
self.get(path, (error, content) => {
if (error) {
console.log(error)
cb(error)
} else {
self.filesContent[path] = content
hashmapize(tree, path, {
'/readonly': filesList[path],
'/content': content
})
cb()
}
})
}, (error) => {
callback(error, tree)
})
const write = (path, content, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'set', { path, content }, (error, result) => callback(error, result))
}
const rename = (path, newpath, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'rename', { oldPath: path, newPath: newpath }, (error, result) => callback(error, result))
}
const remove = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'remove', { path }, (error, result) => callback(error, result))
}
const dir = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'resolveDirectory', { path }, (error, filesList) => callback(error, filesList))
}
const exit = () => { remixd.close() }
const api = { read, write, rename, remove, dir, exit, event: remixd.event }
return api
}
module.exports = SharedFolder

@ -305,7 +305,7 @@ function filepanel (appAPI, filesProvider) {
*/
function connectToLocalhost () {
var container = document.querySelector('.filesystemexplorer')
if (filesProvider['localhost'].files !== null) {
if (filesProvider['localhost'].isConnected()) {
filesProvider['localhost'].close((error) => {
if (error) console.log(error)
})

Loading…
Cancel
Save