diff --git a/usermods/customeffects/customeffects.css b/usermods/customeffects/customeffects.css
new file mode 100644
index 00000000..39ef3967
--- /dev/null
+++ b/usermods/customeffects/customeffects.css
@@ -0,0 +1,12 @@
+.ceTextarea {
+ width: 90%;
+ height: 300px;
+ resize: none;
+ white-space: pre;
+}
+
+#kceEditor {
+ max-width: 490px;
+ display: inline-block;
+}
+
diff --git a/usermods/customeffects/customeffects.js b/usermods/customeffects/customeffects.js
new file mode 100644
index 00000000..78d7cf79
--- /dev/null
+++ b/usermods/customeffects/customeffects.js
@@ -0,0 +1,154 @@
+
+var isCEEditor = false;
+
+function toggleCEEditor(name, segID) {
+ if (isInfo) toggleInfo();
+ if (isNodes) toggleNodes();
+ isCEEditor = !isCEEditor;
+ if (isCEEditor) populateCEEditor(name, segID);
+ d.getElementById('ceEditor').style.transform = (isCEEditor) ? "translateY(0px)":"translateY(100%)";
+}
+
+function fetchAndExecute(url, name, callback)
+{
+ fetch
+ (url+name, {
+ method: 'get'
+ })
+ .then(res => {
+ if (!res.ok) {
+ showToast("File " + name + " not found", true);
+ return "";
+ }
+ return res.text();
+ })
+ .then(text => {
+ callback(text);
+ })
+ .catch(function (error) {
+ showToast("Error getting " + name, true);
+ // showToast(error, true);
+ // console.log(error);
+ presetError(false);
+ })
+ .finally(() => {
+ // if (callback) setTimeout(callback,99);
+ });
+}
+
+function loadLogFile(name, attempt) {
+ var ceLogArea = d.getElementById("ceLogArea");
+ fetchAndExecute((loc?`http://${locip}`:'.') + "/", name , function(logtext)
+ {
+ if (logtext == "") {
+ if (attempt < 10) {
+ ceLogArea.value = ("...........").substring(0, attempt + 1);
+ setTimeout(() =>
+ {
+ loadLogFile(name, attempt + 1);
+ }, 1000);
+ }
+ else
+ ceLogArea.value = "log not found after 10 seconds";
+ }
+ else
+ ceLogArea.value = logtext;
+ });
+}
+
+function saveCE(name, segID) {
+ showToast("Saving " + name);
+
+ var ceProgramArea = d.getElementById("ceProgramArea");
+
+ uploadFileWithText("/" + name, ceProgramArea.value);
+
+ var obj = {"seg": {"id": segID, "reset": true}};
+ requestJson(obj);
+
+ var ceLogArea = d.getElementById("ceLogArea");
+ ceLogArea.value = ".";
+ setTimeout(() =>
+ {
+ loadLogFile(name + ".log", 1);
+ }, 1000);
+}
+
+function populateCEEditor(name, segID)
+{
+ fetchAndExecute((loc?`http://${locip}`:'.') + "/", name + ".wled", function(text)
+ {
+ var cn=`Custom Effects Editor
+ ${name}.wled
+
+
+
+
+
+
+
+ Custom Effects Library
+ Custom Effects Help
+
Compile and Run Log
+
+ Run log > 3 seconds is send to Serial Ouput.`;
+
+ d.getElementById('kceEditor').innerHTML = cn;
+
+ var ceLogArea = d.getElementById("ceLogArea");
+ ceLogArea.value = ".";
+ loadLogFile(name + ".wled.log", 1);
+
+ });
+}
+
+function downloadCEFile(name) {
+ var url = "https://raw.githubusercontent.com/MoonModules/WLED-Effects/master/CustomEffects/wled/";
+
+ fetchAndExecute(url, name, function(text) {
+ console.log(text);
+ if (name == "wled.json" || name == "presets.json") {
+ if (!confirm('Are you sure to download/overwrite ' + name + '?'))
+ return;
+ uploadFileWithText("/" + name, text);
+ }
+ else
+ {
+ var ceProgramArea = d.getElementById("ceProgramArea");
+ ceProgramArea.value = text;
+ }
+ });
+
+ return;
+
+ var request = new XMLHttpRequest();
+ request.onload = function() {
+ if (name == "wled.json" || name == "presets.json") {
+ if (!confirm('Are you sure to download ' + name + '?'))
+ return;
+ uploadFileWithText("/" + name, request.response);
+ }
+ else
+ {
+ var ceProgramArea = d.getElementById("ceProgramArea");
+ ceProgramArea.value = request.response;
+ }
+ }
+ request.open("GET", url);
+ request.send();
+ }
+
+function loadCETemplate(name) {
+ var ceProgramArea = d.getElementById("ceProgramArea");
+ ceProgramArea.value = `/*
+ Custom Effects Template
+ */
+ program ${name}
+ {
+ function renderFrame()
+ {
+ setPixelColor(counter, colorFromPalette(counter, counter))
+ }
+ }`;
+
+}
\ No newline at end of file
diff --git a/usermods/customeffects/usermod_v2_customeffects.h b/usermods/customeffects/usermod_v2_customeffects.h
index 891b4891..389fa8ac 100644
--- a/usermods/customeffects/usermod_v2_customeffects.h
+++ b/usermods/customeffects/usermod_v2_customeffects.h
@@ -100,10 +100,16 @@ class CustomEffectsUserMod : public Usermod {
unsigned long lastTime = 0; //will be used to download new forecast every hour
char errorMessage[100] = "";
+ bool enabled = false;
+ bool initDone = false;
+
public:
void setup() {
- strip.addEffect(255, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT);
+ if (!initDone)
+ strip.addEffect(255, &mode_customEffect, _data_FX_MODE_CUSTOMEFFECT);
+ initDone = true;
+ enabled = true;
}
void connected() {
@@ -140,6 +146,12 @@ class CustomEffectsUserMod : public Usermod {
void addToJsonState(JsonObject& root)
{
//root["user0"] = userVar0;
+ if (!initDone) return; // prevent crash on boot applyPreset()
+ JsonObject usermod = root[FPSTR(_name)];
+ if (usermod.isNull()) {
+ usermod = root.createNestedObject(FPSTR(_name));
+ }
+ usermod["on"] = enabled;
}
@@ -196,4 +208,4 @@ class CustomEffectsUserMod : public Usermod {
};
// strings to reduce flash memory usage (used more than twice)
-const char CustomEffectsUserMod::_name[] PROGMEM = "Custom Effects";
+const char CustomEffectsUserMod::_name[] PROGMEM = "CustomEffects";
diff --git a/wled00/data/index.htm b/wled00/data/index.htm
index f4b50c44..c9ddeaa3 100644
--- a/wled00/data/index.htm
+++ b/wled00/data/index.htm
@@ -51,6 +51,7 @@
setTimeout(()=>{h.appendChild(l)},100);
+