Commit 2748c0e0 authored by serapath's avatar serapath

add .resolveDirectory + refactor remove .list & .listAsTree

parent 14764b0c
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
"remix-debugger": "latest", "remix-debugger": "latest",
"remix-lib": "latest", "remix-lib": "latest",
"remix-solidity": "latest", "remix-solidity": "latest",
"remixd": "^0.1.2", "remixd": "git+https://github.com/ethereum/remixd.git",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
"selenium-standalone": "^6.0.1", "selenium-standalone": "^6.0.1",
"solc": "https://github.com/ethereum/solc-js", "solc": "https://github.com/ethereum/solc-js",
......
...@@ -453,10 +453,15 @@ function run () { ...@@ -453,10 +453,15 @@ function run () {
}) })
// insert ballot contract if there are no files available // insert ballot contract if there are no files available
if (!loadingFromGist && Object.keys(filesProviders['browser'].list()).length === 0) { if (!loadingFromGist) {
if (!filesProviders['browser'].set(examples.ballot.name, examples.ballot.content)) { filesProviders['browser'].resolveDirectory('', (error, filesList) => {
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 (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 window.syncStorage = chromeCloudStorageSync
......
...@@ -49,7 +49,9 @@ function Compiler (handleImportCall) { ...@@ -49,7 +49,9 @@ function Compiler (handleImportCall) {
self.lastCompilationResult = null self.lastCompilationResult = null
self.event.trigger('compilationFinished', [false, {'error': { formattedMessage: error, severity: 'error' }}, files]) self.event.trigger('compilationFinished', [false, {'error': { formattedMessage: error, severity: 'error' }}, files])
} else { } else {
compileJSON(input, optimize ? 1 : 0) setTimeout(function () {
compileJSON(input, optimize ? 1 : 0)
}, 0)
} }
}) })
} }
...@@ -233,7 +235,7 @@ function Compiler (handleImportCall) { ...@@ -233,7 +235,7 @@ function Compiler (handleImportCall) {
// Set a safe fallback until the new one is loaded // Set a safe fallback until the new one is loaded
setCompileJSON(function (source, optimize) { setCompileJSON(function (source, optimize) {
compilationFinished({error: 'Compiler not yet loaded.'}) compilationFinished({ error: { formattedMessage: 'Compiler not yet loaded.' } })
}) })
var newScript = document.createElement('script') var newScript = document.createElement('script')
......
...@@ -80,7 +80,8 @@ class BasicReadOnlyExplorer { ...@@ -80,7 +80,8 @@ class BasicReadOnlyExplorer {
// } // }
// } // }
// //
listAsTree () { resolveDirectory (path, callback /* (error, filesList) => { } */) {
// path = '' + (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
...@@ -107,7 +108,7 @@ class BasicReadOnlyExplorer { ...@@ -107,7 +108,7 @@ class BasicReadOnlyExplorer {
'/content': self.get(path) '/content': self.get(path)
}) })
}) })
return tree setTimeout(_ => callback(null, tree), 0)
} }
removePrefix (path) { removePrefix (path) {
......
...@@ -7,23 +7,17 @@ function Files (storage) { ...@@ -7,23 +7,17 @@ function Files (storage) {
this.event = event this.event = event
var readonly = {} var readonly = {}
this.type = 'browser' this.type = 'browser'
this.filesTree = null
this.exists = function (path) { this.exists = function (path) {
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
// NOTE: ignore the config file // NOTE: ignore the config file
if (path === '.remix.config') { if (path === '.remix.config') return false
return false
}
return this.isReadOnly(unprefixedpath) || storage.exists(unprefixedpath) return this.isReadOnly(unprefixedpath) || storage.exists(unprefixedpath)
} }
this.init = function (cb) { this.init = function (cb) {
listAsTree(this, this.list(), (error, tree) => { this.resolveDirectory('', (error, filesTree) => cb && cb(error))
this.filesTree = tree
if (cb) cb(error)
})
} }
this.get = function (path, cb) { this.get = function (path, cb) {
...@@ -113,29 +107,6 @@ function Files (storage) { ...@@ -113,29 +107,6 @@ function Files (storage) {
return false 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 // Tree model for files
// { // {
...@@ -148,23 +119,49 @@ function Files (storage) { ...@@ -148,23 +119,49 @@ function Files (storage) {
// } // }
// } // }
// //
this.listAsTree = function (path, level) { this.resolveDirectory = function (path, callback) {
var nodes = path ? path.split('/') : [] var self = this
var tree = this.filesTree // path = '' + (path || '')
try { setTimeout(function () {
while (nodes.length) { function hashmapize (obj, path, val) {
var key = nodes.shift() var nodes = path.split('/')
if (key) tree = tree[key] 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) { var filesList = {}
tree = {} // add r/w filesList to the list
} storage.keys().forEach((path) => {
if (level) { // NOTE: as a temporary measure do not show the config file
var leveltree = {} if (path !== '.remix.config') {
build(tree, level, leveltree) filesList[self.type + '/' + path] = false
tree = leveltree }
} })
return tree // 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 // rename .browser-solidity.json to .remix.config
...@@ -176,44 +173,3 @@ function Files (storage) { ...@@ -176,44 +173,3 @@ function Files (storage) {
} }
module.exports = Files 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) { ...@@ -346,17 +346,21 @@ function fileExplorer (appAPI, files) {
} }
function fileAdded (filepath) { function fileAdded (filepath) {
var el = treeView.render(files.listAsTree()) self.files.resolveDirectory('./', (error, files) => {
el.className = css.fileexplorer if (error) console.error(error)
self.element.parentElement.replaceChild(el, self.element) var element = self.treeView.render(files)
self.element = el element.className = css.fileexplorer
self.element.parentElement.replaceChild(element, self.element)
self.element = element
})
} }
} }
/* /*
HELPER FUNCTIONS 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' li.style.position = 'relative'
var span = li.firstChild var span = li.firstChild
// add focus // add focus
...@@ -367,7 +371,8 @@ function adaptEnvironment (label, focus, hover, li) { ...@@ -367,7 +371,8 @@ function adaptEnvironment (label, focus, hover, li) {
span.addEventListener('mouseout', hover) 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 var span = li.firstChild
li.style.position = undefined li.style.position = undefined
// remove focus // remove focus
...@@ -411,11 +416,18 @@ function expandPathTo (li) { ...@@ -411,11 +416,18 @@ function expandPathTo (li) {
} }
fileExplorer.prototype.init = function () { fileExplorer.prototype.init = function () {
var files = this.files.listAsTree() var self = this
var element = this.treeView.render(files) self.files.resolveDirectory('/', (error, files) => {
element.className = css.fileexplorer if (error) console.error(error)
element.events = this.events var element = self.treeView.render(files)
element.api = this.api element.className = css.fileexplorer
this.element = element element.events = self.events
return element 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 { ...@@ -120,28 +120,34 @@ class FileManager {
} }
switchFile (file) { switchFile (file) {
var self = this
if (!file) { if (!file) {
var fileList = Object.keys(this.opt.filesProviders['browser'].list()) self.opt.filesProviders['browser'].resolveDirectory('', (error, filesList) => {
if (fileList.length) { if (error) console.error(error)
file = fileList[0] var fileList = Object.keys(flatten(filesList))
} if (fileList.length) {
} file = fileList[0]
if (!file) return if (file) _switchFile(file)
this.saveCurrentFile() }
this.opt.config.set('currentFile', file) })
this.refreshTabs(file) } else _switchFile(file)
this.fileProviderOf(file).get(file, (error, content) => { function _switchFile () {
if (error) { self.saveCurrentFile()
console.log(error) self.opt.config.set('currentFile', file)
} else { self.refreshTabs(file)
if (this.fileProviderOf(file).isReadOnly(file)) { self.fileProviderOf(file).get(file, (error, content) => {
this.opt.editor.openReadOnly(file, content) if (error) {
console.log(error)
} else { } 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) { fileProviderOf (file) {
...@@ -173,3 +179,23 @@ class FileManager { ...@@ -173,3 +179,23 @@ 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
}
}
'use strict' 'use strict'
var async = require('async')
var EventManager = require('remix-lib').EventManager 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) { constructor (remixd) {
this.event = new EventManager() this.event = new EventManager()
this.remixd = remixd this._remixd = remixd
this.files = null this.remixd = remixapi(remixd, this)
this.filesContent = {}
this.filesTree = null
this.type = 'localhost' this.type = 'localhost'
this.error = { this.error = { 'EEXIST': 'File already exists' }
'EEXIST': 'File already exists' this._isReady = true
}
this.remixd.event.register('notified', (data) => { remixd.event.register('notified', (data) => {
if (data.scope === 'sharedfolder') { if (data.scope === 'sharedfolder') {
if (data.name === 'created') { if (data.name === 'created') {
this.init(() => { this.init(() => {
...@@ -24,7 +67,7 @@ class SharedFolder { ...@@ -24,7 +67,7 @@ class SharedFolder {
this.event.trigger('fileRemoved', [this.type + '/' + data.value.path]) this.event.trigger('fileRemoved', [this.type + '/' + data.value.path])
}) })
} else if (data.name === 'changed') { } 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) { if (error) {
console.log(error) console.log(error)
} else { } else {
...@@ -38,38 +81,43 @@ class SharedFolder { ...@@ -38,38 +81,43 @@ class SharedFolder {
}) })
} }
isConnected () {
return this._isReady
}
close (cb) { close (cb) {
this.remixd.close() this.remixd.exit()
this.files = null this._isReady = false
this.filesTree = null
cb() cb()
} }
init (cb) { init (cb) {
this.remixd.call('sharedfolder', 'list', {}, (error, filesList) => { this._isReady = true
if (error) { cb()
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)
})
}
})
} }
// @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) { 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 if (!this.files) return false
return this.files[path] !== undefined return this.files[path] !== undefined
} }
get (path, cb) { get (path, cb) {
var unprefixedpath = this.removePrefix(path) 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) { if (!error) {
this.filesContent[path] = content this.filesContent[path] = content
cb(error, content) cb(error, content)
...@@ -83,7 +131,7 @@ class SharedFolder { ...@@ -83,7 +131,7 @@ class SharedFolder {
set (path, content, cb) { set (path, content, cb) {
var unprefixedpath = this.removePrefix(path) 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) if (cb) cb(error, result)
var path = this.type + '/' + unprefixedpath var path = this.type + '/' + unprefixedpath
this.filesContent[path] this.filesContent[path]
...@@ -103,7 +151,7 @@ class SharedFolder { ...@@ -103,7 +151,7 @@ class SharedFolder {
remove (path) { remove (path) {
var unprefixedpath = this.removePrefix(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) if (error) console.log(error)
var path = this.type + '/' + unprefixedpath var path = this.type + '/' + unprefixedpath
delete this.filesContent[path] delete this.filesContent[path]
...@@ -116,7 +164,7 @@ class SharedFolder { ...@@ -116,7 +164,7 @@ class SharedFolder {
rename (oldPath, newPath, isFolder) { rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath) var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath) 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) { if (error) {
console.log(error) console.log(error)
if (this.error[error.code]) error = this.error[error.code] if (this.error[error.code]) error = this.error[error.code]
...@@ -134,27 +182,26 @@ class SharedFolder { ...@@ -134,27 +182,26 @@ class SharedFolder {
return true return true
} }
list () { //
return this.files // Tree model for files
} // {
// 'a': { }, // empty directory 'a'
listAsTree (path, level) { // 'b': {
var nodes = path ? path.split('/') : [] // 'c': {}, // empty directory 'b/c'
var tree = this.filesTree // 'd': { '/readonly': true, '/content': 'Hello World' } // files 'b/c/d'
try { // 'e': { '/readonly': false, '/path': 'b/c/d' } // symlink to 'b/c/d'
while (nodes.length) { // 'f': { '/readonly': false, '/content': '<executable>', '/mode': 0755 }
var key = nodes.shift() // }
if (key) tree = tree[key] // }
} //
} catch (e) { resolveDirectory (path, callback) {
tree = {} var self = this
} path = '' + (path || '')
if (level) { path = pathtool.join('./', path)
var leveltree = {} buildList(self, path, (error, fileTree) => {
build(tree, level, leveltree) if (error) return callback(error)
tree = leveltree callback(null, { [self.type]: fileTree })
} })
return tree
} }
removePrefix (path) { removePrefix (path) {
...@@ -162,67 +209,33 @@ class SharedFolder { ...@@ -162,67 +209,33 @@ class SharedFolder {
} }
} }
// function remixapi (remixd, self) {
// Tree model for files const read = (path, callback) => {
// { path = '' + (path || '')
// 'a': { }, // empty directory 'a' path = pathtool.join('./', path)
// 'b': { remixd.call('sharedfolder', 'get', { path }, (error, content) => callback(error, content))
// '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
} }
const write = (path, content, callback) => {
var tree = {} path = '' + (path || '')
path = pathtool.join('./', path)
// This does not include '.remix.config', because it is filtered remixd.call('sharedfolder', 'set', { path, content }, (error, result) => callback(error, result))
// inside list(). }
async.eachSeries(Object.keys(filesList), function (path, cb) { const rename = (path, newpath, callback) => {
self.get(path, (error, content) => { path = '' + (path || '')
if (error) { path = pathtool.join('./', path)
console.log(error) remixd.call('sharedfolder', 'rename', { oldPath: path, newPath: newpath }, (error, result) => callback(error, result))
cb(error) }
} else { const remove = (path, callback) => {
self.filesContent[path] = content path = '' + (path || '')
hashmapize(tree, path, { path = pathtool.join('./', path)
'/readonly': filesList[path], remixd.call('sharedfolder', 'remove', { path }, (error, result) => callback(error, result))
'/content': content }
}) const dir = (path, callback) => {
cb() path = '' + (path || '')
} path = pathtool.join('./', path)
}) remixd.call('sharedfolder', 'resolveDirectory', { path }, (error, filesList) => callback(error, filesList))
}, (error) => { }
callback(error, tree) 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) { ...@@ -305,7 +305,7 @@ function filepanel (appAPI, filesProvider) {
*/ */
function connectToLocalhost () { function connectToLocalhost () {
var container = document.querySelector('.filesystemexplorer') var container = document.querySelector('.filesystemexplorer')
if (filesProvider['localhost'].files !== null) { if (filesProvider['localhost'].isConnected()) {
filesProvider['localhost'].close((error) => { filesProvider['localhost'].close((error) => {
if (error) console.log(error) if (error) console.log(error)
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment