Commit 7daae650 authored by yann300's avatar yann300 Committed by GitHub

Merge pull request #581 from ethereum/remidintegration

Display system files through remixd
parents ed6fa6b5 9bfd3e65
...@@ -461,3 +461,69 @@ input[type="file"] { ...@@ -461,3 +461,69 @@ input[type="file"] {
.ace_gutter-cell.ace_breakpoint{ .ace_gutter-cell.ace_breakpoint{
background-color: #F77E79; 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}
}
...@@ -2,6 +2,23 @@ ...@@ -2,6 +2,23 @@
set -e set -e
setupRemixd () {
npm install remixd
mkdir remixdSharedfolder
cd remixdSharedfolder
echo "contract test1 { function get () returns (uint) { return 8; }}" > contract1.sol
echo "contract test2 { function get () returns (uint) { return 9; }}" > contract2.sol
mkdir folder1
cd folder1
echo "contract test1 { function get () returns (uint) { return 10; }}" > contract1.sol
echo "contract test2 { function get () returns (uint) { return 11; }}" > contract2.sol
cd ..
echo 'sharing folder: '
echo $PWD
./../node_modules/.bin/remixd -S $PWD &
cd ..
}
SC_VERSION="4.4.0" SC_VERSION="4.4.0"
SAUCECONNECT_URL="https://saucelabs.com/downloads/sc-$SC_VERSION-linux.tar.gz" SAUCECONNECT_URL="https://saucelabs.com/downloads/sc-$SC_VERSION-linux.tar.gz"
SAUCECONNECT_USERNAME="chriseth" SAUCECONNECT_USERNAME="chriseth"
...@@ -12,9 +29,11 @@ TEST_EXITCODE=0 ...@@ -12,9 +29,11 @@ TEST_EXITCODE=0
npm run serve & npm run serve &
setupRemixd
wget "$SAUCECONNECT_URL" wget "$SAUCECONNECT_URL"
tar -zxvf sc-"$SC_VERSION"-linux.tar.gz tar -zxvf sc-"$SC_VERSION"-linux.tar.gz
./sc-"$SC_VERSION"-linux/bin/sc -u "$SAUCECONNECT_USERNAME" -k "$SAUCECONNECT_ACCESSKEY" -i "$SAUCECONNECT_JOBIDENTIFIER" --readyfile "$SAUCECONNECT_READYFILE" & ./sc-"$SC_VERSION"-linux/bin/sc -u "$SAUCECONNECT_USERNAME" -k "$SAUCECONNECT_ACCESSKEY" -i "$SAUCECONNECT_JOBIDENTIFIER" --no-ssl-bump-domains all --readyfile "$SAUCECONNECT_READYFILE" &
while [ ! -f "$SAUCECONNECT_READYFILE" ]; do while [ ! -f "$SAUCECONNECT_READYFILE" ]; do
sleep .5 sleep .5
done done
......
...@@ -53,6 +53,18 @@ ...@@ -53,6 +53,18 @@
</div> </div>
<script src="build/app.js"></script> <script src="build/app.js"></script>
<div id="modaldialog" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2></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> </body>
</html> </html>
This diff is collapsed.
...@@ -19,6 +19,7 @@ function Config (storage) { ...@@ -19,6 +19,7 @@ function Config (storage) {
} }
this.get = function (key) { this.get = function (key) {
this.ensureStorageUpdated(key)
return this.items[key] return this.items[key]
} }
...@@ -29,6 +30,14 @@ function Config (storage) { ...@@ -29,6 +30,14 @@ function Config (storage) {
} catch (exception) { } catch (exception) {
} }
} }
this.ensureStorageUpdated = function (key) {
if (key === 'currentFile') {
if (this.items[key] && this.items[key] !== '' && this.items[key].indexOf('browser/') !== 0 && this.items[key].indexOf('localhost/') !== 0) {
this.items[key] = 'browser/' + this.items[key]
}
}
}
} }
module.exports = Config module.exports = Config
...@@ -59,6 +59,12 @@ function Editor (editorElement) { ...@@ -59,6 +59,12 @@ function Editor (editorElement) {
e.stop() e.stop()
}) })
this.displayEmptyReadOnlySession = function () {
currentSession = null
editor.setSession(emptySession)
editor.setReadOnly(true)
}
this.setBreakpoint = function (row, css) { this.setBreakpoint = function (row, css) {
editor.session.setBreakpoint(row, css) editor.session.setBreakpoint(row, css)
} }
...@@ -67,6 +73,12 @@ function Editor (editorElement) { ...@@ -67,6 +73,12 @@ function Editor (editorElement) {
editor.setFontSize(editor.getFontSize() + incr) editor.setFontSize(editor.getFontSize() + incr)
} }
this.setText = function (text) {
if (currentSession && sessions[currentSession]) {
sessions[currentSession].setValue(text)
}
}
function createSession (content) { function createSession (content) {
var s = new ace.EditSession(content, 'ace/mode/javascript') var s = new ace.EditSession(content, 'ace/mode/javascript')
s.setUndoManager(new ace.UndoManager()) s.setUndoManager(new ace.UndoManager())
...@@ -87,6 +99,8 @@ function Editor (editorElement) { ...@@ -87,6 +99,8 @@ function Editor (editorElement) {
var session = createSession(content) var session = createSession(content)
sessions[path] = session sessions[path] = session
readOnlySessions[path] = false readOnlySessions[path] = false
} else if (sessions[path].getValue() !== content) {
sessions[path].setValue(content)
} }
switchSession(path) switchSession(path)
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var Treeview = require('ethereum-remix').ui.TreeView var Treeview = require('ethereum-remix').ui.TreeView
var modalDialog = require('./modaldialog')
var EventManager = require('ethereum-remix').lib.EventManager var EventManager = require('ethereum-remix').lib.EventManager
...@@ -33,6 +34,30 @@ var css = csjs` ...@@ -33,6 +34,30 @@ var css = csjs`
module.exports = fileExplorer module.exports = fileExplorer
function fileExplorer (appAPI, files) { function fileExplorer (appAPI, files) {
this.files = files
function remixdDialog () {
return yo`<div>This file has been changed outside of Remix IDE.</div>`
}
this.files.event.register('fileExternallyChanged', (path, content) => {
if (appAPI.currentFile() === path && appAPI.currentContent() !== content) {
modalDialog(path + ' changed', remixdDialog(),
{
label: 'Keep the content displayed in Remix',
fn: () => {}
},
{
label: 'Replace by the new content',
fn: () => {
appAPI.setText(content)
}
}
)
}
})
var self = this
var fileEvents = files.event var fileEvents = files.event
var treeView = new Treeview({ var treeView = new Treeview({
extractData: function (value, tree, key) { extractData: function (value, tree, key) {
...@@ -55,8 +80,10 @@ function fileExplorer (appAPI, files) { ...@@ -55,8 +80,10 @@ function fileExplorer (appAPI, files) {
} }
}, },
formatSelf: function (key, data) { 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}" data-path="${data.path}"
style="${isRoot ? 'font-weight:bold;' : ''}"
onload=${function (el) { adaptEnvironment(el, focus, hover) }} onload=${function (el) { adaptEnvironment(el, focus, hover) }}
onunload=${function (el) { unadaptEnvironment(el, focus, hover) }} onunload=${function (el) { unadaptEnvironment(el, focus, hover) }}
onclick=${editModeOn} onclick=${editModeOn}
...@@ -66,30 +93,35 @@ function fileExplorer (appAPI, files) { ...@@ -66,30 +93,35 @@ function fileExplorer (appAPI, files) {
} }
}) })
this.treeView = treeView
var deleteButton = yo` var deleteButton = yo`
<span class=${css.remove} onclick=${deletePath}> <span class=${css.remove} onclick=${deletePath}>
<i class="fa fa-trash" aria-hidden="true"></i> <i class="fa fa-trash" aria-hidden="true"></i>
</span> </span>
` `
appAPI.event.register('currentFileChanged', (newFile) => { appAPI.event.register('currentFileChanged', (newFile, explorer) => {
fileFocus(newFile) if (explorer === files) {
fileFocus(newFile)
} else {
unfocus(focusElement)
}
}) })
fileEvents.register('fileRemoved', fileRemoved) fileEvents.register('fileRemoved', fileRemoved)
fileEvents.register('fileRenamed', fileRenamed) fileEvents.register('fileRenamed', fileRenamed)
fileEvents.register('fileRenamedError', fileRenamedError)
fileEvents.register('fileAdded', fileAdded) fileEvents.register('fileAdded', fileAdded)
var filepath = null var filepath = null
var focusElement = null var focusElement = null
var textUnderEdit = null var textUnderEdit = null
var element = treeView.render(files.listAsTree())
element.className = css.fileexplorer
var events = new EventManager() var events = new EventManager()
this.events = events
var api = {} var api = {}
api.addFile = function addFile (file) { 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?')) { if (!files.exists(name) || confirm('The file ' + name + ' already exists! Would you like to overwrite it?')) {
var fileReader = new FileReader() var fileReader = new FileReader()
fileReader.onload = function (event) { fileReader.onload = function (event) {
...@@ -100,12 +132,13 @@ function fileExplorer (appAPI, files) { ...@@ -100,12 +132,13 @@ function fileExplorer (appAPI, files) {
fileReader.readAsText(file) fileReader.readAsText(file)
} }
} }
this.api = api
function focus (event) { function focus (event) {
event.cancelBubble = true event.cancelBubble = true
var li = this var li = this
if (focusElement === li) return if (focusElement === li) return
if (focusElement) focusElement.classList.toggle(css.hasFocus) unfocus(focusElement)
focusElement = li focusElement = li
focusElement.classList.toggle(css.hasFocus) focusElement.classList.toggle(css.hasFocus)
var label = getLabelFrom(li) var label = getLabelFrom(li)
...@@ -114,7 +147,15 @@ function fileExplorer (appAPI, files) { ...@@ -114,7 +147,15 @@ function fileExplorer (appAPI, files) {
if (isFile) events.trigger('focus', [filepath]) if (isFile) events.trigger('focus', [filepath])
} }
function unfocus (el) {
if (focusElement) focusElement.classList.toggle(css.hasFocus)
focusElement = null
}
function hover (event) { function hover (event) {
var path = this.querySelector('label').dataset.path
if (path === self.files.type) return // can't delete the root node
if (event.type === 'mouseout') { if (event.type === 'mouseout') {
var exitedTo = event.toElement || event.relatedTarget var exitedTo = event.toElement || event.relatedTarget
if (this.contains(exitedTo)) return if (this.contains(exitedTo)) return
...@@ -128,7 +169,7 @@ function fileExplorer (appAPI, files) { ...@@ -128,7 +169,7 @@ function fileExplorer (appAPI, files) {
} }
function getElement (path) { 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) if (label) return getLiFrom(label)
} }
...@@ -142,7 +183,7 @@ function fileExplorer (appAPI, files) { ...@@ -142,7 +183,7 @@ function fileExplorer (appAPI, files) {
if (isFolder) path += '/' if (isFolder) path += '/'
if (confirm(`Do you really want to delete "${path}" ?`)) { if (confirm(`Do you really want to delete "${path}" ?`)) {
li.parentElement.removeChild(li) li.parentElement.removeChild(li)
removeSubtree(files, path) removeSubtree(files, path, isFolder)
} }
} }
...@@ -160,12 +201,26 @@ function fileExplorer (appAPI, files) { ...@@ -160,12 +201,26 @@ function fileExplorer (appAPI, files) {
function editModeOff (event) { function editModeOff (event) {
var label = this 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.className.indexOf('folder') !== -1
var save = textUnderEdit !== label.innerText 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 && event.which !== 13) save = confirm('Do you want to rename?')
if (save) renameSubtree(label) if (save) {
else label.innerText = textUnderEdit 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.removeAttribute('contenteditable')
label.classList.remove(css.rename) label.classList.remove(css.rename)
} }
...@@ -205,8 +260,6 @@ function fileExplorer (appAPI, files) { ...@@ -205,8 +260,6 @@ function fileExplorer (appAPI, files) {
var path = label.dataset.path var path = label.dataset.path
var newName = path.replace(oldPath, newPath) var newName = path.replace(oldPath, newPath)
label.dataset.path = newName label.dataset.path = newName
var isFile = label.className.indexOf('file') === 0
if (isFile) files.rename(path, newName)
var ul = li.lastChild var ul = li.lastChild
if (ul.tagName === 'UL') { if (ul.tagName === 'UL') {
updateAllLabels([...ul.children], oldPath, newPath) updateAllLabels([...ul.children], oldPath, newPath)
...@@ -227,7 +280,7 @@ function fileExplorer (appAPI, files) { ...@@ -227,7 +280,7 @@ function fileExplorer (appAPI, files) {
if (li) li.parentElement.removeChild(li) if (li) li.parentElement.removeChild(li)
} }
function fileRenamed (oldName, newName) { function fileRenamed (oldName, newName, isFolder) {
var li = getElement(oldName) var li = getElement(oldName)
if (li) { if (li) {
oldName = oldName.split('/') oldName = oldName.split('/')
...@@ -244,16 +297,16 @@ function fileExplorer (appAPI, files) { ...@@ -244,16 +297,16 @@ function fileExplorer (appAPI, files) {
} }
} }
function fileRenamedError (error) {
alert(error)
}
function fileAdded (filepath) { function fileAdded (filepath) {
var el = treeView.render(files.listAsTree()) var el = treeView.render(files.listAsTree())
el.className = css.fileexplorer el.className = css.fileexplorer
element.parentElement.replaceChild(el, element) self.element.parentElement.replaceChild(el, self.element)
element = el self.element = el
} }
element.events = events
element.api = api
return element
} }
/****************************************************************************** /******************************************************************************
HELPER FUNCTIONS HELPER FUNCTIONS
...@@ -290,7 +343,7 @@ function getLabelFrom (li) { ...@@ -290,7 +343,7 @@ function getLabelFrom (li) {
return li.children[0].children[1].children[0] return li.children[0].children[1].children[0]
} }
function removeSubtree (files, path) { function removeSubtree (files, path, isFolder) {
var parts = path.split('/') var parts = path.split('/')
var isFile = parts[parts.length - 1].length var isFile = parts[parts.length - 1].length
var removePaths = isFile ? [path] : Object.keys(files.list()).filter(keep) var removePaths = isFile ? [path] : Object.keys(files.list()).filter(keep)
...@@ -304,6 +357,7 @@ function removeSubtree (files, path) { ...@@ -304,6 +357,7 @@ function removeSubtree (files, path) {
}) })
files.remove(path) files.remove(path)
}) })
if (isFolder) files.remove(path)
} }
function expandPathTo (li) { function expandPathTo (li) {
...@@ -312,3 +366,16 @@ function expandPathTo (li) { ...@@ -312,3 +366,16 @@ function expandPathTo (li) {
if (caret.classList.contains('fa-caret-right')) caret.click() // expand 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') ...@@ -4,6 +4,7 @@ var yo = require('yo-yo')
var EventManager = require('ethereum-remix').lib.EventManager var EventManager = require('ethereum-remix').lib.EventManager
var FileExplorer = require('./file-explorer') var FileExplorer = require('./file-explorer')
var modalDialog = require('./modaldialog')
module.exports = filepanel module.exports = filepanel
...@@ -28,6 +29,9 @@ var css = csjs` ...@@ -28,6 +29,9 @@ var css = csjs`
.newFile { .newFile {
padding : 10px; padding : 10px;
} }
.connectToLocalhost {
padding : 10px;
}
.uploadFile { .uploadFile {
padding : 10px; padding : 10px;
} }
...@@ -46,11 +50,10 @@ var css = csjs` ...@@ -46,11 +50,10 @@ var css = csjs`
} }
.isHidden { .isHidden {
position : absolute; position : absolute;
height : 99% height : 99%;
left : -101%; left : -101%;
} }
.treeview { .treeview {
height : 100%;
background-color : white; background-color : white;
} }
.dragbar { .dragbar {
...@@ -83,25 +86,39 @@ var limit = 60 ...@@ -83,25 +86,39 @@ var limit = 60
var canUpload = window.File || window.FileReader || window.FileList || window.Blob var canUpload = window.File || window.FileReader || window.FileList || window.Blob
var ghostbar = yo`<div class=${css.ghostbar}></div>` var ghostbar = yo`<div class=${css.ghostbar}></div>`
function filepanel (appAPI, files) { function filepanel (appAPI, filesProvider) {
var fileExplorer = new FileExplorer(appAPI, files) var fileExplorer = new FileExplorer(appAPI, filesProvider['browser'])
var fileSystemExplorer = new FileExplorer(appAPI, filesProvider['localhost'])
var dragbar = yo`<div onmousedown=${mousedown} class=${css.dragbar}></div>` 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 target="_blank" href="http://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html">http://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.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 () { function template () {
return yo` return yo`
<div class=${css.container}> <div class=${css.container}>
<div class=${css.fileexplorer}> <div class=${css.fileexplorer}>
<div class=${css.menu}> <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> <i class="fa fa-plus-circle"></i>
</span> </span>
${canUpload ? yo` ${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"> <label class="fa fa-folder-open">
<input type="file" onchange=${uploadFile} multiple /> <input type="file" onchange=${uploadFile} multiple />
</label> </label>
</span> </span>
` : ''} ` : ''}
<span onclick=${connectToLocalhost} class="${css.connectToLocalhost}">
<i class="websocketconn fa fa-link" title="Connect to Localhost"></i>
</span>
<span class=${css.changeeditorfontsize} > <span class=${css.changeeditorfontsize} >
<i class="increditorsize fa fa-plus" aria-hidden="true" title="increase editor font size"></i> <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> <i class="decreditorsize fa fa-minus" aria-hidden="true" title="decrease editor font size"></i>
...@@ -110,7 +127,8 @@ function filepanel (appAPI, files) { ...@@ -110,7 +127,8 @@ function filepanel (appAPI, files) {
<i class="fa fa-angle-double-left"></i> <i class="fa fa-angle-double-left"></i>
</span> </span>
</div> </div>
<div class=${css.treeview}>${fileExplorer}</div> <div class=${css.treeview}>${fileExplorer.init()}</div>
<div class="filesystemexplorer ${css.treeview}"></div>
</div> </div>
${dragbar} ${dragbar}
</div> </div>
...@@ -121,6 +139,33 @@ function filepanel (appAPI, files) { ...@@ -121,6 +139,33 @@ function filepanel (appAPI, files) {
var element = template() var element = template()
element.querySelector('.increditorsize').addEventListener('click', () => { appAPI.editorFontSize(1) }) element.querySelector('.increditorsize').addEventListener('click', () => { appAPI.editorFontSize(1) })
element.querySelector('.decreditorsize').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 // TODO please do not add custom javascript objects, which have no
// relation to the DOM to DOM nodes // relation to the DOM to DOM nodes
element.events = events element.events = events
...@@ -128,6 +173,10 @@ function filepanel (appAPI, files) { ...@@ -128,6 +173,10 @@ function filepanel (appAPI, files) {
appAPI.switchToFile(path) appAPI.switchToFile(path)
}) })
fileSystemExplorer.events.register('focus', function (path) {
appAPI.switchToFile(path)
})
return element return element
function toggle (event) { function toggle (event) {
...@@ -182,11 +231,41 @@ function filepanel (appAPI, files) { ...@@ -182,11 +231,41 @@ function filepanel (appAPI, files) {
} }
function createNewFile () { function createNewFile () {
var newName = appAPI.createName('Untitled') var newName = filesProvider['browser'].type + '/' + appAPI.createName('Untitled.sol')
if (!files.set(newName, '')) { if (!filesProvider['browser'].set(newName, '')) {
alert('Failed to create file ' + newName) alert('Failed to create file ' + newName)
} else { } else {
appAPI.switchToFile(newName) 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(),
{ label: 'Connect',
fn: () => {
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) { ...@@ -6,40 +6,52 @@ function Files (storage) {
var event = new EventManager() var event = new EventManager()
this.event = event this.event = event
var readonly = {} var readonly = {}
this.type = 'browser'
this.exists = function (path) { this.exists = function (path) {
var unprefixedpath = this.removePrefix(path)
// NOTE: ignore the config file // NOTE: ignore the config file
if (path === '.remix.config') { if (path === '.remix.config') {
return false return false
} }
return this.isReadOnly(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 // NOTE: ignore the config file
if (path === '.remix.config') { if (path === '.remix.config') {
return null 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) { this.set = function (path, content) {
var unprefixedpath = this.removePrefix(path)
// NOTE: ignore the config file // NOTE: ignore the config file
if (path === '.remix.config') { if (path === '.remix.config') {
return false return false
} }
if (!this.isReadOnly(path)) { if (!this.isReadOnly(unprefixedpath)) {
var exists = storage.exists(path) var exists = storage.exists(unprefixedpath)
if (!storage.set(path, content)) { if (!storage.set(unprefixedpath, content)) {
return false return false
} }
if (!exists) { if (!exists) {
event.trigger('fileAdded', [path, false]) event.trigger('fileAdded', [this.type + '/' + unprefixedpath, false])
} else { } else {
event.trigger('fileChanged', [path]) event.trigger('fileChanged', [this.type + '/' + unprefixedpath])
} }
return true return true
} }
...@@ -48,9 +60,10 @@ function Files (storage) { ...@@ -48,9 +60,10 @@ function Files (storage) {
} }
this.addReadOnly = function (path, content) { this.addReadOnly = function (path, content) {
if (!storage.exists(path)) { var unprefixedpath = this.removePrefix(path)
readonly[path] = content if (!storage.exists(unprefixedpath)) {
event.trigger('fileAdded', [path, true]) readonly[unprefixedpath] = content
event.trigger('fileAdded', [this.type + '/' + unprefixedpath, true])
return true return true
} }
...@@ -58,31 +71,35 @@ function Files (storage) { ...@@ -58,31 +71,35 @@ function Files (storage) {
} }
this.isReadOnly = function (path) { this.isReadOnly = function (path) {
path = this.removePrefix(path)
return readonly[path] !== undefined return readonly[path] !== undefined
} }
this.remove = function (path) { this.remove = function (path) {
if (!this.exists(path)) { var unprefixedpath = this.removePrefix(path)
if (!this.exists(unprefixedpath)) {
return false return false
} }
if (this.isReadOnly(path)) { if (this.isReadOnly(unprefixedpath)) {
readonly[path] = undefined readonly[unprefixedpath] = undefined
} else { } else {
if (!storage.remove(path)) { if (!storage.remove(unprefixedpath)) {
return false return false
} }
} }
event.trigger('fileRemoved', [path]) event.trigger('fileRemoved', [this.type + '/' + unprefixedpath])
return true return true
} }
this.rename = function (oldPath, newPath) { this.rename = function (oldPath, newPath, isFolder) {
if (!this.isReadOnly(oldPath) && storage.exists(oldPath)) { var unprefixedoldPath = this.removePrefix(oldPath)
if (!storage.rename(oldPath, newPath)) { var unprefixednewPath = this.removePrefix(newPath)
if (!this.isReadOnly(unprefixedoldPath) && storage.exists(unprefixedoldPath)) {
if (!storage.rename(unprefixedoldPath, unprefixednewPath)) {
return false return false
} }
event.trigger('fileRenamed', [oldPath, newPath]) event.trigger('fileRenamed', [this.type + '/' + unprefixedoldPath, this.type + '/' + unprefixednewPath, isFolder])
return true return true
} }
return false return false
...@@ -92,21 +109,25 @@ function Files (storage) { ...@@ -92,21 +109,25 @@ function Files (storage) {
var files = {} var files = {}
// add r/w files to the list // 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 // NOTE: as a temporary measure do not show the config file
if (path !== '.remix.config') { if (path !== '.remix.config') {
files[path] = false files[this.type + '/' + path] = false
} }
}) })
// add r/o files to the list // add r/o files to the list
Object.keys(readonly).forEach(function (path) { Object.keys(readonly).forEach((path) => {
files[path] = true files[this.type + '/' + path] = true
}) })
return files return files
} }
this.removePrefix = function (path) {
return path.indexOf(this.type + '/') === 0 ? path.replace(this.type + '/', '') : path
}
// //
// Tree model for files // Tree model for files
// { // {
...@@ -146,7 +167,6 @@ function Files (storage) { ...@@ -146,7 +167,6 @@ function Files (storage) {
'/content': self.get(path) '/content': self.get(path)
}) })
}) })
return tree return tree
} }
......
'use strict'
var async = require('async')
var EventManager = require('ethereum-remix').lib.EventManager
class SharedFolder {
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 === 'sharedfolder') {
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('sharedfolder', '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('sharedfolder', 'list', {}, (error, filesList) => {
if (error) {
cb(error)
} else {
this.files = {}
for (var k in filesList) {
this.files[this.type + '/' + k] = filesList[k]
}
listAsTree(this, this.files, (error, tree) => {
this.filesTree = tree
cb(error)
})
}
})
}
exists (path) {
if (!this.files) return false
return this.files[path] !== undefined
}
get (path, cb) {
var unprefixedpath = this.removePrefix(path)
this.remixd.call('sharedfolder', 'get', {path: unprefixedpath}, (error, content) => {
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('sharedfolder', '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('sharedfolder', '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('sharedfolder', '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 = SharedFolder
...@@ -41,7 +41,7 @@ function Storage (prefix) { ...@@ -41,7 +41,7 @@ function Storage (prefix) {
return safeKeys() return safeKeys()
// filter any names not including the prefix // filter any names not including the prefix
.filter(function (item) { return item.indexOf(prefix, 0) === 0 }) .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) }) .map(function (item) { return item.substr(prefix.length) })
} }
......
module.exports = (title, content, ok, cancel) => {
var okDiv = document.getElementById('modal-footer-ok')
var cancelDiv = document.getElementById('modal-footer-cancel')
okDiv.innerHTML = (ok && ok.label !== undefined) ? ok.label : 'OK'
cancelDiv.innerHTML = (cancel && cancel.label !== undefined) ? cancel.label : 'Cancel'
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 okListenner () {
hide()
if (ok && ok.fn) ok.fn()
removeEventListener()
}
function cancelListenner () {
hide()
if (cancel && cancel.fn) cancel.fn()
removeEventListener()
}
function hide () {
container.style.display = 'none'
}
function show () {
container.style.display = 'block'
}
function removeEventListener () {
okDiv.removeEventListener('click', okListenner)
cancelDiv.removeEventListener('click', cancelListenner)
}
okDiv.addEventListener('click', okListenner)
cancelDiv.addEventListener('click', cancelListenner)
}
'use strict'
var EventManager = require('ethereum-remix').lib.EventManager
var modalDialog = require('../app/modaldialog')
var yo = require('yo-yo')
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) {
function remixdDialog () {
return yo`<div>Connection to Remixd closed. Localhost connection not available anymore.</div>`
}
if (this.connected) {
modalDialog('Lost connection to Remixd!', remixdDialog(), {}, {label: ''})
}
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) { ...@@ -280,13 +280,13 @@ UniversalDApp.prototype.getContractByName = function (contractName) {
} }
UniversalDApp.prototype.getCreateInterface = function ($container, contract) { UniversalDApp.prototype.getCreateInterface = function ($container, contract) {
function remove () {
self.$el.remove()
}
var self = this var self = this
var createInterface = yo`<div class="create"></div>` var createInterface = yo`<div class="create"></div>`
if (self.options.removable) { if (self.options.removable) {
var close = yo`<div class="udapp-close" onclick=${remove}></div>` var close = yo`<div class="udapp-close" onclick=${remove}></div>`
function remove () {
self.$el.remove()
}
createInterface.appendChild(close) createInterface.appendChild(close)
} }
...@@ -348,10 +348,10 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar ...@@ -348,10 +348,10 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar
var appendFunctions = function (address, $el) { var appendFunctions = function (address, $el) {
if ($el) $el = $el.get(0) if ($el) $el = $el.get(0)
function remove () { $instance.remove() }
var $instance = $(`<div class="instance ${cssInstance.instance}"/>`) var $instance = $(`<div class="instance ${cssInstance.instance}"/>`)
if (self.options.removable_instances) { if (self.options.removable_instances) {
var close = yo`<div class="udapp-close" onclick=${remove}></div>` var close = yo`<div class="udapp-close" onclick=${remove}></div>`
function remove () { $instance.remove() }
$instance.get(0).appendChild(close) $instance.get(0).appendChild(close)
} }
var context = self.executionContext.isVM() ? 'memory' : 'blockchain' var context = self.executionContext.isVM() ? 'memory' : 'blockchain'
......
...@@ -21,7 +21,7 @@ function testContracts (browser, contractCode, compiledContractNames, callback) ...@@ -21,7 +21,7 @@ function testContracts (browser, contractCode, compiledContractNames, callback)
.clearValue('#input textarea') .clearValue('#input textarea')
.click('.newFile') .click('.newFile')
.setValue('#input textarea', contractCode, function () {}) .setValue('#input textarea', contractCode, function () {})
.waitForElementPresent('.udapp .create', 5000, true, function () { .waitForElementPresent('.udapp .create', 50000, true, function () {
checkCompiledContracts(browser, compiledContractNames, callback) checkCompiledContracts(browser, compiledContractNames, callback)
}) })
} }
...@@ -6,7 +6,7 @@ var sauce = require('./sauce') ...@@ -6,7 +6,7 @@ var sauce = require('./sauce')
var sources = { var sources = {
'sources': { 'sources': {
'Untitled.sol': examples.ballot.content 'browser/Untitled.sol': examples.ballot.content
} }
} }
...@@ -27,7 +27,7 @@ function runTests (browser, testData) { ...@@ -27,7 +27,7 @@ function runTests (browser, testData) {
browser browser
.waitForElementVisible('.newFile', 10000) .waitForElementVisible('.newFile', 10000)
.click('.envView') .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() browser.end()
}) })
} }
...@@ -5,7 +5,7 @@ var sauce = require('./sauce') ...@@ -5,7 +5,7 @@ var sauce = require('./sauce')
var sources = { var sources = {
'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; } }` contract TestContract { function f() returns (uint) { return 8; } }`
} }
} }
...@@ -27,7 +27,7 @@ function runTests (browser) { ...@@ -27,7 +27,7 @@ function runTests (browser) {
browser browser
.waitForElementVisible('.newFile', 10000) .waitForElementVisible('.newFile', 10000)
.click('.envView') .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') browser.click('.create .constructor .call')
.waitForElementPresent('.instance .call[title="f"]') .waitForElementPresent('.instance .call[title="f"]')
.click('.instance .call[title="f"]') .click('.instance .call[title="f"]')
......
...@@ -6,17 +6,17 @@ var sauce = require('./sauce') ...@@ -6,17 +6,17 @@ var sauce = require('./sauce')
var sources = { var sources = {
'sources': { 'sources': {
'ballot.sol': examples.ballot.content, 'browser/ballot.sol': examples.ballot.content,
'test/client/credit.sol': '', 'browser/test/client/credit.sol': '',
'src/voting.sol': '', 'browser/src/voting.sol': '',
'src/leasing.sol': '', 'browser/src/leasing.sol': '',
'src/gmbh/contract.sol': false, 'browser/src/gmbh/contract.sol': false,
'src/gmbh/test.sol': false, 'browser/src/gmbh/test.sol': false,
'src/gmbh/company.sol': false, 'browser/src/gmbh/company.sol': false,
'src/gmbh/node_modules/ballot.sol': false, 'browser/src/gmbh/node_modules/ballot.sol': false,
'src/ug/finance.sol': false, 'browser/src/ug/finance.sol': false,
'app/solidity/mode.sol': true, 'browser/app/solidity/mode.sol': true,
'app/ethereum/constitution.sol': true 'browser/app/ethereum/constitution.sol': true
} }
} }
......
'use strict'
var contractHelper = require('../helpers/contracts')
var init = require('../helpers/init')
var sauce = require('./sauce')
var sources = {
'sources': {
'localhost/folder1/contract2.sol': `contract test2 { function get () returns (uint) { return 11; }}`
}
}
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'@sources': function () {
return sources
},
'SharedFolderExplorer': function (browser) {
runTests(browser)
},
tearDown: sauce
}
function runTests (browser, testData) {
if (browser.options.desiredCapabilities.browserName === 'safari') {
console.log('don\'t run remixd test for safari: sauce labs doesn\'t seems to handle websocket')
browser.end()
return
}
browser
.waitForElementVisible('.newFile', 10000)
.click('.websocketconn')
.waitForElementVisible('#modal-footer-ok', 10000)
.click('#modal-footer-ok')
.waitForElementVisible('[data-path="localhost"]', 100000)
.click('[data-path="localhost"]')
.click('[data-path="localhost/folder1"]')
.assert.containsText('[data-path="localhost/contract1.sol"]', 'contract1.sol')
.assert.containsText('[data-path="localhost/contract2.sol"]', 'contract2.sol')
.assert.containsText('[data-path="localhost/folder1/contract1.sol"]', 'contract1.sol')
.assert.containsText('[data-path="localhost/folder1/contract2.sol"]', 'contract2.sol')
.click('[data-path="localhost/folder1/contract2.sol"]')
contractHelper.testContracts(browser, sources.sources['localhost/folder1/contract2.sol'], ['localhost/folder1/contract2.sol:test2'], () => {
browser.end()
})
}
...@@ -5,7 +5,7 @@ var sauce = require('./sauce') ...@@ -5,7 +5,7 @@ var sauce = require('./sauce')
var sources = { var sources = {
'sources': { 'sources': {
'Untitled.sol': 'contract test1 {} contract test2 {}' 'browser/Untitled.sol': 'contract test1 {} contract test2 {}'
} }
} }
...@@ -26,7 +26,7 @@ function runTests (browser) { ...@@ -26,7 +26,7 @@ function runTests (browser) {
browser browser
.waitForElementVisible('.newFile', 10000) .waitForElementVisible('.newFile', 10000)
.click('.envView') .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() browser.end()
}) })
} }
...@@ -6,7 +6,7 @@ var dom = require('../helpers/dom') ...@@ -6,7 +6,7 @@ var dom = require('../helpers/dom')
var sources = { var sources = {
'sources': { 'sources': {
'Untitled.sol': ` 'browser/Untitled.sol': `
contract test1 { address test = tx.origin; } contract test1 { address test = tx.origin; }
contract test2 {} contract test2 {}
contract TooMuchGas { contract TooMuchGas {
...@@ -33,13 +33,13 @@ function runTests (browser) { ...@@ -33,13 +33,13 @@ function runTests (browser) {
browser browser
.waitForElementVisible('.newFile', 10000) .waitForElementVisible('.newFile', 10000)
.click('.envView') .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 browser
.click('.staticanalysisView') .click('.staticanalysisView')
.click('#staticanalysisView button') .click('#staticanalysisView button')
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () { .waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
dom.listSelectorContains(['Untitled.sol:2:33: Use of tx.origin', dom.listSelectorContains(['browser/Untitled.sol:2:33: Use of tx.origin',
'Fallback function of contract Untitled.sol:TooMuchGas requires too much gas'], 'Fallback function of contract browser/Untitled.sol:TooMuchGas requires too much gas'],
'#staticanalysisresult .warning span', '#staticanalysisresult .warning span',
browser, function () { browser, function () {
browser.end() 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