parent
a51ff50181
commit
40f8abe827
@ -0,0 +1,318 @@ |
||||
/* 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 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> |
||||
` |
||||
|
||||
fileEvents.register('fileFocus', 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 (confirm(` |
||||
Do you really want to delete "${path}" ? |
||||
${isFolder ? '(and all contained files and folders)' : ''} |
||||
`)) {
|
||||
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 allPaths = Object.keys(files.list()) // @TODO: change `files`
|
||||
var removePaths = allPaths.filter(function (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) |
||||
} |
||||
} |
||||
} |
@ -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…
Reference in new issue