├── fetch.html.gz ├── icons ├── icon.png ├── icon_r.png ├── icon.svg └── icon_r.svg ├── .gitignore ├── LICENSE ├── Test ├── longpress custom.min.js ├── longpress.min.js ├── test.html ├── longpress custom.js ├── longpress.js ├── test.css ├── efc.min.js └── test.min.js ├── README.md ├── fetch.min.html └── fetch.html /fetch.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chromoxdor/easyfetch/HEAD/fetch.html.gz -------------------------------------------------------------------------------- /icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chromoxdor/easyfetch/HEAD/icons/icon.png -------------------------------------------------------------------------------- /icons/icon_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chromoxdor/easyfetch/HEAD/icons/icon_r.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS Specific Hidden Files #### 2 | 3 | # MacOSX Finder ***** 4 | .DS_Store 5 | 6 | 7 | /doku 8 | /sketch 9 | test/test_bkup.css 10 | test/test.min.css 11 | test/test 2.js 12 | .vscode 13 | fetch.min.html 14 | 15 | -------------------------------------------------------------------------------- /icons/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icons/icon_r.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 chromoxdor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Test/longpress custom.min.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"use strict";let t=null;const o=10,a=10;let i={x:0,y:0},s=1e3;const c="ontouchstart"in e||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0,r="PointerEvent"in e||e.navigator&&"msPointerEnabled"in e.navigator?{down:"pointerdown",up:"pointerup",move:"pointermove",leave:"pointerleave"}:c?{down:"touchstart",up:"touchend",move:"touchmove",leave:"touchleave"}:{down:"mousedown",up:"mouseup",move:"mousemove",leave:"mouseleave"};function u(e){v();const t=l(e),o=new CustomEvent("long-press",{bubbles:!0,cancelable:!0,detail:(a=t,{clientX:a.clientX,clientY:a.clientY,offsetX:a.offsetX,offsetY:a.offsetY,pageX:a.pageX,pageY:a.pageY,screenX:a.screenX,screenY:a.screenY})});var a;this.dispatchEvent(o)||n.addEventListener("click",m,!0)}function l(e){return e.changedTouches?e.changedTouches[0]:e}function d(e,n=s){v();const o=e.target;t=setTimeout((()=>u.call(o,e)),n)}function v(){t&&(clearTimeout(t),t=null)}function m(e){n.removeEventListener("click",m,!0),e.preventDefault(),e.stopImmediatePropagation()}n.addEventListener(r.down,(function(e){const n=l(e);i={x:n.clientX,y:n.clientY},d(e)}),!0),n.addEventListener(r.move,(function(e){const n=l(e);(Math.abs(i.x-n.clientX)>o||Math.abs(i.y-n.clientY)>a)&&v()}),!0),n.addEventListener(r.up,v,!0),n.addEventListener(r.leave,v,!0),n.addEventListener("wheel",v,!0),n.addEventListener("scroll",v,!0),navigator.userAgent.toLowerCase().includes("android")||n.addEventListener("contextmenu",v,!0),e.setLongPressDelay=function(e){s=e}}(window,document); -------------------------------------------------------------------------------- /Test/longpress.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"use strict";let n=null;const o=10,a=10;let i={x:0,y:0};const s="ontouchstart"in e||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0,u="PointerEvent"in e||e.navigator&&"msPointerEnabled"in e.navigator?{down:"pointerdown",up:"pointerup",move:"pointermove",leave:"pointerleave"}:s?{down:"touchstart",up:"touchend",move:"touchmove",leave:"touchleave"}:{down:"mousedown",up:"mouseup",move:"mousemove",leave:"mouseleave"};"function"!=typeof e.CustomEvent&&(e.CustomEvent=function(e,n={bubbles:!1,cancelable:!1,detail:void 0}){const o=t.createEvent("CustomEvent");return o.initCustomEvent(e,n.bubbles,n.cancelable,n.detail),o},e.CustomEvent.prototype=e.Event.prototype);const c=e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||(t=>e.setTimeout(t,1e3/60));function r(e){v();const n=l(e),o=new CustomEvent("long-press",{bubbles:!0,cancelable:!0,detail:(a=n,{clientX:a.clientX,clientY:a.clientY,offsetX:a.offsetX,offsetY:a.offsetY,pageX:a.pageX,pageY:a.pageY,screenX:a.screenX,screenY:a.screenY})});var a;this.dispatchEvent(o)||t.addEventListener("click",d,!0)}function l(e){return e.changedTouches?e.changedTouches[0]:e}function m(o){v();const a=o.target,i=parseInt(function(e,n,o){for(;e&&e!==t.documentElement;){const t=e.getAttribute(n);if(t)return t;e=e.parentNode}return o}(a,"data-long-press-delay","1000"),10);n=function(t,n){if(!c)return e.setTimeout(t,n);const o=(new Date).getTime(),a={},i=()=>{(new Date).getTime()-o>=n?t():a.value=c(i)};return a.value=c(i),a}(r.bind(a,o),i)}function v(){var t;(t=n)&&(e.cancelAnimationFrame||e.clearTimeout)(t.value),n=null}function d(e){t.removeEventListener("click",d,!0),e.preventDefault(),e.stopImmediatePropagation()}t.addEventListener(u.down,(function(e){const t=l(e);i={x:t.clientX,y:t.clientY},m(e)}),!0),t.addEventListener(u.move,(function(e){const t=l(e);(Math.abs(i.x-t.clientX)>o||Math.abs(i.y-t.clientY)>a)&&v()}),!0),t.addEventListener(u.up,v,!0),t.addEventListener(u.leave,v,!0),t.addEventListener("wheel",v,!0),t.addEventListener("scroll",v,!0),navigator.userAgent.toLowerCase().includes("android")||t.addEventListener("contextmenu",v,!0)}(window,document); -------------------------------------------------------------------------------- /Test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |
no info found...
21 | 22 | 23 | 24 |
25 |
20251110/1
ℹ︎ 26 |
27 |
×︎
28 |
29 |
 
30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 | ☰︎ 40 | ⌂︎ 42 |
43 |
44 |
45 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Test/longpress custom.js: -------------------------------------------------------------------------------- 1 | (function (window, document) { 2 | 'use strict'; 3 | 4 | let timer = null; 5 | const maxDiff = { x: 10, y: 10 }; 6 | let startPos = { x: 0, y: 0 }; 7 | let longPressDelay = 1000; // Default delay 8 | 9 | const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; 10 | const hasPointerEvents = 'PointerEvent' in window || (window.navigator && 'msPointerEnabled' in window.navigator); 11 | 12 | const eventMap = hasPointerEvents 13 | ? { down: 'pointerdown', up: 'pointerup', move: 'pointermove', leave: 'pointerleave' } 14 | : isTouch 15 | ? { down: 'touchstart', up: 'touchend', move: 'touchmove', leave: 'touchleave' } 16 | : { down: 'mousedown', up: 'mouseup', move: 'mousemove', leave: 'mouseleave' }; 17 | 18 | function fireLongPressEvent(originalEvent) { 19 | clearTimer(); 20 | const unifiedEvent = unifyEvent(originalEvent); 21 | const customEvent = new CustomEvent('long-press', { 22 | bubbles: true, 23 | cancelable: true, 24 | detail: getEventDetails(unifiedEvent) 25 | }); 26 | 27 | const allowClickEvent = this.dispatchEvent(customEvent); 28 | if (!allowClickEvent) { 29 | document.addEventListener('click', preventDefaultClick, true); 30 | } 31 | } 32 | 33 | function unifyEvent(e) { 34 | return e.changedTouches ? e.changedTouches[0] : e; 35 | } 36 | 37 | function getEventDetails(e) { 38 | return { 39 | clientX: e.clientX, 40 | clientY: e.clientY, 41 | offsetX: e.offsetX, 42 | offsetY: e.offsetY, 43 | pageX: e.pageX, 44 | pageY: e.pageY, 45 | screenX: e.screenX, 46 | screenY: e.screenY 47 | }; 48 | } 49 | 50 | function startTimer(e, delay = longPressDelay) { 51 | clearTimer(); 52 | const el = e.target; 53 | timer = setTimeout(() => fireLongPressEvent.call(el, e), delay); 54 | } 55 | 56 | function clearTimer() { 57 | if (timer) { 58 | clearTimeout(timer); 59 | timer = null; 60 | } 61 | } 62 | 63 | function preventDefaultClick(e) { 64 | document.removeEventListener('click', preventDefaultClick, true); 65 | e.preventDefault(); 66 | e.stopImmediatePropagation(); 67 | } 68 | 69 | function handleMouseDown(e) { 70 | const unifiedEvent = unifyEvent(e); 71 | startPos = { x: unifiedEvent.clientX, y: unifiedEvent.clientY }; 72 | startTimer(e); 73 | } 74 | 75 | function handleMouseMove(e) { 76 | const unifiedEvent = unifyEvent(e); 77 | if ( 78 | Math.abs(startPos.x - unifiedEvent.clientX) > maxDiff.x || 79 | Math.abs(startPos.y - unifiedEvent.clientY) > maxDiff.y 80 | ) { 81 | clearTimer(); 82 | } 83 | } 84 | 85 | // Event listeners 86 | document.addEventListener(eventMap.down, handleMouseDown, true); 87 | document.addEventListener(eventMap.move, handleMouseMove, true); 88 | document.addEventListener(eventMap.up, clearTimer, true); 89 | document.addEventListener(eventMap.leave, clearTimer, true); 90 | document.addEventListener("wheel", clearTimer, true); 91 | document.addEventListener("scroll", clearTimer, true); 92 | 93 | // Only add 'contextmenu' event if NOT on Android 94 | if (!navigator.userAgent.toLowerCase().includes("android")) { 95 | document.addEventListener("contextmenu", clearTimer, true); 96 | } 97 | 98 | // Allow setting delay dynamically 99 | window.setLongPressDelay = function (delay) { 100 | longPressDelay = delay; 101 | }; 102 | 103 | }(window, document)); -------------------------------------------------------------------------------- /Test/longpress.js: -------------------------------------------------------------------------------- 1 | (function (window, document) { 2 | 'use strict'; 3 | 4 | let timer = null; 5 | const maxDiff = { x: 10, y: 10 }; 6 | let startPos = { x: 0, y: 0 }; 7 | 8 | const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; 9 | const hasPointerEvents = 'PointerEvent' in window || (window.navigator && 'msPointerEnabled' in window.navigator); 10 | 11 | const eventMap = hasPointerEvents 12 | ? { down: 'pointerdown', up: 'pointerup', move: 'pointermove', leave: 'pointerleave' } 13 | : isTouch 14 | ? { down: 'touchstart', up: 'touchend', move: 'touchmove', leave: 'touchleave' } 15 | : { down: 'mousedown', up: 'mouseup', move: 'mousemove', leave: 'mouseleave' }; 16 | 17 | // Polyfill for CustomEvent 18 | if (typeof window.CustomEvent !== 'function') { 19 | window.CustomEvent = function (event, params = { bubbles: false, cancelable: false, detail: undefined }) { 20 | const evt = document.createEvent('CustomEvent'); 21 | evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); 22 | return evt; 23 | }; 24 | window.CustomEvent.prototype = window.Event.prototype; 25 | } 26 | 27 | // Polyfill for requestAnimationFrame 28 | const requestAnimFrame = window.requestAnimationFrame || 29 | window.webkitRequestAnimationFrame || 30 | window.mozRequestAnimationFrame || 31 | window.oRequestAnimationFrame || 32 | window.msRequestAnimationFrame || 33 | ((callback) => window.setTimeout(callback, 1000 / 60)); 34 | 35 | function requestTimeout(fn, delay) { 36 | if (!requestAnimFrame) return window.setTimeout(fn, delay); 37 | 38 | const start = new Date().getTime(); 39 | const handle = {}; 40 | 41 | const loop = () => { 42 | const delta = new Date().getTime() - start; 43 | if (delta >= delay) { 44 | fn(); 45 | } else { 46 | handle.value = requestAnimFrame(loop); 47 | } 48 | }; 49 | handle.value = requestAnimFrame(loop); 50 | return handle; 51 | } 52 | 53 | function clearRequestTimeout(handle) { 54 | if (handle) { 55 | (window.cancelAnimationFrame || window.clearTimeout)(handle.value); 56 | } 57 | } 58 | 59 | function fireLongPressEvent(originalEvent) { 60 | clearTimer(); 61 | const unifiedEvent = unifyEvent(originalEvent); 62 | const customEvent = new CustomEvent('long-press', { 63 | bubbles: true, 64 | cancelable: true, 65 | detail: getEventDetails(unifiedEvent) 66 | }); 67 | 68 | const allowClickEvent = this.dispatchEvent(customEvent); 69 | if (!allowClickEvent) { 70 | document.addEventListener('click', preventDefaultClick, true); 71 | } 72 | } 73 | 74 | function unifyEvent(e) { 75 | return e.changedTouches ? e.changedTouches[0] : e; 76 | } 77 | 78 | function getEventDetails(e) { 79 | return { 80 | clientX: e.clientX, 81 | clientY: e.clientY, 82 | offsetX: e.offsetX, 83 | offsetY: e.offsetY, 84 | pageX: e.pageX, 85 | pageY: e.pageY, 86 | screenX: e.screenX, 87 | screenY: e.screenY 88 | }; 89 | } 90 | 91 | function startTimer(e) { 92 | clearTimer(); 93 | const el = e.target; 94 | const delay = parseInt(getNearestAttribute(el, 'data-long-press-delay', '1000'), 10); 95 | timer = requestTimeout(fireLongPressEvent.bind(el, e), delay); 96 | } 97 | 98 | function clearTimer() { 99 | clearRequestTimeout(timer); 100 | timer = null; 101 | } 102 | 103 | function preventDefaultClick(e) { 104 | document.removeEventListener('click', preventDefaultClick, true); 105 | e.preventDefault(); 106 | e.stopImmediatePropagation(); 107 | } 108 | 109 | function handleMouseDown(e) { 110 | const unifiedEvent = unifyEvent(e); 111 | startPos = { x: unifiedEvent.clientX, y: unifiedEvent.clientY }; 112 | startTimer(e); 113 | } 114 | 115 | function handleMouseMove(e) { 116 | const unifiedEvent = unifyEvent(e); 117 | if ( 118 | Math.abs(startPos.x - unifiedEvent.clientX) > maxDiff.x || 119 | Math.abs(startPos.y - unifiedEvent.clientY) > maxDiff.y 120 | ) { 121 | clearTimer(); 122 | } 123 | } 124 | 125 | function getNearestAttribute(el, attributeName, defaultValue) { 126 | while (el && el !== document.documentElement) { 127 | const value = el.getAttribute(attributeName); 128 | if (value) return value; 129 | el = el.parentNode; 130 | } 131 | return defaultValue; 132 | } 133 | 134 | // Event listeners 135 | document.addEventListener(eventMap.down, handleMouseDown, true); 136 | document.addEventListener(eventMap.move, handleMouseMove, true); 137 | document.addEventListener(eventMap.up, clearTimer, true); 138 | document.addEventListener(eventMap.leave, clearTimer, true); 139 | document.addEventListener("wheel", clearTimer, true); 140 | document.addEventListener("scroll", clearTimer, true); 141 | 142 | // Only add 'contextmenu' event if NOT on Android 143 | if (!navigator.userAgent.toLowerCase().includes("android")) { 144 | document.addEventListener("contextmenu", clearTimer, true); 145 | } 146 | }(window, document)); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | If you like my work consider a donation: [![donate](https://img.shields.io/badge/donate-ko--fi-orange)](https://ko-fi.com/chromoxdor) 2 | 3 | 4 | # EasyFetch - an alternative dashboard for ESPeasy 5 | 6 | EasyFetch is an alternative dashboard in Tile Optic for ESPEasy (https://github.com/letscontrolit/ESPEasy). It was created to have a quick and relatively simple way of visualising data and interacting with the hardware (buttons & sliders) in a browser. Especially where no bigger infrastructure like a home automation server is needed (the ability to write complex rules in ESPEasy and the ability of communication between the nodes via a simple P2P network can often make a full-blown home automation system unnecessary). 7 | 8 | ![easyfetchmain](https://github.com/chromoxdor/easyfetch/assets/33860956/cd19b11f-40d6-42ef-9f26-86ef43c0920c) 9 | 10 | *** 11 | 12 | ### Button cheat sheet: 13 | cheatsheet 14 | 15 | *** 16 | ## Overview: 17 | The Dashboard consists of different elements with different purposes: 18 | 19 | 1. **main-section:** the tasks you have in espeasy are rendered here in tiles. There are a lot of options to customize these. 20 | There are 3 possible categories: 21 | 1. big-values 22 | 2. slider 23 | 3. the rest: buttons and "ordinary" tiles 24 | 4. charts 25 | 26 | 2. **unit-menu:** Here are all the units listed, that are connected via the controller called ESPEasy P2P Networking (https://espeasy.readthedocs.io/en/latest/Controller/C013.html) 27 | * Next to the unit name is the unit number. 28 | * The symbol "⊙" leading the unit name marks the unit which contains the EasyFetch file. 29 | 30 | 3. **unit-name:** The unit-name of the unit that is actually selected. Next to it is the signal strength in dBm of this unit. 31 | * Clicking it enters full-screen mode on supported browsers 32 | 33 | 4. **Menu and Split View Buttons:** 34 | 35 | * The left button opens the unit list. 36 | * The right button opens the devices page of the ESPEasy unit in a split view (on smartphones, it overlays the page instead due to limited space). If long-clicked, this button leaves EasyFetch and directs to the tools page of the selected unit. 37 | 38 | main 39 | 40 | ## Installing Fetch: 41 | 42 | 1. Download the [fetch.html.gz](https://github.com/chromoxdor/EasyFetch/raw/refs/heads/main/fetch.html.gz) file to your computer. Alternatively, you can rename it to index.htm.gz to replace the main page. 43 | 44 | 2. Open your browser and navigate to http://. Replace with your device’s Local IP Address. 45 | 46 | 3. Click on the „Tools“ tab. 47 | 48 | 4. Select the „Filesystem->File browser“ button. 49 | 50 | 5. Click the „Upload“ button. 51 | 52 | 6. Click the „Browse…“ button and select the fetch.html file. 53 | 54 | 7. Click the „Upload“ button again. 55 | 56 | Now, open your browser and view the new dashboard at http:///fetch.html.gz. You should now see all the ESPEasy device tasks and states. 57 | 58 | (Note: If you have multiple ESPEasy devices in a network, you’ll need only one device with the fetch.html.gz file as the primary device when all devices communicate via the p2p controller. ESPEasy P2P Networking must be added in /Controllers. For more information, see https://espeasy.readthedocs.io/en/latest/Controller/C013.html?highlight=p2p.) 59 | 60 | 61 | ### Display options: 62 | *** 63 | **1. Ordinary Tile:** 64 | 65 | - Each task is presented as a tile, organised by its task number. 66 | - The upper left corner of each tile displays the task name. If the task has associated values, they are aligned right and displayed below the task name. 67 | - These tiles and the buttons associated with them can be clicked and long-clicked, triggering events that can be accessed in rules. 68 | - Short clicks trigger events with the name `event`, while long clicks trigger events with the name `long`. 69 | - Here’s an example of a rule: 70 | 71 | ``` 72 | on sensorevent do 73 | dosomething 74 | endon 75 | ``` 76 | 77 | 78 | s1 79 | 80 | 81 | *** 82 | 83 | **2. Buttons:** 84 | - Buttons are basically ordinary tiles where the valuenames/values are not rendered 85 | - They have the ability to change their color depending on their state 86 | ![button simple](https://user-images.githubusercontent.com/33860956/159255555-d7caea8e-4913-4a4b-98b7-f9a83e5c4f3c.png) 87 | (the device here is a sonoff s20 and since we do not need the first button, since its the hardwarebutton, on our dahboard we can hide it with the "XX" option. the second button is the actual relay so the state changes depending on the gpio state) 88 | 89 | The rule for it: 90 |

 91 |           On buttonevent do
 92 |            gpiotoggle,12
 93 |           endon
 94 |          
95 | 96 | 2. Name a dummy device something that consists "dButtons" and every value becomes a button. 97 | - You can add an option for colorbuttons with "?C"(see picture below) 98 | - put "&\" to the end of the valuename to send this buttonevent to a specific device. (e.g. valuename "button&2" will result in this command: `SendTo,2,"event,ButtonEvent"` or if longclicked `SendTo,2,"event,ButtonLong"`) 99 | - put "&A" to the end of the valuename and the event will be send to all connected nodes 100 | 101 | b1 102 | b1f 103 | 104 | - Alert: whenever the value of “btnState” / “btnStateC” or any value in a "dButtons" task is 2 the tile becomes red. 105 | 106 | 107 | *** 108 | 109 | **3. Slider** 110 | 111 | - Slider: there are two types of slider. The “ordinary” slider and the “time" slider 112 | - Every slider calls an event when finished sliding. (e.g. “sliderEvent”) 113 | - To create a slider name a dummy device either "vSlider", "nvSlider" or "tSlider" 114 | 115 | 1. The ordinary slider: There are two versions too: 116 | 117 | 1. The slider with values displayed: name a task something consisting of “vSlider” and every item will become a Slider with values shown while sliding 118 | 119 | 2. The slider with values hidden: name a task something consisting of “nvSlider” 120 | 121 | - For both kinds of slider you can set a minimum, a maximum and the steps. 122 | - To achieve this add ??? to the itemname (e.g. slider?0?100?0.1) 123 | - For the slider that shows values (vSlider) you can also add a unit of measurement if you set you personal range (e.g. slider?0?100?0.1?°C) 124 | 125 | Notice: if you use this you must use it altogether. Standard values if unset are min=0 max=1023 step=1. 126 | 127 | vS1 128 | 129 | - If you add "Sw" to the Taskname (e.g. “vSliderSw” ), a "switch" function is added to the slider. If you click on the left 1/10th of the slider, the value becomes the set minimum (default=0) and if you click on the right 1/10th, it becomes the maximum (default=1023) 130 | 131 | 2. The "time" slider: Name a task something consisting of “tSlider” and every item will become a "time" slider. (Important! To make this work you need to set the number of decimals to 4) 132 | - The "time" slider stores the values of both times in one number. This makes it easier to store these values with the regulator - level 133 | control plugin since only one plugin for both values is needed 134 | - This slider has two thumbs for two time values (e.g. on and off time). Both times are stored in the corresponding taskvalue. The code 135 | example shows how to make use of it (for persistant storage of the values add the "level control" plugin): 136 |

137 |        On System#Boot Do //retrieve the values after a power loss back from the "level control" plugin
138 |           TaskValueSet,tslider,1,[timekeepXX#getlevel]/10000 
139 |           Let,1,[tSlider#Time] 
140 |           Let,2,[var#1]*10000-[var#1#F]*10000
141 |        Endon
142 | 
143 |        On tSliderEvent do
144 |           TimerSet,2,10 // after 10secs store the value in the "level control" plugin
145 |           Let,1,[tSlider#Time]
146 |           Let,2,[var#1]*10000-[var#1#F]*10000
147 |        endon
148 | 
149 |        On Rules#Timer=2 Do
150 |           config,task,timekeepXX,SetLevel,[tSlider#Time]*10000  //level stores only two digits so we make an integer 
151 |        Endon
152 | 
153 |        On Clock#Time=All,**:** Do
154 |           If %syssec_d%/60>=[var#1#F] And %syssec_d%/60<[var#2]
155 |             GPIO,2,1
156 |           Else
157 |             GPIO,2,0
158 |           Endif
159 |        Endon
160 |       
161 | 162 | Bildschirmfoto 2022-12-04 um 20 02 16 163 | Bildschirmfoto 2022-12-04 um 20 02 29 164 | 165 | 166 | *** 167 | 168 | **4. Big values** 169 | 170 | - Big values are displayed when the taskname consist of “bigVal” or if colored “bigValC” 171 | - You can call an item of a “bigVal” Task something that consists “clock”/”time”, “date”, “year” to get this displayed independent of the value. (german speaking persons can also use “uhr” / "zeit", “datum” -> for date format with dots and "jahr") 172 | 173 | n2 174 | bv1 175 | 176 | 177 | *** 178 | 179 | **5. Additional things** 180 | 181 | - Thingspeak: add a thingspeak value to display the last value of a field of a public thingspeak channel, use this scheme for a valuename: 182 | "name&&" optional you can give it a unit of measurement with ? (like in every other field): 183 | 184 | ts1 ts1f 185 | 186 | 187 | ts2ts2f 188 | 189 | - Grid layout (desktop view): 190 | - the amount of colums (1,2,3 or maximum 4) is determined by the ammount of tiles. 191 | - 1 tile = one colum 192 | - 2 - 4 tiles = 2 colums 193 | - 5 - 9 tiles = 3 colums 194 | - \> 9 tiles = 4 colums 195 | - The amount of "big values" however is prioritized for rendering the grid layout and constraints it. 196 | - e.g. if a 4 colum grid is preferred just create a dummy-device with 4 values and call it "bigVal" 197 | but if you only have 3 values to display you can add an empty "big value" tile by calling an valuename "noVal" or hide it with "XX" 198 | ![gridnorm](https://user-images.githubusercontent.com/33860956/159264739-6e322a4a-6f8e-46b3-be83-8b2ee6d7c4e7.png) 199 | ![grid_noval](https://user-images.githubusercontent.com/33860956/159264715-8d959949-29cb-42e3-b78b-e73d1c439bd4.png) 200 | ![grid_XX](https://user-images.githubusercontent.com/33860956/159264731-da04fd8d-4006-409d-b324-659d877b5fcd.png) 201 | 202 | - with this "trick" you can have a 2 colum grid layout even wihout displaying any "big values": 203 | ![grid_2](https://user-images.githubusercontent.com/33860956/159265483-e06527c4-25ba-4f0a-bf9c-196242ae849a.png) 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /Test/test.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | font-family: sans-serif; 4 | font-size: 12pt; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | body { 10 | user-select: none; 11 | -webkit-user-select: none; 12 | margin: 0; 13 | background: #e8e8e8; 14 | color: #4e4a4a; 15 | } 16 | 17 | #dateV { 18 | opacity: 0; 19 | top: 10px; 20 | writing-mode: vertical-lr; 21 | position: fixed; 22 | pointer-events: none; 23 | } 24 | 25 | #openSys:hover #dateV { 26 | opacity: 1; 27 | } 28 | 29 | #container { 30 | display: flex; 31 | height: 100vh; 32 | justify-content: center; 33 | } 34 | 35 | #framie { 36 | height: 100%; 37 | transition: .8s; 38 | position: relative; 39 | z-index: 2; 40 | padding-bottom: 50px; 41 | left: 1px; 42 | } 43 | 44 | iframe { 45 | width: 100%; 46 | height: 100%; 47 | border: 0; 48 | background: #fff; 49 | box-shadow: 4px 0 13px -7px #000; 50 | } 51 | 52 | #unitId { 53 | padding: 1vh; 54 | width: 110vw; 55 | z-index: 1; 56 | display: flex; 57 | position: fixed; 58 | transition: 0.3s ease-out; 59 | justify-content: center; 60 | } 61 | 62 | #allList { 63 | display: grid; 64 | transform: translate3d(0, 0, 0); 65 | /*safari blur issue fix*/ 66 | grid-row-gap: 1vh; 67 | margin: auto; 68 | padding: 5px; 69 | transition: .8s ease-out; 70 | flex-direction: column; 71 | align-content: center; 72 | flex-wrap: wrap; 73 | padding-bottom: 50px; 74 | padding-top: 40px; 75 | color: #fff; 76 | } 77 | 78 | #allList>div>:not(.bigNum) { 79 | box-shadow: 0 0 10px 1px #00000026; 80 | } 81 | 82 | .allExtra { 83 | grid-row-gap: .5vh !important; 84 | } 85 | 86 | #bigNumber { 87 | display: grid; 88 | gap: 1vh; 89 | transition: .8s ease-out; 90 | } 91 | 92 | .bigNum { 93 | display: grid; 94 | gap: 1vh; 95 | justify-content: start; 96 | transition: .8s ease-out; 97 | border-radius: 5px 98 | } 99 | 100 | .bigNumOne { 101 | justify-content: center; 102 | } 103 | 104 | .bigNumWrap { 105 | width: -webkit-fill-available; 106 | display: flex; 107 | justify-items: stretch; 108 | cursor: default; 109 | padding: 4.2px; 110 | border-radius: 5px; 111 | width: 150px; 112 | height: 75px; 113 | flex-direction: column; 114 | background: #837c7c; 115 | flex-wrap: nowrap; 116 | justify-content: space-between; 117 | box-shadow: 0 0 10px 1px #00000026; 118 | } 119 | 120 | #sensorList { 121 | display: grid; 122 | gap: 1vh; 123 | justify-content: center; 124 | transition: .8s ease-out; 125 | } 126 | 127 | #sliderList { 128 | display: grid; 129 | gap: 1vh; 130 | transition: .8s ease-out; 131 | min-width: 305px 132 | } 133 | 134 | #sliderList .sensorset { 135 | width: inherit; 136 | height: 40px; 137 | background: #837c7c; 138 | } 139 | 140 | .clickables:active, 141 | .push:active { 142 | transform: scale(0.95); 143 | transition: .1s; 144 | } 145 | 146 | .slider, 147 | .npSl { 148 | -webkit-appearance: none; 149 | position: absolute; 150 | height: 40px; 151 | } 152 | 153 | .slider { 154 | background: #847d7d; 155 | width: calc(100% + 44.2px); 156 | left: -24.2px; 157 | top: 0; 158 | } 159 | 160 | .noI { 161 | pointer-events: none; 162 | } 163 | 164 | .npSl { 165 | width: 100%; 166 | left: -.2px; 167 | position: inherit; 168 | height: 40px; 169 | overflow: hidden; 170 | border-radius: 5px; 171 | } 172 | 173 | .slider::-webkit-slider-thumb { 174 | -webkit-appearance: none; 175 | width: 0; 176 | height: 50px; 177 | cursor: pointer; 178 | box-shadow: -100vw 0 0 100vw #458fd1; 179 | border-left: 20px solid #458fd1; 180 | border-right: 20px solid #837c7c; 181 | } 182 | 183 | /* .thT::-webkit-slider-thumb{ 184 | border-right: 90 !important; 185 | } */ 186 | 187 | .slider::-moz-range-thumb { 188 | width: 0; 189 | height: 70px; 190 | cursor: pointer; 191 | box-shadow: -100vw 0 0 100vw #458fd1; 192 | border-left: 20px solid #458fd1; 193 | border-right: 20px solid #837c7c; 194 | } 195 | 196 | .npSl::-webkit-slider-thumb { 197 | -webkit-appearance: none; 198 | width: 20px; 199 | height: 42px; 200 | cursor: pointer; 201 | box-shadow: 0 0 2px 0 #fff; 202 | background: #3e88cb; 203 | } 204 | 205 | .npSl::-moz-range-thumb { 206 | width: 20px; 207 | height: 42px; 208 | cursor: pointer; 209 | box-shadow: 0 0 2px 0 #fff; 210 | background: #3e88cb; 211 | border: 0; 212 | } 213 | 214 | .npH { 215 | background-image: linear-gradient(to right, red, yellow, lime, cyan, blue, magenta, red); 216 | } 217 | 218 | .npV { 219 | background-image: linear-gradient(to right, #333, white); 220 | } 221 | 222 | .npS { 223 | background-image: linear-gradient(to right, white, #333); 224 | } 225 | 226 | .slTS, 227 | .thermO { 228 | -webkit-appearance: none; 229 | position: absolute; 230 | height: 40px; 231 | z-index: 1; 232 | border-radius: 5px; 233 | width: 100%; 234 | top: 0; 235 | pointer-events: none; 236 | } 237 | 238 | .slTS { 239 | mix-blend-mode: overlay; 240 | background: #8a8787; 241 | } 242 | 243 | .slTS:hover { 244 | background: transparent; 245 | } 246 | 247 | .slTS:nth-child(1)::-webkit-slider-thumb { 248 | border-left: 10px solid #d25e42; 249 | border-right: 10px solid #458fd1; 250 | } 251 | 252 | .slTS:nth-child(2)::-webkit-slider-thumb { 253 | border-left: 10px solid #458fd1; 254 | border-right: 10px solid #d25e42; 255 | } 256 | 257 | .slTS:nth-child(1)::-moz-range-thumb { 258 | border-left: 10px solid #d25e42; 259 | border-right: 10px solid #458fd1; 260 | } 261 | 262 | .slTS:nth-child(2)::-moz-range-thumb { 263 | border-left: 10px solid #458fd1; 264 | border-right: 10px solid #d25e42; 265 | } 266 | 267 | .thermO::-webkit-slider-thumb { 268 | border: 4px solid #d25e42; 269 | } 270 | 271 | .thermO::-moz-range-thumb { 272 | border: 4px solid #d25e42; 273 | } 274 | 275 | .slTS::-webkit-slider-thumb { 276 | width: 0; 277 | height: 40px; 278 | box-shadow: 0 0 10px 10px #00000026; 279 | } 280 | 281 | .slTS::-moz-range-thumb { 282 | width: 0; 283 | height: 100vh; 284 | } 285 | 286 | .slTHU::-moz-range-thumb { 287 | cursor: pointer; 288 | appearance: none; 289 | pointer-events: all; 290 | } 291 | 292 | .slTHU::-webkit-slider-thumb { 293 | -webkit-appearance: none; 294 | background: none; 295 | cursor: pointer; 296 | appearance: none; 297 | pointer-events: all; 298 | } 299 | 300 | .thermO::-webkit-slider-thumb { 301 | width: 25px; 302 | height: 40px; 303 | border-radius: 5px; 304 | box-shadow: 0 0 10px 10px #00000026, inset 0 0 5px 2px #00000026; 305 | } 306 | 307 | .thermO::-moz-range-thumb { 308 | background: none; 309 | width: 20px; 310 | height: 32px; 311 | border-radius: 5px; 312 | box-shadow: 0 0 10px 10px #00000026, inset 0 0 5px 2px #00000026; 313 | } 314 | 315 | .slTimeSet { 316 | display: flex; 317 | align-items: center; 318 | justify-content: center; 319 | } 320 | 321 | .slTimeSetWrap { 322 | height: 40px; 323 | padding: 4.2px; 324 | border-radius: 5px; 325 | overflow: hidden; 326 | position: relative; 327 | } 328 | 329 | .slTimeText, 330 | .sliderAmount { 331 | position: absolute; 332 | right: 4.2px; 333 | bottom: 0; 334 | font-weight: 400; 335 | z-index: 2; 336 | pointer-events: none; 337 | } 338 | 339 | .thermO { 340 | background: none; 341 | } 342 | 343 | .sensorset:not(.btnTile) { 344 | background: #847d7d; 345 | } 346 | 347 | .sensorset { 348 | display: flex; 349 | justify-items: stretch; 350 | flex-direction: column; 351 | padding: 4.2px; 352 | flex-wrap: nowrap; 353 | border-radius: 5px; 354 | width: 150px; 355 | overflow: hidden; 356 | position: relative; 357 | align-items: stretch; 358 | cursor: pointer; 359 | } 360 | 361 | #sensorList > * { 362 | min-height: 60px 363 | } 364 | 365 | .valueBig { 366 | font-size: 27pt; 367 | text-align: right; 368 | } 369 | 370 | .valuesBig { 371 | overflow-wrap: anywhere; 372 | } 373 | 374 | .valWrap { 375 | background: #635d5d; 376 | border-radius: 5px; 377 | text-align: end; 378 | display: flex; 379 | align-items: center; 380 | justify-content: flex-end; 381 | width: 100%; 382 | } 383 | 384 | .vInputs { 385 | height: 30px; 386 | width: 84%; 387 | border: 0; 388 | background: none; 389 | text-align: end; 390 | color: inherit; 391 | } 392 | 393 | input:focus-visible { 394 | width: inherit; 395 | } 396 | 397 | input::placeholder { 398 | opacity: 1; 399 | color: #fff; 400 | } 401 | 402 | input:focus::placeholder { 403 | opacity: .2; 404 | } 405 | 406 | input::-webkit-outer-spin-button, 407 | input::-webkit-inner-spin-button { 408 | -webkit-appearance: none; 409 | margin: 0; 410 | } 411 | 412 | input[type=number] { 413 | -moz-appearance: textfield; 414 | } 415 | 416 | .kindInput { 417 | padding: 0 2px 0 1px; 418 | } 419 | 420 | .on, 421 | .push:active { 422 | background: #3e88cb !important; 423 | } 424 | 425 | .alert { 426 | background: #d8524f !important; 427 | } 428 | 429 | .btnTile { 430 | background: #635d5d 431 | } 432 | 433 | .sensors { 434 | padding-bottom: 4px; 435 | overflow-wrap: anywhere; 436 | z-index: 2; 437 | pointer-events: none; 438 | } 439 | 440 | .row { 441 | display: flex; 442 | justify-content: space-between; 443 | align-items: flex-end; 444 | } 445 | 446 | .odd { 447 | padding-right: 1px; 448 | overflow-wrap: anywhere; 449 | } 450 | 451 | .odd:hover { 452 | overflow: visible; 453 | } 454 | 455 | .even { 456 | display: flex; 457 | word-spacing: -2px; 458 | white-space: nowrap; 459 | height: 20px; 460 | align-items: center; 461 | justify-content: flex-end; 462 | } 463 | 464 | .cron { 465 | padding-bottom: 1pt; 466 | text-align: end 467 | } 468 | 469 | #opener { 470 | height: 50px; 471 | bottom: 0; 472 | z-index: 2; 473 | width: 100%; 474 | position: fixed 475 | } 476 | 477 | .open { 478 | background: none; 479 | display: flex; 480 | max-width: 275px; 481 | align-items: center; 482 | margin: auto; 483 | padding-bottom: 2vh; 484 | justify-content: space-between; 485 | } 486 | 487 | .sidenav { 488 | height: 100%; 489 | width: 280px; 490 | position: fixed; 491 | z-index: 20; 492 | top: 0; 493 | left: -280px; 494 | background: #111111d9; 495 | transition: .6s; 496 | overflow-y: hidden; 497 | display: flex; 498 | flex-direction: column; 499 | align-items: center; 500 | cursor: pointer; 501 | justify-content: center; 502 | color: #fff; 503 | } 504 | 505 | .menueItem { 506 | display: grid; 507 | align-items: center; 508 | justify-content: start; 509 | grid-template-columns: 25px auto 510 | } 511 | 512 | .numberUnit { 513 | padding-left: 2px; 514 | font-size: 12px; 515 | color: #818181 516 | } 517 | 518 | #menueList div:hover { 519 | color: #444 520 | } 521 | 522 | .nc { 523 | word-break: break-word 524 | } 525 | 526 | .sideNbtn { 527 | position: absolute; 528 | bottom: 2%; 529 | font-size: 35px; 530 | color: #b3b3b3 531 | } 532 | 533 | #openSys { 534 | left: 20px; 535 | width: 40px 536 | } 537 | 538 | #closeBtn { 539 | right: 20px 540 | } 541 | 542 | #menueList { 543 | display: flex; 544 | flex-direction: column; 545 | overflow: scroll; 546 | -ms-overflow-style: none; 547 | scrollbar-width: none; 548 | transition: .2s; 549 | padding: 6px; 550 | margin-bottom: 50px; 551 | } 552 | 553 | #menueList::-webkit-scrollbar { 554 | display: none; 555 | } 556 | 557 | #menueList div { 558 | padding: 3px; 559 | font-size: 25px 560 | } 561 | 562 | #sysInfo { 563 | background: #ffffff17; 564 | color: #fff; 565 | transition: .5s; 566 | border-radius: 5px; 567 | width: 200px; 568 | height: 0; 569 | overflow: hidden; 570 | position: absolute; 571 | top: 5px; 572 | display: flex; 573 | flex-direction: column; 574 | justify-content: space-evenly 575 | } 576 | 577 | .syspair { 578 | display: flex; 579 | justify-content: flex-start 580 | } 581 | 582 | .syspair>div { 583 | font-size: 8pt !important; 584 | } 585 | 586 | .syspair div:nth-child(1) { 587 | text-align: end; 588 | width: 35%; 589 | padding-right: 5px 590 | } 591 | 592 | .menueWrap { 593 | transition: .5s; 594 | height: 185px; 595 | flex-shrink: 999 596 | } 597 | 598 | .bigSpan:nth-child(1) { 599 | grid-row: 1 / span 2; 600 | height: 100%; 601 | } 602 | #opener, 603 | #unitId { 604 | background: #e8e8e874 605 | } 606 | 607 | @media screen and (max-height: 450px) { 608 | /* #framie { 609 | padding-bottom: 40px; 610 | } */ 611 | 612 | /* #opener { 613 | height: 40px 614 | } */ 615 | } 616 | 617 | @media screen and (max-width: 799px) { 618 | #framie { 619 | position: absolute; 620 | } 621 | } 622 | 623 | @media screen and (max-width: 450px) { 624 | #allList { 625 | grid-row-gap: 5px; 626 | min-width: unset; 627 | overflow-y: auto 628 | } 629 | 630 | /* #opener { 631 | height: 40px 632 | } */ 633 | 634 | /* #framie { 635 | padding-bottom: 40px 636 | } */ 637 | 638 | .allExtra { 639 | grid-row-gap: 2.5px !important 640 | } 641 | 642 | #bigNumber { 643 | grid-row-gap: 5px 644 | } 645 | 646 | #sliderList { 647 | gap: 5px 1vh 648 | } 649 | 650 | #sensorList { 651 | gap: 5px 652 | } 653 | 654 | .bigNum { 655 | gap: 5px; 656 | } 657 | 658 | .sensors { 659 | padding-bottom: 2% 660 | } 661 | } 662 | 663 | @media (prefers-color-scheme: dark) { 664 | body { 665 | background: #000; 666 | color: #b3b3b3 667 | } 668 | 669 | input::placeholder, 670 | #allList { 671 | color: #b3b3b3 672 | } 673 | 674 | iframe, 675 | .slTimeSetWrap { 676 | background: #000 677 | } 678 | 679 | #opener, 680 | #unitId { 681 | background: #000000a8 682 | } 683 | 684 | .bigNumWrap { 685 | background: #444 686 | } 687 | 688 | .slTS { 689 | mix-blend-mode: lighten; 690 | background: #444 !important 691 | } 692 | 693 | .slTS:nth-child(1)::-webkit-slider-thumb { 694 | border-left: 10px solid #6d1343; 695 | border-right: 10px solid #44607a; 696 | } 697 | 698 | .slTS:nth-child(2)::-webkit-slider-thumb { 699 | border-left: 10px solid #44607a; 700 | border-right: 10px solid #6d1343; 701 | } 702 | 703 | .slTS:nth-child(1)::-moz-range-thumb { 704 | border-left: 10px solid #6d1343; 705 | border-right: 10px solid #44607a; 706 | } 707 | 708 | .slTS:nth-child(2)::-moz-range-thumb { 709 | border-left: 10px solid #44607a; 710 | border-right: 10px solid #6d1343; 711 | } 712 | 713 | .thermO::-webkit-slider-thumb { 714 | border: 4px solid #882c29; 715 | } 716 | 717 | .thermO::-moz-range-thumb { 718 | border: 4px solid #882c29; 719 | } 720 | 721 | .slider { 722 | background: #444 723 | } 724 | 725 | .slider::-webkit-slider-thumb { 726 | box-shadow: -100vw 0 0 100vw #44607a; 727 | border-left: 20px solid #44607a; 728 | border-right: 20px solid #444 729 | } 730 | 731 | .slider::-moz-range-thumb { 732 | box-shadow: -100vw 0 0 100vw #44607a; 733 | border-left: 20px solid #44607a; 734 | border-right: 20px solid #444 735 | } 736 | 737 | /* .npSl::-webkit-slider-thumb { 738 | box-shadow: 0 0 4px 3px grey; 739 | background: #44607a; 740 | } 741 | 742 | .npSl::-moz-range-thumb { 743 | box-shadow: 0 0 4px 3px grey; 744 | background: #44607a; 745 | } */ 746 | 747 | .npS, 748 | .npH, 749 | .npV { 750 | background-color: #9b9b9b; 751 | background-blend-mode: multiply; 752 | } 753 | 754 | .on, 755 | .push:active { 756 | background: #44607a !important 757 | } 758 | 759 | .alert { 760 | background: #553044 !important 761 | } 762 | 763 | .valueBig { 764 | background: none; 765 | } 766 | 767 | .btnTile, 768 | .valWrap { 769 | background: #333; 770 | } 771 | 772 | .sensorset:not(.btnTile), 773 | #sliderList .sensorset { 774 | background: #444; 775 | } 776 | } -------------------------------------------------------------------------------- /Test/efc.min.js: -------------------------------------------------------------------------------- 1 | var currentDivId,menu;window.efc=!0,window.configMode=!1;var saveButton=null,resetButton=null,toggleButton=null;const pointerEventsStyle=document.createElement("style");var contextIsAlready=!1;let tempName="";var mOpen,nOpen,selectionDataOld,interactionHandled=!1;const efcVersion="20251016/1",expected="20251210/1";function addContext(){/(android)/i.test(navigator.userAgent)||document.addEventListener("contextmenu",handleRightClick,!0)}function handleRightClick(e){e.preventDefault(),removeHighlighting();let t,n=e.target.closest('div[id^="efc"]');if(addPointerEvents(),null===n){t=e.target;const o=Array.from(t.children);o.length>0&&o.forEach((o=>{if(o.id.includes("efc")){const o=document.elementFromPoint(e.clientX,e.clientY);o&&(n=o,n.classList.contains("sensors")&&(n=o?.parentElement),t.id.startsWith("sliderList")&&(n=o?.parentElement?.parentElement))}}))}if(n&&(n.id.startsWith("efc")||"unitId"===n.id)){const t=n.querySelector("span");isittime=!1,updateSaveButton("initial"),currentDivId=n.id,rebuildContextMenu(n.id,n.className,t),e.detail.clientX?(xCoord=e.detail.clientX,yCoord=e.detail.clientY):(xCoord=e.clientX,yCoord=e.clientY),menu.style.display="block";const o=document.getElementById("custom-menu");if(o&&o.offsetWidth>0){const e=o.offsetWidth,t=o.offsetHeight,n=window.scrollX,i=window.scrollY;let l=xCoord+n+5;l+e>window.innerWidth+n&&(l=window.innerWidth+n-e-10),l=Math.max(10,l);let a=yCoord+i;const r=i+40,d=i+window.innerHeight-t-50;a=Math.min(Math.max(a,r),d),o.style.position="absolute",o.style.left=`${l}px`,o.style.top=`${a}px`}}else(t.id.startsWith("allList")||t.id.startsWith("container"))&&updateSaveButton("initial")}function highlightMatchingDivs(e,t){let n=e.substring(0,e.lastIndexOf(","));if(!n)return;document.querySelectorAll(`div[id*="${n.split("=")[1]}"]`).forEach((e=>{e.style.outline="2px solid #ffcc00"}));let o=document.querySelectorAll(`[id="${e}"]`);o.length>0&&o.forEach((e=>{!t.includes("bigNumWrap")&&!t.includes("bigSingles")||contextIsAlready?e.style.outline="2px solid red":e.parentElement&&(e.parentElement.style.setProperty("transition","none","important"),e.parentElement.style.outline="2px solid red")}))}function removeHighlighting(){const e=document.getElementById("allList");e.style.setProperty("transition-property","none");e.querySelectorAll("div").forEach((e=>e.style.outline="")),setTimeout((()=>{e.style.removeProperty("transition")}),50)}function parseDivId(e){if(e.startsWith("efc")){let[t,n]=e.split("="),o=n.match(/(\d+),(\d+),(\d+)([A-Z]?)$/);return o?{deviceName:t.split(":")[1],deviceType:parseInt(o[1]),deviceIndex:o[2],valueIndex:o[4]||o[3]}:null}return null}function rebuildContextMenu(e,t,n){let o=parseDivId(currentDivId);if(!o)return;let{deviceName:i,deviceType:l,deviceIndex:a,valueIndex:r}=o;const d=[43,1,81].includes(l);let s=selectionData[a]?selectionData[a][r]:{},c=selectionData[a]?.A?.val;menu.innerHTML="";let u=document.createElement("select");u.id="menu-main-select",u.dataset.key="val";let p="";"bigVal"===c||"bigVS"===s?.val?tempName!==i?(contextIsAlready=!1,tempName=i):contextIsAlready=!0:(contextIsAlready=!1,tempName=""),highlightMatchingDivs(e,t),"A"===r&&1!==l||"bigVal"===c&&!contextIsAlready?(p='\n \n \n ',l="A"):"bigSingle"!==i||contextIsAlready?33===l?(p='\n \n \n \n \n \n \n \n \n \n ',(checkBigSinglesLength()<4||4==checkBigSinglesLength()&&"bigVS"===s?.val)&&(p+='')):(p='\n \n \n ',(checkBigSinglesLength()<4||4==checkBigSinglesLength()&&"bigVS"===s?.val)&&(p+='')):l="S",u.innerHTML=p,(d||contextIsAlready&&"bigVal"===c||"S"===l)&&(u.style.display="none"),menu.appendChild(u),menu.appendChild(document.createElement("br")),u.addEventListener("change",(e=>{updateMenuFields(l,e.target.value,i,a),saveSelections(!1)}));let m=u.dataset.key;"bigVal"===c&&"A"===l&&(s=selectionData[a]?.A),void 0!==s&&void 0!==s[m]&&(u.value=s[m],d&&(u.value="")),updateMenuFields(l,u.value,i,a,r,d,t,n)}function updateMenuFields(e,t,n,o,i,l,a,r){let d=document.getElementById("dynamic-fields");d&&menu.removeChild(d),d=document.createElement("div"),d.id="dynamic-fields",d.style.display="grid","S"===e?addColorPicker(d,"color: ","SBC"):("A"===e&&["dButtons","pButtons"].includes(t)&&addCheckbox(d," no input","noI"),"C"===e&&addNumberInput(d," columns","cols"),["vSlider","nvSlider","thSlider"].includes(t)&&addTextInput(d,"range: ","range",t),["dButtons","vSlider"].includes(t)&&33===e&&addCheckbox(d," no input","noI"),["dButtons","pButtons"].includes(t)&&addCheckbox(d," invert","inv"),["tSlider"].includes(t)&&33===e&&(addTextInput(d,"taskname: ","timeName"),addDropdown(d,"day"),addDropdown(d,"fields")),(!["thSlider","tSlider","dButtons","pButtons","bigVal"].includes(t)&&"A"!==e&&1!==e||contextIsAlready&&"A"!==e)&&(r?.title||addTextInput(d,"UoM: ","unit"),addCheckbox(d," hide name","noV")),["dButtons"].includes(t)&&33===e&&addTextInput(d,"sendTo: ","sendTo"),"A"!==e||l||(addCheckbox(d," chart","chart"),selectionData[o]?.A?.chart&&addCheckbox(d," always Y","Y"),a?.includes("chart")?addColorPicker(d,"color: ","color",!0):addColorPicker(d,"color: ","color")),("A"===e&&!["bigVal"].includes(t)||l||contextIsAlready||"bigVS"===t||"vSlider"===t||33===e&&!["none"].includes(t))&&addNumberInput(d,"order: ","order"),"none"===t&&!hasNonEmptyValues(selectionData,o)||contextIsAlready||addResetButton(d,o,n),addCheckbox(d," hide entry","hide")),menu.appendChild(d);let s=parseDivId(currentDivId);if(s){let t,{deviceIndex:n,valueIndex:o}=s;"A"===e&&(o="A"),t="S"!==e?selectionData[n]?selectionData[n][o]:{}:selectionData?.S,d.querySelectorAll("input, select, button").forEach((e=>{let n=e.dataset.key;void 0!==t&&void 0!==t[n]&&("checkbox"===e.type?e.checked=1===t[n]:("number"===e.type||e.tagName.toLowerCase(),e.value=t[n]))}))}d.addEventListener("input",(()=>saveSelections(!1)))}function addTextInput(e,t,n,o){let i=document.createElement("label");i.innerText=t;let l=document.createElement("input");l.type="text","unit"===n&&(l.maxLength=6,l.style.width="6ch"),"range"===n&&(["vSlider","nvSlider"].includes(o)?l.placeholder="0,1024,1":["thSlider"].includes(o)&&(l.placeholder="5,35,1"),l.addEventListener("input",(e=>{l.value=l.value.replace(/[^0-9.,-]/g,"")})),l.addEventListener("blur",(()=>{if(""===l.value.trim())return;/^-?\d+(\.\d+)?,-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(l.value)||(alert("Invalid format! Please enter three values separated by two commas (e.g., 0,1024,0.5)"),l.value="")}))),"sendTo"===n&&(l.placeholder="nodeNR(,GPIO)",l.style.width="15ch",l.addEventListener("input",(()=>{l.value=l.value.replace(/[^0-9,]/g,"")})),l.addEventListener("blur",(()=>{if(""===l.value.trim())return;/^\d+$|^\d+,?\d+$/.test(l.value)||(alert("Invalid format! Please enter either a nodenumber (e.g., 12) or a nodenumber + comma + the GPIO to toggle (e.g., 2,12)."),l.value="")}))),l.dataset.key=n,e.appendChild(i),e.appendChild(l)}function addNumberInput(e,t,n){let o=document.createElement("label");o.innerText=t,o.style.marginRight="5px";let i=document.createElement("input");if(i.type="number",i.dataset.key=n,i.style.width="4ch",i.style.textAlign="center","order"===n){i.min="0",i.max="255",i.step="1",i.value="0";let t=document.createElement("div");t.style.display="inline-flex",t.style.alignItems="center",t.style.gap="5px";let n=document.createElement("button");n.innerText="−",n.type="button",n.style.width="25px";let a=document.createElement("button");function l(e){let t=parseInt(i.value)+e;i.value=t,saveSelections(!1)}a.innerText="+",a.type="button",a.style.width="25px",a.addEventListener("click",(()=>l(1))),n.addEventListener("click",(()=>l(-1))),t.appendChild(n),t.appendChild(i),t.appendChild(a),e.appendChild(o),e.appendChild(t)}else e.appendChild(o),e.appendChild(i)}function addCheckbox(e,t,n){let o=document.createElement("label"),i=document.createElement("input");i.type="checkbox",i.dataset.key=n,o.style.padding="5px 0",o.appendChild(i),o.appendChild(document.createTextNode(t)),e.appendChild(o)}function addDropdown(e,t){const n={day:{label:"Day:",options:["All","Mon","Tue","Wed","Thu","Fri","Sat","Sun","Wrk","Wkd"]},fields:{label:"Fields:",options:["1-2","3-4","5-6","7-8"]}};if(!n[t])return;const{label:o,options:i}=n[t],l=document.createElement("div");l.style.padding="5px 0";const a=document.createElement("span");a.textContent=o,a.style.marginRight="5px";const r=document.createElement("select");r.dataset.key=t,r.style.padding="2px 5px",i.forEach((e=>{const t=document.createElement("option");t.value=e,t.textContent=e,r.appendChild(t)})),l.appendChild(a),l.appendChild(r),e.appendChild(l)}function addColorPicker(e,t,n,o){let i=document.createElement("label");i.innerText=t;let l=document.createElement("input");l.type="color",l.dataset.key=n,o&&(i.style.display="none",l.style.display="none"),e.appendChild(i),e.appendChild(l)}function addResetButton(e,t,n){let o=document.createElement("button");e.appendChild(document.createElement("br")),o.innerText=`reset ${n}`,o.style.backgroundColor="#7d1414",o.style.color="white",o.style.padding="2px",o.addEventListener("click",(()=>deleteDevice(t))),e.appendChild(o)}function hasNonEmptyValues(e,t){return!(!e[t]||"object"!=typeof e[t])&&Object.values(e[t]).some((e=>Object.values(e).some((e=>0!==e&&""!==e&&void 0!==e))))}function deleteDevice(e){delete selectionData[e],saveSelections(!0),closeContextMenu()}function saveSelections(e){let t=parseDivId(currentDivId);if(!t)return;let{deviceName:n,deviceIndex:o,valueIndex:i}=t;selectionData[o]||(selectionData[o]={}),selectionData[o][i]||(selectionData[o][i]={});let l=document.getElementById("dynamic-fields"),a={};selectionData.unit=unitName,selectionData.nr=unitNr;let r=document.getElementById("menu-main-select");if(r){let e=r.dataset.key;e&&(a[e]=r.value)}l&&l.querySelectorAll("input, select").forEach((e=>{let t=e.dataset.key;"hide"===t&&(html2=""),"checkbox"===e.type?a[t]=e.checked?1:0:"number"===e.type?a[t]=parseFloat(e.value)||0:"text"===e.type?a[t]=e.value:"color"===t&&"#000000"===e.value?a[t]="":a[t]=e.value})),"bigVal"!==selectionData[o]?.A?.val||contextIsAlready||(i="A"),e||("bigSingle"!==n||contextIsAlready?selectionData[o][i]=a:selectionData.S=a),removeEmptyKeys(selectionData),updateSaveButton(),extraConfig()}function keepOnlyA(e){if(e&&"object"==typeof e)for(const t in e)"A"!==t&&delete e[t]}function updateSaveButton(e){"initial"!==e||selectionDataOld||(closeNav(),selectionDataOld=JSON.stringify(selectionData));const t="hide"===e,n={true:["☰︎","⌂︎"],false:["⚙︎","×︎"]};nOpen&&(nOpen.innerHTML=n[t][0]),mOpen&&(mOpen.innerHTML=n[t][1]),saveButton.style.display=!t&&selectionDataOld&&selectionDataOld!==JSON.stringify(selectionData)?"block":"none",resetButton.style.display=t?"none":"block",toggleButton.style.display=!t&&hasHiddenProperty(selectionData)?"block":"none",t&&closeContextMenu(),window.configMode=!t}function createSaveButton(){(saveButton=document.createElement("button")).innerText="Save Settings",saveButton.style.position="fixed",saveButton.style.top="10px",saveButton.style.left="calc(50% - 40px)",saveButton.style.transform="translateX(-120px)",saveButton.style.padding="10px 15px",saveButton.style.background="#28a745",saveButton.style.color="white",saveButton.style.border="none",saveButton.style.borderRadius="5px",saveButton.style.cursor="pointer",saveButton.style.zIndex="3",saveButton.style.display="none",saveButton.addEventListener("click",saveToFile),document.body.appendChild(saveButton),(resetButton=document.createElement("button")).innerText="Reset File",resetButton.style.position="fixed",resetButton.style.top="10px",resetButton.style.left="calc(50% + 30px)",resetButton.style.transform="translateX(-50%)",resetButton.style.padding="10px 15px",resetButton.style.background="red",resetButton.style.color="white",resetButton.style.border="none",resetButton.style.borderRadius="5px",resetButton.style.cursor="pointer",resetButton.style.zIndex="3",resetButton.style.display="none",resetButton.addEventListener("click",(()=>saveToFile("del"))),document.body.appendChild(resetButton),(toggleButton=document.createElement("button")).innerText=hiddenOverride?"Hide":"Show",toggleButton.style.position="fixed",toggleButton.style.top="10px",toggleButton.style.left="calc(50% + 128px)",toggleButton.style.transform="translateX(-50%)",toggleButton.style.padding="10px 15px",toggleButton.style.background="#007bff",toggleButton.style.color="white",toggleButton.style.border="none",toggleButton.style.borderRadius="5px",toggleButton.style.cursor="pointer",toggleButton.style.zIndex="3",toggleButton.style.display="none",document.body.appendChild(toggleButton),toggleButton.addEventListener("click",(()=>{hiddenOverride=!hiddenOverride,toggleButton.innerText=hiddenOverride?"Hide":"Show"}))}function saveToFile(e){"use strict";updateJsonArray(selectionData);let t=JSON.stringify(selectionData,null,2);if("del"===e){const e=`Are you sure you want to delete ALL the settings of ${selectionData.unit}?`;if(!confirm(e))return;t=" ",selectionData.unit&&(efcArray=efcArray.filter((e=>e.unit!==selectionData.unit)))}let n=JSON.stringify(efcArray,null,2);loadScript("https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js",(function(){const o=pako.gzip(t),i=new File([o],"efc.json.gz",{type:"application/gzip"}),l=new FormData;l.append("file",i);const a=`${baseUrl}/upload`;if((unitNr!==unitNr1&&"N"!=e||!isMain)&&fetchFile(a,l),isMain){const e=pako.gzip(n),t=new File([e],"main_efc.json.gz",{type:"application/gzip"}),o=new FormData;o.append("file",t),fetchFile("/upload",o)}})),"M"!==e&&exitConfig()}function fetchFile(e,t){const n=new AbortController,o=setTimeout((()=>n.abort()),5500);fetch(e,{method:"POST",body:t,mode:"cors",signal:n.signal}).then((e=>{if(clearTimeout(o),!e.ok)throw new Error(`HTTP error! Status: ${e.status}`);runonce2=!0})).catch((e=>{clearTimeout(o)}))}function saveUrlToServer(e,t){fetch(e).then((e=>{if(!e.ok)throw new Error(`Failed to fetch: ${e.statusText}`);return e.blob()})).then((e=>{const n=new File([e],t,{type:e.type}),o=new FormData;o.append("file",n),fetchFile("/upload",o),alert("Upgrade Successful"),document.cookie="efcVersion=; path=/; max-age=0",location.reload()})).catch((e=>{}))}function createMenu(){if(nOpen=document.getElementById("nOpen"),mOpen=document.getElementById("mOpen"),document.getElementById("areaChart")?.addEventListener("resize",updateYAxisVisibility),mOpen.onclick=function(e){configMode?(e.preventDefault(),e.stopPropagation(),exitConfig()):(splitOn(),topF())},nOpen.addEventListener("long-press",(()=>{"none"===saveButton.style.display?(updateSaveButton("initial"),setLongPressDelay(200),"ontouchstart"in window&&document.addEventListener("long-press",handleRightClick,!0)):exitConfig()})),"ontouchstart"in window){let e=0;const t=200;document.addEventListener("touchstart",(t=>{e=Date.now()}),{passive:!0}),document.addEventListener("touchend",(n=>{Date.now()-eReceived data: ${JSON.stringify(e)}

`}function updateJsonArray(e){let t=efcArray.findIndex((t=>t.unit===e.unit));-1!==t?efcArray[t]=e:efcArray.push(e)}function removeEmptyKeys(e){for(let t in e)if(e.hasOwnProperty(t)){const n=e[t];"object"==typeof n&&null!==n?(removeEmptyKeys(n),0===Object.keys(n).length&&delete e[t]):""!==n&&null!=n||delete e[t]}}function loadScript(e,t){const n=document.createElement("script");n.src=e,n.type="text/javascript",n.onload=t,document.head.appendChild(n)}function addPointerEvents(){pointerEventsStyle.innerHTML="#container * { pointer-events: all !important; }",document.head.appendChild(pointerEventsStyle)}function removePointerEvents(){pointerEventsStyle&&pointerEventsStyle.remove()}function hasHiddenProperty(e){for(const t of Object.values(e))if("object"==typeof t&&null!==t&&(1===t.hide||hasHiddenProperty(t)))return!0;return!1}function checkBigSinglesLength(){return document.querySelectorAll(".bigSingles").length}function exitConfig(){closeNav(),localEfc=!1,noCors=!1,selectionData={},selectionDataOld="",htmlold="",updateSaveButton("hide"),hiddenOverride=!1,setLongPressDelay(600),document.removeEventListener("long-press",handleRightClick,!0),runonce2=!0,setTimeout(newFJ,200)}function uploadJson(){const e=prompt("Paste your JSON here:");if(null!==e&&e.length>2){let t=e;e.startsWith("[")&&e.endsWith("]")&&(t=e.slice(1,-1));try{selectionData=JSON.parse(t),updateSaveButton()}catch(e){}}}function transferGzFile(e){loadScript("https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js",(()=>{fetch("/main_efc.json.gz").then((e=>{if(!e.ok)throw new Error(`Failed to fetch source file (${e.status})`);return e.arrayBuffer()})).then((t=>{const n=pako.gzip(t),o=new File([n],"main_efc.json.gz",{type:"application/gzip"}),i=new FormData;i.append("file",o),fetchFile(e,i)})).catch((e=>{}))}))}function extraConfig(){updateSaveButton();const e=document.getElementById("menueList");e.innerHTML="",e.innerHTML+=`\n

Device specific settings
for ${unitName}:

\n `,e.innerHTML+=`\n
\n
\n
\n ${[2,3,4].map((e=>`\n \n `)).join("")}\n
\n
\n`,e.innerHTML+=`\n
\n
\n \n \n
\n `,JSON.stringify(selectionData).includes('"chart":1')&&(e.innerHTML+=`\n
\n
\n \n \n
\n `),isMain&&localEfc&&(e.innerHTML+='\n
\n \n \n (Integrate the local config data
\n to your Main device.
It will be added to main_efc.json.gz)\n
\n '),unitNr===unitNr1&&isMain?(e.innerHTML+='\n




\n

Gerneral Settings:

\n
\n
\n \n \n
\n ',e.innerHTML+='\n
\n \n \n (Integrate the config from a device
\n by copying the contents of its
efc.json file\n and pasting them into
the upload field)
\n Fallback for esp826 devices
with CORS issues)\n
\n\n ',e.innerHTML+='\n
\n \n
\n \n ',renderDropdown()):unitNr!==unitNr1||isMain||(e.innerHTML+='\n
\n \n \n (Make this device the main device
\n in your Network.
It will create a main_efc.json.gz file.
\n Configs of all device will be stored there.)\n
\n '),e.innerHTML+='\n



\n

Extra Settings:

\n No need to press Save
for these settings!
\n ';const t=["Sound","Colormode inverted"];["Snd","Col"].forEach(((n,o)=>{const i=t[o],l=document.cookie.includes(`${n}=1`)?"checked":"";e.innerHTML+=`\n
\n \n \n
\n `}))}function makeMain(){isMain=!0,saveToFile()}document.addEventListener("DOMContentLoaded",(()=>{const e=window.location.href.match(/\/([^\/?#]+\.html?.gz)/i)?.[1]||"index.htm.gz",t=document.getElementById("dateV")?.textContent.trim();if(!t)return;document.getElementById("dateV").innerHTML=`${t} (efc:20251016/1)`;const n=document.cookie.match(/efcVersion=([^;]+)/)?.[1];if(n===expected)return;const[o,i]=expected.split("/"),[l,a]=t.split("/"),r=parseInt(i.match(/\d+/)?.[0]||"0"),d=parseInt(a.match(/\d+/)?.[0]||"0");if(!r||!d)return alert("Invalid build number format");if(l{const o=n.split(",").map((e=>e.trim()));return`rgba(${o[0]},${o[1]},${o[2]},${1-t})`}))}var chartCdnAlready=!1;function makeChart(){function e(){cD.forEach((e=>{if(!e.chart)return;let t=e.chart.data,n=`${e.device}chart`,o=document.getElementById(n);if(o)if(t.datasets.forEach(((e,t)=>{const n=colorArray[t%colorArray.length];e.backgroundColor=transparentize(n,.75),e.borderColor=n,e.borderWidth=1.2,e.tension=.3,e.fill=!0,e.pointRadius=6,e.pointHoverRadius=6,e.pointBackgroundColor="rgba(0, 0, 0, 0)",e.pointBorderColor="rgba(0, 0, 0, 0)",e.pointHoverBackgroundColor=n,e.pointHoverBorderColor="black"})),chartInstances[n]){let e=chartInstances[n];e.data.labels=t.labels,e.data.datasets=t.datasets,e.update(),updateYAxisVisibility()}else chartInstances[n]=new Chart(o.getContext("2d"),{type:"line",data:t,options:{interaction:{intersect:!1,mode:"index"},responsive:!0,maintainAspectRatio:!1,animation:{duration:0,onComplete:()=>{rmImg()}},plugins:{legend:{display:!1},tooltip:{enabled:!0,yAlign:"center",displayColors:!0,itemSort:(e,t)=>t.raw-e.raw,callbacks:{labelColor:e=>({backgroundColor:e.dataset.borderColor,borderColor:e.dataset.borderColor,borderWidth:2})}}},scales:{x:{display:!1}},elements:{line:{tension:.2},point:{radius:0,hoverRadius:6}}},plugins:[multiply]}),updateYAxisVisibility()}))}checkColumns(),cD&&(colorArray=getColorScheme(),chartCdnAlready?e():loadScript("https://cdn.jsdelivr.net/npm/chart.js",(()=>{chartCdnAlready=!0,e()})))}function updateYAxisVisibility(){const e=coloumnSet>2,[,t]=getComputedStyle(document.body).backgroundColor.match(/\d+/g);Object.values(chartInstances).forEach((n=>{n&&(Object.keys(n.options.scales).forEach((o=>{const i=n.options.scales[o];o.startsWith("y-")&&(i.display=e||!!selectionData[n.canvas.offsetParent?.id.match(/=(\d+),(\d+)/)?.[2]]?.A?.Y,i.ticks={...i.ticks,color:"0"===t?"grey":"white",count:5},i.grid.tickLength=0,i.grid={...i.grid,tickLength:0,drawOnChartArea:!0,color:e=>0===e.tick?.value?"#af170085":"0"===t?"#3d3d3d":"#756f6f"}),o.startsWith("y-r")&&(i.position="right")})),n.update())}))}let l=!0;function checkColumns(){let e=coloumnSet<3;e&&l&&(htmlold="",newFJ()),l=!e}function snapshotAllCanvases(){if(rmImg(),jStats){document.querySelectorAll("canvas").forEach((e=>{const t=new Image;t.src=e.toDataURL();const n=e.getBoundingClientRect();t.style.position="fixed",t.style.top=n.top+"px",t.style.left=n.left+"px",t.style.width=n.width+"px",t.style.height=n.height+"px",t.style.transition="opacity 0.6s",t.style.pointerEvents="none",t.style.zIndex=1,t.classList.add("cImg"),document.body.appendChild(t)}))}}function rmImg(e=!1){if(e){document.querySelectorAll("#allList div div").forEach((e=>{e.style.transition="opacity 0.3s",e.style.opacity="0"})),document.querySelectorAll("canvas").forEach((e=>e.remove()))}document.querySelectorAll(".cImg").forEach((e=>e.remove()))}function renderDropdown(){const e=document.getElementById("unitsList");e.innerHTML="";const t=document.createElement("select");t.id="unitSelect",t.className="clickables",t.style="margin-right:10px; padding:5px;";const n=new Option("-- Select a unit --","");t.add(n),efcArray.map(((e,t)=>({item:e,idx:t}))).filter((e=>e.item.unit&&e.item.nr)).sort(((e,t)=>e.item.nr-t.item.nr)).forEach((e=>{const n=new Option(`${e.item.unit} (${e.item.nr})`,e.idx);t.add(n)})),document.getElementById("deleteEntry").style.background="red",document.getElementById("deleteEntry").style.pointerEvents="all",e.appendChild(t)}function deleteSelectedEntry(){const e=document.getElementById("unitSelect"),t=parseInt(e.value);if(isNaN(t))return alert("No entry selected.");if(!e.value)return void alert("Please select a unit to delete.");efcArray.splice(t,1);saveToFile("M"),document.getElementById("deleteEntry").style.background="grey",document.getElementById("deleteEntry").style.pointerEvents="none",setTimeout((()=>renderDropdown()),2e3)} -------------------------------------------------------------------------------- /Test/test.min.js: -------------------------------------------------------------------------------- 1 | var jsonPath,nNr,nP,nP2,nIV,iIV,unitNr,unitNr1,navOpen,manNav,gesVal,iO,html,fJ,firstRun=!0,hasParams=1,isittime=1,unitName="",html2="",htmlold="",shouldShowOld=!1,selectionData={},efcArray=[],cD=[],jStats=!1,nTh=0,nThX=1,durationF=2e3,fetchAbort=new AbortController;let isFetching=!1;const cmD="control?cmd=",diV="";var coloumnSet,myJson2,tsX,tsY,teX,teY,tsTime,baseUrl="",runonce2=!0,hiddenOverride=!1,isMain=!1,localEfc=!1,noCors=!1;const c=new(window.AudioContext||window.webkitAudioContext);var bigLength;async function fetchJson(e){if(inIframe)return void(window.location.href="/tools");try{for(const e of document.styleSheets)for(const t of e.cssRules)if(t.conditionText?.includes("prefers-color-scheme")){const e=document.cookie.includes("Col=1");t.media.mediaText=`(prefers-color-scheme: ${e?"light":"dark"})`}}catch(e){}if(!isittime)return;let t=new URLSearchParams(window.location.search);myParam=t.get("unit"),"reboot"==t.get("cmd")&&(window.location.href=window.location.origin+"/tools?cmd=reboot"),null==myParam&&(hasParams=0),someoneEn=0,window.efc&&rmImg(),runonce2&&!configMode?await getEfcData():selectionData.unit;let n,i=window.efc&&2!=e?"?showpluginstats=1":"";if(jsonPath||(jsonPath="/json"),fetchAbort=new AbortController,isFetching)return;isFetching=!0,configMode||(durationF=selectionData?.iV??2e3,nThX=selectionData?.CiV??5);try{const e=await getUrl(jsonPath+(jStats&&nTh%nThX==0?i:""),{controller:fetchAbort,signal:fetchAbort.signal});if(fetchAbort.signal.aborted)return;if(n=await e.json(),unitName=n.System["Unit Name"],unitNr=n.System["Unit Number"],!window.configMode&&isMain&&await getEfcUnitData(unitName),JSON.stringify(selectionData).includes('"chart":1')){if(!jStats)return nTh=0,jStats=!0,isFetching=!1,void newFJ(1);nTh++}else cD=[],jStats=!1}catch(e){if("AbortError"===e.name)return;if(e.toString().includes("double-quoted"))return jStats=!1,void newFJ()}finally{isFetching=!1}if(!isittime||!n)return;html="";let s="",o="",l="",a='
',c='
';sysInfo=n?.System;let r=n?.System["Unit Number"];const d=n.WiFi?.RSSI??n["WiFi Station"]?.RSSI??n["WiFi AP"]?.RSSI??null,u=n.nodes.find((e=>e.nr===r))?.ip||"N/A";let m=[{label:"Sysinfo of",value:unitName},{label:"Local Time",value:sysInfo["Local Time"]},{label:"Uptime",value:minutesToDhm(sysInfo.Uptime)},{label:"Load",value:`${sysInfo.Load}%`},sysInfo["Internal Temperature"]&&{label:"Temp",value:`${sysInfo["Internal Temperature"]}°C`,style:`color: ${sysInfo["Internal Temperature"]>55?"red":"inherit"};`},{label:"Free Ram",value:sysInfo["Free RAM"]},{label:"Free Stack",value:sysInfo["Free Stack"]},{label:"IP Address",value:u},...null!=d?[{label:"RSSI",value:`${d} dBm`}]:[],{label:"Build",value:sysInfo.Build},{label:"Eco Mode",value:"true"===sysInfo["CPU Eco Mode"]?"on":"off"}].filter(Boolean).map((e=>`\n
\n
${e.label}:
\n
${e.value}
\n
\n `)).join(""),[g,v]=n.System["Local Time"].split(" ");v=v.split(":").slice(0,-1).join(":");let[$,h,f]=g.split("-");if(n.Sensors.length||hasParams){for(const e of n.Sensors){const{TaskNumber:t,TaskDeviceNumber:i,TaskName:r,TaskDeviceGPIO1:d,PluginStats:u}=e;if(u){const e=cD.find((e=>e.device===r));e?e.chart=u:cD.push({device:r,chart:u})}let m=r,g=r;taskEnabled=e.TaskEnabled.toString();const[,T]=getComputedStyle(document.body).backgroundColor.match(/\d+/g);tBGS=selectionData?.S?.SBC&&"#000000"!==selectionData?.S?.SBC?`background:${selectionData?.S?.SBC}${"0"===T?"80":""}`:"";const E='sensorset clickables" onclick="playSound(3000), ',I=`
${changeNN(m)}
`;exC=[87,38,41,42].includes(i),exC2=!e.Type?.includes("Display");let w=!hiddenOverride&&(1===selectionData[t]?.A?.hide||m.endsWith("XX"));const L=selectionData[t]?.A?.order||"0",D=selectionData[t]?.A?.chart||0;let x=`efc:${g}=${i},${t}`;if("true"===taskEnabled&&(!w||D)&&!exC&&exC2&&!hasParams)if(D&&window.efc&&(html2+=`
${m}
`),e.TaskValues){someoneEn=1;let u=!1,D=!1,C=!0;for(const k of e.TaskValues){var{ValueNumber:p,Value:y,Name:S,NrDecimals:b,UoM:N}=k;let e=`${x},${p}`,P=selectionData?.[t]?.[p],U=selectionData[t]?.A?.val,A=selectionData[t]?.A?.inv||P?.inv;tBG=selectionData[t]?.A?.color&&"#000000"!==selectionData[t].A.color?`background:${selectionData[t].A.color}${"0"===T?"80":""}`:"";let B=P?.val||"",M=N??(P?.unit||""),V=`${M}`,F=1===P?.noI?"noI":"",O=P?.order||"0";const H=P?.sendTo,[X="",j=""]=("string"==typeof H?H.split(","):[])||[];if(isHidden=!hiddenOverride&&(1===P?.hide||S.toString().includes("XX")),[slMin,slMax,slStep]=P?.range?P.range.split(","):"thSlider"===B?[5,35,1]:[0,1024,1],m=U&&"none"!==U&&"chart"!==U?U:B&&"none"!==B?B:r,D||B&&!["","none","bigVS"].includes(B)&&"bigVal"!==m||(D=u=!0),wasUsed=!1,num2Value="number"==typeof y?y.toFixed(b):y,itemName=1===P?.noV?"":S.toString(),itemNameChanged=changeNN(itemName),bS="",1==i)if(wasUsed=!0,"btnStateC"===itemName&&y<2||1===y?bS="on":2===y&&(bS="alert"),d&&"State"===itemName){html+=`
${I}`}else if("pState"===itemName&&d){const t=`${m}?${d}`;html+=`
\n
${m}
\n
`}else itemName.includes("btnState")?("ibtnState"===itemName&&(y=1==y?0:1),html+=`
${I}`):wasUsed=!1;if(1!==i&&!isHidden&&!m.includes("bigVal"))if(33!==i&&(F="noI"),wasUsed=!0,A&&(y=1==y?0:1),"C"===M&&y<2||1===y?bS="on":2===y&&(bS="alert"),["dButtons","vInput","pButtons"].some((e=>r.includes(e)))&&S.includes("noVal")){(!S.includes("noValAuto")||window.innerWidth>=450&&document.cookie.includes("Two=1"))&&(html+=`
`)}else if(m.includes("dButtons")){if(y>-1){j&&getRemoteGPIOState(t,X,j,n.System["Unit Number"],p),X&&(itemName=`${itemName}&${X}G${j}`);const i="A"===X?`getNodes('${itemName}')`:`buttonClick('${itemName}')`;html+=`
${itemNameChanged}
`}}else if(m.includes("pButtons")&&y>-1)html+=`
\n
${itemNameChanged}
\n
`;else if(m.includes("vInput"))html+=`\n
\n
\n ${itemNameChanged}\n
\n
\n \n
${V}
\n
\n
`;else if(m.includes("vSlider"))2==i&&""!=itemName&&(itemNameChanged=g),num2Value=Number(num2Value).toFixed((slStep.toString().split(".")[1]||"").length),itemName="noVal"===itemName?" ":itemName,html2+=`
${itemNameChanged}
`:`">
${itemNameChanged}
${num2Value}${V}
`;else if(m.includes("tSlider")){4!==b&&(itemName="For the Time slider the value must have
4 decimals!"),slT1=y.toFixed(4),slT2=(slT1+"").split(".")[1],slT1=Math.floor(slT1),hour1=Math.floor(slT1/60),minute1=slT1%60;const n=minute1.toString().padStart(2,"0");hour2=Math.floor(slT2/60),minute2=slT2%60;const i=minute2.toString().padStart(2,"0"),s='\n ${itemName}\n
\n ${hour1}:${n}-\n ${hour2}:${i}\n
\n
\n ${s}${slT1}" id="${itemName}:L">\n ${s}${slT2}" id="${itemName}:R">\n
\n
\n `}else if(m.includes("thSlider")||m.includes("thermoSlider")){3!==b&&(itemName="For the Thermo slider the value
must have 3 decimals!"),slT1=y.toFixed(3),slT2=100*(slT1-Math.floor(slT1)),slT2=slT2.toFixed(1),slT1=(Math.floor(slT1)/10).toFixed(1);const n=`type="range" min="${slMin}" max="${slMax}" step="${slStep}" value="`,i=`
${itemName}
`;html2+=`\n
\n ${i}\n
\n
☀︎${slT1}°C
\n
☉︎${slT2}°C
\n
\n
\n \n \n
\n
\n `}else if(m.includes("nPix")){const n=["H","S","V"].includes(M.toUpperCase())?M.toUpperCase():"H";html2+=`
`}else wasUsed=!1;if(m.includes("bigVal")){wasUsed=!0,C&&(C=!1,o+=a);let t=`
';let e=`${c}${itemName}
${v}
`:n.includes("datum")?o+=`${e}date" class="valueBig">${f}.${h}
`:n.includes("date")?o+=`${e}date" class="valueBig">${h}-${f}`:n.includes("year")||n.includes("jahr")?o+=`${e}year" class="valueBig">${$}`:itemName.includes("noVal")?o+=`${c}
`:o+=`${c}${itemNameChanged}
${num2Value}${V}
`}}if(B.includes("bigVS")&&!m.includes("bigVal")){l+=`
',isHidden?l+=`${c}
`:l+=`${c}${itemNameChanged}
${num2Value}${V}
`}wasUsed||(43==i?u&&(1===y&&(bS="on"),html+=`
\n
${m}
\n
⏲︎
\n
`):"bigVal"===U||w||(u&&(s+=`
${I}`),isHidden&&!B.includes("bigVS")||(s+=81===i?`\n
\n
${itemNameChanged}
\n
${y}
\n
`:`\n
\n
${itemNameChanged}
\n
${num2Value}${V}
\n
`))),u=!1}html+=diV,s+=diV,o+=diV}else s+=`\n
\n
${m}
\n
\n
\n `,someoneEn=1,document.getElementById("sensorList").innerHTML=s}someoneEn||hasParams||(html+='
no tasks enabled or visible...
')}else html+='
no tasks configured...
';if(html+=s,l){let e="";e=a+l+diV,o+=e}document.getElementById("sysInfo").innerHTML=m,document.getElementById("sensorList").innerHTML=html;const T=document.getElementById("allList")?.offsetWidth>400;htmlold===html2&&T===shouldShowOld||(window.efc&&!configMode&&snapshotAllCanvases(),document.getElementById("sliderList").innerHTML=orderFunction(html2),chartInstances={}),shouldShowOld=T,htmlold=html2,html2="",document.getElementById("bigNumber").innerHTML=o,firstRun&&(document.cookie.includes("Snd=")||mC("Snd"),clearTimeout(fJ),fJ=setInterval(fetchJson,durationF),unitNr1=n.System["Unit Number"],initIP="(IP unset)"===u?"192.168.4.1":u,nP2=`http://${initIP}/devices`,nP=`http://${initIP}/tools`,myJson2=n,getNodes(),longPressS(),longPressN(),addEonce(),firstRun=!1),styleU=unitNr===unitNr1?!1===isMain?"⊙︎":"☖︎":"",localEfc&&unitNr!=unitNr1&&(styleU="*"+styleU),hasParams||(document.getElementById("unitId").innerHTML=`${styleU}${unitName} (${d})`,document.getElementById("unitT").innerHTML=`${styleU}${unitName}`),unitNr===unitNr1&&(myJson2=n),1==e&&getNodes(void 0,void 0,"ch"),paramS(),changeCss(),resizeText(),window.efc&&cD.length&&makeChart(),window.configMode||longPressB()}function orderFunction(e){let t=document.createElement("div");t.innerHTML=e;let n=[...t.children];return[...n.filter((e=>(parseInt(e.getAttribute("order"))||0)>0)).sort(((e,t)=>parseInt(e.getAttribute("order"))-parseInt(t.getAttribute("order")))),...n.filter((e=>0===(parseInt(e.getAttribute("order"))||0))),...n.filter((e=>(parseInt(e.getAttribute("order"))||0)<0)).sort(((e,t)=>parseInt(t.getAttribute("order"))-parseInt(e.getAttribute("order"))))].map((e=>e.outerHTML)).join("")}async function getRemoteGPIOState(e,t,n,i,s){const o=Number(t)===unitNr1?`${cmD}SendTo,${i},'taskvalueset,${e},${s},[Plugin%23GPIO%23Pinstate%23${n}]'`:`${cmD}SendTo,${t},'SendTo,${i},"taskvalueset,${e},${s},\\[Plugin%23GPIO%23Pinstate%23${n}\\]"'`;return await getUrl(o)}function changeNN(e){return e.replace(/\?.*$/,"").replace(/\&.*$/,"").replace(/_/g," ").replace(/(?")}function changeCss(){let e,t,n=" auto";document.querySelectorAll(".chart, input[type=range]").length?document.getElementById("allList").classList.remove("allExtra"):document.getElementById("allList").classList.add("allExtra");var i=document.querySelectorAll(".bigNum"),s=document.getElementById("sensorList"),o=s.getElementsByClassName("sensorset").length;(bigLength=Math.max(...Array.from(i,(e=>e.children.length))))<2&&cD.length&&(bigLength=2),selectionData?.G>0&&(bigLength=selectionData.G),t=0,i.length||(t=o),4==bigLength||t>9?(e=n+n+n+n,coloumnSet=4):3==bigLength||t>4?(e=n+n+n,coloumnSet=3):2==bigLength||t>1?(e=n+n,coloumnSet=2):1==bigLength&&o<2||t<2&&!i.length?(e=n,i.length&&i.forEach((e=>e.classList.add("bigNumOne"))),coloumnSet=1):(e=n+n,coloumnSet=2);let l=!1;if(widthLimit=150*coloumnSet+coloumnSet*(window.innerHeight/100),(window.innerWidth1||o>1)&&(coloumnSet=2,e=n+n,bigLength=2)),document.querySelectorAll(".chart").forEach((e=>{e.style.width=`calc(${150*bigLength}px + ${bigLength-1}vh)`})),i.forEach((e=>{let t=5;for(;e.children.length
'}if(document.querySelectorAll("#sensorList").forEach((e=>{e.innerHTML=orderFunction(e.innerHTML)})),i.forEach((e=>{e.innerHTML=orderFunction(e.innerHTML);let t=Array.from(e.children);3===t.length&&t.forEach((e=>e.classList.add("big3")))})),l){document.querySelectorAll(".big3").forEach((e=>e.classList.add("bigSpan")))}s.style.setProperty("grid-template-columns",e),i.forEach((t=>{t.style.setProperty("grid-template-columns",e)})),2==coloumnSet&&1==bigLength&&document.getElementsByClassName("bigNum")[0].appendChild(Object.assign(document.createElement("div"),{className:"bigNumWrap"})),document.getElementById("sensorList").innerHTML=s.innerHTML}function paramS(){document.querySelectorAll(".slTHU").forEach((e=>{e.addEventListener("input",updateSlTS),e.addEventListener("change",sliderChTS),e.addEventListener("pointerup",(e=>{iIV=setTimeout(blurInput,1e3)}))})),document.querySelectorAll(".sL").forEach((e=>{e.addEventListener("input",updateSlider),e.addEventListener("change",sliderChange),e.addEventListener("pointerup",(e=>{iIV=setTimeout(blurInput,1e3)}))})),neoS=document.querySelectorAll(".npS"),neoS.forEach((e=>{hVal=document.getElementById(e.id.split("?")[0]+"?H")?.value,vVal=document.getElementById(e.id.split("?")[0]+"?V")?.value||20,vVal<20&&(vVal=20),e.style.backgroundImage="linear-gradient(to right, hsl(0,0%,"+vVal+"%),hsl("+hVal+",100%,50%))"})),window.efc&&addContext()}function addEonce(){document.addEventListener("touchstart",(e=>{e.touches.length>1||(tsTime=Date.now(),tsX=e.touches[0].screenX,tsY=e.touches[0].screenY)})),document.addEventListener("touchend",(e=>{if(e.changedTouches.length>1)return;const t=Date.now();teX=e.changedTouches[0].screenX,teY=e.changedTouches[0].screenY;const n=teX-tsX,i=teY-tsY,s=t-tsTime;Math.abs(n)>40&&Math.abs(i)<30&&s<300&&(n>0&&!navOpen&&openNav(),n<0&&navOpen&&closeNav())})),document.addEventListener("mousemove",(e=>{manNav||navOpen||e.clientX<10&&-280===document.getElementById("mySidenav").offsetLeft&&openNav()})),document.getElementById("mySidenav").addEventListener("mouseleave",(e=>{manNav||closeNav()})),window.efc&&createMenu()}function checkDirection(){touchtime=msTe-msTs,touchDistX=teX-tsX,touchDistY=teY-tsY,teX40&&Math.abs(touchDistY)<30&&touchtime<250&&closeNav(),teX>tsX&&!navOpen&&Math.abs(touchDistX)>40&&Math.abs(touchDistY)<30&&touchtime<250&&openNav()}function updateSlTS(e){isittime=0;const t=e.target,n="setpoint"===t.id,i=t.closest(".slTimeSetWrap");if(n){return void(i.querySelector(".slTimeText .setT").textContent=Number(t.value).toFixed(1))}const s="L"===t.id.slice(-1)?1:2,o=i.querySelector(`.hAmount${s}`),l=i.querySelector(`.mAmount${s}`),a=Math.floor(t.value/60),c=t.value%60;o.textContent=a,l.textContent=c.toString().padStart(2,"0")}function updateSlider(e){isittime=0;const t=e.target;if(!t.className.includes("noVal")){const e=t.closest("div.sensorset").querySelector(".sliderAmount"),n=t.step.includes(".")?t.step.split(".")[1].length:0,i=Number(t.value).toFixed(n);e.firstChild.textContent=i}if("npSl"===t.classList[1]){const e=t.id.split("?")[0],n=document.getElementById(`${e}?H`)?.value,i=document.getElementById(`${e}?S`)?.value;let s=document.getElementById(`${e}?V`)?.value||0;if(gesVal=[n,i,s],n&&i){s=Math.max(s,20);document.getElementById(`${e}?S`).style.backgroundImage=`linear-gradient(to right, hsl(0,0%,${s}%), hsl(${n},100%,50%))`}}}function sliderChTS(e){playSound(4e3);const t=e.target,n=t.parentNode.parentNode,i=t.parentNode.id,s=t.id.split(":")[0],o=t.id.split(":")[1],l=n.classList[2],a=(n.classList[1],e.target.value);if("setpoint"===s){const e=`taskvalueset,${l},${10*parseFloat(t.closest(".slTimeSetWrap").querySelector(".isT").textContent)+a/100}`,n=`event,${i}Event=${10*a}`;unitNr===unitNr1?(getUrl(`${cmD}${e}`),getUrl(`${cmD}${n}`)):(getUrl(`${cmD}SendTo,${nNr},"${e}"`),getUrl(`${cmD}SendTo,${nNr},"${n}"`))}else{const e="L"===o,t=document.getElementById(e?`${s}:R`:`${s}:L`).value,n=t.toString().padStart(4,"0"),i=e?`${a}.${n}`:`${t}.${a.toString().padStart(4,"0")}`,c=`taskvalueset,${l},${i}`,r=`event,${s}Event=${i}`;unitNr===unitNr1?(getUrl(`${cmD}${c}`),getUrl(`${cmD}${r}`)):(getUrl(`${cmD}SendTo,${nNr},"${c}"`),getUrl(`${cmD}SendTo,${nNr},"${r}"`))}}function sliderChange(e){playSound(4e3);const t=e.target;let n=t.value;const i="npSl"===t.classList[1],s=(t.id.match(/\?/g)||[]).length>=3||"npSl"==t.classList[1]?t.id.split("?")[0]:t.id;gesVal&&(gesVal=gesVal.filter(Boolean));const o=`taskvalueset,${t.classList[2]},${n}`,l=i?`event,${s}Event=${gesVal}`:`event,${s}Event=${n}`;unitNr===unitNr1?(getUrl(`${cmD}${o}`),getUrl(`${cmD}${l}`)):(getUrl(`${cmD}SendTo,${nNr},"${o}"`),getUrl(`${cmD}SendTo,${nNr},"${l}"`))}function buttonClick(e){if(e.includes("&")){const[t,n]=e.split("&"),[i,s]=n.split("G"),o=s&&!s.endsWith("L");getUrl(unitNr==i?o?`${cmD}GPIOToggle,${s}`:`${cmD}event,${t}Event`:o?`${cmD}SendTo,${i},"GPIOToggle,${s}"`:`${cmD}SendTo,${i},"event,${t}Event"`)}else if(e.includes("|")){const[t,n]=e.split("|");unitNr===unitNr1?(getUrl(`${cmD}GPIO,${n},[Plugin%23GPIO%23Pinstate%23${n}]`),getUrl(`${cmD}GPIOToggle,${n}`),getUrl(`${cmD}event,${t}Event`)):(getUrl(`${cmD}SendTo,${nNr},"GPIO,${n},\\[Plugin%23GPIO%23Pinstate%23${n}\\]"`),getUrl(`${cmD}SendTo,${nNr},"GPIOToggle,${n}"`),getUrl(`${cmD}SendTo,${nNr},"event,${t}Event"`))}else{const t=`event,${e}Event`;getUrl(unitNr===unitNr1?`${cmD}${t}`:`${cmD}SendTo,${nNr},"${t}"`)}setTimeout((()=>newFJ(2)),500)}function pushClick(e,t){if(isittime=0===t?1:0,0===t&&playSound(1e3),e.includes("?")){const n=e.split("?")[1],i=`${cmD}gpio,${n},${t}`;getUrl(unitNr===unitNr1?i:`${cmD}SendTo,${nNr},"${i}"`)}else if(e.includes("&")){const[n,i]=e.split("&");getUrl(`${cmD}SendTo,${i},"event,${n}Event=${t}"`)}else{const n=`${cmD}event,${e}Event=${t}`;getUrl(unitNr===unitNr1?n:`${cmD}SendTo,${nNr},"${n}"`)}}function getInput(e,t){if("click"===event.type&&(isittime=0,iIV=setTimeout(blurInput,8e3),e.addEventListener("blur",(e=>{clearTimeout(iIV),isittime=1,setTimeout((()=>newFJ(2)),400)}))),e.value.length>12&&(e.value=e.value.slice(0,12)),"Enter"===event.key||"click"===event.type&&!t){if(isittime=1,e.value){playSound(4e3);const t=`taskvalueset,${e.classList[1]},${e.value}`;getUrl(unitNr===unitNr1?cmD+t:`${cmD}SendTo,${nNr},"${t}"`),buttonClick(e.id.split("_vI")[0])}else setTimeout((()=>newFJ(2)),400);clearTimeout(iIV)}else"Escape"===event.key?document.getElementById(e.id).value="":(clearTimeout(iIV),iIV=setTimeout(blurInput,5e3))}function blurInput(){isittime=1}function openNav(e){navOpen=1,e&&(manNav=1),clearInterval(nIV),nIV=setInterval(getNodes,1e4);const t=document.getElementById("mySidenav");-280===t.offsetLeft?(window.configMode?(clearInterval(nIV),extraConfig()):getNodes(),t.style.left="0"):closeNav()}function closeNav(){manNav=0,navOpen=0,clearInterval(nIV),document.getElementById("mySidenav").style.left="-280px"}function openSys(){0===document.getElementById("sysInfo").offsetHeight?(document.getElementById("menueWrap1").style.flexShrink="0",document.getElementById("sysInfo").style.height="180px"):(document.getElementById("sysInfo").style.height="0",document.getElementById("menueWrap1").style.flexShrink="999")}async function getNodes(e,t,n){let i="",s=-1;if(n||unitNr===unitNr1||(response=await getUrl("/json"),myJson2=await response.json()),myJson2&&(myJson2.nodes.forEach((n=>{if(s++,n.nr==myParam&&hasParams)return nodeChange(s),void(hasParams=0);if(styleN=n.nr===unitNr1?isMain?n.nr===unitNr?"☗︎":"☖︎":n.nr===unitNr?"⊙︎":"⊚︎":n.nr===unitNr?"·︎":"",i+=`\n `,e||t){const i=t?"Long":"Event";getUrl(n.nr===unitNr1?`${cmD}event,${e}${i}`:`${cmD}SendTo,${n.nr},"event,${e}${i}"`)}})),s=0,document.getElementById("menueList").innerHTML=i,hasParams)){let e='
can not find node # '+myParam+"...
";document.getElementById("sensorList").innerHTML=e,hasParams=0,setTimeout(fetchJson,3e3)}}function nodeChange(e){const t=myJson2.nodes[e];t&&unitNr!==t.nr&&(document.getElementById("unitId").innerHTML=`contacting ${t.name}...`,cD=[],isMain||(runonce2=!0),firstRun=!1,localEfc=!1,noCors=!1,selectionData={},jStats=!1,window.efc&&(rmImg(!0),window.configMode&&exitConfig()),nNr=t.nr,nN=t.name,baseUrl=`http://${t.ip}`,nP=`${baseUrl}/tools`,nP2=`${baseUrl}/devices`,jsonPath=`${baseUrl}/json`,history.replaceState(null,null,nNr===unitNr1?"?":`?unit=${nNr}`),newFJ(1)),window.innerWidth<450&&0===document.getElementById("sysInfo").offsetHeight&&closeNav()}function resizeText(){const e=({clientWidth:e,clientHeight:t,scrollWidth:n,scrollHeight:i})=>n>e||i>t;(({element:t,elements:n,minSize:i=10,maxSize:s=115,step:o=1,unit:l="pt"})=>{(n||[t]).forEach((t=>{let n=i,a=!1;const c=t.parentNode;for(;!a&&n`,closeNav()}}function topF(){document.body.scrollTop=0,document.documentElement.scrollTop=0}const longPressN=()=>document.getElementById("mOpen").addEventListener("long-press",(()=>configMode?null:window.location.href=nP));function longPressS(){document.body.addEventListener("long-press",(function(e){e.preventDefault();const t={closeBtn:"Snd",openSys:"Two",unitId:"Col"};t[e.target.id]&&mC(t[e.target.id])}))}function mC(e){const t=1==(document.cookie.match(`(^|;)\\s*${e}\\s*=\\s*([^;]+)`)?.pop()||"");playSound(t?500:900),document.cookie=`${e}=${t?0:1}; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; SameSite=Lax;`}function longPressB(){setLongPressDelay(600);document.querySelectorAll(".clickables").forEach((e=>{e.addEventListener("long-press",(async function(t){t.preventDefault();const n=e.querySelector(".sensors"),i=e.querySelector(".nodes");let s;if(i)getNodes(i.textContent,"1");else{const[e,t]=n.id.split("&"),i=n.id.split(">")[1];s=t?i&&i.endsWith("L")?`${cmD}SendTo,${t},"GPIOToggle,${i.split("L")[0]}"`:`${cmD}SendTo,${t},"event,${e}Long"`:unitNr===unitNr1?`${cmD}event,${n.id}Long`:`${cmD}SendTo,${nNr},"event,${n.id}Long"`,await getUrl(s),setTimeout((()=>newFJ(2)),400)}playSound(1e3),isittime=0,iIV=setTimeout(blurInput,600)}))}))}function minutesToDhm(e){const t=(e,t)=>e?`${e} ${t}${e-1?"s":""} `:"";return t(e/1440|0,"day")+t(e%1440/60|0,"hour")+t(e%60,"minute")}function playSound(e){500===e?receiveNote("R"):e>=900&&e<=1e3?receiveNote("Y"):e>1e3&&receiveNote("G"),(document.cookie.includes("Snd=1")||e<1e3)&&(isittime||3e3!==e)&&(o=c.createOscillator(),g=c.createGain(),frequency=e,o.frequency.value=frequency,o.type="sawtooth",o.connect(g),g.connect(c.destination),g.gain.setValueAtTime(.05,0),o.start(0),g.gain.exponentialRampToValueAtTime(1e-5,c.currentTime+.01),o.stop(c.currentTime+.01))}async function getUrl(e,t={}){if(!window.configMode||e.includes("/json")){const n=t.controller||new AbortController,i=n.signal;let s=!1;const o=e.includes("/json")||e.includes("json.gz")?5500:1800;setTimeout((()=>{i.aborted||(s=!0,n.abort())}),o);try{return await fetch(e,{...t,signal:i})}catch(e){return("AbortError"!==e.name||s)&&receiveNote("R"),null}}}async function getEfcData(e=!1){localEfc&&(e=!0),runonce2=!1;let t=0;for(;t<1;){let n;if(!e&&(n=await getUrl("/main_efc.json.gz"),n?.ok))return efcArray=await n.json(),isittime=!0,isMain=!0,efcArray;if(n=await getUrl(`${baseUrl}/efc.json.gz`),null!=n&&404!=n.status||!e||(noCors=!0),n?.ok){const e=await n.text();if(1===e.length)return;return(selectionData=JSON.parse(e)).unit&&isMain&&(localEfc=!0),isittime=!0,selectionData}t++,await new Promise((e=>setTimeout(e,500)))}isittime=!0,localEfc=!1}async function getEfcUnitData(e){if(!0===localEfc)return;const t=efcArray?.find((t=>t.unit===e));(selectionData=structuredClone(t??{unit:e})).unit&&1===Object.keys(selectionData).length&&!noCors&&await getEfcData(!0)}function newFJ(e){fetchAbort?.abort(),clearTimeout(fJ),isFetching=!1,fetchJson(e),fJ=setInterval(fetchJson,durationF)}function receiveNote(e){const t={G:"rgb(0, 255, 0)",Y:"rgb(255, 251, 0)",R:"rgb(255, 0, 0)",N:"none"};document.getElementById("unitId").style.boxShadow=0===e||"N"===e?t.N:"inset 0px 11px 17px -11px "+t[e],e&&setTimeout((()=>receiveNote(0)),300)}function rgbToHex(e){const t=e.match(/\d+/g);return t?"#"+t.slice(0,3).map((e=>(+e).toString(16).padStart(2,"0"))).join(""):"#000"}document.addEventListener("visibilitychange",(()=>{"visible"===document.visibilityState?(nTh=0,runonce2=!0,newFJ()):clearTimeout(fJ)}));const meta=document.querySelector('meta[name="theme-color"]')||Object.assign(document.head.appendChild(document.createElement("meta")),{name:"theme-color"}),link=document.head.appendChild(Object.assign(document.createElement("link"),{rel:"manifest"}));function update(){const e=rgbToHex(getComputedStyle(document.body).backgroundColor);meta.content=e;const t=document.querySelector('link[rel="icon"][type="image/svg+xml"]')?.href||"";if(t.startsWith("data:image/svg+xml")){const e={icons:[{src:t,sizes:"any",type:"image/svg+xml"}]};link.href=URL.createObjectURL(new Blob([JSON.stringify(e)],{type:"application/json"}))}}document.addEventListener("DOMContentLoaded",update),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",update);let inIframe=!1;try{inIframe=window.self!==window.top}catch(e){inIframe=!0}!function(e,t){"use strict";let n=null;let i={x:0,y:0},s=1e3;const o="ontouchstart"in e||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0,l="PointerEvent"in e||e.navigator&&"msPointerEnabled"in e.navigator?{down:"pointerdown",up:"pointerup",move:"pointermove",leave:"pointerleave"}:o?{down:"touchstart",up:"touchend",move:"touchmove",leave:"touchleave"}:{down:"mousedown",up:"mouseup",move:"mousemove",leave:"mouseleave"};function a(e){r();const n=c(e),i=new CustomEvent("long-press",{bubbles:!0,cancelable:!0,detail:(s=n,{clientX:s.clientX,clientY:s.clientY,offsetX:s.offsetX,offsetY:s.offsetY,pageX:s.pageX,pageY:s.pageY,screenX:s.screenX,screenY:s.screenY})});var s;this.dispatchEvent(i)||t.addEventListener("click",d,!0)}function c(e){return e.changedTouches?e.changedTouches[0]:e}function r(){n&&(clearTimeout(n),n=null)}function d(e){t.removeEventListener("click",d,!0),e.preventDefault(),e.stopImmediatePropagation()}t.addEventListener(l.down,(function(e){const t=c(e);i={x:t.clientX,y:t.clientY},function(e,t=s){r();const i=e.target;n=setTimeout((()=>a.call(i,e)),t)}(e)}),!0),t.addEventListener(l.move,(function(e){const t=c(e);(Math.abs(i.x-t.clientX)>10||Math.abs(i.y-t.clientY)>10)&&r()}),!0),t.addEventListener(l.up,r,!0),t.addEventListener(l.leave,r,!0),t.addEventListener("wheel",r,!0),t.addEventListener("scroll",r,!0),navigator.userAgent.toLowerCase().includes("android")||t.addEventListener("contextmenu",r,!0),e.setLongPressDelay=function(e){s=e}}(window,document); -------------------------------------------------------------------------------- /fetch.min.html: -------------------------------------------------------------------------------- 1 |
no info found...
20251210/1
ℹ︎
×︎
 
☰︎⌂︎
-------------------------------------------------------------------------------- /fetch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 21 | 22 |
23 |
24 |
25 |
no info found...
26 | 27 | 28 | 29 |
30 |
20251210/1
ℹ︎ 31 |
32 |
×︎
33 |
34 |
 
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 | ☰︎ 45 | ⌂︎ 47 |
48 |
49 |
50 | 51 | --------------------------------------------------------------------------------