You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
627 lines
17 KiB
627 lines
17 KiB
<!--This is the plain html source of the hex encoded Editor-Page embedded in SPIFFSEditor.cpp --> |
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<title>ESP Editor</title> |
|
<style type="text/css" media="screen"> |
|
.cm { |
|
z-index: 300; |
|
position: absolute; |
|
left: 5px; |
|
border: 1px solid #444; |
|
background-color: #F5F5F5; |
|
display: none; |
|
box-shadow: 0 0 10px rgba( 0, 0, 0, .4 ); |
|
font-size: 12px; |
|
font-family: sans-serif; |
|
font-weight:bold; |
|
} |
|
.cm ul { |
|
list-style: none; |
|
top: 0; |
|
left: 0; |
|
margin: 0; |
|
padding: 0; |
|
} |
|
.cm li { |
|
position: relative; |
|
min-width: 60px; |
|
cursor: pointer; |
|
} |
|
.cm span { |
|
color: #444; |
|
display: inline-block; |
|
padding: 6px; |
|
} |
|
.cm li:hover { background: #444; } |
|
.cm li:hover span { color: #EEE; } |
|
.tvu ul, .tvu li { |
|
padding: 0; |
|
margin: 0; |
|
list-style: none; |
|
} |
|
.tvu input { |
|
position: absolute; |
|
opacity: 0; |
|
} |
|
.tvu { |
|
font: normal 12px Verdana, Arial, Sans-serif; |
|
-moz-user-select: none; |
|
-webkit-user-select: none; |
|
user-select: none; |
|
color: #444; |
|
line-height: 16px; |
|
} |
|
.tvu span { |
|
margin-bottom:5px; |
|
padding: 0 0 0 18px; |
|
cursor: pointer; |
|
display: inline-block; |
|
height: 16px; |
|
vertical-align: middle; |
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC') no-repeat; |
|
background-position: 0px 0px; |
|
} |
|
.tvu span:hover { |
|
text-decoration: underline; |
|
} |
|
@media screen and (-webkit-min-device-pixel-ratio:0){ |
|
.tvu{ |
|
-webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s; |
|
} |
|
|
|
@-webkit-keyframes webkit-adjacent-element-selector-bugfix { |
|
from { |
|
padding: 0; |
|
} |
|
to { |
|
padding: 0; |
|
} |
|
} |
|
} |
|
#uploader { |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
left: 0; |
|
height:28px; |
|
line-height: 24px; |
|
padding-left: 10px; |
|
background-color: #444; |
|
color:#EEE; |
|
} |
|
#tree { |
|
position: absolute; |
|
top: 28px; |
|
bottom: 0; |
|
left: 0; |
|
width:160px; |
|
padding: 8px; |
|
} |
|
#editor, #preview { |
|
position: absolute; |
|
top: 28px; |
|
right: 0; |
|
bottom: 0; |
|
left: 160px; |
|
border-left:1px solid #EEE; |
|
} |
|
#preview { |
|
background-color: #EEE; |
|
padding:5px; |
|
} |
|
#loader { |
|
position: absolute; |
|
top: 36%; |
|
right: 40%; |
|
} |
|
.loader { |
|
z-index: 10000; |
|
border: 8px solid #b5b5b5; /* Grey */ |
|
border-top: 8px solid #3498db; /* Blue */ |
|
border-bottom: 8px solid #3498db; /* Blue */ |
|
border-radius: 50%; |
|
width: 240px; |
|
height: 240px; |
|
animation: spin 2s linear infinite; |
|
display:none; |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
</style> |
|
<script> |
|
if (typeof XMLHttpRequest === "undefined") { |
|
XMLHttpRequest = function () { |
|
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {} |
|
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {} |
|
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} |
|
throw new Error("This browser does not support XMLHttpRequest."); |
|
}; |
|
} |
|
|
|
function ge(a){ |
|
return document.getElementById(a); |
|
} |
|
function ce(a){ |
|
return document.createElement(a); |
|
} |
|
|
|
function sortByKey(array, key) { |
|
return array.sort(function(a, b) { |
|
var x = a[key]; var y = b[key]; |
|
return ((x < y) ? -1 : ((x > y) ? 1 : 0)); |
|
}); |
|
} |
|
|
|
|
|
var QueuedRequester = function () { |
|
this.queue = []; |
|
this.running = false; |
|
this.xmlhttp = null; |
|
} |
|
QueuedRequester.prototype = { |
|
_request: function(req){ |
|
this.running = true; |
|
if(!req instanceof Object) return; |
|
var that = this; |
|
|
|
function ajaxCb(x,d){ return function(){ |
|
if (x.readyState == 4){ |
|
ge("loader").style.display = "none"; |
|
d.callback(x.status, x.responseText); |
|
if(that.queue.length === 0) that.running = false; |
|
if(that.running) that._request(that.queue.shift()); |
|
} |
|
}} |
|
|
|
ge("loader").style.display = "block"; |
|
|
|
var p = ""; |
|
if(req.params instanceof FormData){ |
|
p = req.params; |
|
} else if(req.params instanceof Object){ |
|
for (var key in req.params) { |
|
if(p === "") |
|
p += (req.method === "GET")?"?":""; |
|
else |
|
p += "&"; |
|
p += encodeURIComponent(key)+"="+encodeURIComponent(req.params[key]); |
|
}; |
|
} |
|
|
|
this.xmlhttp = new XMLHttpRequest(); |
|
this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req); |
|
if(req.method === "GET"){ |
|
this.xmlhttp.open(req.method, req.url+p, true); |
|
this.xmlhttp.send(); |
|
} else { |
|
this.xmlhttp.open(req.method, req.url, true); |
|
if(p instanceof String) |
|
this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); |
|
this.xmlhttp.send(p); |
|
} |
|
}, |
|
stop: function(){ |
|
if(this.running) this.running = false; |
|
if(this.xmlhttp && this.xmlhttp.readyState < 4){ |
|
this.xmlhttp.abort(); |
|
} |
|
}, |
|
add: function(method, url, params, callback){ |
|
this.queue.push({url:url,method:method,params:params,callback:callback}); |
|
if(!this.running){ |
|
this._request(this.queue.shift()); |
|
} |
|
} |
|
} |
|
|
|
var requests = new QueuedRequester(); |
|
|
|
function createFileUploader(element, tree, editor){ |
|
var xmlHttp; |
|
|
|
var refresh = ce("button"); |
|
refresh.innerHTML = 'Refresh List'; |
|
ge(element).appendChild(refresh); |
|
|
|
var input = ce("input"); |
|
input.type = "file"; |
|
input.multiple = false; |
|
input.name = "data"; |
|
input.id="upload-select"; |
|
ge(element).appendChild(input); |
|
|
|
var path = ce("input"); |
|
path.id = "upload-path"; |
|
path.type = "text"; |
|
path.name = "path"; |
|
path.defaultValue = "/"; |
|
ge(element).appendChild(path); |
|
|
|
var button = ce("button"); |
|
button.innerHTML = 'Upload'; |
|
ge(element).appendChild(button); |
|
|
|
var mkfile = ce("button"); |
|
mkfile.innerHTML = 'Create'; |
|
ge(element).appendChild(mkfile); |
|
|
|
var filename = ce("input"); |
|
filename.id = "editor-filename"; |
|
filename.type = "text"; |
|
filename.disabled= true; |
|
filename.size = 20; |
|
ge(element).appendChild(filename); |
|
|
|
var savefile = ce("button"); |
|
savefile.innerHTML = ' Save ' ; |
|
ge(element).appendChild(savefile); |
|
|
|
function httpPostProcessRequest(status, responseText){ |
|
if(status != 200) |
|
alert("ERROR["+status+"]: "+responseText); |
|
else |
|
tree.refreshPath(path.value); |
|
} |
|
function createPath(p){ |
|
var formData = new FormData(); |
|
formData.append("path", p); |
|
requests.add("PUT", "/edit", formData, httpPostProcessRequest); |
|
} |
|
|
|
mkfile.onclick = function(e){ |
|
createPath(path.value); |
|
editor.loadUrl(path.value); |
|
path.value="/"; |
|
}; |
|
|
|
savefile.onclick = function(e){ |
|
editor.execCommand('saveCommand'); |
|
}; |
|
|
|
refresh.onclick = function(e){ |
|
tree.refreshPath(path.value); |
|
}; |
|
|
|
button.onclick = function(e){ |
|
if(input.files.length === 0){ |
|
return; |
|
} |
|
var formData = new FormData(); |
|
formData.append("data", input.files[0], path.value); |
|
requests.add("POST", "/edit", formData, httpPostProcessRequest); |
|
var uploadPath= ge("upload-path"); |
|
uploadPath.value="/"; |
|
var uploadSelect= ge("upload-select"); |
|
uploadSelect.value=""; |
|
}; |
|
input.onchange = function(e){ |
|
if(input.files.length === 0) return; |
|
var filename = input.files[0].name; |
|
var ext = /(?:\.([^.]+))?$/.exec(filename)[1]; |
|
var name = /(.*)\.[^.]+$/.exec(filename)[1]; |
|
if(typeof name !== undefined){ |
|
filename = name; |
|
} |
|
path.value = "/"+filename+"."+ext; |
|
}; |
|
} |
|
|
|
function createTree(element, editor){ |
|
var preview = ge("preview"); |
|
var treeRoot = ce("div"); |
|
treeRoot.className = "tvu"; |
|
ge(element).appendChild(treeRoot); |
|
|
|
function loadDownload(path){ |
|
ge('download-frame').src = "/edit?download="+path; |
|
} |
|
|
|
function loadPreview(path){ |
|
var edfname = ge("editor-filename"); |
|
edfname.value=path; |
|
ge("editor").style.display = "none"; |
|
preview.style.display = "block"; |
|
preview.innerHTML = '<img src="/edit?edit='+path+'&_cb='+Date.now()+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />'; |
|
} |
|
|
|
function fillFileMenu(el, path){ |
|
var list = ce("ul"); |
|
el.appendChild(list); |
|
var action = ce("li"); |
|
list.appendChild(action); |
|
if(isImageFile(path)){ |
|
action.innerHTML = "<span>Preview</span>"; |
|
action.onclick = function(e){ |
|
loadPreview(path); |
|
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
}; |
|
} else if(isTextFile(path)){ |
|
action.innerHTML = "<span>Edit</span>"; |
|
action.onclick = function(e){ |
|
editor.loadUrl(path); |
|
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
}; |
|
} |
|
var download = ce("li"); |
|
list.appendChild(download); |
|
download.innerHTML = "<span>Download</span>"; |
|
download.onclick = function(e){ |
|
loadDownload(path); |
|
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
}; |
|
var delFile = ce("li"); |
|
list.appendChild(delFile); |
|
delFile.innerHTML = "<span>Delete</span>"; |
|
delFile.onclick = function(e){ |
|
httpDelete(path); |
|
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
}; |
|
} |
|
|
|
function showContextMenu(event, path, isfile){ |
|
var divContext = ce("div"); |
|
var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; |
|
var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft; |
|
var left = event.clientX + scrollLeft; |
|
var top = event.clientY + scrollTop; |
|
divContext.className = 'cm'; |
|
divContext.style.display = 'block'; |
|
divContext.style.left = left + 'px'; |
|
divContext.style.top = top + 'px'; |
|
fillFileMenu(divContext, path); |
|
document.body.appendChild(divContext); |
|
var width = divContext.offsetWidth; |
|
var height = divContext.offsetHeight; |
|
divContext.onmouseout = function(e){ |
|
if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){ |
|
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(divContext); |
|
} |
|
}; |
|
} |
|
|
|
function createTreeLeaf(path, name, size){ |
|
var leaf = ce("li"); |
|
leaf.id = name; |
|
var label = ce("span"); |
|
label.innerHTML = name; |
|
leaf.appendChild(label); |
|
leaf.onclick = function(e){ |
|
if(isTextFile(leaf.id.toLowerCase())){ |
|
editor.loadUrl(leaf.id); |
|
} else if(isImageFile(leaf.id.toLowerCase())){ |
|
loadPreview(leaf.id); |
|
} |
|
}; |
|
leaf.oncontextmenu = function(e){ |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
showContextMenu(e, leaf.id, true); |
|
}; |
|
return leaf; |
|
} |
|
|
|
function addList(parent, path, items){ |
|
sortByKey(items, 'name'); |
|
var list = ce("ul"); |
|
parent.appendChild(list); |
|
var ll = items.length; |
|
for(var i = 0; i < ll; i++){ |
|
if(items[i].type === "file") |
|
list.appendChild(createTreeLeaf(path, items[i].name, items[i].size)); |
|
} |
|
|
|
} |
|
|
|
function isTextFile(path){ |
|
var ext = /(?:\.([^.]+))?$/.exec(path)[1]; |
|
if(typeof ext !== undefined){ |
|
switch(ext){ |
|
case "txt": |
|
case "htm": |
|
case "html": |
|
case "js": |
|
case "css": |
|
case "xml": |
|
case "json": |
|
case "conf": |
|
case "ini": |
|
case "h": |
|
case "c": |
|
case "cpp": |
|
case "php": |
|
case "hex": |
|
case "ino": |
|
case "pde": |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
function isImageFile(path){ |
|
var ext = /(?:\.([^.]+))?$/.exec(path)[1]; |
|
if(typeof ext !== undefined){ |
|
switch(ext){ |
|
case "png": |
|
case "jpg": |
|
case "gif": |
|
case "bmp": |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
this.refreshPath = function(path){ |
|
treeRoot.removeChild(treeRoot.childNodes[0]); |
|
httpGet(treeRoot, "/"); |
|
}; |
|
|
|
function delCb(path){ |
|
return function(status, responseText){ |
|
if(status != 200){ |
|
alert("ERROR["+status+"]: "+responseText); |
|
} else { |
|
treeRoot.removeChild(treeRoot.childNodes[0]); |
|
httpGet(treeRoot, "/"); |
|
} |
|
} |
|
} |
|
|
|
function httpDelete(filename){ |
|
var formData = new FormData(); |
|
formData.append("path", filename); |
|
requests.add("DELETE", "/edit", formData, delCb(filename)); |
|
} |
|
|
|
function getCb(parent, path){ |
|
return function(status, responseText){ |
|
if(status == 200) |
|
addList(parent, path, JSON.parse(responseText)); |
|
} |
|
} |
|
|
|
function httpGet(parent, path){ |
|
requests.add("GET", "/edit", { list: path }, getCb(parent, path)); |
|
} |
|
|
|
httpGet(treeRoot, "/"); |
|
return this; |
|
} |
|
|
|
function createEditor(element, file, lang, theme, type){ |
|
function getLangFromFilename(filename){ |
|
var lang = "plain"; |
|
var ext = /(?:\.([^.]+))?$/.exec(filename)[1]; |
|
if(typeof ext !== undefined){ |
|
switch(ext){ |
|
case "txt": lang = "plain"; break; |
|
case "hex": lang = "plain"; break; |
|
case "conf": lang = "plain"; break; |
|
case "htm": lang = "html"; break; |
|
case "js": lang = "javascript"; break; |
|
case "h": lang = "c_cpp"; break; |
|
case "c": lang = "c_cpp"; break; |
|
case "cpp": lang = "c_cpp"; break; |
|
case "css": |
|
case "scss": |
|
case "php": |
|
case "html": |
|
case "json": |
|
case "xml": |
|
case "ini": lang = ext; |
|
} |
|
} |
|
return lang; |
|
} |
|
|
|
if(typeof file === "undefined") file = "/index.html"; |
|
|
|
if(typeof lang === "undefined"){ |
|
lang = getLangFromFilename(file); |
|
} |
|
|
|
if(typeof theme === "undefined") theme = "textmate"; |
|
|
|
if(typeof type === "undefined"){ |
|
type = "text/"+lang; |
|
if(lang === "c_cpp") type = "text/plain"; |
|
} |
|
|
|
var editor = ace.edit(element); |
|
function httpPostProcessRequest(status, responseText){ |
|
if(status != 200) alert("ERROR["+status+"]: "+responseText); |
|
} |
|
function httpPost(filename, data, type){ |
|
var formData = new FormData(); |
|
formData.append("data", new Blob([data], { type: type }), filename); |
|
requests.add("POST", "/edit", formData, httpPostProcessRequest); |
|
} |
|
function httpGetProcessRequest(status, responseText){ |
|
ge("preview").style.display = "none"; |
|
ge("editor").style.display = "block"; |
|
if(status == 200) |
|
editor.setValue(responseText); |
|
else |
|
editor.setValue(""); |
|
editor.clearSelection(); |
|
} |
|
function httpGet(theUrl){ |
|
requests.add("GET", "/edit", { edit: theUrl }, httpGetProcessRequest); |
|
} |
|
|
|
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang); |
|
editor.setTheme("ace/theme/"+theme); |
|
editor.$blockScrolling = Infinity; |
|
editor.getSession().setUseSoftTabs(true); |
|
editor.getSession().setTabSize(2); |
|
editor.setHighlightActiveLine(true); |
|
editor.setShowPrintMargin(false); |
|
editor.commands.addCommand({ |
|
name: 'saveCommand', |
|
bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, |
|
exec: function(editor) { |
|
httpPost(file, editor.getValue()+"", type); |
|
}, |
|
readOnly: false |
|
}); |
|
editor.commands.addCommand({ |
|
name: 'undoCommand', |
|
bindKey: {win: 'Ctrl-Z', mac: 'Command-Z'}, |
|
exec: function(editor) { |
|
editor.getSession().getUndoManager().undo(false); |
|
}, |
|
readOnly: false |
|
}); |
|
editor.commands.addCommand({ |
|
name: 'redoCommand', |
|
bindKey: {win: 'Ctrl-Shift-Z', mac: 'Command-Shift-Z'}, |
|
exec: function(editor) { |
|
editor.getSession().getUndoManager().redo(false); |
|
}, |
|
readOnly: false |
|
}); |
|
editor.loadUrl = function(filename){ |
|
var edfname = ge("editor-filename"); |
|
edfname.value=filename; |
|
file = filename; |
|
lang = getLangFromFilename(file); |
|
type = "text/"+lang; |
|
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang); |
|
httpGet(file); |
|
}; |
|
return editor; |
|
} |
|
function onBodyLoad(){ |
|
var vars = {}; |
|
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; }); |
|
var editor = createEditor("editor", vars.file, vars.lang, vars.theme); |
|
var tree = createTree("tree", editor); |
|
createFileUploader("uploader", tree, editor); |
|
if(typeof vars.file === "undefined") vars.file = "/index.htm"; |
|
editor.loadUrl(vars.file); |
|
}; |
|
</script> |
|
<script id='ace' src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript" charset="utf-8"></script> |
|
<script> |
|
if (typeof ace.edit == "undefined") { |
|
var script = document.createElement('script'); |
|
script.src = "/ace.js"; |
|
script.async = false; |
|
document.head.appendChild(script); |
|
} |
|
</script> |
|
</head> |
|
<body onload="onBodyLoad();"> |
|
<div id="loader" class="loader"></div> |
|
<div id="uploader"></div> |
|
<div id="tree"></div> |
|
<div id="editor"></div> |
|
<div id="preview" style="display:none;"></div> |
|
<iframe id=download-frame style='display:none;'></iframe> |
|
</body> |
|
</html>
|
|
|