Merge pull request #449 from serapath-contribution/file-explorer

File explorer (WIP)
pull/1/head
chriseth 8 years ago committed by GitHub
commit 62cd87978b
  1. 24
      assets/css/browser-solidity.css
  2. 9
      index.html
  3. 1
      nightwatch.js
  4. 31
      package.json
  5. 177
      src/app.js
  6. 21
      src/app/editor.js
  7. 319
      src/app/file-explorer.js
  8. 173
      src/app/file-panel.js
  9. 12
      src/app/storage.js
  10. 40
      test-browser/tests/fileExplorer.js

@ -1,3 +1,6 @@
html { box-sizing: border-box; }
*, *:before, *:after { box-sizing: inherit; }
body {
padding: 0;
font-size: 12px;
@ -39,11 +42,11 @@ body {
left: 0;
}
.files-wrapper {
#tabs-bar {
position: absolute;
overflow: hidden;
top: 0;
left: 5em;
left: 200px;
right: 3em;
}
@ -75,8 +78,6 @@ body {
color: #999;
}
.newFile,
.uploadFile,
.toggleRHP {
display: block;
float: left;
@ -124,18 +125,6 @@ body {
display: inline-block;
}
#input {
border-top: 3px solid #F4F6FF;
padding-top: 0.5em;
font-size: 15px;
position: absolute;
top: 2.5em;
left: 0;
right: 0;
bottom: 0;
min-width: 20vw;
}
#righthand-panel {
position: absolute;
top: 0;
@ -161,6 +150,7 @@ body {
float: right;
height: 90%;
background-color: white;
padding-right: 1%;
}
#header #menu {
@ -475,7 +465,7 @@ body {
bottom: 0;
cursor: col-resize;
z-index: 999;
border-right: 3px solid #F4F6FF;
border-right: 2px solid #C6CFF7;
}
#editor .ace-tm .ace_gutter,

@ -41,15 +41,16 @@
<body>
<div id="editor">
<span class="newFile" title="New File"><i class="fa fa-plus-circle" aria-hidden="true"></i></span>
<span class="uploadFile" title="Open local file"><label class="fa fa-folder-open"><input type="file" class="inputFile" multiple /></label></span>
<div class="files-wrapper">
<div id="tabs-bar">
<div class="scroller scroller-left"><i class="fa fa-chevron-left "></i></div>
<div class="scroller scroller-right"><i class="fa fa-chevron-right "></i></div>
<ul id="files" class="nav nav-tabs"></ul>
</div>
<span class="toggleRHP" title="Toggle right hand panel"><i class="fa fa-angle-double-right"></i></span>
<div id="input"></div>
<div id="editor-container">
<div id="filepanel"></div>
<div id="input"></div>
</div>
<div id="dragbar"></div>
</div>

@ -63,7 +63,6 @@ module.exports = {
'browserName': 'internet explorer',
'javascriptEnabled': true,
'acceptSslCerts': true,
'platform': 'WIN8.1',
'version': '11',
'build': 'build-' + TRAVIS_JOB_NUMBER,
'tunnel-identifier': 'browsersolidity_tests_' + TRAVIS_JOB_NUMBER

@ -5,39 +5,14 @@
"description": "Minimalistic browser-based Solidity IDE",
"devDependencies": {
"async": "^2.1.2",
"babel-cli": "^6.16.0",
"babel-eslint": "^7.1.1",
"babel-plugin-check-es2015-constants": "^6.8.0",
"babel-plugin-transform-es2015-arrow-functions": "^6.8.0",
"babel-plugin-transform-es2015-block-scoped-functions": "^6.8.0",
"babel-plugin-transform-es2015-block-scoping": "^6.18.0",
"babel-plugin-transform-es2015-classes": "^6.18.0",
"babel-plugin-transform-es2015-computed-properties": "^6.8.0",
"babel-plugin-transform-es2015-destructuring": "^6.18.0",
"babel-plugin-transform-es2015-duplicate-keys": "^6.8.0",
"babel-plugin-transform-es2015-for-of": "^6.18.0",
"babel-plugin-transform-es2015-function-name": "^6.9.0",
"babel-plugin-transform-es2015-literals": "^6.8.0",
"babel-plugin-transform-es2015-object-super": "^6.8.0",
"babel-plugin-transform-es2015-parameters": "^6.18.0",
"babel-plugin-transform-es2015-shorthand-properties": "^6.18.0",
"babel-plugin-transform-es2015-spread": "^6.8.0",
"babel-plugin-transform-es2015-sticky-regex": "^6.8.0",
"babel-plugin-transform-es2015-template-literals": "^6.8.0",
"babel-plugin-transform-es2015-unicode-regex": "^6.11.0",
"babel-plugin-transform-regenerator": "^6.16.1",
"babel-polyfill": "^6.22.0",
"babel-plugin-yo-yoify": "^0.3.3",
"babel-polyfill": "^6.22.0",
"babel-preset-es2015": "^6.24.0",
"babelify": "^7.3.0",
"brace": "^0.8.0",
"browserify": "^13.0.0",
"browserify-reload": "^1.0.3",
"csjs-inject": "^1.0.1",
"csslint": "^1.0.2",
"ethereum-remix": "https://github.com/ethereum/remix",
"ethereumjs-abi": "https://github.com/ethereumjs/ethereumjs-abi",
@ -160,6 +135,6 @@
"start": "npm-run-all -lpr serve watch onchange",
"test": "standard; npm run csslint; node test/index.js",
"test-browser": "npm-run-all -lpr selenium downloadsolc make-mock-compiler serve browsertest",
"watch": "watchify src/index.js -dv --delay 0 -p browserify-reload -o '| npm run sourcemap'"
"watch": "watchify src/index.js --transform-key=development -dv -p browserify-reload -o build/app.js"
}
}

@ -1,10 +1,11 @@
/* global alert, confirm, prompt, FileReader, Option, Worker, chrome */
/* global alert, confirm, prompt, Option, Worker, chrome */
'use strict'
var async = require('async')
var $ = require('jquery')
var base64 = require('js-base64').Base64
var swarmgw = require('swarmgw')
var csjs = require('csjs-inject')
var QueryParams = require('./app/query-params')
var queryParams = new QueryParams()
@ -24,6 +25,7 @@ var FormalVerification = require('./app/formalVerification')
var EventManager = require('./lib/eventManager')
var StaticAnalysis = require('./app/staticanalysis/staticAnalysisView')
var OffsetToLineColumnConverter = require('./lib/offsetToLineColumnConverter')
var FilePanel = require('./app/file-panel')
var examples = require('./app/example-contracts')
@ -44,10 +46,13 @@ window.addEventListener('message', function (ev) {
var run = function () {
var self = this
this.event = new EventManager()
var storage = new Storage()
var files = new Files(storage)
var config = new Config(storage)
var currentFile
var fileStorage = new Storage('sol:')
var files = new Files(fileStorage)
var config = new Config(fileStorage)
var uiStorage = new Storage('sol-ui:')
var ui = new Files(uiStorage)
ui.set('currentFile', '')
// return all the files, except the temporary/readonly ones
function packageFiles () {
@ -84,12 +89,6 @@ var run = function () {
loadFiles(filesToLoad)
}
// -------- check file upload capabilities -------
if (!(window.File || window.FileReader || window.FileList || window.Blob)) {
$('.uploadFile').remove()
}
// ------------------ gist load ----------------
var loadingFromGist = gistHandler.handleLoad(queryParams.get(), function (gistId) {
@ -159,27 +158,74 @@ var run = function () {
chromeCloudSync()
// ----------------- editor ----------------------
var editor = new Editor(document.getElementById('input'))
// ----------------- tabbed menu -------------------
$('#options li').click(function (ev) {
var $el = $(this)
selectTab($el)
})
var selectTab = function (el) {
var match = /[a-z]+View/.exec(el.get(0).className)
if (!match) return
var cls = match[0]
if (!el.hasClass('active')) {
el.parent().find('li').removeClass('active')
$('#optionViews').attr('class', '').addClass(cls)
el.addClass('active')
// ---------------- FilePanel --------------------
/****************************************************************************
var sources = {
'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
}
Object.keys(sources).forEach(function (key) { files.set(key, sources[key]) })
/****************************************************************************/
var css = csjs`
.filepanel {
display : flex;
width : 200px;
}
`
var filepanel = document.querySelector('#filepanel')
filepanel.className = css.filepanel
var FilePanelAPI = {
createName: createNonClashingName,
switchToFile: switchToFile,
ui: ui.event
}
var el = new FilePanel(FilePanelAPI, files)
filepanel.appendChild(el)
var api = el.api
api.register('ui', function changeLayout (data) {
var value
if (data.type === 'minimize') {
value = -parseInt(window['filepanel'].style.width)
value = (isNaN(value) ? -window['filepanel'].getBoundingClientRect().width : value)
window['filepanel'].style.position = 'absolute'
window['filepanel'].style.left = (value - 5) + 'px'
window['filepanel'].style.width = -value + 'px'
window['tabs-bar'].style.left = '45px'
} else if (data.type === 'maximize') {
value = -parseInt(window['filepanel'].style.left) + 'px'
window['filepanel'].style.position = 'static'
window['filepanel'].style.width = value
window['filepanel'].style.left = ''
window['tabs-bar'].style.left = value
} else {
window['filepanel'].style.width = data.width + 'px'
window['tabs-bar'].style.left = data.width + 'px'
}
self.event.trigger('tabChanged', [cls])
}
})
api.register('focus', function (path) {
[...window.files.querySelectorAll('.file .name')].forEach(function (span) {
if (span.innerText === path) switchToFile(path) // @TODO: scroll into view
})
})
files.event.register('fileRenamed', function (oldName, newName) {
[...window.files.querySelectorAll('.file .name')].forEach(function (span) {
if (span.innerText === oldName) span.innerText = newName
})
})
files.event.register('fileRemoved', function (path) {
if (path === ui.get('currentFile')) ui.set('currentFile', '')
})
// ------------------ gist publish --------------
$('#gist').click(function () {
@ -221,39 +267,26 @@ var run = function () {
}).appendTo('body')
})
// ----------------- file selector-------------
var $filesEl = $('#files')
var FILE_SCROLL_DELTA = 300
$('.newFile').on('click', function () {
var newName = createNonClashingName('Untitled')
if (!files.set(newName, '')) {
alert('Failed to create file ' + newName)
} else {
switchToFile(newName)
}
// ---------------- tabbed menu ------------------
$('#options li').click(function (ev) {
var $el = $(this)
selectTab($el)
})
// ----------------- file upload -------------
$('.inputFile').on('change', function () {
var fileList = $('input.inputFile')[0].files
for (var i = 0; i < fileList.length; i++) {
var name = fileList[i].name
if (!files.exists(name) || confirm('The file ' + name + ' already exists! Would you like to overwrite it?')) {
var fileReader = new FileReader()
fileReader.onload = function (ev) {
if (!files.set(name, ev.target.result)) {
alert('Failed to create file ' + name)
} else {
switchToFile(name)
}
}
fileReader.readAsText(fileList[i])
}
var selectTab = function (el) {
var match = /[a-z]+View/.exec(el.get(0).className)
if (!match) return
var cls = match[0]
if (!el.hasClass('active')) {
el.parent().find('li').removeClass('active')
$('#optionViews').attr('class', '').addClass(cls)
el.addClass('active')
}
})
self.event.trigger('tabChanged', [cls])
}
var $filesEl = $('#files')
var FILE_SCROLL_DELTA = 300
// Switch tab
$filesEl.on('click', '.file:not(.active)', function (ev) {
@ -289,7 +322,7 @@ var run = function () {
if (!files.rename(originalName, newName)) {
alert('Error while renaming file')
} else {
currentFile = null
ui.set('currentFile', '')
switchToFile(newName)
editor.discard(originalName)
}
@ -310,7 +343,7 @@ var run = function () {
if (!files.remove(name)) {
alert('Error while removing file')
} else {
currentFile = null
ui.set('currentFile', '')
switchToNextFile()
editor.discard(name)
}
@ -323,7 +356,7 @@ var run = function () {
function switchToFile (file) {
editorSyncFile()
currentFile = file
ui.set('currentFile', file)
if (files.isReadOnly(file)) {
editor.openReadOnly(file, files.get(file))
@ -354,10 +387,10 @@ var run = function () {
$filesEl.append($('<li class="file"><span class="name">' + name + '</span><span class="remove"><i class="fa fa-close"></i></span></li>'))
}
var currentFileOpen = !!currentFile
var currentFileOpen = !!ui.get('currentFile')
if (currentFileOpen) {
var active = $('#files .file').filter(function () { return $(this).find('.name').text() === currentFile })
var active = $('#files .file').filter(function () { return $(this).find('.name').text() === ui.get('currentFile') })
active.addClass('active')
}
$('#input').toggle(currentFileOpen)
@ -368,7 +401,6 @@ var run = function () {
})
}
var $filesWrapper = $('.files-wrapper')
var $scrollerRight = $('.scroller-right')
var $scrollerLeft = $('.scroller-left')
@ -381,12 +413,8 @@ var run = function () {
return itemsWidth
}
// function widthOfHidden () {
// return ($filesWrapper.outerWidth() - widthOfList() - getLeftPosi())
// }
function widthOfVisible () {
return $filesWrapper.outerWidth()
return document.querySelector('#editor-container').offsetWidth
}
function getLeftPosi () {
@ -610,7 +638,7 @@ var run = function () {
this.fullLineMarker = null
if (lineColumnPos) {
var source = compiler.lastCompilationResult.data.sourceList[location.file] // auto switch to that tab
if (currentFile !== source) {
if (ui.get('currentFile') !== source) {
switchToFile(source)
}
this.statementMarker = editor.addMarker(lineColumnPos, 'highlightcode')
@ -734,12 +762,12 @@ var run = function () {
var rendererAPI = {
error: (file, error) => {
if (file === currentFile) {
if (file === ui.get('currentFile')) {
editor.addAnnotation(error)
}
},
errorClick: (errFile, errLine, errCol) => {
if (errFile !== currentFile && files.exists(errFile)) {
if (errFile !== ui.get('currentFile') && files.exists(errFile)) {
switchToFile(errFile)
}
editor.gotoLine(errLine, errCol)
@ -788,6 +816,7 @@ var run = function () {
function runCompiler () {
editorSyncFile()
var currentFile = ui.get('currentFile')
if (currentFile) {
var target = currentFile
var sources = {}
@ -797,6 +826,7 @@ var run = function () {
}
function editorSyncFile () {
var currentFile = ui.get('currentFile')
if (currentFile) {
var input = editor.get(currentFile)
files.set(currentFile, input)
@ -808,6 +838,7 @@ var run = function () {
var saveTimeout = null
function editorOnChange () {
var currentFile = ui.get('currentFile')
if (!currentFile) {
return
}

@ -2,13 +2,34 @@
var EventManager = require('../lib/eventManager')
var csjs = require('csjs-inject')
var ace = require('brace')
var Range = ace.acequire('ace/range').Range
require('../mode-solidity.js')
var css = csjs`
.editor-container {
display : flex;
position : absolute;
top : 2.5em;
left : 0;
right : 0;
bottom : 0;
min-width : 20vw;
}
.ace-editor {
top : 4px;
border-top : 3px solid transparent;
font-size : 15px;
width : 100%;
}
`
document.querySelector('#editor-container').className = css['editor-container']
function Editor (editorElement) {
var editor = ace.edit(editorElement)
editorElement.editor = editor // required to access the editor during tests
editorElement.className += ' ' + css['ace-editor']
var event = new EventManager()
this.event = event
var sessions = {}

@ -0,0 +1,319 @@
/* global FileReader, confirm, alert */
var yo = require('yo-yo')
var csjs = require('csjs-inject')
var Treeview = require('ethereum-remix').ui.TreeView
var EventManager = require('../lib/eventManager')
var css = csjs`
.fileexplorer {
box-sizing : border-box;
}
.folder,
.file {
font-size : 14px;
}
.hasFocus {
background-color : #F4F6FF;
}
.rename {
background-color : white;
}
.remove {
align-self : center;
padding-left : 10px;
}
.activeMode {
display : flex;
justify-content : space-between;
margin-right : 10px;
padding-right : 19px;
}
ul {
padding : 0;
}
`
module.exports = fileExplorer
function fileExplorer (appAPI, files) {
var fileEvents = files.event
var appUI = appAPI.ui
var tv = new Treeview({
extractData: function (value, tree, key) {
var newValue = {}
// var isReadOnly = false
var isFile = false
Object.keys(value).filter(function keep (x) {
if (x === '/content') isFile = true
// if (x === '/readOnly') isReadOnly = true
if (x[0] !== '/') return true
}).forEach(function (x) { newValue[x] = value[x] })
return {
path: (tree || {}).path ? tree.path + '/' + key : key,
children: isFile ? undefined
: value instanceof Array ? value.map((item, index) => ({
key: index, value: item
})) : value instanceof Object ? Object.keys(value).map(subkey => ({
key: subkey, value: value[subkey]
})) : undefined
}
},
formatSelf: function (key, data) {
return yo`<label class=${data.children ? css.folder : css.file}
data-path="${data.path}"
onload=${function (el) { adaptEnvironment(el, focus, hover) }}
onunload=${function (el) { unadaptEnvironment(el, focus, hover) }}
onclick=${editModeOn}
onkeydown=${editModeOff}
onblur=${editModeOff}
>${key}</label>`
}
})
var deleteButton = yo`
<span class=${css.remove} onclick=${deletePath}>
<i class="fa fa-trash" aria-hidden="true"></i>
</span>
`
appUI.register('currentFile', fileFocus)
fileEvents.register('fileRemoved', fileRemoved)
fileEvents.register('fileRenamed', fileRenamed)
fileEvents.register('fileAdded', fileAdded)
fileEvents.register('fileChanged', fileChanged)
var filepath = null
var focusElement = null
var textUnderEdit = null
var element = tv.render(files.listAsTree())
element.className = css.fileexplorer
var api = new EventManager()
api.addFile = function addFile (file) {
var name = 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) {
var success = files.set(name, event.target.result)
if (!success) alert('Failed to create file ' + name)
else api.trigger('focus', [name])
}
fileReader.readAsText(file)
}
}
function focus (event) {
event.cancelBubble = true
var li = this
if (focusElement === li) return
if (focusElement) focusElement.classList.toggle(css.hasFocus)
focusElement = li
focusElement.classList.toggle(css.hasFocus)
var label = getLabelFrom(li)
var filepath = label.dataset.path
var isFile = label.className.indexOf('file') === 0
if (isFile) api.trigger('focus', [filepath])
}
function hover (event) {
if (event.type === 'mouseout') {
var exitedTo = event.toElement || event.relatedTarget
if (this.contains(exitedTo)) return
this.style.backgroundColor = ''
this.style.paddingRight = '19px'
return this.removeChild(deleteButton)
}
this.style.backgroundColor = '#F4F6FF'
this.style.paddingRight = '0px'
this.appendChild(deleteButton)
}
function getElement (path) {
var label = element.querySelector(`label[data-path="${path}"]`)
if (label) return getLiFrom(label)
}
function deletePath (event) {
event.cancelBubble = true
var span = this
var li = span.parentElement.parentElement
var label = getLabelFrom(li)
var path = label.dataset.path
var isFolder = !!~label.className.indexOf('folder')
if (isFolder) path += '/'
if (confirm(`Do you really want to delete "${path}" ?`)) {
li.parentElement.removeChild(li)
removeSubtree(files, path)
}
}
function editModeOn (event) {
var label = this
var li = getLiFrom(label)
var classes = li.className
if (~classes.indexOf('hasFocus') && !label.getAttribute('contenteditable')) {
textUnderEdit = label.innerText
label.setAttribute('contenteditable', true)
label.classList.add(css.rename)
label.focus()
}
}
function editModeOff (event) {
var label = this
if (event.type === 'blur' || event.which === 27 || event.which === 13) {
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
label.removeAttribute('contenteditable')
label.classList.remove(css.rename)
}
}
function renameSubtree (label, dontcheck) {
var oldPath = label.dataset.path
var newPath = oldPath
newPath = newPath.split('/')
newPath[newPath.length - 1] = label.innerText
newPath = newPath.join('/')
if (!dontcheck) {
var allPaths = Object.keys(files.list())
for (var i = 0, len = allPaths.length, path, err; i < len; i++) {
path = allPaths[i]
if (files.isReadOnly(path)) {
err = 'path contains readonly elements'
break
} else if (path.indexOf(newPath) === 0) {
err = 'new path is conflicting with another existing path'
break
}
}
}
if (err) {
alert(`couldn't rename - ${err}`)
label.innerText = textUnderEdit
} else {
textUnderEdit = label.innerText
updateAllLabels([getElement(oldPath)], oldPath, newPath)
}
}
function updateAllLabels (lis, oldPath, newPath) {
lis.forEach(function (li) {
var label = getLabelFrom(li)
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)
}
})
}
function fileChanged (filepath) { }
function fileFocus (path) {
if (filepath === path) return
filepath = path
var el = getElement(filepath)
expandPathTo(el)
setTimeout(function focusNode () { el.click() }, 0)
}
function fileRemoved (filepath) {
var li = getElement(filepath)
if (li) li.parentElement.removeChild(li)
}
function fileRenamed (oldName, newName) {
var li = getElement(oldName)
if (li) {
oldName = oldName.split('/')
newName = newName.split('/')
var index = oldName.reduce(function (idx, key, i) {
return oldName[i] !== newName[i] ? i : idx
}, undefined)
var newKey = newName[index]
var oldPath = oldName.slice(0, index + 1).join('/')
li = getElement(oldPath)
var label = getLabelFrom(li)
label.innerText = newKey
renameSubtree(label, true)
}
}
function fileAdded (filepath) {
var el = tv.render(files.listAsTree())
el.className = css.fileexplorer
element.parentElement.replaceChild(el, element)
element = el
fileFocus(filepath)
appAPI.switchToFile(filepath)
}
element.api = api
return element
}
/******************************************************************************
HELPER FUNCTIONS
******************************************************************************/
function adaptEnvironment (label, focus, hover) {
var li = getLiFrom(label)
li.style.position = 'relative'
var span = li.firstChild
// add focus
li.addEventListener('click', focus)
// add hover
span.classList.add(css.activeMode)
span.addEventListener('mouseover', hover)
span.addEventListener('mouseout', hover)
}
function unadaptEnvironment (label, focus, hover) {
var li = getLiFrom(label)
var span = li.firstChild
li.style.position = undefined
// remove focus
li.removeEventListener('click', focus)
// remove hover
span.classList.remove(css.activeMode)
span.removeEventListener('mouseover', hover)
span.removeEventListener('mouseout', hover)
}
function getLiFrom (label) {
return label.parentElement.parentElement.parentElement
}
function getLabelFrom (li) {
return li.children[0].children[1].children[0]
}
function removeSubtree (files, path) {
var parts = path.split('/')
var isFile = parts[parts.length - 1].length
var removePaths = isFile ? [path] : Object.keys(files.list()).filter(keep)
function keep (p) { return ~p.indexOf(path) }
removePaths.forEach(function (path) {
[...window.files.querySelectorAll('.file .name')].forEach(function (span) {
if (span.innerText === path) {
var li = span.parentElement
li.parentElement.removeChild(li) // delete tab
}
})
files.remove(path)
})
}
function expandPathTo (li) {
while ((li = li.parentElement.parentElement) && li.tagName === 'LI') {
var caret = li.firstChild.firstChild
if (caret.classList.contains('fa-caret-right')) caret.click() // expand
}
}

@ -0,0 +1,173 @@
/* global alert */
var csjs = require('csjs-inject')
var yo = require('yo-yo')
var EventManager = require('../lib/eventManager')
var FileExplorer = require('./file-explorer')
module.exports = filepanel
var css = csjs`
.container {
display : flex;
flex-direction : row;
width : 100%;
box-sizing : border-box;
}
.fileexplorer {
display : flex;
flex-direction : column;
position : relative;
top : -33px;
width : 100%;
}
.menu {
display : flex;
flex-direction : row;
}
.newFile {
padding : 10px;
}
.uploadFile {
padding : 10px;
}
.toggleLHP {
display : flex;
justify-content : flex-end;
padding : 10px;
width : 100%;
font-weight : bold;
cursor : pointer;
color : black;
}
.isVisible {
position : absolute;
left : 35px;
}
.isHidden {
position : absolute;
height : 99%
left : -101%;
}
.treeview {
height : 100%;
background-color : white;
}
.dragbar {
position : relative;
top : 6px;
cursor : col-resize;
z-index : 999;
border-right : 2px solid #C6CFF7;
}
.ghostbar {
width : 3px;
background-color : #C6CFF7;
opacity : 0.5;
position : absolute;
cursor : col-resize;
z-index : 9999;
top : 0;
bottom : 0;
}
`
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)
var dragbar = yo`<div onmousedown=${mousedown} class=${css.dragbar}></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">
<i class="fa fa-plus-circle"></i>
</span>
${canUpload ? yo`
<span class=${css.uploadFile} title="Open local file">
<label class="fa fa-folder-open">
<input type="file" onchange=${uploadFile} multiple />
</label>
</span>
` : ''}
<span class=${css.toggleLHP} onclick=${toggle} title="Toggle left hand panel">
<i class="fa fa-angle-double-left"></i>
</span>
</div>
<div class=${css.treeview}>${fileExplorer}</div>
</div>
${dragbar}
</div>
`
}
var api = new EventManager()
var element = template()
element.api = api
fileExplorer.api.register('focus', function (path) {
api.trigger('focus', [path])
})
return element
function toggle (event) {
var isHidden = element.classList.toggle(css.isHidden)
this.classList.toggle(css.isVisible)
this.children[0].classList.toggle('fa-angle-double-right')
this.children[0].classList.toggle('fa-angle-double-left')
api.trigger('ui', [{ type: isHidden ? 'minimize' : 'maximize' }])
}
function uploadFile (event) {
;[...this.files].forEach(fileExplorer.api.addFile)
}
function mousedown (event) {
event.preventDefault()
if (event.which === 1) {
moveGhostbar(event)
document.body.appendChild(ghostbar)
document.addEventListener('mousemove', moveGhostbar)
document.addEventListener('mouseup', removeGhostbar)
document.addEventListener('keydown', cancelGhostbar)
}
}
function cancelGhostbar (event) {
if (event.keyCode === 27) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
}
}
function moveGhostbar (event) {
var rhp = window['righthand-panel'].offsetLeft
var newpos = (event.pageX < limit) ? limit : event.pageX
newpos = (newpos < (rhp - limit)) ? newpos : (rhp - limit)
ghostbar.style.left = newpos + 'px'
}
function removeGhostbar (event) {
document.body.removeChild(ghostbar)
document.removeEventListener('mousemove', moveGhostbar)
document.removeEventListener('mouseup', removeGhostbar)
document.removeEventListener('keydown', cancelGhostbar)
var width = (event.pageX < limit) ? limit : event.pageX
element.style.width = width + 'px'
api.trigger('ui', [{ type: 'resize', width: width }])
}
function createNewFile () {
var newName = appAPI.createName('Untitled')
if (!files.set(newName, '')) {
alert('Failed to create file ' + newName)
} else {
appAPI.switchToFile(newName)
}
}
}

@ -1,17 +1,17 @@
'use strict'
function Storage () {
function Storage (prefix) {
this.exists = function (name) {
return this.get(name) !== null
}
this.get = function (name) {
return window.localStorage.getItem('sol:' + name)
return window.localStorage.getItem(prefix + name)
}
this.set = function (name, content) {
try {
window.localStorage.setItem('sol:' + name, content)
window.localStorage.setItem(prefix + name, content)
} catch (exception) {
return false
}
@ -19,7 +19,7 @@ function Storage () {
}
this.remove = function (name) {
window.localStorage.removeItem('sol:' + name)
window.localStorage.removeItem(prefix + name)
return true
}
@ -40,7 +40,7 @@ function Storage () {
this.keys = function () {
return safeKeys()
// filter any names not including sol:
.filter(function (item) { return item.indexOf('sol:', 0) === 0 })
.filter(function (item) { return item.indexOf(prefix, 0) === 0 })
// remove sol: from filename
.map(function (item) { return item.replace(/^sol:/, '') })
}
@ -49,7 +49,7 @@ function Storage () {
safeKeys().forEach(function (name) {
if (name.indexOf('sol-cache-file-', 0) === 0) {
var content = window.localStorage.getItem(name)
window.localStorage.setItem(name.replace(/^sol-cache-file-/, 'sol:'), content)
window.localStorage.setItem(name.replace(/^sol-cache-file-/, prefix), content)
window.localStorage.removeItem(name)
}
})

@ -0,0 +1,40 @@
'use strict'
var examples = require('../../src/app/example-contracts')
var init = require('../helpers/init')
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
}
}
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'@sources': function () {
return sources
},
'FileExplorer': function (browser) {
runTests(browser)
},
tearDown: sauce
}
function runTests (browser, testData) {
browser
.waitForElementPresent('#filepanel ul li', 10000, true, function () {})
.end()
}
Loading…
Cancel
Save