From 42c727d9ded738f878746b54e48a4965003084ca Mon Sep 17 00:00:00 2001 From: Thokoop <11939567+Thokoop@users.noreply.github.com> Date: Tue, 8 Apr 2025 18:44:37 +0200 Subject: [PATCH 1/6] Improvements in settings and mode selection on one page. --- src/SplitFlapDisplay.ino | 1 - src/SplitFlapMqtt.cpp | 1 - src/web/custom-text.html | 216 ---------- src/web/index.css | 10 + src/web/index.html | 209 ++++++++-- src/web/index.js | 164 +++++++- src/web/mode.html | 85 ---- src/web/settings.html | 879 +++++++++++++++++++-------------------- 8 files changed, 739 insertions(+), 826 deletions(-) delete mode 100644 src/web/custom-text.html delete mode 100644 src/web/mode.html diff --git a/src/SplitFlapDisplay.ino b/src/SplitFlapDisplay.ino index b27d393..4e08418 100644 --- a/src/SplitFlapDisplay.ino +++ b/src/SplitFlapDisplay.ino @@ -82,7 +82,6 @@ void setup() { splitflapMqtt.setup(); splitflapMqtt.setDisplay(&display); display.setMqtt(&splitflapMqtt); - display.homeToString(""); display.writeString("OK"); delay(250); diff --git a/src/SplitFlapMqtt.cpp b/src/SplitFlapMqtt.cpp index 8b04d19..728c1f3 100644 --- a/src/SplitFlapMqtt.cpp +++ b/src/SplitFlapMqtt.cpp @@ -69,7 +69,6 @@ void SplitFlapMqtt::connectToMqtt() { "\"unique_id\":\"splitflap_sensor_" + mdns + "\"," "\"state_topic\":\"" + topic_state + "\"," "\"availability_topic\":\"" + topic_avail + "\"," - "\"device_class\":\"none\"," "\"entity_category\":\"diagnostic\"," "\"device\":{" "\"identifiers\":[\"splitflap_" + mdns + "\"]," diff --git a/src/web/custom-text.html b/src/web/custom-text.html deleted file mode 100644 index b62ece0..0000000 --- a/src/web/custom-text.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - - - - - - - -
-
- < -

-
- -
- - -
- -
- - -
- -
- -
- -
- -
- - -
- -
- - -
-
- -
- - -
- - - -
-
- - - diff --git a/src/web/index.css b/src/web/index.css index f1d8c73..0087959 100644 --- a/src/web/index.css +++ b/src/web/index.css @@ -1 +1,11 @@ @import "tailwindcss"; + +input[type="number"].no-buttons::-webkit-outer-spin-button, +input[type="number"].no-buttons::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"].no-buttons { + -moz-appearance: textfield; /* Firefox */ +} \ No newline at end of file diff --git a/src/web/index.html b/src/web/index.html index 680fe70..b65a2ca 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -1,52 +1,171 @@ - - - - - - - - - - + + + + + + + + + +
+
+

+
+
+ + + + + + + + + Settings +
+
+ diff --git a/src/web/index.js b/src/web/index.js index 13baf2a..8e4575a 100644 --- a/src/web/index.js +++ b/src/web/index.js @@ -4,48 +4,180 @@ window.Alpine = Alpine; document.addEventListener("alpine:init", () => { Alpine.data("page", (type) => ({ get header() { - return (this.settings.name || "Split Flap") + " " + type; + return (this.settings.name || "Split Flap"); }, loading: { settings: true, + timezones: true, }, - + saving: false, dialog: { show: false, message: "", type: null, }, - - settings: {}, + settings: { + mode: 2, + }, errors: {}, + timezones: {}, + + // Control page specific + singleMode: true, + singleWord: "", + multiWord: "", + multiWords: [], + delay: 1, + centerText: false, + + get processing() { + return this.saving || this.loading.settings || this.loading.timezones; + }, + + // πŸ†• Helper for per-module input arrays + get addressArray() { + return this.settings.moduleAddresses?.split(',').map(s => s.trim()) || []; + }, + setAddress(index, value) { + const arr = this.addressArray; + arr[index] = value; + this.settings.moduleAddresses = arr.join(','); + }, + + get offsetArray() { + return this.settings.moduleOffsets?.split(',').map(s => s.trim()) || []; + }, + setOffset(index, value) { + const arr = this.offsetArray; + arr[index] = value; + this.settings.moduleOffsets = arr.join(','); + }, init() { this.loadSettings(); + if (type === "Settings") { + this.loadTimezones(); + } }, loadSettings() { fetch("/settings") - .then((response) => response.json()) + .then((res) => res.json()) .then((data) => { - Object.keys(data).forEach((key) => { - this.settings[key] = data[key]; - }); + Object.assign(this.settings, data); + }) + .catch(() => this.showDialog("Failed to load settings", "error", true)) + .finally(() => { + this.loading.settings = false; + }); + }, + + loadTimezones() { + fetch("/timezones.json") + .then((res) => res.json()) + .then((data) => { + this.timezones = data; }) - .catch((error) => - this.showDialog( - "Failed to load settings. Refresh the page.", - "error", - true, - ), + .catch(() => + this.showDialog("Failed to load timezones. Refresh the page.", "error", true) ) - .finally(() => (this.loading.settings = false)); + .finally(() => (this.loading.timezones = false)); + }, + + updateDisplay() { + if (this.settings.mode === 6) { + if (this.delay < 1) { + return this.showDialog("Delay must be at least 1 second.", "error"); + } + + if (this.singleMode && this.singleWord.trim() === "") { + return this.showDialog("Single word cannot be empty.", "error"); + } + + if (!this.singleMode && this.multiWords.length === 0) { + return this.showDialog("Word list cannot be empty.", "error"); + } + } + + fetch("/settings", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ mode: this.settings.mode }), + }); + + if (this.settings.mode === 6) { + fetch("/text", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + mode: this.singleMode ? "single" : "multiple", + words: this.singleMode ? [this.singleWord] : this.multiWords, + delay: this.delay, + center: this.centerText, + }), + }) + .then((res) => res.json()) + .then((res) => this.showDialog(res.message, res.type)) + .catch((err) => this.showDialog(err.message, "error")); + } else { + this.showDialog("Mode updated successfully.", "success"); + } + }, + + addWord() { + if (this.multiWord.trim() !== "") { + this.multiWords.push(this.multiWord.trim()); + } + this.multiWord = ""; + }, + + removeWord(index) { + this.multiWords.splice(index, 1); }, - showDialog(message, type, persistent = false) { + save() { + this.saving = true; + this.errors = {}; + + fetch("/settings", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(this.settings), + }) + .then((res) => res.json()) + .then((data) => { + this.errors = data.errors || {}; + this.showDialog(data.message, data.type, data.persistent); + if (data.redirect) { + setTimeout(() => { + window.location.href = data.redirect; + }, 10000); + } + }) + .catch(() => this.showDialog("Failed to save settings.", "error")) + .finally(() => (this.saving = false)); + }, + + reset() { + if (confirm("Are you sure you want to reset settings to defaults?")) { + fetch("/settings/reset", { method: "POST" }) + .then((res) => res.json()) + .then((data) => { + this.showDialog(data.message, data.type, data.persistent); + this.loadSettings(); + }) + .catch(() => { + this.showDialog("Failed to reset settings.", "error"); + }); + } + }, + + showDialog(message, type = "success", persistent = false) { this.dialog.message = message; this.dialog.type = type; this.dialog.show = true; + if (!persistent) { setTimeout(() => (this.dialog.show = false), 3000); } diff --git a/src/web/mode.html b/src/web/mode.html deleted file mode 100644 index 543b59e..0000000 --- a/src/web/mode.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - -
-
- < -

-
- - - - - -
- -
- - diff --git a/src/web/settings.html b/src/web/settings.html index 111fca8..8db021c 100644 --- a/src/web/settings.html +++ b/src/web/settings.html @@ -1,533 +1,488 @@ - - - - - - - - - - + + + + + + + + + +
-
-
- < -

-
- -

- General Settings -

- - - -
- - - -
- - -
- - -
- +

+ General Settings +

+ +
+
+ +
- - - - -
+ class="w-full p-3 mt-2 text-sm text-white bg-red-700 rounded-md" + x-cloak + x-show="errors.key === 'name'" + x-text="errors.message" + >
-
+
+ + +
+
+
-

+ Timezone + +
+ +
+ - Wi-Fi Settings -

- - + + + +
+
+
+ +

+ Wi-Fi Settings +

+ +
+
+
- - +
+
+
+
+
+ + + +
-

- MQTT Settings -

+

+ MQTT Settings +

+
+
+
+
+
+
- +
+
+ - - +
+
+ +
+
-

- Hardware Settings -

+

+ Hardware Settings +

- - -
- - + + - - -
- +
+
+
+
- +
+
+
+
- + +
+ +
+
+ + +
+ +
+
+ + + +
+
+
+ + +
+
-
+ +
+ + +
+
-
-