edit.htm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. <!--This is the plain html source of the hex encoded Editor-Page embedded in SPIFFSEditor.cpp -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <title>ESP Editor</title>
  6. <style type="text/css" media="screen">
  7. .cm {
  8. z-index: 300;
  9. position: absolute;
  10. left: 5px;
  11. border: 1px solid #444;
  12. background-color: #F5F5F5;
  13. display: none;
  14. box-shadow: 0 0 10px rgba( 0, 0, 0, .4 );
  15. font-size: 12px;
  16. font-family: sans-serif;
  17. font-weight:bold;
  18. }
  19. .cm ul {
  20. list-style: none;
  21. top: 0;
  22. left: 0;
  23. margin: 0;
  24. padding: 0;
  25. }
  26. .cm li {
  27. position: relative;
  28. min-width: 60px;
  29. cursor: pointer;
  30. }
  31. .cm span {
  32. color: #444;
  33. display: inline-block;
  34. padding: 6px;
  35. }
  36. .cm li:hover { background: #444; }
  37. .cm li:hover span { color: #EEE; }
  38. .tvu ul, .tvu li {
  39. padding: 0;
  40. margin: 0;
  41. list-style: none;
  42. }
  43. .tvu input {
  44. position: absolute;
  45. opacity: 0;
  46. }
  47. .tvu {
  48. font: normal 12px Verdana, Arial, Sans-serif;
  49. -moz-user-select: none;
  50. -webkit-user-select: none;
  51. user-select: none;
  52. color: #444;
  53. line-height: 16px;
  54. }
  55. .tvu span {
  56. margin-bottom:5px;
  57. padding: 0 0 0 18px;
  58. cursor: pointer;
  59. display: inline-block;
  60. height: 16px;
  61. vertical-align: middle;
  62. background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC') no-repeat;
  63. background-position: 0px 0px;
  64. }
  65. .tvu span:hover {
  66. text-decoration: underline;
  67. }
  68. @media screen and (-webkit-min-device-pixel-ratio:0){
  69. .tvu{
  70. -webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;
  71. }
  72. @-webkit-keyframes webkit-adjacent-element-selector-bugfix {
  73. from {
  74. padding: 0;
  75. }
  76. to {
  77. padding: 0;
  78. }
  79. }
  80. }
  81. #uploader {
  82. position: absolute;
  83. top: 0;
  84. right: 0;
  85. left: 0;
  86. height:28px;
  87. line-height: 24px;
  88. padding-left: 10px;
  89. background-color: #444;
  90. color:#EEE;
  91. }
  92. #tree {
  93. position: absolute;
  94. top: 28px;
  95. bottom: 0;
  96. left: 0;
  97. width:160px;
  98. padding: 8px;
  99. }
  100. #editor, #preview {
  101. position: absolute;
  102. top: 28px;
  103. right: 0;
  104. bottom: 0;
  105. left: 160px;
  106. border-left:1px solid #EEE;
  107. }
  108. #preview {
  109. background-color: #EEE;
  110. padding:5px;
  111. }
  112. #loader {
  113. position: absolute;
  114. top: 36%;
  115. right: 40%;
  116. }
  117. .loader {
  118. z-index: 10000;
  119. border: 8px solid #b5b5b5; /* Grey */
  120. border-top: 8px solid #3498db; /* Blue */
  121. border-bottom: 8px solid #3498db; /* Blue */
  122. border-radius: 50%;
  123. width: 240px;
  124. height: 240px;
  125. animation: spin 2s linear infinite;
  126. display:none;
  127. }
  128. @keyframes spin {
  129. 0% { transform: rotate(0deg); }
  130. 100% { transform: rotate(360deg); }
  131. }
  132. </style>
  133. <script>
  134. if (typeof XMLHttpRequest === "undefined") {
  135. XMLHttpRequest = function () {
  136. try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
  137. try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
  138. try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
  139. throw new Error("This browser does not support XMLHttpRequest.");
  140. };
  141. }
  142. function ge(a){
  143. return document.getElementById(a);
  144. }
  145. function ce(a){
  146. return document.createElement(a);
  147. }
  148. function sortByKey(array, key) {
  149. return array.sort(function(a, b) {
  150. var x = a[key]; var y = b[key];
  151. return ((x < y) ? -1 : ((x > y) ? 1 : 0));
  152. });
  153. }
  154. var QueuedRequester = function () {
  155. this.queue = [];
  156. this.running = false;
  157. this.xmlhttp = null;
  158. }
  159. QueuedRequester.prototype = {
  160. _request: function(req){
  161. this.running = true;
  162. if(!req instanceof Object) return;
  163. var that = this;
  164. function ajaxCb(x,d){ return function(){
  165. if (x.readyState == 4){
  166. ge("loader").style.display = "none";
  167. d.callback(x.status, x.responseText);
  168. if(that.queue.length === 0) that.running = false;
  169. if(that.running) that._request(that.queue.shift());
  170. }
  171. }}
  172. ge("loader").style.display = "block";
  173. var p = "";
  174. if(req.params instanceof FormData){
  175. p = req.params;
  176. } else if(req.params instanceof Object){
  177. for (var key in req.params) {
  178. if(p === "")
  179. p += (req.method === "GET")?"?":"";
  180. else
  181. p += "&";
  182. p += encodeURIComponent(key)+"="+encodeURIComponent(req.params[key]);
  183. };
  184. }
  185. this.xmlhttp = new XMLHttpRequest();
  186. this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req);
  187. if(req.method === "GET"){
  188. this.xmlhttp.open(req.method, req.url+p, true);
  189. this.xmlhttp.send();
  190. } else {
  191. this.xmlhttp.open(req.method, req.url, true);
  192. if(p instanceof String)
  193. this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  194. this.xmlhttp.send(p);
  195. }
  196. },
  197. stop: function(){
  198. if(this.running) this.running = false;
  199. if(this.xmlhttp && this.xmlhttp.readyState < 4){
  200. this.xmlhttp.abort();
  201. }
  202. },
  203. add: function(method, url, params, callback){
  204. this.queue.push({url:url,method:method,params:params,callback:callback});
  205. if(!this.running){
  206. this._request(this.queue.shift());
  207. }
  208. }
  209. }
  210. var requests = new QueuedRequester();
  211. function createFileUploader(element, tree, editor){
  212. var xmlHttp;
  213. var refresh = ce("button");
  214. refresh.innerHTML = 'Refresh List';
  215. ge(element).appendChild(refresh);
  216. var input = ce("input");
  217. input.type = "file";
  218. input.multiple = false;
  219. input.name = "data";
  220. input.id="upload-select";
  221. ge(element).appendChild(input);
  222. var path = ce("input");
  223. path.id = "upload-path";
  224. path.type = "text";
  225. path.name = "path";
  226. path.defaultValue = "/";
  227. ge(element).appendChild(path);
  228. var button = ce("button");
  229. button.innerHTML = 'Upload';
  230. ge(element).appendChild(button);
  231. var mkfile = ce("button");
  232. mkfile.innerHTML = 'Create';
  233. ge(element).appendChild(mkfile);
  234. var filename = ce("input");
  235. filename.id = "editor-filename";
  236. filename.type = "text";
  237. filename.disabled= true;
  238. filename.size = 20;
  239. ge(element).appendChild(filename);
  240. var savefile = ce("button");
  241. savefile.innerHTML = ' Save ' ;
  242. ge(element).appendChild(savefile);
  243. function httpPostProcessRequest(status, responseText){
  244. if(status != 200)
  245. alert("ERROR["+status+"]: "+responseText);
  246. else
  247. tree.refreshPath(path.value);
  248. }
  249. function createPath(p){
  250. var formData = new FormData();
  251. formData.append("path", p);
  252. requests.add("PUT", "/edit", formData, httpPostProcessRequest);
  253. }
  254. mkfile.onclick = function(e){
  255. createPath(path.value);
  256. editor.loadUrl(path.value);
  257. path.value="/";
  258. };
  259. savefile.onclick = function(e){
  260. editor.execCommand('saveCommand');
  261. };
  262. refresh.onclick = function(e){
  263. tree.refreshPath(path.value);
  264. };
  265. button.onclick = function(e){
  266. if(input.files.length === 0){
  267. return;
  268. }
  269. var formData = new FormData();
  270. formData.append("data", input.files[0], path.value);
  271. requests.add("POST", "/edit", formData, httpPostProcessRequest);
  272. var uploadPath= ge("upload-path");
  273. uploadPath.value="/";
  274. var uploadSelect= ge("upload-select");
  275. uploadSelect.value="";
  276. };
  277. input.onchange = function(e){
  278. if(input.files.length === 0) return;
  279. var filename = input.files[0].name;
  280. var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
  281. var name = /(.*)\.[^.]+$/.exec(filename)[1];
  282. if(typeof name !== undefined){
  283. filename = name;
  284. }
  285. path.value = "/"+filename+"."+ext;
  286. };
  287. }
  288. function createTree(element, editor){
  289. var preview = ge("preview");
  290. var treeRoot = ce("div");
  291. treeRoot.className = "tvu";
  292. ge(element).appendChild(treeRoot);
  293. function loadDownload(path){
  294. ge('download-frame').src = "/edit?download="+path;
  295. }
  296. function loadPreview(path){
  297. var edfname = ge("editor-filename");
  298. edfname.value=path;
  299. ge("editor").style.display = "none";
  300. preview.style.display = "block";
  301. preview.innerHTML = '<img src="/edit?edit='+path+'&_cb='+Date.now()+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />';
  302. }
  303. function fillFileMenu(el, path){
  304. var list = ce("ul");
  305. el.appendChild(list);
  306. var action = ce("li");
  307. list.appendChild(action);
  308. if(isImageFile(path)){
  309. action.innerHTML = "<span>Preview</span>";
  310. action.onclick = function(e){
  311. loadPreview(path);
  312. if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
  313. };
  314. } else if(isTextFile(path)){
  315. action.innerHTML = "<span>Edit</span>";
  316. action.onclick = function(e){
  317. editor.loadUrl(path);
  318. if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
  319. };
  320. }
  321. var download = ce("li");
  322. list.appendChild(download);
  323. download.innerHTML = "<span>Download</span>";
  324. download.onclick = function(e){
  325. loadDownload(path);
  326. if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
  327. };
  328. var delFile = ce("li");
  329. list.appendChild(delFile);
  330. delFile.innerHTML = "<span>Delete</span>";
  331. delFile.onclick = function(e){
  332. httpDelete(path);
  333. if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el);
  334. };
  335. }
  336. function showContextMenu(event, path, isfile){
  337. var divContext = ce("div");
  338. var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;
  339. var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft;
  340. var left = event.clientX + scrollLeft;
  341. var top = event.clientY + scrollTop;
  342. divContext.className = 'cm';
  343. divContext.style.display = 'block';
  344. divContext.style.left = left + 'px';
  345. divContext.style.top = top + 'px';
  346. fillFileMenu(divContext, path);
  347. document.body.appendChild(divContext);
  348. var width = divContext.offsetWidth;
  349. var height = divContext.offsetHeight;
  350. divContext.onmouseout = function(e){
  351. if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){
  352. if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(divContext);
  353. }
  354. };
  355. }
  356. function createTreeLeaf(path, name, size){
  357. var leaf = ce("li");
  358. leaf.id = name;
  359. var label = ce("span");
  360. label.innerHTML = name;
  361. leaf.appendChild(label);
  362. leaf.onclick = function(e){
  363. if(isTextFile(leaf.id.toLowerCase())){
  364. editor.loadUrl(leaf.id);
  365. } else if(isImageFile(leaf.id.toLowerCase())){
  366. loadPreview(leaf.id);
  367. }
  368. };
  369. leaf.oncontextmenu = function(e){
  370. e.preventDefault();
  371. e.stopPropagation();
  372. showContextMenu(e, leaf.id, true);
  373. };
  374. return leaf;
  375. }
  376. function addList(parent, path, items){
  377. sortByKey(items, 'name');
  378. var list = ce("ul");
  379. parent.appendChild(list);
  380. var ll = items.length;
  381. for(var i = 0; i < ll; i++){
  382. if(items[i].type === "file")
  383. list.appendChild(createTreeLeaf(path, items[i].name, items[i].size));
  384. }
  385. }
  386. function isTextFile(path){
  387. var ext = /(?:\.([^.]+))?$/.exec(path)[1];
  388. if(typeof ext !== undefined){
  389. switch(ext){
  390. case "txt":
  391. case "htm":
  392. case "html":
  393. case "js":
  394. case "css":
  395. case "xml":
  396. case "json":
  397. case "conf":
  398. case "ini":
  399. case "h":
  400. case "c":
  401. case "cpp":
  402. case "php":
  403. case "hex":
  404. case "ino":
  405. case "pde":
  406. return true;
  407. }
  408. }
  409. return false;
  410. }
  411. function isImageFile(path){
  412. var ext = /(?:\.([^.]+))?$/.exec(path)[1];
  413. if(typeof ext !== undefined){
  414. switch(ext){
  415. case "png":
  416. case "jpg":
  417. case "gif":
  418. case "bmp":
  419. return true;
  420. }
  421. }
  422. return false;
  423. }
  424. this.refreshPath = function(path){
  425. treeRoot.removeChild(treeRoot.childNodes[0]);
  426. httpGet(treeRoot, "/");
  427. };
  428. function delCb(path){
  429. return function(status, responseText){
  430. if(status != 200){
  431. alert("ERROR["+status+"]: "+responseText);
  432. } else {
  433. treeRoot.removeChild(treeRoot.childNodes[0]);
  434. httpGet(treeRoot, "/");
  435. }
  436. }
  437. }
  438. function httpDelete(filename){
  439. var formData = new FormData();
  440. formData.append("path", filename);
  441. requests.add("DELETE", "/edit", formData, delCb(filename));
  442. }
  443. function getCb(parent, path){
  444. return function(status, responseText){
  445. if(status == 200)
  446. addList(parent, path, JSON.parse(responseText));
  447. }
  448. }
  449. function httpGet(parent, path){
  450. requests.add("GET", "/edit", { list: path }, getCb(parent, path));
  451. }
  452. httpGet(treeRoot, "/");
  453. return this;
  454. }
  455. function createEditor(element, file, lang, theme, type){
  456. function getLangFromFilename(filename){
  457. var lang = "plain";
  458. var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
  459. if(typeof ext !== undefined){
  460. switch(ext){
  461. case "txt": lang = "plain"; break;
  462. case "hex": lang = "plain"; break;
  463. case "conf": lang = "plain"; break;
  464. case "htm": lang = "html"; break;
  465. case "js": lang = "javascript"; break;
  466. case "h": lang = "c_cpp"; break;
  467. case "c": lang = "c_cpp"; break;
  468. case "cpp": lang = "c_cpp"; break;
  469. case "css":
  470. case "scss":
  471. case "php":
  472. case "html":
  473. case "json":
  474. case "xml":
  475. case "ini": lang = ext;
  476. }
  477. }
  478. return lang;
  479. }
  480. if(typeof file === "undefined") file = "/index.html";
  481. if(typeof lang === "undefined"){
  482. lang = getLangFromFilename(file);
  483. }
  484. if(typeof theme === "undefined") theme = "textmate";
  485. if(typeof type === "undefined"){
  486. type = "text/"+lang;
  487. if(lang === "c_cpp") type = "text/plain";
  488. }
  489. var editor = ace.edit(element);
  490. function httpPostProcessRequest(status, responseText){
  491. if(status != 200) alert("ERROR["+status+"]: "+responseText);
  492. }
  493. function httpPost(filename, data, type){
  494. var formData = new FormData();
  495. formData.append("data", new Blob([data], { type: type }), filename);
  496. requests.add("POST", "/edit", formData, httpPostProcessRequest);
  497. }
  498. function httpGetProcessRequest(status, responseText){
  499. ge("preview").style.display = "none";
  500. ge("editor").style.display = "block";
  501. if(status == 200)
  502. editor.setValue(responseText);
  503. else
  504. editor.setValue("");
  505. editor.clearSelection();
  506. }
  507. function httpGet(theUrl){
  508. requests.add("GET", "/edit", { edit: theUrl }, httpGetProcessRequest);
  509. }
  510. if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
  511. editor.setTheme("ace/theme/"+theme);
  512. editor.$blockScrolling = Infinity;
  513. editor.getSession().setUseSoftTabs(true);
  514. editor.getSession().setTabSize(2);
  515. editor.setHighlightActiveLine(true);
  516. editor.setShowPrintMargin(false);
  517. editor.commands.addCommand({
  518. name: 'saveCommand',
  519. bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
  520. exec: function(editor) {
  521. httpPost(file, editor.getValue()+"", type);
  522. },
  523. readOnly: false
  524. });
  525. editor.commands.addCommand({
  526. name: 'undoCommand',
  527. bindKey: {win: 'Ctrl-Z', mac: 'Command-Z'},
  528. exec: function(editor) {
  529. editor.getSession().getUndoManager().undo(false);
  530. },
  531. readOnly: false
  532. });
  533. editor.commands.addCommand({
  534. name: 'redoCommand',
  535. bindKey: {win: 'Ctrl-Shift-Z', mac: 'Command-Shift-Z'},
  536. exec: function(editor) {
  537. editor.getSession().getUndoManager().redo(false);
  538. },
  539. readOnly: false
  540. });
  541. editor.loadUrl = function(filename){
  542. var edfname = ge("editor-filename");
  543. edfname.value=filename;
  544. file = filename;
  545. lang = getLangFromFilename(file);
  546. type = "text/"+lang;
  547. if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
  548. httpGet(file);
  549. };
  550. return editor;
  551. }
  552. function onBodyLoad(){
  553. var vars = {};
  554. var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; });
  555. var editor = createEditor("editor", vars.file, vars.lang, vars.theme);
  556. var tree = createTree("tree", editor);
  557. createFileUploader("uploader", tree, editor);
  558. if(typeof vars.file === "undefined") vars.file = "/index.htm";
  559. editor.loadUrl(vars.file);
  560. };
  561. </script>
  562. <script id='ace' src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript" charset="utf-8"></script>
  563. <script>
  564. if (typeof ace.edit == "undefined") {
  565. var script = document.createElement('script');
  566. script.src = "/ace.js";
  567. script.async = false;
  568. document.head.appendChild(script);
  569. }
  570. </script>
  571. </head>
  572. <body onload="onBodyLoad();">
  573. <div id="loader" class="loader"></div>
  574. <div id="uploader"></div>
  575. <div id="tree"></div>
  576. <div id="editor"></div>
  577. <div id="preview" style="display:none;"></div>
  578. <iframe id=download-frame style='display:none;'></iframe>
  579. </body>
  580. </html>