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