Skip to content

Commit

Permalink
Add file edit, UserModLive add external functions and run method
Browse files Browse the repository at this point in the history
index.js:
- add type fileEdit
- change type file to fileUpload
- add table header for delete column
- add function uploadFileWithText

SysModFiles
- remove flLink
- add flTIme showing file.getLastWrite
- add flEdit (initFileEdit)

SysModUI
- rename initFile to initFileUpload
- add initFileEdit

UserModLive
- add pragma once
- show: add time3 and time4
- fileName as class variable
- script.onChange: call run
- add external functions (resetShowStats, print functions, trigo functions)
- add run method
  • Loading branch information
ewoudwijma committed Jul 10, 2024
1 parent 904212b commit a88675c
Show file tree
Hide file tree
Showing 9 changed files with 1,680 additions and 1,469 deletions.
113 changes: 102 additions & 11 deletions data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function ppf() {
let logNode = gId("log");
let sep = "";
if (logNode) {
// console.log("logNode", logNode);
if (logNode.value.length > 64000) logNode.value = ""; //reset if too big
// console.log("conslog", theArgs);
for (var i = 0; i < arguments.length; i++) {
Expand Down Expand Up @@ -369,6 +370,61 @@ function createHTML(json, parentNode = null, rowNr = UINT8_MAX) {
varNode.rows = 10;
varNode.readOnly = variable.ro;
varNode.addEventListener('dblclick', (event) => {toggleModal(event.target);});
} else if (variable.type == "fileEdit") {

varNode = cE("input");
varNode.type = "button";
varNode.disabled = variable.ro;
varNode.value = "🔍"; //initial label, button.value is the label shown on the button
varNode.addEventListener('click', (event) => {
let url = `http://${window.location.hostname}/file/`;
console.log("fileEdit event.target", event.target, url, varNode.getAttribute("fName"));
fetchAndExecute(url, varNode.getAttribute("fName"), event.target, function(varNode, text) { //send node.id as parameter
// console.log("fetchAndExecute", text); //in case of invalid commandJson
console.log("fileEdit fetched", varNode.getAttribute("fName"), text);

//removeChild
let modalView = gId('modalView');

if (modalView) {
//delete old nodes
while (modalView.firstChild) {
modalView.removeChild(modalView.lastChild);
}

let h1Node = cE("h1");
h1Node.innerText = "Edit " + varNode.getAttribute("fName");
modalView.appendChild(h1Node);

//cancel
let cancelButtonNode = cE("input");
cancelButtonNode.type = "button";
cancelButtonNode.value = "cancel";
cancelButtonNode.addEventListener('click', (event) => {
modalView.style.transform = (false) ? "translateY(0px)":"translateY(100%)";
});
modalView.appendChild(cancelButtonNode);

let textAreaNode = cE("textarea");

//save
let saveButtonNode = cE("input");
saveButtonNode.type = "button";
saveButtonNode.value = "save";
saveButtonNode.addEventListener('click', (event) => {
console.log("fileEdit save", varNode.getAttribute("fName"), textAreaNode);
uploadFileWithText("/" + varNode.getAttribute("fName"), textAreaNode.value);
modalView.style.transform = (false) ? "translateY(0px)":"translateY(100%)";
});
modalView.appendChild(saveButtonNode);

textAreaNode.value = text;
// varNode.rows = 10;
modalView.appendChild(textAreaNode);
modalView.style.transform = (true) ? "translateY(0px)":"translateY(100%)";
}
});
});
}
else if (variable.type == "url") {
varNode = cE("a");
Expand Down Expand Up @@ -441,18 +497,18 @@ function createHTML(json, parentNode = null, rowNr = UINT8_MAX) {
varNode = cE("progress");
varNode.min = variable.min?variable.min:0; //if not specified then unsigned value (min=0)
if (variable.max) varNode.max = variable.max;
} else if (variable.type == "file") {
} else if (variable.type == "fileUpload") {
//https://github.com/smford/esp32-asyncwebserver-fileupload-example/blob/master/example-01/example-01.ino

varNode = cE("span");

inputNode = cE("input");
inputNode.type = variable.type;
inputNode.type = "file";
inputNode.addEventListener('change', (event) => {
let fileNode = event.target;
let file = fileNode.files[0];
let formData = new FormData();
console.log("file " + variable.id, file, formData, file.size);
console.log("fileUpload " + variable.id, file, formData, file.size);
fileNode.parentNode.querySelector("progress").max = Math.round(file.size / 10000); //set progress max in blocks of 10K

formData.append("file", file);
Expand Down Expand Up @@ -528,6 +584,13 @@ function createHTML(json, parentNode = null, rowNr = UINT8_MAX) {
createHTML(variable.n, varNode, rowNr); //details (e.g. module)
}

//add a th for the delete button
if (variable.type == "table" && !variable.ro) {
thNode = cE("th");
thNode.innerText = "-";
varNode.querySelector('thead').querySelector("tr").appendChild(thNode);
}

//don't call onUI on table rows (the table header calls onUI and propagate this to table row columns in changeHTML when needed - e.g. select)
if (variable.fun == null || variable.fun == -2) { //request processed
variable.chk = "gen2";
Expand Down Expand Up @@ -893,7 +956,7 @@ function changeHTML(variable, commandJson, rowNr = UINT8_MAX) {
if (Array.isArray(commandJson.value)) {
console.log("changeHTML value table", variable, node, commandJson, rowNr);
//remove table rows
let tbodyNode = cE('tbody'); //the tbody of node will be replaced
let tbodyNode = cE("tbody"); //the tbody of node will be replaced
//replace the table body
node.replaceChild(tbodyNode, node.querySelector("tbody")); //replace <table><tbody> by tbodyNode //add to dom asap

Expand Down Expand Up @@ -1052,7 +1115,7 @@ function changeHTML(variable, commandJson, rowNr = UINT8_MAX) {
node.value = commandJson.value;
}
}
else if (node.className == "file") {
else if (node.className == "fileUpload") {
if (variable.ro) { //text and numbers read only
// console.log("changeHTML value span not select", variable, node, commandJson, rowNr);
} else {
Expand All @@ -1064,7 +1127,7 @@ function changeHTML(variable, commandJson, rowNr = UINT8_MAX) {
progressNode.hidden = true;
spanNode.innerText = "🟢";
inputNode.value = null;
console.log("succes");
console.log("fileUpload succes");
}
else if (commandJson.value == UINT16_MAX - 20) {
progressNode.hidden = true;
Expand All @@ -1082,6 +1145,13 @@ function changeHTML(variable, commandJson, rowNr = UINT8_MAX) {
} else if (node.className == "textarea") {
node.value += commandJson.value;
node.scrollTop = node.scrollHeight;
} else if (node.className == "fileEdit") {
let value = commandJson.value;
// console.log("change button", variable, node, value);
if (Array.isArray(commandJson.value) && rowNr != UINT8_MAX) {
value = commandJson.value[rowNr];
}
if (value) node.setAttribute('fName', value); //don't change the value / prompt of the button
} else {//inputs and progress type
if (variable.ro && nodeType == "span") { //text and numbers read only
// console.log("changeHTML value span not select", variable, node, commandJson, rowNr);
Expand Down Expand Up @@ -1158,7 +1228,7 @@ function changeHTML(variable, commandJson, rowNr = UINT8_MAX) {
console.log("changeHTML file requested", variable.id, rowNr, commandJson);

//we need to send a request which the server can handle using request variable
let url = `http://${window.location.hostname}/file`;
let url = `http://${window.location.hostname}/file/`;
fetchAndExecute(url, commandJson.file, node.id, function(id, text) { //send node.id as parameter
// console.log("fetchAndExecute", text); //in case of invalid commandJson
var ledmapJson = JSON.parse(text);
Expand Down Expand Up @@ -1306,8 +1376,14 @@ function toggleModal(varNode) { //canvas or textarea
// console.log("toggleModal", varNode);
isModal = !isModal;

if (isModal) {
let modalView = gId('modalView');

if (isModal) {
//delete old nodes
while (modalView.firstChild) {
modalView.removeChild(modalView.lastChild);
}

modalPlaceHolder = cE(varNode.nodeName.toLocaleLowerCase()); //create canvas or textarea
modalPlaceHolder.width = varNode.width;
modalPlaceHolder.height = varNode.height;
Expand All @@ -1316,10 +1392,10 @@ function toggleModal(varNode) { //canvas or textarea

// let btn = cE("button");
// btn.innerText = "close";
// btn.addEventListener('click', (event) => {toggleModal(varNode);});
// btn.addEventListener('click', (event) => {toggleModal(event.target);});
// gId('modalView').appendChild(btn);

gId('modalView').appendChild(varNode);
modalView.appendChild(varNode);
varNode.width = window.innerWidth;
varNode.height = window.innerHeight;
// console.log("toggleModal +", varNode, modalPlaceHolder, varNode.getBoundingClientRect(), modalPlaceHolder.getBoundingClientRect().width, modalPlaceHolder.getBoundingClientRect().height, modalPlaceHolder.width, modalPlaceHolder.height);
Expand All @@ -1333,7 +1409,7 @@ function toggleModal(varNode) { //canvas or textarea
modalPlaceHolder.parentNode.replaceChild(varNode, modalPlaceHolder); // //replace by varNode. modalPlaceHolder loses rect
}

gId('modalView').style.transform = (isModal) ? "translateY(0px)":"translateY(100%)";
modalView.style.transform = (isModal) ? "translateY(0px)":"translateY(100%)";
}
// https://stackoverflow.com/questions/324303/cut-and-paste-moving-nodes-in-the-dom-with-javascript

Expand Down Expand Up @@ -1483,6 +1559,21 @@ function fetchAndExecute(url, name, parms, callback, callError = null)
});
}

function uploadFileWithText(name, text)
{
var req = new XMLHttpRequest();
req.addEventListener('load', function(){console.log("uploadFileWithText load", this.responseText, this.status);});
req.addEventListener('error', function(e){console.log("uploadFileWithText error", e);});
req.open("POST", "/upload");
var formData = new FormData();

var blob = new Blob([text], {type : 'application/text'});
var fileOfBlob = new File([blob], name);
formData.append("upload", fileOfBlob);

req.send(formData);
}

function setInstanceTableColumns() {

let tbl = gId("insTbl");
Expand Down
11 changes: 7 additions & 4 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ build_flags =
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 ;tbd experiment without
;-D CONFIG_ASYNC_TCP_TASK_STACK_SIZE ; 8192*2 here as default !!!
lib_deps =
https://github.com/MoonModules/ESPAsyncWebServer.git @ 3.2.2 ; + queueLength
https://github.com/MoonModules/ESPAsyncWebServer.git @ 3.2.2 ; + queueLength (see https://github.com/esphome/ESPAsyncWebServer/pull/38)

; ESPAsyncWebServer AirCoookie v2.0.7 version (2.2.1 is latest)
; [ESPAsyncWebServer]
Expand All @@ -31,6 +31,10 @@ lib_deps =
; https://github.com/ewoudwijma/ESPAsyncWebServer.git#v2.0.7 ;aircoookie + getClients + 64
; ; https://github.com/ewoudwijma/ESPAsyncWebServer.git#v2.2.1 ;crashes on ws refererence in sendDataWs !!

; see also:
; https://github.com/esphome/ESPAsyncWebServer/issues/34 ; Decide which ESPAsyncWebServer fork should be canonical?
; https://github.com/mathieucarbou/ESPAsyncWebServer/discussions/38 ; Should we try to establish this as the de facto successor fork of ESPAsyncWebServer?
; https://github.com/mathieucarbou/ESPAsyncWebServer ; Important recommendations

;Work in progress - not compiling yet
[PsychicHttp]
Expand Down Expand Up @@ -65,14 +69,13 @@ lib_deps =
build_flags =
-D STARBASE_USERMOD_LIVE
lib_deps =
https://github.com/hpwit/ASMParser.git#memory
; about 1% / 19KB flash
https://github.com/hpwit/ASMParser.git#memory ; about 1% / 19KB flash

[STARBASE]
build_flags =
-D APP=StarBase
-D PIOENV=$PIOENV
-D VERSION=24070509 ; Date and time (GMT!), update at every commit!!
-D VERSION=24071009 ; Date and time (GMT!), update at every commit!!
-D LFS_THREADSAFE ; enables use of semaphores in LittleFS driver
-D STARBASE_DEVMODE
${ESPAsyncWebServer.build_flags} ;alternatively PsychicHttp
Expand Down
40 changes: 32 additions & 8 deletions src/Sys/SysModFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "SysModWeb.h"
#include "SysModPrint.h"
#include "SysModModel.h"
#include "SysModSystem.h"

// #include <FS.h>

Expand Down Expand Up @@ -74,21 +75,43 @@ void SysModFiles::setup() {
default: return false;
}});

ui->initURL(tableVar, "flLink", nullptr, true, [this](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
// ui->initURL(tableVar, "flLink", nullptr, true, [this](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
// case onSetValue:
// for (forUnsigned8 rowNr = 0; rowNr < fileList.size(); rowNr++) {
// char urlString[32] = "file/";
// strncat(urlString, fileList[rowNr].name, sizeof(urlString)-1);
// mdl->setValue(var, JsonString(urlString, JsonString::Copied), rowNr);
// }
// return true;
// case onUI:
// ui->setLabel(var, "Show");
// return true;
// default: return false;
// }});

ui->initNumber(tableVar, "flTime", UINT16_MAX, 0, UINT16_MAX, true, [this](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
case onSetValue:
for (forUnsigned8 rowNr = 0; rowNr < fileList.size(); rowNr++) {
char urlString[32] = "file/";
strncat(urlString, fileList[rowNr].name, sizeof(urlString)-1);
mdl->setValue(var, JsonString(urlString, JsonString::Copied), rowNr);
}
for (forUnsigned8 rowNr = 0; rowNr < fileList.size(); rowNr++)
mdl->setValue(var, fileList[rowNr].time, rowNr);
return true;
case onUI:
ui->setLabel(var, "Time");
return true;
default: return false;
}});

ui->initFileEdit(tableVar, "flEdit", nullptr, false, [this](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
case onSetValue:
for (forUnsigned8 rowNr = 0; rowNr < fileList.size(); rowNr++)
mdl->setValue(var, JsonString(fileList[rowNr].name, JsonString::Copied), rowNr);
return true;
case onUI:
ui->setLabel(var, "Show");
ui->setLabel(var, "Edit");
return true;
default: return false;
}});

ui->initFile(parentVar, "upload", nullptr, UINT16_MAX, false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
ui->initFileUpload(parentVar, "upload", nullptr, UINT16_MAX, false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
case onUI:
ui->setLabel(var, "Upload File");
default: return false;
Expand Down Expand Up @@ -121,6 +144,7 @@ void SysModFiles::loop20ms() {
FileDetails details;
strcpy(details.name, file.name());
details.size = file.size();
details.time = file.getLastWrite(); // - millis()/1000; if (details.time < 0) details.time = 0;
fileList.push_back(details);
file.close();
file = root.openNextFile();
Expand Down
1 change: 1 addition & 0 deletions src/Sys/SysModFiles.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
struct FileDetails {
char name[32];
size_t size;
time_t time;
};

class SysModFiles: public SysModule {
Expand Down
5 changes: 3 additions & 2 deletions src/Sys/SysModSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ void SysModSystem::setup() {
ui->initProgress(parentVar, "heap", (ESP.getHeapSize()-ESP.getFreeHeap()) / 1000, 0, ESP.getHeapSize()/1000, true, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
case onChange:
var["max"] = ESP.getHeapSize()/1000; //makes sense?
web->addResponseV(var["id"], "comment", "f:%d / t:%d (l:%d) B", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getMaxAllocHeap());
web->addResponseV(var["id"], "comment", "f:%d / t:%d (l:%d) B [%d %d]", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getMaxAllocHeap(), esp_get_free_heap_size(), esp_get_free_internal_heap_size());
//temporary add esp_get_free_heap_size(), esp_get_free_internal_heap_size() to see if/how it differs
return true;
default: return false;
}});
Expand Down Expand Up @@ -185,7 +186,7 @@ void SysModSystem::setup() {
// ui->initText(parentVar, "date", __DATE__, 16, true);
// ui->initText(parentVar, "time", __TIME__, 16, true);

ui->initFile(parentVar, "update", nullptr, UINT16_MAX, false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
ui->initFileUpload(parentVar, "update", nullptr, UINT16_MAX, false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun
case onUI:
ui->setLabel(var, "OTA Update");
return true;
Expand Down
8 changes: 6 additions & 2 deletions src/Sys/SysModUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ class SysModUI: public SysModule {
return initVarAndUpdate<const char *>(parent, id, "text", value, 0, max, readOnly, varFun);
}

JsonObject initFile(JsonObject parent, const char * id, const char * value = nullptr, unsigned16 max = 32, bool readOnly = false, VarFun varFun = nullptr) {
return initVarAndUpdate<const char *>(parent, id, "file", value, 0, max, readOnly, varFun);
JsonObject initFileUpload(JsonObject parent, const char * id, const char * value = nullptr, unsigned16 max = 32, bool readOnly = false, VarFun varFun = nullptr) {
return initVarAndUpdate<const char *>(parent, id, "fileUpload", value, 0, max, readOnly, varFun);
}
JsonObject initPassword(JsonObject parent, const char * id, const char * value = nullptr, unsigned8 max = 32, bool readOnly = false, VarFun varFun = nullptr) {
return initVarAndUpdate<const char *>(parent, id, "password", value, 0, max, readOnly, varFun);
Expand Down Expand Up @@ -148,6 +148,10 @@ class SysModUI: public SysModule {
return initVarAndUpdate<const char *>(parent, id, "textarea", value, 0, 0, readOnly, varFun);
}

JsonObject initFileEdit(JsonObject parent, const char * id, const char * value = nullptr, bool readOnly = false, VarFun varFun = nullptr) {
return initVarAndUpdate<const char *>(parent, id, "fileEdit", value, 0, 0, readOnly, varFun);
}

JsonObject initURL(JsonObject parent, const char * id, const char * value = nullptr, bool readOnly = false, VarFun varFun = nullptr) {
return initVarAndUpdate<const char *>(parent, id, "url", value, 0, 0, readOnly, varFun);
}
Expand Down
9 changes: 9 additions & 0 deletions src/Sys/SysModWeb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#include "SysModPins.h"

#include "User/UserModMDNS.h"
// #ifdef STARBASE_USERMOD_LIVE
// #include "../User/UserModLive.h"
// #endif

#include "html_ui.h"

Expand Down Expand Up @@ -500,6 +503,12 @@ void SysModWeb::serveUpload(WebRequest *request, const String& filename, size_t
request->send(200, "text/plain", F("File Uploaded!"));

files->filesChanged = true;

//if sc files send command to live
// #ifdef STARBASE_USERMOD_LIVE
// if (filename.indexOf(".sc") > 0)
// liveM->run(filename.c_str());
// #endif
}
}

Expand Down
Loading

0 comments on commit a88675c

Please sign in to comment.