c8a7537157
Fixed ESP32
586 lines
16 KiB
HTML
586 lines
16 KiB
HTML
<!-- This file has been written by Me-No-Dev
|
|
see https://github.com/me-no-dev/ and is part
|
|
of the sample code of ESPAsyncWebServer
|
|
see https://github.com/me-no-dev/ESPAsyncWebServer -->
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>ESP8266 SPIFFS File Editor</title>
|
|
<meta name="author" content="Me-No-Dev" />
|
|
<meta name="Description" content="ESP8266 SPIFFS File Web Editor"/>
|
|
<link rel="shortcut icon" href="favicon.ico"/>
|
|
<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;
|
|
background-color: #222;
|
|
}
|
|
#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>
|
|
function ge(a){
|
|
return document.getElementById(a);
|
|
}
|
|
function ce(a){
|
|
return document.createElement(a);
|
|
}
|
|
|
|
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+"="+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 input = ce("input");
|
|
input.type = "file";
|
|
input.multiple = false;
|
|
input.name = "data";
|
|
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 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){
|
|
if(path.value.indexOf(".") === -1) return;
|
|
createPath(path.value);
|
|
editor.loadUrl(path.value);
|
|
};
|
|
|
|
savefile.onclick = function(e){
|
|
editor.execCommand('saveCommand');
|
|
};
|
|
|
|
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);
|
|
};
|
|
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;
|
|
}
|
|
if(typeof ext !== undefined){
|
|
if(ext === "html") ext = "htm";
|
|
else if(ext === "jpeg") ext = "jpg";
|
|
filename = filename + "." + ext;
|
|
}
|
|
if(path.value === "/" || path.value.lastIndexOf("/") === 0){
|
|
path.value = "/"+filename;
|
|
} else {
|
|
path.value = path.value.substring(0, path.value.lastIndexOf("/")+1)+filename;
|
|
}
|
|
};
|
|
}
|
|
|
|
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){
|
|
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 = (((path == "/")?"":path)+"/"+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){
|
|
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":
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isImageFile(path){
|
|
var ext = /(?:\.([^.]+))?$/.exec(path)[1];
|
|
if(typeof ext !== undefined){
|
|
switch(ext){
|
|
case "png":
|
|
case "jpg":
|
|
case "gif":
|
|
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.htm";
|
|
|
|
if(typeof lang === "undefined"){
|
|
lang = getLangFromFilename(file);
|
|
}
|
|
|
|
if(typeof theme === "undefined") theme = "monokai";
|
|
|
|
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
|
|
});
|
|
httpGet(file);
|
|
editor.loadUrl = function(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);
|
|
};
|
|
</script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js" type="text/javascript" charset="utf-8"></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> |