lorol / LITTLEFS

LittleFS library for arduino-esp32
GNU General Public License v2.0
168 stars 68 forks source link

thank you so much 4 the espasync littlefs fsbrowser #47

Open ldijkman opened 2 years ago

ldijkman commented 2 years ago

thank you so much 4 the espasync littlefs fsbrowser

https://www.youtube.com/watch?v=1gPBMtsRDFA

greet luberth netherlands

ldijkman commented 2 years ago

still playing with the editor

would like to have html preview and a colorpicker addon

https://codepen.io/ldijkman/pen/LYdZpYp

2022-07-12-203422_1920x1080_scrot

2022-07-11-205741_1920x1080_scrot

ldijkman commented 2 years ago

html preview working a bit

2022-07-16-110355_1920x1080_scrot

<!-- 
save upload or create/copy this file (as *watheveryouwanttonameit* .html)
to your ESP8266 ESP32 espasync webserver

will give you an ace js web/cloud editor with live HTML preview

not perfect but it works a bit

function update on editor textcontent change 
shows only changes after save button is pressed
reload on every key stroke
iframe shows html result
i am not handy with divs
would be nice if the source and preview divs are resizable by mouse drag
maybe a checkbox for preview panel on / off
would like to have the colorpicker addon in the code editor
https://codepen.io/ldijkman/pen/LYdZpYp
-->

<!DOCTYPE html>
<html lang="en">
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Electra's ESP Editor</title>
<link rel="apple-touch-icon" href="/ace.ico" type="image/x-icon">
<link rel="shortcut icon" href="/ace.ico" type="image/x-icon">
<link rel="icon" href="/ace.ico" type="image/x-icon">
<style type="text/css" media="screen">
  label {
    font-size: 12px;
    font-family: sans-serif
  }

  .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: 700
  }

  .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 li,
  .tvu ul {
    padding: 0;
    margin: 0;
    list-style: none
  }

  .tvu input {
    position: absolute;
    opacity: 0
  }

  .tvu {
    font: 400 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: 0 0
  }

  .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: 100px;
    padding: 8px
  }

  #editor,
  #preview {
    position: absolute;
    top: 28px;
    right: 0;
    bottom: 0;
    left: 120px;
    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;
    border-top: 8px solid #3498db;
    border-bottom: 8px solid #3498db;
    border-radius: 50%;
    width: 240px;
    height: 240px;
    animation: spin 2s linear infinite;
    display: none
  }

  @keyframes spin {
    0% {
      transform: rotate(0)
    }

    100% {
      transform: rotate(360deg)
    }
  }
</style>

<style>
    body {
      font-family: 'Roboto', sans-serif;
      background-color: whitesmoke;
    }

    .p1,
    .header {
      font-size: 32px;
      text-align: center;
    }

    .button {
      text-align: center;
      font-size: 16px;
      justify-content: center;
      align-items: center;
    }

    .p2 {
      text-align: left;
      font-size: 24px;
    }

    html,
    body {
      margin: 0;
      padding: 0;
      height: 100%;
      width: 100%;
      ooverflow: hidden;
    }

    #editor {
      height: 95%;
      width: 50%;
      display: inline-block;
      border:1px solid black;
    }

    #container {
      height: 100%;
      width: auto;
      white-space: nowrap;
      ooverflow: hidden;
      position: relative;
    }

    #iframediv {
      float: right;
      margin-top: 28px;
      height: 100%; 
      width: 38%;
      display: inline-block;
      border:1px solid black;

    }

#iframe {

      top: 28px;
      height: 100%; 
      width: 100%;
      display: inline-block;

    }
    .ace_editor .ace_marker-layer .ace_bracket {
      display: none
    }
     .color {
        color: orange;   
      }

  </style>

<!--
AceEditor ColorPicker AddOn
https://codepen.io/easylogic/pen/RwVOGed
I made a colorpicker for ace editor. #4725
easylogic started this conversation in Ideas
https://github.com/ajaxorg/ace/discussions/4725
-->

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ace-colorpicker@0.0.12/addon/ace-colorpicker.css" />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/ace-colorpicker@0.0.12/addon/ace-colorpicker.min.js" ></script> 

<script>

   function loadpick() {  
       // does not work
      console.log("load colorpicker function loadpick");
    AceColorPicker.load(ace.edit,editor);  
    }

   function update() {  
     if(document.getElementById("Preview").checked){
        var idoc = document.getElementById('iframe').contentWindow.document;
        idoc.open();
        idoc.write(ace.edit("editor").getValue()); 
        //console.log(ace.edit("editor").getValue());
        idoc.close();
     }
    }

    function updatefrombutton() {  
        var idoc = document.getElementById('iframe').contentWindow.document;
        idoc.open();
        idoc.write(ace.edit("editor").getValue()); 
        //console.log(ace.edit("editor").getValue());
        idoc.close();
         document.getElementById("save").setAttribute("title", "Save file to ESP LittleFS"); 
    }

  function ge(e) {
    return document.getElementById(e)
  }

  function ce(e) {
    return document.createElement(e)
  }

  function sortByKey(e, t) {
    return e.sort((function(e, n) {
      var a = e[t],
        i = n[t];
      return i > a ? -1 : a > i ? 1 : 0
    }))
  }

  function createFileUploader(e, t, n) {
    var a = /(iPhone)*(OS ([7-9]|1[0-1])_)/i.test(navigator.userAgent);

    function i(e, n) {
      200 != e ? alert("ERROR[" + e + "]: " + n) : t.refreshPath(d.value)
    }
    var o = ce("button");
    o.innerHTML = "Root Dir", ge(e).appendChild(o);
    var c = ce("input");
    c.type = "file", c.multiple = !1, c.name = "data", c.id = "upload-select", ge(e).appendChild(c);
    var d = ce("input");
    d.id = "upload-path", d.type = "text", d.name = "path", d.defaultValue = "/", ge(e).appendChild(d);
    var s = ce("button");
    s.innerHTML = "Upload", ge(e).appendChild(s);
    var r = ce("button");
    r.innerHTML = "Create", ge(e).appendChild(r);
    var l = ce("input");
    l.id = "editor-filename", l.type = "text", l.disabled = !0, l.size = 20, ge(e).appendChild(l);
    var u = ce("input");
    u.id = "ipad-fix", u.title="i have no idea what this checkbox does", u.name = "ipad-fix", u.type = "checkbox", u.checked = !!a;
    var p = ce("label");
    p.for = u.id, p.innerHTML = " Alt.";
    var m = ce("button");
    m.innerHTML = " Save ",m.id = "save",m.title="Save file to ESP LittleFS", ge(e).appendChild(m), ge(e).appendChild(p), ge(e).appendChild(u), r.onclick = function(e) {
      (function(e) {
        var t = new FormData;
        t.append("path", e), requests.add("PUT", "/edit", t, i)
      })(d.value), n.loadUrl(d.value), d.value = "/"
    }, m.onclick = function(e) {
      if (u.checked) {
        var t = ace.edit("editor").getValue(),
          a = new FormData;
        a.append("rawname", l.value);
        var o = 0;
        const e = 4096;
        for (var c = 0; c < t.length; c += e) {
          var d = t.substring(c, c + e);
          a.append("raw" + o, d), o++
        }
        requests.add("POST", "/edit", a, i)
      } else n.execCommand("saveCommand")
    }, o.onclick = function(e) {
      t.refreshPath(d.value)
    }, s.onclick = function(e) {
      if (0 !== c.files.length) {
        var t = new FormData;
        t.append("data", c.files[0], d.value), requests.add("POST", "/edit", t, i), ge("upload-path").value = "/", ge("upload-select").value = ""
      }
    }, c.onchange = function(e) {
      if (0 !== c.files.length) {
        var t = c.files[0].name,
          n = /(?:\.([^.]+))?$/.exec(t)[1],
          a = /(.*)\.[^.]+$/.exec(t)[1];
        void 0 !== typeof a && (t = a), d.value = "/" + t + "." + n
      }
    }

  }

  function createTree(e, t) {
    function n(e) {
      ge("editor-filename").value = e, ge("editor").style.display = "none", p.style.display = "block", p.innerHTML = '<img src="/edit?edit=' + e + "&_cb=" + Date.now() + '" style="max-width:100%; max-height:100%; margin:auto; display:block;" />'
    }

    function a(e, a) {
      var i = ce("ul");
      e.appendChild(i);
      var o = ce("li");
      i.appendChild(o), s(a) ? (o.innerHTML = "<span>Preview</span>", o.onclick = function(t) {
        n(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
      }) : d(a) && (o.innerHTML = "<span>Edit</span>", o.onclick = function(n) {
        t.loadUrl(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
      });
      var l = ce("li");
      i.appendChild(l), d(a) || s(a) || function(e) {
        var t = /(?:.([^.]+))?$/.exec(e)[1];
        if (void 0 !== typeof t) switch (t) {
          case "ico":
          case "gz":
          case "zip":
          case "wav":
          case "mp3":
          case "pdf":
            return !0
        }
        return !1
      }(a) ? (l.innerHTML = "<span>Download</span>", l.onclick = function(t) {
        (function(e) {
          ge("download-frame").src = "/edit?download=" + e
        })(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
      }) : c(a) && (i.appendChild(l), l.innerHTML = "<span>ChDir</span>", l.onclick = function(t) {
        m.removeChild(m.childNodes[0]), u(m, a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
      });
      var p = ce("li");
      i.appendChild(p), p.innerHTML = "<span>Delete</span>", p.onclick = function(t) {
        r(a), document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(e)
      }
    }

    function i(e, t, n) {
      var i = ce("div"),
        o = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop,
        c = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft,
        d = e.clientX + c,
        s = e.clientY + o;
      i.className = "cm", i.style.display = "block", i.style.left = d + "px", i.style.top = s + "px", a(i, t), document.body.appendChild(i);
      var r = i.offsetWidth,
        l = i.offsetHeight;
      i.onmouseout = function(e) {
        (e.clientX < d || e.clientX > d + r || e.clientY < s || e.clientY > s + l) && document.body.getElementsByClassName("cm").length > 0 && document.body.removeChild(i)
      }
    }

    function o(e, a, o) {
      var r = ce("li");
      r.id = a;
      var l = ce("span");
      return l.innerHTML = a, r.appendChild(l), r.onclick = function(e) {
        d(r.id.toLowerCase()) ? t.loadUrl(r.id) : s(r.id.toLowerCase()) ? n(r.id) : c(r.id) && (m.removeChild(m.childNodes[0]) && u(m, r.id.toLowerCase()))
      }, r.oncontextmenu = function(e) {
        e.preventDefault(), e.stopPropagation(), i(e, r.id)
      }, r
    }

    function c(e) {
      return -1 == e.indexOf(".")
    }

    function d(e) {
      var t = /(?:.([^.]+))?$/.exec(e)[1];
      if (void 0 !== typeof t) switch (t) {
        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 !0
      }
      return !1
    }

    function s(e) {
      var t = /(?:.([^.]+))?$/.exec(e)[1];
      if (void 0 !== typeof t) switch (t) {
        case "png":
        case "jpg":
        case "gif":
        case "bmp":
          return !0
      }
      return !1
    }

    function r(e) {
      var t = new FormData;
      t.append("path", e), requests.add("DELETE", "/edit", t, (function(e, t) {
        200 != e ? alert("ERROR[" + e + "]: " + t) : (m.removeChild(m.childNodes[0]), u(m, "/"))
      }))
    }

    function l(e, t) {
      return function(t, n) {
        200 == t && function(e, t, n) {
          sortByKey(n, "name");
          var a = ce("ul");
          e.appendChild(a);
          for (var i = n.length, c = 0; i > c; c++) "file" === n[c].type && a.appendChild(o(0, n[c].name, n[c].size))
        }(e, 0, JSON.parse(n))
      }
    }

    function u(e, t) {
      requests.add("GET", "/edit", {
        list: t
      }, l(e))
    }
    var p = ge("preview"),
      m = ce("div");
    return m.className = "tvu", ge(e).appendChild(m), this.refreshPath = function(e) {
      m.removeChild(m.childNodes[0]), u(m, "/")
    }, u(m, "/"), this
  }

  function createEditor(e, t, n, a, i) {
    function o(e) {
      var t = "plain",
        n = /(?:.([^.]+))?$/.exec(e)[1];
      if (void 0 !== typeof n) switch (n) {
        case "txt":
        case "hex":
        case "conf":
          t = "plain";
          break;
        case "htm":
          t = "html";
          break;
        case "js":
          t = "javascript";
          break;
        case "h":
        case "c":
        case "cpp":
          t = "c_cpp";
          break;
        case "css":
        case "scss":
        case "php":
        case "html":
        case "json":
        case "xml":
        case "ini":
          t = n
      }
      return t
    }

    function c(e, t) {
      200 != e && alert("ERROR[" + e + "]: " + t)
    }

    function d(e, t) {
      ge("preview").style.display = "none", 
      ge("editor").style.display = "block", 
      200 == e ? s.setValue(t) : s.setValue(""), s.clearSelection()
    }
    void 0 === t && (t = "/schedule.html"), 
    void 0 === n && (n = o(t)), 
    void 0 === a && (a = "monokai"), 
    void 0 === i && (i = "text/" + n, "c_cpp" === n && (i = "text/plain"));
    var s = ace.edit(e);
    return "plain" !== n && 
    //s.session.setMode("ace/mode/"+n, () => { AceColorPicker.load(ace.edit, editor))}),
s.session.setMode("ace/mode/"+n, function() {loadpick()}),

    s.setTheme("ace/theme/" + a), 
    s.$blockScrolling = 1 / 0, 
    s.getSession().setUseSoftTabs(!0), 
    s.getSession().setTabSize(2), 
    s.getSession().setUseWorker(!0), 
    s.setHighlightActiveLine(!0), 
    s.setShowPrintMargin(!1), 
    s.getSession().on('change', function() {update()}),
    s.commands.addCommand({
      name: "saveCommand",
      bindKey: {
        win: "Ctrl-S",
        mac: "Command-S"
      },
      exec: function(e) {
        ! function(e, t, n) {
          var a = new FormData;
          a.append("data", new Blob([t], {
            type: n
          }), e), requests.add("POST", "/edit", a, c)
        }(t, e.getValue() + "", i)
      },
      readOnly: !1
    }), s.commands.addCommand({
      name: "undoCommand",
      bindKey: {
        win: "Ctrl-Z",
        mac: "Command-Z"
      },
      exec: function(e) {
        e.getSession().getUndoManager().undo(!1)
      },
      readOnly: !1
    }), s.commands.addCommand({
      name: "redoCommand",
      bindKey: {
        win: "Ctrl-Shift-Z",
        mac: "Command-Shift-Z"
      },
      exec: function(e) {
        e.getSession().getUndoManager().redo(!1)
      },
      readOnly: !1
    }), s.loadUrl = function(e) {
      ge("editor-filename").value = e, n = o(t = e), i = "text/" + n, "plain" !== n && s.getSession().setMode("ace/mode/" + n),
        function(e) {
          requests.add("GET", "/edit", {
            edit: e
          }, d)
        }(t)
    }, s
  }

  function onBodyLoad() {
    var e = {},
      t = (window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (function(t, n, a) {
        e[n] = a
      })), createEditor("editor", e.file, e.lang, e.theme)),
      n = createTree("tree", t);
    window.define = ace.define, window.require = ace.require, ace.config.set("basePath", "/"), ace.config.set("workerPath", "/"), createFileUploader("uploader", n, t), void 0 === e.file && (e.file = "/schedule.html"), t.loadUrl(e.file)
  }
  "undefined" == typeof XMLHttpRequest && (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.")
  });
  var QueuedRequester = function() {
    this.queue = [], this.running = !1, this.xmlhttp = null
  };
  QueuedRequester.prototype = {
    _request: function(e) {
      if (this.running = !0, !(!e instanceof Object)) {
        var t = this;
        ge("loader").style.display = "block";
        var n = "";
        if (e.params instanceof FormData) n = e.params;
        else if (e.params instanceof Object)
          for (var a in e.params) n += "" === n ? "GET" === e.method ? "?" : "" : "&", n += encodeURIComponent(a) + "=" + encodeURIComponent(e.params[a]);
        this.xmlhttp = new XMLHttpRequest, this.xmlhttp.onreadystatechange = function(e, n) {
          return function() {
            4 == e.readyState && (ge("loader").style.display = "none", n.callback(e.status, e.responseText), 0 === t.queue.length && (t.running = !1), t.running && t._request(t.queue.shift()))
          }
        }(this.xmlhttp, e), "GET" === e.method ? (this.xmlhttp.open(e.method, e.url + n, !0), this.xmlhttp.send()) : (this.xmlhttp.open(e.method, e.url, !0), n instanceof String && this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), this.xmlhttp.send(n))
      }
    },
    stop: function() {
      this.running && (this.running = !1), this.xmlhttp && this.xmlhttp.readyState < 4 && this.xmlhttp.abort()
    },
    add: function(e, t, n, a) {
      this.queue.push({
        url: t,
        method: e,
        params: n,
        callback: a
      }), this.running || this._request(this.queue.shift())
    }
  };
  var requests = new QueuedRequester
</script>
 <script type="text/javascript" src="https://ajaxorg.github.io/ace-builds/src/ace.js"></script> 
<script>
  if (void 0 === ace.edit) {
    var script = document.createElement("script");
    script.src = "/ace.js", script.async = !1, document.head.appendChild(script)
  }
</script>

<body onload="onBodyLoad()">
<!--
would like to use / have another the colorpicker addon in the code editor
active on edited text
but i do not know how to implement it in this html/script code
https://codepen.io/ldijkman/pen/LYdZpYp
-->
  <script type="text/javascript" src="https://jscolor.com/release/2.4/jscolor-2.4.8/jscolor.js" ></script>   

<div id="container">
   <div id="loader" class="loader"></div>
    <div id="uploader">
        <div style="float:right;">
             <input type="checkbox" class="Preview" id ="Preview" name="Preview" title="reload preview on every keystroke on/off" checked>
             <input type="button" value="Preview" onClick="updatefrombutton();" title="reload by button if checkbox is off" ></label>&nbsp;&nbsp;&nbsp;
             Color: <input id="to-select-text" onClick="this.select();" value="#3399FF80" data-jscolor="{}" title="colorpicker" >
             <button id="copy-button" title="maybe copy works" >Copy</button>
        </div>
    </div>

    <div id="tree"></div>

    <div id="editor"></div> 
    <div id="iframediv"><iframe id="iframe" frameborder="0"></iframe></div>

    <div id="preview" style="display:none"></div>

    <iframe id="download-frame" style="display:none"></iframe>
</div>

<script>
// https://codepen.io/fabean/pen/GprQJa
let button = document.getElementById('copy-button');

button.addEventListener('click', function(e) {
  e.preventDefault();
  document.execCommand('copy', false, document.getElementById('to-select-text').select());
});
</script>

<!--  https://ldijkman.github.io/Electra/    -->