Commit 22ea754f authored by yann300's avatar yann300

open-save workspace

parent f3380cc3
...@@ -9,7 +9,6 @@ soljson.js.* ...@@ -9,7 +9,6 @@ soljson.js.*
npm-debug.log* npm-debug.log*
remix remix
.DS_Store .DS_Store
contracts
TODO TODO
.tern-port .tern-port
temp_publish_docker temp_publish_docker
\ No newline at end of file
test README
\ No newline at end of file
...@@ -18,7 +18,7 @@ import { LandingPage } from './app/ui/landing-page/landing-page' ...@@ -18,7 +18,7 @@ import { LandingPage } from './app/ui/landing-page/landing-page'
import { MainPanel } from './app/components/main-panel' import { MainPanel } from './app/components/main-panel'
import FetchAndCompile from './app/compiler/compiler-sourceVerifier-fetchAndCompile' import FetchAndCompile from './app/compiler/compiler-sourceVerifier-fetchAndCompile'
import migrateFileSystem from './migrateFileSystem' import migrateFileSystem, { migrateToWorkspace } from './migrateFileSystem'
var isElectron = require('is-electron') var isElectron = require('is-electron')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
...@@ -28,14 +28,13 @@ var registry = require('./global/registry') ...@@ -28,14 +28,13 @@ var registry = require('./global/registry')
var loadFileFromParent = require('./loadFilesFromParent') var loadFileFromParent = require('./loadFilesFromParent')
var { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConverter') var { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConverter')
var QueryParams = require('./lib/query-params') var QueryParams = require('./lib/query-params')
var GistHandler = require('./lib/gist-handler')
var Storage = remixLib.Storage var Storage = remixLib.Storage
var RemixDProvider = require('./app/files/remixDProvider') var RemixDProvider = require('./app/files/remixDProvider')
var Config = require('./config') var Config = require('./config')
var examples = require('./app/editor/examples')
var modalDialogCustom = require('./app/ui/modal-dialog-custom') var modalDialogCustom = require('./app/ui/modal-dialog-custom')
var FileManager = require('./app/files/fileManager') var FileManager = require('./app/files/fileManager')
var FileProvider = require('./app/files/fileProvider') var FileProvider = require('./app/files/fileProvider')
var WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
var toolTip = require('./app/ui/tooltip') var toolTip = require('./app/ui/tooltip')
var CompilerMetadata = require('./app/files/compiler-metadata') var CompilerMetadata = require('./app/files/compiler-metadata')
var CompilerImport = require('./app/compiler/compiler-imports') var CompilerImport = require('./app/compiler/compiler-imports')
...@@ -145,6 +144,9 @@ class App { ...@@ -145,6 +144,9 @@ class App {
registry.put({ api: self._components.filesProviders.browser, name: 'fileproviders/browser' }) registry.put({ api: self._components.filesProviders.browser, name: 'fileproviders/browser' })
self._components.filesProviders.localhost = new RemixDProvider(self.appManager) self._components.filesProviders.localhost = new RemixDProvider(self.appManager)
registry.put({ api: self._components.filesProviders.localhost, name: 'fileproviders/localhost' }) registry.put({ api: self._components.filesProviders.localhost, name: 'fileproviders/localhost' })
self._components.filesProviders.workspace = new WorkspaceFileProvider()
registry.put({ api: self._components.filesProviders.workspace, name: 'fileproviders/workspace' })
registry.put({ api: self._components.filesProviders, name: 'fileproviders' }) registry.put({ api: self._components.filesProviders, name: 'fileproviders' })
migrateFileSystem(self._components.filesProviders.browser) migrateFileSystem(self._components.filesProviders.browser)
...@@ -438,20 +440,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org ...@@ -438,20 +440,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
// get the file list from the parent iframe // get the file list from the parent iframe
loadFileFromParent(fileManager) loadFileFromParent(fileManager)
// get the file from gist migrateToWorkspace(fileManager)
const gistHandler = new GistHandler()
const loadedFromGist = gistHandler.loadFromGist(params, fileManager) filePanel.initWorkspace()
if (!loadedFromGist) {
// insert example contracts if there are no files to show
self._components.filesProviders.browser.resolveDirectory('/', (error, filesList) => {
if (error) console.error(error)
if (Object.keys(filesList).length === 0) {
for (const file in examples) {
fileManager.writeFile(examples[file].name, examples[file].content)
}
}
})
}
if (params.code) { if (params.code) {
try { try {
......
...@@ -77,7 +77,7 @@ module.exports = class CompilerImports extends Plugin { ...@@ -77,7 +77,7 @@ module.exports = class CompilerImports extends Plugin {
} }
cb(null, content, cleanUrl, type, url) cb(null, content, cleanUrl, type, url)
} catch (e) { } catch (e) {
return cb('Unable to import url : ' + e.message) return cb(new Error('not found ' + url))
} }
} }
...@@ -88,9 +88,9 @@ module.exports = class CompilerImports extends Plugin { ...@@ -88,9 +88,9 @@ module.exports = class CompilerImports extends Plugin {
(error, content, cleanUrl, type, url) => { (error, content, cleanUrl, type, url) => {
if (error) return cb(error) if (error) return cb(error)
if (this.fileManager) { if (this.fileManager) {
const browser = this.fileManager.fileProviderOf('browser/') const workspace = this.fileManager.getProvider('workspace')
const path = targetPath || type + '/' + cleanUrl const path = targetPath || type + '/' + cleanUrl
if (browser) browser.addExternal(path, content, url) if (workspace) workspace.addExternal(path, content, url)
} }
cb(null, content) cb(null, content)
}) })
...@@ -123,7 +123,6 @@ module.exports = class CompilerImports extends Plugin { ...@@ -123,7 +123,6 @@ module.exports = class CompilerImports extends Plugin {
} }
provider.exists(url, (error, exist) => { provider.exists(url, (error, exist) => {
if (error) return reject(error) if (error) return reject(error)
if (!exist && provider.type === 'localhost') return reject(new Error(`not found ${url}`))
/* /*
if the path is absolute and the file does not exist, we can stop here if the path is absolute and the file does not exist, we can stop here
......
...@@ -355,7 +355,6 @@ class Editor extends Plugin { ...@@ -355,7 +355,6 @@ class Editor extends Plugin {
- URL prepended with "browser" - URL prepended with "browser"
- URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL - URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL
*/ */
if (!path.startsWith('localhost') && !path.startsWith('browser')) path = `browser/${path}`
if (!this.sessions[path]) { if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path)) const session = this._createSession(content, this._getMode(path))
this.sessions[path] = session this.sessions[path] = session
......
'use strict' 'use strict'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { joinPath } from '../../lib/helper'
var CompilerAbstract = require('../compiler/compiler-abstract') var CompilerAbstract = require('../compiler/compiler-abstract')
const profile = { const profile = {
...@@ -21,11 +22,11 @@ class CompilerMetadata extends Plugin { ...@@ -21,11 +22,11 @@ class CompilerMetadata extends Plugin {
} }
_JSONFileName (path, contractName) { _JSONFileName (path, contractName) {
return path + '/' + this.innerPath + '/' + contractName + '.json' return joinPath(path, this.innerPath, contractName + '.json')
} }
_MetadataFileName (path, contractName) { _MetadataFileName (path, contractName) {
return path + '/' + this.innerPath + '/' + contractName + '_metadata' + '.json' return joinPath(path, this.innerPath, contractName + '_metadata.json')
} }
onActivation () { onActivation () {
...@@ -33,9 +34,9 @@ class CompilerMetadata extends Plugin { ...@@ -33,9 +34,9 @@ class CompilerMetadata extends Plugin {
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => { this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
if (!self.config.get('settings/generate-contract-metadata')) return if (!self.config.get('settings/generate-contract-metadata')) return
const compiler = new CompilerAbstract(languageVersion, data, source) const compiler = new CompilerAbstract(languageVersion, data, source)
var provider = self.fileManager.currentFileProvider() var provider = self.fileManager.fileProviderOf(source.target)
var path = self.fileManager.currentPath() var path = self.fileManager.extractPathOf(source.target)
if (provider && path) { if (provider) {
compiler.visitContracts((contract) => { compiler.visitContracts((contract) => {
if (contract.file !== source.target) return if (contract.file !== source.target) return
...@@ -117,7 +118,7 @@ class CompilerMetadata extends Plugin { ...@@ -117,7 +118,7 @@ class CompilerMetadata extends Plugin {
path = this.fileManager.currentPath() path = this.fileManager.currentPath()
} }
if (provider && path) { if (provider) {
this.blockchain.detectNetwork((err, { id, name } = {}) => { this.blockchain.detectNetwork((err, { id, name } = {}) => {
if (err) { if (err) {
console.log(err) console.log(err)
......
...@@ -506,7 +506,7 @@ fileExplorer.prototype.toGist = function (id) { ...@@ -506,7 +506,7 @@ fileExplorer.prototype.toGist = function (id) {
} }
// If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer. // If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
const folder = id ? 'browser/gists/' + id : 'browser/' const folder = id ? '/gists/' + id : '/'
this.packageFiles(this.files, folder, (error, packaged) => { this.packageFiles(this.files, folder, (error, packaged) => {
if (error) { if (error) {
console.log(error) console.log(error)
...@@ -596,7 +596,7 @@ fileExplorer.prototype.packageFiles = function (filesProvider, directory, callba ...@@ -596,7 +596,7 @@ fileExplorer.prototype.packageFiles = function (filesProvider, directory, callba
}) })
} }
fileExplorer.prototype.createNewFile = function (parentFolder = 'browser') { fileExplorer.prototype.createNewFile = function (parentFolder = '/') {
const self = this const self = this
modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => { modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
if (!input) input = 'New file' if (!input) input = 'New file'
......
...@@ -39,6 +39,7 @@ const createError = (err) => { ...@@ -39,6 +39,7 @@ const createError = (err) => {
class FileManager extends Plugin { class FileManager extends Plugin {
constructor (editor, appManager) { constructor (editor, appManager) {
super(profile) super(profile)
this.mode = 'browser'
this.openedFiles = {} // list all opened files this.openedFiles = {} // list all opened files
this.events = new EventEmitter() this.events = new EventEmitter()
this.editor = editor this.editor = editor
...@@ -48,6 +49,10 @@ class FileManager extends Plugin { ...@@ -48,6 +49,10 @@ class FileManager extends Plugin {
this.init() this.init()
} }
setMode (mode) {
this.mode = mode
}
/** /**
* Emit error if path doesn't exist * Emit error if path doesn't exist
* @param {string} path path of the file/directory * @param {string} path path of the file/directory
...@@ -265,6 +270,7 @@ class FileManager extends Plugin { ...@@ -265,6 +270,7 @@ class FileManager extends Plugin {
config: this._components.registry.get('config').api, config: this._components.registry.get('config').api,
browserExplorer: this._components.registry.get('fileproviders/browser').api, browserExplorer: this._components.registry.get('fileproviders/browser').api,
localhostExplorer: this._components.registry.get('fileproviders/localhost').api, localhostExplorer: this._components.registry.get('fileproviders/localhost').api,
workspaceExplorer: this._components.registry.get('fileproviders/workspace').api,
filesProviders: this._components.registry.get('fileproviders').api filesProviders: this._components.registry.get('fileproviders').api
} }
this._deps.browserExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) }) this._deps.browserExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) })
...@@ -275,6 +281,11 @@ class FileManager extends Plugin { ...@@ -275,6 +281,11 @@ class FileManager extends Plugin {
this._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) }) this._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.localhostExplorer.event.register('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) }) this._deps.localhostExplorer.event.register('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
this._deps.localhostExplorer.event.register('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) }) this._deps.localhostExplorer.event.register('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
this._deps.workspaceExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) })
this._deps.workspaceExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
this._deps.workspaceExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
this._deps.workspaceExplorer.event.register('fileAdded', (path) => { this.fileAddedEvent(path) })
this.getCurrentFile = this.file this.getCurrentFile = this.file
this.getFile = this.readFile this.getFile = this.readFile
this.getFolder = this.readdir this.getFolder = this.readdir
...@@ -349,7 +360,7 @@ class FileManager extends Plugin { ...@@ -349,7 +360,7 @@ class FileManager extends Plugin {
extractPathOf (file) { extractPathOf (file) {
var reg = /(.*)(\/).*/ var reg = /(.*)(\/).*/
var path = reg.exec(file) var path = reg.exec(file)
return path ? path[1] : null return path ? path[1] : '/'
} }
getFileContent (path) { getFileContent (path) {
...@@ -468,19 +479,9 @@ class FileManager extends Plugin { ...@@ -468,19 +479,9 @@ class FileManager extends Plugin {
} }
if (file) return _openFile(file) if (file) return _openFile(file)
else { else {
var browserProvider = this._deps.filesProviders.browser
browserProvider.resolveDirectory('browser', (error, filesProvider) => {
if (error) console.error(error)
var fileList = Object.keys(filesProvider)
if (fileList.length) {
_openFile(browserProvider.type + '/' + fileList[0])
} else {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected') this.emit('noFileSelected')
this.events.emit('noFileSelected') this.events.emit('noFileSelected')
} }
})
}
} }
getProvider (name) { getProvider (name) {
...@@ -488,11 +489,14 @@ class FileManager extends Plugin { ...@@ -488,11 +489,14 @@ class FileManager extends Plugin {
} }
fileProviderOf (file) { fileProviderOf (file) {
if (file.indexOf('localhost') === 0) { if (file.startsWith('localhost') || this.mode === 'localhost') {
return this._deps.filesProviders.localhost return this._deps.filesProviders.localhost
} }
if (file.startsWith('browser')) {
return this._deps.filesProviders.browser return this._deps.filesProviders.browser
} }
return this._deps.filesProviders.workspace
}
// returns the list of directories inside path // returns the list of directories inside path
dirList (path) { dirList (path) {
...@@ -501,10 +505,8 @@ class FileManager extends Plugin { ...@@ -501,10 +505,8 @@ class FileManager extends Plugin {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.readdir(path).then((ls) => { this.readdir(path).then((ls) => {
const promises = Object.keys(ls).map((item, index) => { const promises = Object.keys(ls).map((item, index) => {
const root = (path.indexOf('/') === -1) ? path : path.substr(0, path.indexOf('/')) if (ls[item].isDirectory && !dirPaths.includes(item)) {
const curPath = `${root}/${item}` // adding 'browser' or 'localhost' dirPaths.push(item)
if (ls[item].isDirectory && !dirPaths.includes(curPath)) {
dirPaths.push(curPath)
resolve(dirPaths) resolve(dirPaths)
} }
return new Promise((resolve, reject) => { resolve() }) return new Promise((resolve, reject) => { resolve() })
...@@ -579,6 +581,14 @@ class FileManager extends Plugin { ...@@ -579,6 +581,14 @@ class FileManager extends Plugin {
if (callback) callback(error) if (callback) callback(error)
}) })
} }
async createWorkspace (name) {
const workspaceProvider = this._deps.filesProviders.workspace
const workspacePath = 'browser/' + workspaceProvider.workspacesPath + '/' + name
const workspaceRootPath = 'browser/' + workspaceProvider.workspacesPath
if (!this.exists(workspaceRootPath)) await this.mkdir(workspaceRootPath)
if (!this.exists(workspacePath)) await this.mkdir(workspacePath)
}
} }
module.exports = FileManager module.exports = FileManager
...@@ -193,6 +193,39 @@ class FileProvider { ...@@ -193,6 +193,39 @@ class FileProvider {
}) })
} }
/**
* copy the folder recursively
* @param {string} path is the folder to be copied over
* @param {string} destination is the destination folder
*/
copyFolderToJson (path) {
return new Promise((resolve, reject) => {
const json = {}
path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path)) {
try {
const items = window.remixFileSystem.readdirSync(path)
if (items.length !== 0) {
items.forEach(async (item, index) => {
const file = {}
const curPath = `${path}${path.endsWith('/') ? '' : '/'}${item}`
if (window.remixFileSystem.statSync(curPath).isDirectory()) {
file.children = await this.copyFolderToJson(curPath)
} else {
file.content = window.remixFileSystem.readFileSync(curPath, 'utf8')
}
json[curPath] = file
})
}
} catch (e) {
console.log(e)
return reject(e)
}
}
return resolve(json)
})
}
removeFile (path) { removeFile (path) {
path = this.removePrefix(path) path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path) && !window.remixFileSystem.statSync(path).isDirectory()) { if (window.remixFileSystem.existsSync(path) && !window.remixFileSystem.statSync(path).isDirectory()) {
...@@ -227,6 +260,8 @@ class FileProvider { ...@@ -227,6 +260,8 @@ class FileProvider {
if (files) { if (files) {
files.forEach(element => { files.forEach(element => {
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
element = element.replace(/^\/|\/$/g, '') // remove first and last slash
const absPath = (path === '/' ? '' : path) + '/' + element const absPath = (path === '/' ? '' : path) + '/' + element
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() } ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() }
// ^ ret does not accept path starting with '/' // ^ ret does not accept path starting with '/'
...@@ -243,7 +278,6 @@ class FileProvider { ...@@ -243,7 +278,6 @@ class FileProvider {
} }
_normalizePath (path) { _normalizePath (path) {
if (path.indexOf('/') !== 0) path = '/' + path
return this.type + path return this.type + path
} }
} }
......
...@@ -23,23 +23,23 @@ module.exports = class RemixDProvider { ...@@ -23,23 +23,23 @@ module.exports = class RemixDProvider {
}) })
this._appManager.on('remixd', 'folderAdded', (path) => { this._appManager.on('remixd', 'folderAdded', (path) => {
this.event.trigger('folderAdded', [this.addPrefix(path)]) this.event.trigger('folderAdded', [path])
}) })
this._appManager.on('remixd', 'fileAdded', (path) => { this._appManager.on('remixd', 'fileAdded', (path) => {
this.event.trigger('fileAdded', [this.addPrefix(path)]) this.event.trigger('fileAdded', [path])
}) })
this._appManager.on('remixd', 'fileChanged', (path) => { this._appManager.on('remixd', 'fileChanged', (path) => {
this.event.trigger('fileChanged', [this.addPrefix(path)]) this.event.trigger('fileChanged', [path])
}) })
this._appManager.on('remixd', 'fileRemoved', (path) => { this._appManager.on('remixd', 'fileRemoved', (path) => {
this.event.trigger('fileRemoved', [this.addPrefix(path)]) this.event.trigger('fileRemoved', [path])
}) })
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => { this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
this.event.trigger('fileRemoved', [this.addPrefix(oldPath), this.addPrefix(newPath)]) this.event.trigger('fileRemoved', [oldPath, newPath])
}) })
this._appManager.on('remixd', 'rootFolderChanged', () => { this._appManager.on('remixd', 'rootFolderChanged', () => {
...@@ -136,7 +136,7 @@ module.exports = class RemixDProvider { ...@@ -136,7 +136,7 @@ module.exports = class RemixDProvider {
const unprefixedpath = this.removePrefix(path) const unprefixedpath = this.removePrefix(path)
this._appManager.call('remixd', 'remove', { path: unprefixedpath }) this._appManager.call('remixd', 'remove', { path: unprefixedpath })
.then(result => { .then(result => {
const path = this.type + '/' + unprefixedpath const path = unprefixedpath
delete this.filesContent[path] delete this.filesContent[path]
resolve(true) resolve(true)
...@@ -154,8 +154,8 @@ module.exports = class RemixDProvider { ...@@ -154,8 +154,8 @@ module.exports = class RemixDProvider {
if (!this._isReady) return new Promise((resolve, reject) => reject(new Error('provider not ready'))) if (!this._isReady) return new Promise((resolve, reject) => reject(new Error('provider not ready')))
return this._appManager.call('remixd', 'rename', { oldPath: unprefixedoldPath, newPath: unprefixednewPath }) return this._appManager.call('remixd', 'rename', { oldPath: unprefixedoldPath, newPath: unprefixednewPath })
.then(result => { .then(result => {
const newPath = this.type + '/' + unprefixednewPath const newPath = unprefixednewPath
const oldPath = this.type + '/' + unprefixedoldPath const oldPath = unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath] this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath] delete this.filesContent[oldPath]
...@@ -181,12 +181,6 @@ module.exports = class RemixDProvider { ...@@ -181,12 +181,6 @@ module.exports = class RemixDProvider {
return path return path
} }
addPrefix (path) {
if (path.indexOf(this.type + '/') === 0) return path
if (path[0] === '/') return 'localhost' + path
return 'localhost/' + path
}
resolveDirectory (path, callback) { resolveDirectory (path, callback) {
var self = this var self = this
if (path[0] === '/') path = path.substring(1) if (path[0] === '/') path = path.substring(1)
......
'use strict'
const FileProvider = require('./fileProvider')
class WorkspaceFileProvider extends FileProvider {
constructor () {
super('')
this.workspacesPath = '.workspaces'
}
setWorkspace (workspace) {
workspace = workspace.replace(/^\/|\/$/g, '') // remove first and last slash
this.workspace = workspace
}
removePrefix (path) {
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
if (path.startsWith(this.workspacesPath + '/' + this.workspace)) return path
if (path.startsWith(this.workspace)) return this.workspacesPath + '/' + this.workspace
path = super.removePrefix(path)
const ret = this.workspacesPath + '/' + this.workspace + '/' + (path === '/' ? '' : path)
return ret.replace(/^\/|\/$/g, '')
}
resolveDirectory (path, callback) {
super.resolveDirectory(path, (error, files) => {
if (error) return callback(error)
const unscoped = {}
for (const file in files) {
unscoped[file.replace(this.workspacesPath + '/' + this.workspace + '/', '')] = files[file]
}
callback(null, unscoped)
})
}
_normalizePath (path) {
return path.replace(this.workspacesPath + '/' + this.workspace + '/', '')
}
}
module.exports = WorkspaceFileProvider
...@@ -7,10 +7,13 @@ import { FileExplorer } from '@remix-ui/file-explorer' // eslint-disable-line ...@@ -7,10 +7,13 @@ import { FileExplorer } from '@remix-ui/file-explorer' // eslint-disable-line
import './styles/file-panel-styles.css' import './styles/file-panel-styles.css'
var yo = require('yo-yo') var yo = require('yo-yo')
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
// var FileExplorer = require('../files/file-explorer')
var { RemixdHandle } = require('../files/remixd-handle.js') var { RemixdHandle } = require('../files/remixd-handle.js')
var { GitHandle } = require('../files/git-handle.js') var { GitHandle } = require('../files/git-handle.js')
var globalRegistry = require('../../global/registry') var globalRegistry = require('../../global/registry')
var examples = require('../editor/examples')
var GistHandler = require('../../lib/gist-handler')
var QueryParams = require('../../lib/query-params')
const modalDialog = require('../ui/modal-dialog-custom')
var canUpload = window.File || window.FileReader || window.FileList || window.Blob var canUpload = window.File || window.FileReader || window.FileList || window.Blob
...@@ -54,13 +57,16 @@ module.exports = class Filepanel extends ViewPlugin { ...@@ -54,13 +57,16 @@ module.exports = class Filepanel extends ViewPlugin {
fileManager: this._components.registry.get('filemanager').api, fileManager: this._components.registry.get('filemanager').api,
config: this._components.registry.get('config').api config: this._components.registry.get('config').api
} }
this.LOCALHOST = '<Connect Localhost>'
this.hideRemixdExplorer = true this.hideRemixdExplorer = true
this.remixdExplorer = { this.remixdExplorer = {
hide: () => { hide: () => {
this._deps.fileManager.setMode('browser')
this.hideRemixdExplorer = true this.hideRemixdExplorer = true
this.renderComponent() this.renderComponent()
}, },
show: () => { show: () => {
this._deps.fileManager.setMode('localhost')
this.hideRemixdExplorer = false this.hideRemixdExplorer = false
this.renderComponent() this.renderComponent()
} }
...@@ -93,9 +99,54 @@ module.exports = class Filepanel extends ViewPlugin { ...@@ -93,9 +99,54 @@ module.exports = class Filepanel extends ViewPlugin {
this.remixdExplorer.hide() this.remixdExplorer.hide()
}) })
this.currentWorkspace = null
this.renderComponent() this.renderComponent()
} }
initWorkspace () {
const queryParams = new QueryParams()
const params = queryParams.get()
// get the file from gist
const gistHandler = new GistHandler()
const loadedFromGist = gistHandler.loadFromGist(params, this._deps.fileManager)
if (!loadedFromGist) {
// insert example contracts if there are no files to show
this._deps.fileProviders.browser.resolveDirectory('/', async (error, filesList) => {
if (error) console.error(error)
if (Object.keys(filesList).length === 0) {
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
for (const file in examples) {
await this._deps.fileManager.writeFile('browser/' + workspacesPath + '/default_workspace/' + examples[file].name, examples[file].content)
}
this.setWorkspace('default_workspace')
}
})
} else {
this.setWorkspace('gists')
}
}
refreshWorkspacesList () {
if (!document.getElementById('workspacesSelect')) return
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
this._deps.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, fileTree) => {
if (error) console.error(error)
const items = fileTree
items[this.LOCALHOST] = { isLocalHost: true }
ReactDOM.render(
(
Object.keys(items)
.filter((item) => fileTree[item].isDirectory || fileTree[item].isLocalHost)
.map((folder) => {
folder = folder.replace(workspacesPath + '/', '')
return <option selected={this.currentWorkspace === folder} value={folder}>{folder}</option>
})), document.getElementById('workspacesSelect')
)
if (!this.currentWorkspace) this.setWorkspace(Object.keys(fileTree)[0].replace(workspacesPath + '/', ''))
})
}
resetFocus (value) { resetFocus (value) {
this.reset = value this.reset = value
this.renderComponent() this.renderComponent()
...@@ -125,6 +176,19 @@ module.exports = class Filepanel extends ViewPlugin { ...@@ -125,6 +176,19 @@ module.exports = class Filepanel extends ViewPlugin {
return this.el return this.el
} }
setWorkspace (name) {
this._deps.fileManager.removeTabsOf(this._deps.fileProviders.workspace)
this.currentWorkspace = name
if (name === this.LOCALHOST) {
this.call('manager', 'activatePlugin', 'remixd')
} else {
this._deps.fileProviders.workspace.setWorkspace(name)
this.call('manager', 'deactivatePlugin', 'remixd')
}
// TODO remove the opened tabs from the previous workspace
this.renderComponent()
}
/** /**
* *
* @param item { id: string, name: string, type?: string[], path?: string[], extension?: string[], pattern?: string[] } * @param item { id: string, name: string, type?: string[], path?: string[], extension?: string[], pattern?: string[] }
...@@ -139,24 +203,104 @@ module.exports = class Filepanel extends ViewPlugin { ...@@ -139,24 +203,104 @@ module.exports = class Filepanel extends ViewPlugin {
this.renderComponent() this.renderComponent()
} }
renameWorkspace () {
modalDialog.prompt('Rename Workspace', 'Please choose a name for the workspace', this.currentWorkspace, async (value) => {
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
await this._deps.fileManager.rename('browser/' + workspacesPath + '/' + this.currentWorkspace, 'browser/workspaces/' + value)
setTimeout(async () => {
this.setWorkspace(value)
}, 2000)
})
}
async createWorkspace () {
const workspace = `workspace_${Date.now()}`
modalDialog.prompt('New Workspace', 'Please choose a name for the workspace', workspace, (value) => {
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
this._deps.fileProviders.browser.createDir(workspacesPath + '/' + value, async () => {
this.setWorkspace(value)
setTimeout(async () => {
for (const file in examples) {
await this._deps.fileManager.writeFile(`${examples[file].name}`, examples[file].content)
}
}, 2000)
})
})
}
deleteCurrentWorkspace () {
if (!this.currentWorkspace) return
modalDialog.confirm('Delete Workspace', 'Please confirm workspace deletion', () => {
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
this._deps.fileProviders.browser.remove(workspacesPath + '/' + this.currentWorkspace)
this.currentWorkspace = null
this.renderComponent()
})
}
renderComponent () { renderComponent () {
ReactDOM.render( ReactDOM.render(
<div className='remixui_container'> <div className='remixui_container'>
<div className='remixui_fileexplorer' onClick={() => this.resetFocus(true)}> <div className='remixui_fileexplorer' onClick={() => this.resetFocus(true)}>
<div>
<header>
<div class="mb-2">
<label className="form-check-label" htmlFor="workspacesSelect">
Workspaces
</label>
<span className="remixui_menu">
<span
id='workspaceCreate'
data-id='workspaceCreate'
onClick={(e) => {
e.stopPropagation()
this.createWorkspace()
}}
className='far fa-plus-square remixui_menuicon'
title='Create a new Workspace'>
</span>
<span
hidden={this.currentWorkspace === this.LOCALHOST}
id='workspaceRename'
data-id='workspaceRename'
onClick={(e) => {
e.stopPropagation()
this.renameWorkspace()
}}
className='far fa-edit remixui_menuicon'
title='Rename current Workspace'>
</span>
<span
hidden={this.currentWorkspace === this.LOCALHOST}
id='workspaceDelete'
data-id='workspaceDelete'
onClick={(e) => {
e.stopPropagation()
this.deleteCurrentWorkspace()
}}
className='fas fa-trash'
title='Delete current Workspace'>
</span>
</span>
<select id="workspacesSelect" data-id="workspacesSelect" onChange={(e) => this.setWorkspace(e.target.value)} className="form-control custom-select">
</select>
</div>
</header>
</div>
<div className='remixui_fileExplorerTree'> <div className='remixui_fileExplorerTree'>
<div> <div>
<div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'> <div className='pl-2 remixui_treeview' data-id='filePanelFileExplorerTree'>
{ this.hideRemixdExplorer && this.currentWorkspace &&
<FileExplorer <FileExplorer
name='browser' name={this.currentWorkspace}
registry={this._components.registry} registry={this._components.registry}
filesProvider={this._deps.fileProviders.browser} filesProvider={this._deps.fileProviders.workspace}
menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']} menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']}
plugin={this} plugin={this}
focusRoot={this.reset} focusRoot={this.reset}
contextMenuItems={this.registeredMenuItems} contextMenuItems={this.registeredMenuItems}
displayInput={this.displayNewFile}
externalUploads={this.uploadFileEvent}
/> />
}
</div> </div>
<div className='pl-2 filesystemexplorer remixui_treeview'> <div className='pl-2 filesystemexplorer remixui_treeview'>
{ !this.hideRemixdExplorer && { !this.hideRemixdExplorer &&
...@@ -171,10 +315,27 @@ module.exports = class Filepanel extends ViewPlugin { ...@@ -171,10 +315,27 @@ module.exports = class Filepanel extends ViewPlugin {
/> />
} }
</div> </div>
<div className='pl-2 remixui_treeview'>
{ false && <FileExplorer
name='browser'
registry={this._components.registry}
filesProvider={this._deps.fileProviders.browser}
menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']}
plugin={this}
focusRoot={this.reset}
contextMenuItems={this.registeredMenuItems}
displayInput={this.displayNewFile}
externalUploads={this.uploadFileEvent}
/>
}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
, this.el) , this.el)
setTimeout(() => {
this.refreshWorkspacesList()
}, 500)
} }
} }
...@@ -54,3 +54,6 @@ ...@@ -54,3 +54,6 @@
margin-bottom: 2em; margin-bottom: 2em;
word-break: break-word; word-break: break-word;
} }
.remixui_menuicon {
padding-right : 10px;
}
...@@ -227,6 +227,7 @@ class CompileTab extends ViewPlugin { ...@@ -227,6 +227,7 @@ class CompileTab extends ViewPlugin {
* @param {object} settings {evmVersion, optimize, runs, version, language} * @param {object} settings {evmVersion, optimize, runs, version, language}
*/ */
async compileWithParameters (compilationTargets, settings) { async compileWithParameters (compilationTargets, settings) {
settings.version = settings.version || this.compilerContainer.data.selectedVersion
const res = await compile(compilationTargets, settings) const res = await compile(compilationTargets, settings)
return res return res
} }
......
...@@ -35,7 +35,7 @@ module.exports = class TestTab extends ViewPlugin { ...@@ -35,7 +35,7 @@ module.exports = class TestTab extends ViewPlugin {
this.runningTestsNumber = 0 this.runningTestsNumber = 0
this.readyTestsNumber = 0 this.readyTestsNumber = 0
this.areTestsRunning = false this.areTestsRunning = false
this.defaultPath = 'browser/tests' this.defaultPath = 'tests'
this.offsetToLineColumnConverter = offsetToLineColumnConverter this.offsetToLineColumnConverter = offsetToLineColumnConverter
appManager.event.on('activate', (name) => { appManager.event.on('activate', (name) => {
...@@ -551,13 +551,18 @@ module.exports = class TestTab extends ViewPlugin { ...@@ -551,13 +551,18 @@ module.exports = class TestTab extends ViewPlugin {
updateDirList () { updateDirList () {
for (var o of this.uiPathList.querySelectorAll('option')) o.remove() for (var o of this.uiPathList.querySelectorAll('option')) o.remove()
this.uiPathList.appendChild(yo`<option>browser</option>`) this.testTabLogic.dirList('/').then((options) => {
if (this.testTabLogic.isRemixDActive()) this.uiPathList.appendChild(yo`<option>localhost</option>`) options.forEach((path) => this.uiPathList.appendChild(yo`<option>${path}</option>`))
if (!this._view.el) return })
/*
It is not possible anymore to see folder from outside of the current workspace
if (this.inputPath.value) {
this.testTabLogic.dirList(this.inputPath.value).then((options) => { this.testTabLogic.dirList(this.inputPath.value).then((options) => {
options.forEach((path) => this.uiPathList.appendChild(yo`<option>${path}</option>`)) options.forEach((path) => this.uiPathList.appendChild(yo`<option>${path}</option>`))
}) })
} }
*/
}
render () { render () {
this.onActivationInternal() this.onActivationInternal()
......
...@@ -5,7 +5,7 @@ const remixPath = require('path') ...@@ -5,7 +5,7 @@ const remixPath = require('path')
class TestTabLogic { class TestTabLogic {
constructor (fileManager) { constructor (fileManager) {
this.fileManager = fileManager this.fileManager = fileManager
this.currentPath = 'browser/tests' this.currentPath = '/tests'
} }
setCurrentPath (path) { setCurrentPath (path) {
......
...@@ -22,7 +22,6 @@ function Config (storage) { ...@@ -22,7 +22,6 @@ function Config (storage) {
} }
this.get = function (key) { this.get = function (key) {
this.ensureStorageUpdated(key)
return this.items[key] return this.items[key]
} }
...@@ -35,23 +34,6 @@ function Config (storage) { ...@@ -35,23 +34,6 @@ function Config (storage) {
} }
} }
this.ensureStorageUpdated = function (key) {
if (key === 'currentFile') {
if (this.items[key] && this.items[key] !== '' &&
this.items[key].indexOf('config/') !== 0 &&
this.items[key].indexOf('browser/') !== 0 &&
this.items[key].indexOf('localhost/') !== 0 &&
this.items[key].indexOf('swarm/') !== 0 &&
this.items[key].indexOf('gist/') !== 0 &&
this.items[key].indexOf('github/') !== 0 &&
this.items[key].indexOf('ipfs/') !== 0 &&
this.items[key].indexOf('http/') !== 0 &&
this.items[key].indexOf('https/') !== 0) {
this.items[key] = 'browser/' + this.items[key]
}
}
}
this.getUnpersistedProperty = function (key) { this.getUnpersistedProperty = function (key) {
return this.unpersistedItems[key] return this.unpersistedItems[key]
} }
......
...@@ -42,25 +42,24 @@ function GistHandler (_window) { ...@@ -42,25 +42,24 @@ function GistHandler (_window) {
} }
this.loadFromGist = (params, fileManager) => { this.loadFromGist = (params, fileManager) => {
const gistProvider = fileManager.fileProviderOf('browser')
const self = this const self = this
return self.handleLoad(params, function (gistId) { return self.handleLoad(params, function (gistId) {
request.get({ request.get({
url: `https://api.github.com/gists/${gistId}`, url: `https://api.github.com/gists/${gistId}`,
json: true json: true
}, (error, response, data = {}) => { }, async (error, response, data = {}) => {
if (error || !data.files) { if (error || !data.files) {
modalDialogCustom.alert(`Gist load error: ${error || data.message}`) modalDialogCustom.alert(`Gist load error: ${error || data.message}`)
return return
} }
const obj = {} const obj = {}
Object.keys(data.files).forEach((element) => { Object.keys(data.files).forEach((element) => {
obj['/gists/' + gistId + '/' + element] = data.files[element] obj['/' + gistId + '/' + element] = data.files[element]
}) })
fileManager.setBatchFiles(obj, 'browser', true, (errorLoadingFile) => { fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (!errorLoadingFile) { if (!errorLoadingFile) {
gistProvider.id = gistId const provider = fileManager.getProvider('workspace')
gistProvider.origGistFiles = data.files provider.lastLoadedGistId = gistId
} }
}) })
}) })
......
...@@ -79,6 +79,11 @@ module.exports = { ...@@ -79,6 +79,11 @@ module.exports = {
? 'fak fa-vyper-mono' : path.endsWith('.lex') ? 'fak fa-vyper-mono' : path.endsWith('.lex')
? 'fak fa-lexon' : path.endsWith('.contract') ? 'fak fa-lexon' : path.endsWith('.contract')
? 'fab fa-ethereum' : 'far fa-file' ? 'fab fa-ethereum' : 'far fa-file'
},
joinPath (...paths) {
paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
if (paths.length === 1) return paths[0]
return paths.join('/')
} }
} }
......
import { Storage } from '@remix-project/remix-lib' import { Storage } from '@remix-project/remix-lib'
import { joinPath } from './lib/helper'
/* /*
Migrating the files to the BrowserFS storage instead or raw localstorage Migrating the files to the BrowserFS storage instead or raw localstorage
...@@ -20,3 +20,31 @@ export default (fileProvider) => { ...@@ -20,3 +20,31 @@ export default (fileProvider) => {
}) })
fileStorageBrowserFS.set(flag, 'done') fileStorageBrowserFS.set(flag, 'done')
} }
export async function migrateToWorkspace (fileManager) {
const browserProvider = fileManager.getProvider('browser')
const workspaceProvider = fileManager.getProvider('workspace')
const flag = 'status'
const fileStorageBrowserWorkspace = new Storage('remix_browserWorkspace_migration:')
if (fileStorageBrowserWorkspace.get(flag) === 'done') return
const files = await browserProvider.copyFolderToJson('/')
console.log(files)
const workspaceName = 'default_workspace'
const workspacePath = joinPath('browser', workspaceProvider.workspacesPath, workspaceName)
await fileManager.createWorkspace(workspaceName)
await populateWorkspace(workspacePath, files, fileManager)
fileStorageBrowserWorkspace.set(flag, 'done')
}
const populateWorkspace = async (workspace, json, fileManager) => {
for (const item in json) {
const isFolder = json[item].content === undefined
if (isFolder) {
await fileManager.mkdir(joinPath(workspace, item))
await populateWorkspace(workspace, json[item].children, fileManager)
} else {
await fileManager.writeFile(joinPath(workspace, item), json[item].content)
}
}
}
...@@ -130,13 +130,12 @@ class PluginLoader { ...@@ -130,13 +130,12 @@ class PluginLoader {
constructor () { constructor () {
const queryParams = new QueryParams() const queryParams = new QueryParams()
this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load. this.donotAutoReload = ['remixd', 'git'] // that would be a bad practice to force loading some plugins at page load.
this.loaders = {} this.loaders = {}
this.loaders.localStorage = { this.loaders.localStorage = {
set: (plugin, actives) => { set: (plugin, actives) => {
if (!this.donotAutoReload.includes(plugin.name)) { const saved = actives.filter((name) => !this.donotAutoReload.includes(name))
localStorage.setItem('workspace', JSON.stringify(actives)) localStorage.setItem('workspace', JSON.stringify(saved))
}
}, },
get: () => { return JSON.parse(localStorage.getItem('workspace')) } get: () => { return JSON.parse(localStorage.getItem('workspace')) }
} }
......
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