Commit 22ea754f authored by yann300's avatar yann300

open-save workspace

parent f3380cc3
......@@ -9,7 +9,6 @@ soljson.js.*
npm-debug.log*
remix
.DS_Store
contracts
TODO
.tern-port
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'
import { MainPanel } from './app/components/main-panel'
import FetchAndCompile from './app/compiler/compiler-sourceVerifier-fetchAndCompile'
import migrateFileSystem from './migrateFileSystem'
import migrateFileSystem, { migrateToWorkspace } from './migrateFileSystem'
var isElectron = require('is-electron')
var csjs = require('csjs-inject')
......@@ -28,14 +28,13 @@ var registry = require('./global/registry')
var loadFileFromParent = require('./loadFilesFromParent')
var { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConverter')
var QueryParams = require('./lib/query-params')
var GistHandler = require('./lib/gist-handler')
var Storage = remixLib.Storage
var RemixDProvider = require('./app/files/remixDProvider')
var Config = require('./config')
var examples = require('./app/editor/examples')
var modalDialogCustom = require('./app/ui/modal-dialog-custom')
var FileManager = require('./app/files/fileManager')
var FileProvider = require('./app/files/fileProvider')
var WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
var toolTip = require('./app/ui/tooltip')
var CompilerMetadata = require('./app/files/compiler-metadata')
var CompilerImport = require('./app/compiler/compiler-imports')
......@@ -145,6 +144,9 @@ class App {
registry.put({ api: self._components.filesProviders.browser, name: 'fileproviders/browser' })
self._components.filesProviders.localhost = new RemixDProvider(self.appManager)
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' })
migrateFileSystem(self._components.filesProviders.browser)
......@@ -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
loadFileFromParent(fileManager)
// get the file from gist
const gistHandler = new GistHandler()
const loadedFromGist = gistHandler.loadFromGist(params, fileManager)
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)
}
}
})
}
migrateToWorkspace(fileManager)
filePanel.initWorkspace()
if (params.code) {
try {
......
......@@ -77,7 +77,7 @@ module.exports = class CompilerImports extends Plugin {
}
cb(null, content, cleanUrl, type, url)
} 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 {
(error, content, cleanUrl, type, url) => {
if (error) return cb(error)
if (this.fileManager) {
const browser = this.fileManager.fileProviderOf('browser/')
const workspace = this.fileManager.getProvider('workspace')
const path = targetPath || type + '/' + cleanUrl
if (browser) browser.addExternal(path, content, url)
if (workspace) workspace.addExternal(path, content, url)
}
cb(null, content)
})
......@@ -123,7 +123,6 @@ module.exports = class CompilerImports extends Plugin {
}
provider.exists(url, (error, exist) => {
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
......
......@@ -355,7 +355,6 @@ class Editor extends Plugin {
- 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
*/
if (!path.startsWith('localhost') && !path.startsWith('browser')) path = `browser/${path}`
if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path))
this.sessions[path] = session
......
'use strict'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import { joinPath } from '../../lib/helper'
var CompilerAbstract = require('../compiler/compiler-abstract')
const profile = {
......@@ -21,11 +22,11 @@ class CompilerMetadata extends Plugin {
}
_JSONFileName (path, contractName) {
return path + '/' + this.innerPath + '/' + contractName + '.json'
return joinPath(path, this.innerPath, contractName + '.json')
}
_MetadataFileName (path, contractName) {
return path + '/' + this.innerPath + '/' + contractName + '_metadata' + '.json'
return joinPath(path, this.innerPath, contractName + '_metadata.json')
}
onActivation () {
......@@ -33,9 +34,9 @@ class CompilerMetadata extends Plugin {
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
if (!self.config.get('settings/generate-contract-metadata')) return
const compiler = new CompilerAbstract(languageVersion, data, source)
var provider = self.fileManager.currentFileProvider()
var path = self.fileManager.currentPath()
if (provider && path) {
var provider = self.fileManager.fileProviderOf(source.target)
var path = self.fileManager.extractPathOf(source.target)
if (provider) {
compiler.visitContracts((contract) => {
if (contract.file !== source.target) return
......@@ -117,7 +118,7 @@ class CompilerMetadata extends Plugin {
path = this.fileManager.currentPath()
}
if (provider && path) {
if (provider) {
this.blockchain.detectNetwork((err, { id, name } = {}) => {
if (err) {
console.log(err)
......
......@@ -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.
const folder = id ? 'browser/gists/' + id : 'browser/'
const folder = id ? '/gists/' + id : '/'
this.packageFiles(this.files, folder, (error, packaged) => {
if (error) {
console.log(error)
......@@ -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
modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => {
if (!input) input = 'New file'
......
......@@ -39,6 +39,7 @@ const createError = (err) => {
class FileManager extends Plugin {
constructor (editor, appManager) {
super(profile)
this.mode = 'browser'
this.openedFiles = {} // list all opened files
this.events = new EventEmitter()
this.editor = editor
......@@ -48,6 +49,10 @@ class FileManager extends Plugin {
this.init()
}
setMode (mode) {
this.mode = mode
}
/**
* Emit error if path doesn't exist
* @param {string} path path of the file/directory
......@@ -265,6 +270,7 @@ class FileManager extends Plugin {
config: this._components.registry.get('config').api,
browserExplorer: this._components.registry.get('fileproviders/browser').api,
localhostExplorer: this._components.registry.get('fileproviders/localhost').api,
workspaceExplorer: this._components.registry.get('fileproviders/workspace').api,
filesProviders: this._components.registry.get('fileproviders').api
}
this._deps.browserExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) })
......@@ -275,6 +281,11 @@ class FileManager extends Plugin {
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('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.getFile = this.readFile
this.getFolder = this.readdir
......@@ -349,7 +360,7 @@ class FileManager extends Plugin {
extractPathOf (file) {
var reg = /(.*)(\/).*/
var path = reg.exec(file)
return path ? path[1] : null
return path ? path[1] : '/'
}
getFileContent (path) {
......@@ -468,18 +479,8 @@ class FileManager extends Plugin {
}
if (file) return _openFile(file)
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.events.emit('noFileSelected')
}
})
this.emit('noFileSelected')
this.events.emit('noFileSelected')
}
}
......@@ -488,10 +489,13 @@ class FileManager extends Plugin {
}
fileProviderOf (file) {
if (file.indexOf('localhost') === 0) {
if (file.startsWith('localhost') || this.mode === 'localhost') {
return this._deps.filesProviders.localhost
}
return this._deps.filesProviders.browser
if (file.startsWith('browser')) {
return this._deps.filesProviders.browser
}
return this._deps.filesProviders.workspace
}
// returns the list of directories inside path
......@@ -501,10 +505,8 @@ class FileManager extends Plugin {
return new Promise((resolve, reject) => {
this.readdir(path).then((ls) => {
const promises = Object.keys(ls).map((item, index) => {
const root = (path.indexOf('/') === -1) ? path : path.substr(0, path.indexOf('/'))
const curPath = `${root}/${item}` // adding 'browser' or 'localhost'
if (ls[item].isDirectory && !dirPaths.includes(curPath)) {
dirPaths.push(curPath)
if (ls[item].isDirectory && !dirPaths.includes(item)) {
dirPaths.push(item)
resolve(dirPaths)
}
return new Promise((resolve, reject) => { resolve() })
......@@ -579,6 +581,14 @@ class FileManager extends Plugin {
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
......@@ -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) {
path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path) && !window.remixFileSystem.statSync(path).isDirectory()) {
......@@ -227,6 +260,8 @@ class FileProvider {
if (files) {
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
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() }
// ^ ret does not accept path starting with '/'
......@@ -243,7 +278,6 @@ class FileProvider {
}
_normalizePath (path) {
if (path.indexOf('/') !== 0) path = '/' + path
return this.type + path
}
}
......
......@@ -23,23 +23,23 @@ module.exports = class RemixDProvider {
})
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.event.trigger('fileAdded', [this.addPrefix(path)])
this.event.trigger('fileAdded', [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.event.trigger('fileRemoved', [this.addPrefix(path)])
this.event.trigger('fileRemoved', [path])
})
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', () => {
......@@ -136,7 +136,7 @@ module.exports = class RemixDProvider {
const unprefixedpath = this.removePrefix(path)
this._appManager.call('remixd', 'remove', { path: unprefixedpath })
.then(result => {
const path = this.type + '/' + unprefixedpath
const path = unprefixedpath
delete this.filesContent[path]
resolve(true)
......@@ -154,8 +154,8 @@ module.exports = class RemixDProvider {
if (!this._isReady) return new Promise((resolve, reject) => reject(new Error('provider not ready')))
return this._appManager.call('remixd', 'rename', { oldPath: unprefixedoldPath, newPath: unprefixednewPath })
.then(result => {
const newPath = this.type + '/' + unprefixednewPath
const oldPath = this.type + '/' + unprefixedoldPath
const newPath = unprefixednewPath
const oldPath = unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
......@@ -181,12 +181,6 @@ module.exports = class RemixDProvider {
return path
}
addPrefix (path) {
if (path.indexOf(this.type + '/') === 0) return path
if (path[0] === '/') return 'localhost' + path
return 'localhost/' + path
}
resolveDirectory (path, callback) {
var self = this
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
This diff is collapsed.
......@@ -54,3 +54,6 @@
margin-bottom: 2em;
word-break: break-word;
}
.remixui_menuicon {
padding-right : 10px;
}
......@@ -227,6 +227,7 @@ class CompileTab extends ViewPlugin {
* @param {object} settings {evmVersion, optimize, runs, version, language}
*/
async compileWithParameters (compilationTargets, settings) {
settings.version = settings.version || this.compilerContainer.data.selectedVersion
const res = await compile(compilationTargets, settings)
return res
}
......
......@@ -35,7 +35,7 @@ module.exports = class TestTab extends ViewPlugin {
this.runningTestsNumber = 0
this.readyTestsNumber = 0
this.areTestsRunning = false
this.defaultPath = 'browser/tests'
this.defaultPath = 'tests'
this.offsetToLineColumnConverter = offsetToLineColumnConverter
appManager.event.on('activate', (name) => {
......@@ -551,12 +551,17 @@ module.exports = class TestTab extends ViewPlugin {
updateDirList () {
for (var o of this.uiPathList.querySelectorAll('option')) o.remove()
this.uiPathList.appendChild(yo`<option>browser</option>`)
if (this.testTabLogic.isRemixDActive()) this.uiPathList.appendChild(yo`<option>localhost</option>`)
if (!this._view.el) return
this.testTabLogic.dirList(this.inputPath.value).then((options) => {
this.testTabLogic.dirList('/').then((options) => {
options.forEach((path) => this.uiPathList.appendChild(yo`<option>${path}</option>`))
})
/*
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) => {
options.forEach((path) => this.uiPathList.appendChild(yo`<option>${path}</option>`))
})
}
*/
}
render () {
......
......@@ -5,7 +5,7 @@ const remixPath = require('path')
class TestTabLogic {
constructor (fileManager) {
this.fileManager = fileManager
this.currentPath = 'browser/tests'
this.currentPath = '/tests'
}
setCurrentPath (path) {
......
......@@ -22,7 +22,6 @@ function Config (storage) {
}
this.get = function (key) {
this.ensureStorageUpdated(key)
return this.items[key]
}
......@@ -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) {
return this.unpersistedItems[key]
}
......
......@@ -42,25 +42,24 @@ function GistHandler (_window) {
}
this.loadFromGist = (params, fileManager) => {
const gistProvider = fileManager.fileProviderOf('browser')
const self = this
return self.handleLoad(params, function (gistId) {
request.get({
url: `https://api.github.com/gists/${gistId}`,
json: true
}, (error, response, data = {}) => {
}, async (error, response, data = {}) => {
if (error || !data.files) {
modalDialogCustom.alert(`Gist load error: ${error || data.message}`)
return
}
const obj = {}
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) {
gistProvider.id = gistId
gistProvider.origGistFiles = data.files
const provider = fileManager.getProvider('workspace')
provider.lastLoadedGistId = gistId
}
})
})
......
......@@ -79,6 +79,11 @@ module.exports = {
? 'fak fa-vyper-mono' : path.endsWith('.lex')
? 'fak fa-lexon' : path.endsWith('.contract')
? '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 { joinPath } from './lib/helper'
/*
Migrating the files to the BrowserFS storage instead or raw localstorage
......@@ -20,3 +20,31 @@ export default (fileProvider) => {
})
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 {
constructor () {
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.localStorage = {
set: (plugin, actives) => {
if (!this.donotAutoReload.includes(plugin.name)) {
localStorage.setItem('workspace', JSON.stringify(actives))
}
const saved = actives.filter((name) => !this.donotAutoReload.includes(name))
localStorage.setItem('workspace', JSON.stringify(saved))
},
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