Commit 2aed8798 authored by yann300's avatar yann300

system files

parent d3537b31
......@@ -461,3 +461,69 @@ input[type="file"] {
.ace_gutter-cell.ace_breakpoint{
background-color: #F77E79;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 6; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Header */
.modal-header {
padding: 2px 16px;
background-color: orange;
color: white;
}
/* Modal Body */
.modal-body {padding: 2px 16px;}
/* Modal Footer */
.modal-footer {
padding: 2px 16px;
background-color: orange;
color: white;
}
/* Modal Content */
.modal-content {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 50%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
#modal-footer-cancel {
margin-left: 1em;
cursor: pointer;
}
#modal-footer-ok {
cursor: pointer;
}
/* Add Animation */
@-webkit-keyframes animatetop {
from {top: -300px; opacity: 0}
to {top: 0; opacity: 1}
}
@keyframes animatetop {
from {top: -300px; opacity: 0}
to {top: 0; opacity: 1}
}
......@@ -53,6 +53,18 @@
</div>
<script src="build/app.js"></script>
<div id="modaldialog" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Localhost connection</h2>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<span id="modal-footer-ok">OK</span><span id="modal-footer-cancel">Cancel</span>
</div>
</div>
</div>
</body>
</html>
This diff is collapsed.
......@@ -59,6 +59,12 @@ function Editor (editorElement) {
e.stop()
})
this.displayEmptyReadOnlySession = function () {
currentSession = null
editor.setSession(emptySession)
editor.setReadOnly(true)
}
this.setBreakpoint = function (row, css) {
editor.session.setBreakpoint(row, css)
}
......@@ -67,6 +73,12 @@ function Editor (editorElement) {
editor.setFontSize(editor.getFontSize() + incr)
}
this.setText = function (text) {
if (currentSession && sessions[currentSession]) {
sessions[currentSession].setValue(text)
}
}
function createSession (content) {
var s = new ace.EditSession(content, 'ace/mode/javascript')
s.setUndoManager(new ace.UndoManager())
......@@ -87,6 +99,8 @@ function Editor (editorElement) {
var session = createSession(content)
sessions[path] = session
readOnlySessions[path] = false
} else if (sessions[path].getValue() !== content) {
sessions[path].setValue(content)
}
switchSession(path)
}
......
......@@ -33,6 +33,17 @@ var css = csjs`
module.exports = fileExplorer
function fileExplorer (appAPI, files) {
this.files = files
this.files.event.register('fileExternallyChanged', (path, content) => {
if (appAPI.currentFile() === path && appAPI.currentContent() !== content) {
if (confirm('This file has been changed outside of the remix.\nDo you want to replace the content displayed in remix by the new one?')) {
appAPI.setText(content)
}
}
})
var self = this
var fileEvents = files.event
var treeView = new Treeview({
extractData: function (value, tree, key) {
......@@ -55,8 +66,11 @@ function fileExplorer (appAPI, files) {
}
},
formatSelf: function (key, data) {
return yo`<label class=${data.children ? css.folder : css.file}
var isRoot = data.path.indexOf('/') === -1
return yo`<label class="${data.children ? css.folder : css.file}"
data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}"
isfolder=${data.children !== undefined}
onload=${function (el) { adaptEnvironment(el, focus, hover) }}
onunload=${function (el) { unadaptEnvironment(el, focus, hover) }}
onclick=${editModeOn}
......@@ -66,30 +80,35 @@ function fileExplorer (appAPI, files) {
}
})
this.treeView = treeView
var deleteButton = yo`
<span class=${css.remove} onclick=${deletePath}>
<i class="fa fa-trash" aria-hidden="true"></i>
</span>
`
appAPI.event.register('currentFileChanged', (newFile) => {
fileFocus(newFile)
appAPI.event.register('currentFileChanged', (newFile, explorer) => {
if (explorer === files) {
fileFocus(newFile)
} else {
unfocus(focusElement)
}
})
fileEvents.register('fileRemoved', fileRemoved)
fileEvents.register('fileRenamed', fileRenamed)
fileEvents.register('fileRenamedError', fileRenamedError)
fileEvents.register('fileAdded', fileAdded)
var filepath = null
var focusElement = null
var textUnderEdit = null
var element = treeView.render(files.listAsTree())
element.className = css.fileexplorer
var events = new EventManager()
this.events = events
var api = {}
api.addFile = function addFile (file) {
var name = file.name
var name = files.type + '/' + file.name
if (!files.exists(name) || confirm('The file ' + name + ' already exists! Would you like to overwrite it?')) {
var fileReader = new FileReader()
fileReader.onload = function (event) {
......@@ -100,12 +119,13 @@ function fileExplorer (appAPI, files) {
fileReader.readAsText(file)
}
}
this.api = api
function focus (event) {
event.cancelBubble = true
var li = this
if (focusElement === li) return
if (focusElement) focusElement.classList.toggle(css.hasFocus)
unfocus(focusElement)
focusElement = li
focusElement.classList.toggle(css.hasFocus)
var label = getLabelFrom(li)
......@@ -114,6 +134,11 @@ function fileExplorer (appAPI, files) {
if (isFile) events.trigger('focus', [filepath])
}
function unfocus (el) {
if (focusElement) focusElement.classList.toggle(css.hasFocus)
focusElement = null
}
function hover (event) {
if (event.type === 'mouseout') {
var exitedTo = event.toElement || event.relatedTarget
......@@ -128,7 +153,7 @@ function fileExplorer (appAPI, files) {
}
function getElement (path) {
var label = element.querySelector(`label[data-path="${path}"]`)
var label = self.element.querySelector(`label[data-path="${path}"]`)
if (label) return getLiFrom(label)
}
......@@ -160,12 +185,26 @@ function fileExplorer (appAPI, files) {
function editModeOff (event) {
var label = this
if (event.type === 'blur' || event.which === 27 || event.which === 13) {
if (event.which === 13) event.preventDefault()
if ((event.type === 'blur' || event.which === 27 || event.which === 13) && label.getAttribute('contenteditable')) {
var isFolder = label.getAttribute('isfolder') === 'true'
var save = textUnderEdit !== label.innerText
if (event.which === 13) event.preventDefault()
if (save && event.which !== 13) save = confirm('Do you want to rename?')
if (save) renameSubtree(label)
else label.innerText = textUnderEdit
if (save) {
var newPath = label.dataset.path
newPath = newPath.split('/')
newPath[newPath.length - 1] = label.innerText
newPath = newPath.join('/')
if (label.innerText.match(/(\/|:|\*|\?|"|<|>|\\|\||')/) !== null) {
alert('special characters are not allowsed')
label.innerText = textUnderEdit
} else if (!files.exists(newPath)) {
files.rename(label.dataset.path, newPath, isFolder)
} else {
alert('File already exists.')
label.innerText = textUnderEdit
}
} else label.innerText = textUnderEdit
label.removeAttribute('contenteditable')
label.classList.remove(css.rename)
}
......@@ -205,8 +244,6 @@ function fileExplorer (appAPI, files) {
var path = label.dataset.path
var newName = path.replace(oldPath, newPath)
label.dataset.path = newName
var isFile = label.className.indexOf('file') === 0
if (isFile) files.rename(path, newName)
var ul = li.lastChild
if (ul.tagName === 'UL') {
updateAllLabels([...ul.children], oldPath, newPath)
......@@ -227,7 +264,7 @@ function fileExplorer (appAPI, files) {
if (li) li.parentElement.removeChild(li)
}
function fileRenamed (oldName, newName) {
function fileRenamed (oldName, newName, isFolder) {
var li = getElement(oldName)
if (li) {
oldName = oldName.split('/')
......@@ -244,16 +281,16 @@ function fileExplorer (appAPI, files) {
}
}
function fileRenamedError (error) {
alert(error)
}
function fileAdded (filepath) {
var el = treeView.render(files.listAsTree())
el.className = css.fileexplorer
element.parentElement.replaceChild(el, element)
element = el
self.element.parentElement.replaceChild(el, self.element)
self.element = el
}
element.events = events
element.api = api
return element
}
/******************************************************************************
HELPER FUNCTIONS
......@@ -312,3 +349,16 @@ function expandPathTo (li) {
if (caret.classList.contains('fa-caret-right')) caret.click() // expand
}
}
fileExplorer.prototype.init = function () {
var files = this.files.listAsTree()
if (!Object.keys(files).length) {
files[this.files.type] = {} // default
}
var element = this.treeView.render(files)
element.className = css.fileexplorer
element.events = this.events
element.api = this.api
this.element = element
return element
}
......@@ -4,6 +4,7 @@ var yo = require('yo-yo')
var EventManager = require('ethereum-remix').lib.EventManager
var FileExplorer = require('./file-explorer')
var modalDialog = require('./modaldialog')
module.exports = filepanel
......@@ -28,6 +29,9 @@ var css = csjs`
.newFile {
padding : 10px;
}
.connectToLocalhost {
padding : 10px;
}
.uploadFile {
padding : 10px;
}
......@@ -46,11 +50,10 @@ var css = csjs`
}
.isHidden {
position : absolute;
height : 99%
height : 99%;
left : -101%;
}
.treeview {
height : 100%;
background-color : white;
}
.dragbar {
......@@ -83,25 +86,39 @@ var limit = 60
var canUpload = window.File || window.FileReader || window.FileList || window.Blob
var ghostbar = yo`<div class=${css.ghostbar}></div>`
function filepanel (appAPI, files) {
var fileExplorer = new FileExplorer(appAPI, files)
function filepanel (appAPI, filesProvider) {
var fileExplorer = new FileExplorer(appAPI, filesProvider['browser'])
var fileSystemExplorer = new FileExplorer(appAPI, filesProvider['localhost'])
var dragbar = yo`<div onmousedown=${mousedown} class=${css.dragbar}></div>`
function remixdDialog () {
return yo`<div><div>This feature allows to interact with your file system from Remix. Once the connection is made the shared folder will be available in the file explorer under <i>localhost</i></div>
<div><i>Remixd</i> first has to be run in your local computer. See <a href="http://remix.readthedocs.io/en/latest/tutorial_mist.html">http://remix.readthedocs.io/en/latest/remixd.html</a> for more details.</div>
<div>Accepting this dialog will start a session between <i>${window.location.href}</i> and your local file system <i>ws://127.0.0.1:65520</i></div>
<div>Please be sure your system is secured enough (port 65520 neither opened nor forwarded).</div>
<div><i class="fa fa-link"></i> will update the connection status.</div>
<div>This feature is still alpha, we recommend to keep a copy of the shared folder.</div>
</div>`
}
function template () {
return yo`
<div class=${css.container}>
<div class=${css.fileexplorer}>
<div class=${css.menu}>
<span onclick=${createNewFile} class="newFile ${css.newFile}" title="New File">
<span onclick=${createNewFile} class="newFile ${css.newFile}" title="Create New File in the Browser Storage Explorer">
<i class="fa fa-plus-circle"></i>
</span>
</span>
${canUpload ? yo`
<span class=${css.uploadFile} title="Open local file">
<span class=${css.uploadFile} title="Add Local file to the Browser Storage Explorer">
<label class="fa fa-folder-open">
<input type="file" onchange=${uploadFile} multiple />
</label>
</span>
` : ''}
<span onclick=${connectToLocalhost} class="${css.connectToLocalhost}">
<i class="websocketconn fa fa-link" title="Connect to Localhost"></i>
</span>
<span class=${css.changeeditorfontsize} >
<i class="increditorsize fa fa-plus" aria-hidden="true" title="increase editor font size"></i>
<i class="decreditorsize fa fa-minus" aria-hidden="true" title="decrease editor font size"></i>
......@@ -110,7 +127,8 @@ function filepanel (appAPI, files) {
<i class="fa fa-angle-double-left"></i>
</span>
</div>
<div class=${css.treeview}>${fileExplorer}</div>
<div class=${css.treeview}>${fileExplorer.init()}</div>
<div class="filesystemexplorer ${css.treeview}"></div>
</div>
${dragbar}
</div>
......@@ -121,6 +139,33 @@ function filepanel (appAPI, files) {
var element = template()
element.querySelector('.increditorsize').addEventListener('click', () => { appAPI.editorFontSize(1) })
element.querySelector('.decreditorsize').addEventListener('click', () => { appAPI.editorFontSize(-1) })
var containerFileSystem = element.querySelector('.filesystemexplorer')
var websocketconn = element.querySelector('.websocketconn')
filesProvider['localhost'].remixd.event.register('connecting', (event) => {
websocketconn.style.color = 'orange'
websocketconn.setAttribute('title', 'Connecting to localhost. ' + JSON.stringify(event))
})
filesProvider['localhost'].remixd.event.register('connected', (event) => {
websocketconn.style.color = 'green'
websocketconn.setAttribute('title', 'Connected to localhost. ' + JSON.stringify(event))
})
filesProvider['localhost'].remixd.event.register('errored', (event) => {
websocketconn.style.color = 'red'
websocketconn.setAttribute('title', 'localhost connection errored. ' + JSON.stringify(event))
if (fileSystemExplorer.element && containerFileSystem.children.length > 0) {
containerFileSystem.removeChild(fileSystemExplorer.element)
}
})
filesProvider['localhost'].remixd.event.register('closed', (event) => {
websocketconn.style.color = '#111111'
websocketconn.setAttribute('title', 'localhost connection closed. ' + JSON.stringify(event))
if (fileSystemExplorer.element && containerFileSystem.children.length > 0) {
containerFileSystem.removeChild(fileSystemExplorer.element)
}
})
// TODO please do not add custom javascript objects, which have no
// relation to the DOM to DOM nodes
element.events = events
......@@ -128,6 +173,10 @@ function filepanel (appAPI, files) {
appAPI.switchToFile(path)
})
fileSystemExplorer.events.register('focus', function (path) {
appAPI.switchToFile(path)
})
return element
function toggle (event) {
......@@ -182,11 +231,39 @@ function filepanel (appAPI, files) {
}
function createNewFile () {
var newName = appAPI.createName('Untitled')
if (!files.set(newName, '')) {
var newName = filesProvider['browser'].type + '/' + appAPI.createName('Untitled.sol')
if (!filesProvider['browser'].set(newName, '')) {
alert('Failed to create file ' + newName)
} else {
appAPI.switchToFile(newName)
}
}
/**
* connect to localhost if no connection and render the explorer
* disconnect from localhost if connected and remove the explorer
*
* @param {String} txHash - hash of the transaction
*/
function connectToLocalhost () {
var container = document.querySelector('.filesystemexplorer')
if (filesProvider['localhost'].files !== null) {
filesProvider['localhost'].close((error) => {
if (error) console.log(error)
})
} else {
modalDialog('Connection to Localhost', remixdDialog(), () => {
filesProvider['localhost'].init((error) => {
if (error) {
console.log(error)
} else {
if (fileSystemExplorer.element && container.children.length > 0) {
container.removeChild(fileSystemExplorer.element)
}
container.appendChild(fileSystemExplorer.init())
}
})
})
}
}
}
......@@ -6,40 +6,52 @@ function Files (storage) {
var event = new EventManager()
this.event = event
var readonly = {}
this.type = 'browser'
this.exists = function (path) {
var unprefixedpath = this.removePrefix(path)
// NOTE: ignore the config file
if (path === '.remix.config') {
return false
}
return this.isReadOnly(path) || storage.exists(path)
return this.isReadOnly(unprefixedpath) || storage.exists(unprefixedpath)
}
this.get = function (path) {
this.init = function (cb) {
cb()
}
this.get = function (path, cb) {
var unprefixedpath = this.removePrefix(path)
// NOTE: ignore the config file
if (path === '.remix.config') {
return null
}
return readonly[path] || storage.get(path)
var content = readonly[unprefixedpath] || storage.get(unprefixedpath)
if (cb) {
cb(null, content)
}
return content
}
this.set = function (path, content) {
var unprefixedpath = this.removePrefix(path)
// NOTE: ignore the config file
if (path === '.remix.config') {
return false
}
if (!this.isReadOnly(path)) {
var exists = storage.exists(path)
if (!storage.set(path, content)) {
if (!this.isReadOnly(unprefixedpath)) {
var exists = storage.exists(unprefixedpath)
if (!storage.set(unprefixedpath, content)) {
return false
}
if (!exists) {
event.trigger('fileAdded', [path, false])
event.trigger('fileAdded', [this.type + '/' + unprefixedpath, false])
} else {
event.trigger('fileChanged', [path])
event.trigger('fileChanged', [this.type + '/' + unprefixedpath])
}
return true
}
......@@ -48,9 +60,10 @@ function Files (storage) {
}
this.addReadOnly = function (path, content) {
if (!storage.exists(path)) {
readonly[path] = content
event.trigger('fileAdded', [path, true])
var unprefixedpath = this.removePrefix(path)
if (!storage.exists(unprefixedpath)) {
readonly[unprefixedpath] = content
event.trigger('fileAdded', [this.type + '/' + unprefixedpath, true])
return true
}
......@@ -58,31 +71,35 @@ function Files (storage) {
}
this.isReadOnly = function (path) {
path = this.removePrefix(path)
return readonly[path] !== undefined
}
this.remove = function (path) {
if (!this.exists(path)) {
var unprefixedpath = this.removePrefix(path)
if (!this.exists(unprefixedpath)) {
return false
}
if (this.isReadOnly(path)) {
readonly[path] = undefined
if (this.isReadOnly(unprefixedpath)) {
readonly[unprefixedpath] = undefined
} else {
if (!storage.remove(path)) {
if (!storage.remove(unprefixedpath)) {
return false
}
}
event.trigger('fileRemoved', [path])
event.trigger('fileRemoved', [this.type + '/' + unprefixedpath])
return true
}
this.rename = function (oldPath, newPath) {
if (!this.isReadOnly(oldPath) && storage.exists(oldPath)) {
if (!storage.rename(oldPath, newPath)) {
this.rename = function (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath)
if (!this.isReadOnly(unprefixedoldPath) && storage.exists(unprefixedoldPath)) {
if (!storage.rename(unprefixedoldPath, unprefixednewPath)) {
return false
}
event.trigger('fileRenamed', [oldPath, newPath])
event.trigger('fileRenamed', [this.type + '/' + unprefixedoldPath, this.type + '/' + unprefixednewPath, isFolder])
return true
}
return false
......@@ -92,21 +109,25 @@ function Files (storage) {
var files = {}
// add r/w files to the list
storage.keys().forEach(function (path) {
storage.keys().forEach((path) => {
// NOTE: as a temporary measure do not show the config file
if (path !== '.remix.config') {
files[path] = false
files[this.type + '/' + path] = false
}
})
// add r/o files to the list
Object.keys(readonly).forEach(function (path) {
files[path] = true
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
// {
......@@ -146,7 +167,6 @@ function Files (storage) {
'/content': self.get(path)
})
})
return tree
}
......
......@@ -41,7 +41,7 @@ function Storage (prefix) {
return safeKeys()
// filter any names not including the prefix
.filter(function (item) { return item.indexOf(prefix, 0) === 0 })
// remove prefix from filename
// remove prefix from filename and add the 'browser' path
.map(function (item) { return item.substr(prefix.length) })
}
......
'use strict'
var async = require('async')
var EventManager = require('ethereum-remix').lib.EventManager
class SystemFiles {
constructor (remixd) {
this.event = new EventManager()
this.remixd = remixd
this.files = null
this.filesContent = {}
this.filesTree = null
this.type = 'localhost'
this.error = {
'EEXIST': 'File already exists'
}
this.remixd.event.register('notified', (data) => {
if (data.scope === 'systemfiles') {
if (data.name === 'created') {
this.init(() => {
this.event.trigger('fileAdded', [this.type + '/' + data.value.path, data.value.isReadOnly, data.value.isFolder])
})
} else if (data.name === 'removed') {
this.init(() => {
this.event.trigger('fileRemoved', [this.type + '/' + data.value.path])
})
} else if (data.name === 'changed') {
this.remixd.call('systemfiles', 'get', {path: data.value}, (error, content) => {
if (error) {
console.log(error)
} else {
var path = this.type + '/' + data.value
this.filesContent[path] = content
this.event.trigger('fileExternallyChanged', [path, content])
}
})
}
}
})
}
close (cb) {
this.remixd.close()
this.files = null
this.filesTree = null
cb()
}
init (cb) {
this.remixd.call('systemfiles', '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)
})
}
})
}
exists (path) {
if (!this.files) return false
return this.files[path] !== undefined
}
get (path, cb) {
var unprefixedpath = this.removePrefix(path)
this.remixd.call('systemfiles', 'get', {path: unprefixedpath}, (error, content) => {
if (!error) {
this.filesContent[path] = content
cb(error, content)
} else {
// display the last known content.
// TODO should perhaps better warn the user that the file is not synced.
cb(null, this.filesContent[path])
}
})
}
set (path, content, cb) {
var unprefixedpath = this.removePrefix(path)
this.remixd.call('systemfiles', 'set', {path: unprefixedpath, content: content}, (error, result) => {
if (cb) cb(error, result)
var path = this.type + '/' + unprefixedpath
this.filesContent[path]
this.event.trigger('fileChanged', [path])
})
return true
}
addReadOnly (path, content) {
return false
}
isReadOnly (path) {
if (this.files) return this.files[path]
return true
}
remove (path) {
var unprefixedpath = this.removePrefix(path)
this.remixd.call('systemfiles', 'remove', {path: unprefixedpath}, (error, result) => {
if (error) console.log(error)
var path = this.type + '/' + unprefixedpath
delete this.filesContent[path]
this.init(() => {
this.event.trigger('fileRemoved', [path])
})
})
}
rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath)
this.remixd.call('systemfiles', 'rename', {oldPath: unprefixedoldPath, newPath: unprefixednewPath}, (error, result) => {
if (error) {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
this.event.trigger('fileRenamedError', [this.error[error.code]])
} else {
var newPath = this.type + '/' + unprefixednewPath
var oldPath = this.type + '/' + unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
this.init(() => {
this.event.trigger('fileRenamed', [oldPath, newPath, isFolder])
})
}
})
return true
}
list () {
return this.files
}
listAsTree () {
return this.filesTree
}
removePrefix (path) {
return path.indexOf(this.type + '/') === 0 ? path.replace(this.type + '/', '') : path
}
}
//
// 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 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().
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)
})
}
module.exports = SystemFiles
module.exports = (title, content, okFn, cancelFn) => {
var modal = document.querySelector('.modal-body')
var modaltitle = document.querySelector('.modal-header h2')
modaltitle.innerHTML = ' - '
if (title) modaltitle.innerHTML = title
modal.innerHTML = ''
if (content) modal.appendChild(content)
var container = document.querySelector('.modal')
container.style.display = container.style.display === 'block' ? hide() : show()
function ok () {
hide()
if (okFn) okFn()
removeEventListener()
}
function cancel () {
hide()
if (cancelFn) cancelFn()
removeEventListener()
}
function blur (event) {
if (event.target === container) {
cancel()
}
}
window.onclick = (event) => {
console.log('clicj windo')
blur(event)
}
function hide () {
container.style.display = 'none'
}
function show () {
container.style.display = 'block'
}
function removeEventListener () {
document.getElementById('modal-footer-ok').removeEventListener('click', ok)
document.getElementById('modal-footer-cancel').removeEventListener('click', cancel)
}
document.getElementById('modal-footer-ok').addEventListener('click', ok)
document.getElementById('modal-footer-cancel').addEventListener('click', cancel)
}
'use strict'
var EventManager = require('ethereum-remix').lib.EventManager
class Remixd {
constructor () {
this.event = new EventManager()
this.callbacks = {}
this.callid = 0
this.socket = null
this.connected = false
}
online () {
return this.socket !== null
}
close () {
if (this.socket) {
this.socket.close()
this.socket = null
}
}
start (cb) {
if (this.socket) {
try {
this.socket.close()
} catch (e) {}
}
this.event.trigger('connecting', [])
this.socket = new WebSocket('ws://localhost:65520', 'echo-protocol') // eslint-disable-line
this.socket.addEventListener('open', (event) => {
this.connected = true
this.event.trigger('connected', [event])
cb()
})
this.socket.addEventListener('message', (event) => {
var data = JSON.parse(event.data)
if (data.type === 'reply') {
if (this.callbacks[data.id]) {
this.callbacks[data.id](data.error, data.result)
delete this.callbacks[data.id]
}
this.event.trigger('replied', [data])
} else if (data.type === 'notification') {
this.event.trigger('notified', [data])
}
})
this.socket.addEventListener('error', (event) => {
this.errored(event)
cb(event)
})
this.socket.addEventListener('close', (event) => {
if (event.wasClean) {
this.connected = false
this.event.trigger('closed', [event])
} else {
this.errored(event)
}
this.socket = null
})
}
errored (event) {
if (this.connected) {
alert('connection to Remixd lost!.') // eslint-disable-line
}
this.connected = false
this.socket = null
this.event.trigger('errored', [event])
}
call (service, fn, args, callback) {
this.ensureSocket((error) => {
if (error) return callback(error)
if (this.socket && this.socket.readyState === this.socket.OPEN) {
var data = this.format(service, fn, args)
this.callbacks[data.id] = callback
this.socket.send(JSON.stringify(data))
} else {
callback('Socket not ready. state:' + this.socket.readyState)
}
})
}
ensureSocket (cb) {
if (this.socket) return cb(null, this.socket)
this.start((error) => {
if (error) {
cb(error)
} else {
cb(null, this.socket)
}
})
}
format (service, fn, args) {
var data = {
id: this.callid,
service: service,
fn: fn,
args: args
}
this.callid++
return data
}
}
module.exports = Remixd
......@@ -280,13 +280,13 @@ UniversalDApp.prototype.getContractByName = function (contractName) {
}
UniversalDApp.prototype.getCreateInterface = function ($container, contract) {
function remove () {
self.$el.remove()
}
var self = this
var createInterface = yo`<div class="create"></div>`
if (self.options.removable) {
var close = yo`<div class="udapp-close" onclick=${remove}></div>`
function remove () {
self.$el.remove()
}
createInterface.appendChild(close)
}
......@@ -348,10 +348,10 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar
var appendFunctions = function (address, $el) {
if ($el) $el = $el.get(0)
function remove () { $instance.remove() }
var $instance = $(`<div class="instance ${cssInstance.instance}"/>`)
if (self.options.removable_instances) {
var close = yo`<div class="udapp-close" onclick=${remove}></div>`
function remove () { $instance.remove() }
$instance.get(0).appendChild(close)
}
var context = self.executionContext.isVM() ? 'memory' : 'blockchain'
......
......@@ -6,7 +6,7 @@ var sauce = require('./sauce')
var sources = {
'sources': {
'Untitled.sol': examples.ballot.content
'browser/Untitled.sol': examples.ballot.content
}
}
......@@ -27,7 +27,7 @@ function runTests (browser, testData) {
browser
.waitForElementVisible('.newFile', 10000)
.click('.envView')
contractHelper.testContracts(browser, sources.sources['Untitled.sol'], ['Untitled.sol:Ballot'], function () {
contractHelper.testContracts(browser, sources.sources['browser/Untitled.sol'], ['browser/Untitled.sol:Ballot'], function () {
browser.end()
})
}
......@@ -5,7 +5,7 @@ var sauce = require('./sauce')
var sources = {
'sources': {
'Untitled.sol': `pragma solidity ^0.4.0;
'browser/Untitled.sol': `pragma solidity ^0.4.0;
contract TestContract { function f() returns (uint) { return 8; } }`
}
}
......@@ -27,7 +27,7 @@ function runTests (browser) {
browser
.waitForElementVisible('.newFile', 10000)
.click('.envView')
contractHelper.testContracts(browser, sources.sources['Untitled.sol'], ['Untitled.sol:TestContract'], function () {
contractHelper.testContracts(browser, sources.sources['browser/Untitled.sol'], ['browser/Untitled.sol:TestContract'], function () {
browser.click('.create .constructor .call')
.waitForElementPresent('.instance .call[title="f"]')
.click('.instance .call[title="f"]')
......
......@@ -6,17 +6,17 @@ var sauce = require('./sauce')
var sources = {
'sources': {
'ballot.sol': examples.ballot.content,
'test/client/credit.sol': '',
'src/voting.sol': '',
'src/leasing.sol': '',
'src/gmbh/contract.sol': false,
'src/gmbh/test.sol': false,
'src/gmbh/company.sol': false,
'src/gmbh/node_modules/ballot.sol': false,
'src/ug/finance.sol': false,
'app/solidity/mode.sol': true,
'app/ethereum/constitution.sol': true
'browser/ballot.sol': examples.ballot.content,
'browser/test/client/credit.sol': '',
'browser/src/voting.sol': '',
'browser/src/leasing.sol': '',
'browser/src/gmbh/contract.sol': false,
'browser/src/gmbh/test.sol': false,
'browser/src/gmbh/company.sol': false,
'browser/src/gmbh/node_modules/ballot.sol': false,
'browser/src/ug/finance.sol': false,
'browser/app/solidity/mode.sol': true,
'browser/app/ethereum/constitution.sol': true
}
}
......
......@@ -5,7 +5,7 @@ var sauce = require('./sauce')
var sources = {
'sources': {
'Untitled.sol': 'contract test1 {} contract test2 {}'
'browser/Untitled.sol': 'contract test1 {} contract test2 {}'
}
}
......@@ -26,7 +26,7 @@ function runTests (browser) {
browser
.waitForElementVisible('.newFile', 10000)
.click('.envView')
contractHelper.testContracts(browser, sources.sources['Untitled.sol'], ['Untitled.sol:test1', 'Untitled.sol:test2'], function () {
contractHelper.testContracts(browser, sources.sources['browser/Untitled.sol'], ['browser/Untitled.sol:test1', 'browser/Untitled.sol:test2'], function () {
browser.end()
})
}
......@@ -6,7 +6,7 @@ var dom = require('../helpers/dom')
var sources = {
'sources': {
'Untitled.sol': `
'browser/Untitled.sol': `
contract test1 { address test = tx.origin; }
contract test2 {}
contract TooMuchGas {
......@@ -33,13 +33,13 @@ function runTests (browser) {
browser
.waitForElementVisible('.newFile', 10000)
.click('.envView')
contractHelper.testContracts(browser, sources.sources['Untitled.sol'], ['Untitled.sol:TooMuchGas', 'Untitled.sol:test1', 'Untitled.sol:test2'], function () {
contractHelper.testContracts(browser, sources.sources['browser/Untitled.sol'], ['browser/Untitled.sol:TooMuchGas', 'browser/Untitled.sol:test1', 'browser/Untitled.sol:test2'], function () {
browser
.click('.staticanalysisView')
.click('#staticanalysisView button')
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
dom.listSelectorContains(['Untitled.sol:2:33: Use of tx.origin',
'Fallback function of contract Untitled.sol:TooMuchGas requires too much gas'],
dom.listSelectorContains(['browser/Untitled.sol:2:33: Use of tx.origin',
'Fallback function of contract browser/Untitled.sol:TooMuchGas requires too much gas'],
'#staticanalysisresult .warning span',
browser, function () {
browser.end()
......
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