Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
B
baas-ide
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
guxukai
baas-ide
Commits
62cd8797
Commit
62cd8797
authored
Apr 12, 2017
by
chriseth
Committed by
GitHub
Apr 12, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #449 from serapath-contribution/file-explorer
File explorer (WIP)
parents
b0234ae5
f09f5a38
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
676 additions
and
127 deletions
+676
-127
browser-solidity.css
assets/css/browser-solidity.css
+7
-17
index.html
index.html
+4
-3
nightwatch.js
nightwatch.js
+0
-1
package.json
package.json
+3
-28
app.js
src/app.js
+103
-72
editor.js
src/app/editor.js
+21
-0
execution-context.js
src/app/execution-context.js
+0
-0
file-explorer.js
src/app/file-explorer.js
+319
-0
file-panel.js
src/app/file-panel.js
+173
-0
storage.js
src/app/storage.js
+6
-6
fileExplorer.js
test-browser/tests/fileExplorer.js
+40
-0
No files found.
assets/css/browser-solidity.css
View file @
62cd8797
html
{
box-sizing
:
border-box
;
}
*,
*
:before
,
*
:after
{
box-sizing
:
inherit
;
}
body
{
padding
:
0
;
font-size
:
12px
;
...
...
@@ -39,11 +42,11 @@ body {
left
:
0
;
}
.files-wrappe
r
{
#tabs-ba
r
{
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
,
...
...
index.html
View file @
62cd8797
...
...
@@ -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=
"editor-container"
>
<div
id=
"filepanel"
></div>
<div
id=
"input"
></div>
</div>
<div
id=
"dragbar"
></div>
</div>
...
...
nightwatch.js
View file @
62cd8797
...
...
@@ -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
...
...
package.json
View file @
62cd8797
...
...
@@ -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
"
}
}
src/app.js
View file @
62cd8797
/* 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'
)
}
self
.
event
.
trigger
(
'tabChanged'
,
[
cls
])
// ---------------- 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'
}
})
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
}
...
...
src/app/editor.js
View file @
62cd8797
...
...
@@ -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
=
{}
...
...
src/app/execution-context.js
View file @
62cd8797
src/app/file-explorer.js
0 → 100755
View file @
62cd8797
/* 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
}
}
src/app/file-panel.js
0 → 100644
View file @
62cd8797
/* 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
)
}
}
}
src/app/storage.js
View file @
62cd8797
'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
)
}
})
...
...
test-browser/tests/fileExplorer.js
0 → 100644
View file @
62cd8797
'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
()
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment