Compare commits
254 Commits
Author | SHA1 | Date | |
---|---|---|---|
0f3a808e9e
|
|||
59430669af | |||
834a2a2010 | |||
59633c3f74 | |||
a3b253665d | |||
31973aeac9 | |||
b47d595194 | |||
a53124de42 | |||
a958feb152 | |||
ab5ac2dc1d | |||
d77daf0c8e | |||
eb21863e09 | |||
e3e5c34953 | |||
c2dadf536e | |||
0fe60e91a9 | |||
b1fd9f71a4 | |||
271fd9dd02 | |||
c131a6ad1c | |||
f06781bc03 | |||
9a4cb277e9 | |||
d8b9c35655 | |||
896dc97c75 | |||
7dcf6a10f4 | |||
576890e033 | |||
d9a4854855 | |||
f2fa9ba652 | |||
a797ae1afa | |||
c12f97de48 | |||
df0c6faa4d | |||
ba048185db | |||
64b30d13a6 | |||
446f83c6c2 | |||
334bf16b09 | |||
9cd15db1ad | |||
b33be0057f | |||
bc34078f42 | |||
fa097ce329 | |||
55b291eb50 | |||
bc6e2bb8af | |||
fed603e219 | |||
12a3fb6c80 | |||
9eddf2c504 | |||
6a513e9f65 | |||
254912aee5 | |||
3b8f71a0f5 | |||
a9460c8c69 | |||
dd0fe3165f | |||
c059e30763 | |||
5909471962 | |||
cf535110ea | |||
f8462bcd48 | |||
f5dd757240 | |||
9b228800f9 | |||
16adba2ec3
|
|||
e3638beb14
|
|||
29964a83f2 | |||
e1fe13bac6
|
|||
3cbae2ff1b
|
|||
cdd0f5bac3 | |||
6a7cfd857c | |||
2a5815c579 | |||
7e5331d54f | |||
0ab401c3b1 | |||
232ca3ead4
|
|||
06460fcc4d
|
|||
8331255355
|
|||
6800f0c208
|
|||
6f744a7995
|
|||
c5846ca644
|
|||
19e187a280
|
|||
ec1862cfe6 | |||
78317ce5f2 | |||
1d78608786 | |||
aaea0fb877 | |||
1df56772d9 | |||
eebefd915c | |||
777ceb2bed | |||
c66f528702
|
|||
aa03c1683d
|
|||
6b3056ab31 | |||
95d13ec5a9 | |||
de0c74ab4b | |||
1a8407a0ec | |||
5bec1ed988 | |||
98ec85ea8b | |||
6a9c989766 | |||
0587918621 | |||
48826caee6 | |||
9ab8a84ff0 | |||
d12da60df4 | |||
8cdedaf3cb | |||
02d981cc2b | |||
4827688635
|
|||
b71bc81c0d
|
|||
a6ddd48abb
|
|||
8c0161b181
|
|||
3cf1aa7f49
|
|||
313a069d4c | |||
34d974b03b | |||
1de52c939f | |||
950a1fb029 | |||
5a167091cc | |||
57a81dbee3 | |||
acaf6898fd | |||
37bbb9208d | |||
6b2ef81b12 | |||
1e5ee117c5 | |||
db4164f621 | |||
bfd645d7a1 | |||
99cf344d93
|
|||
291ff8ed80
|
|||
cda1a573d6
|
|||
6506f6fe1c
|
|||
eec13e4934
|
|||
81366af7f1
|
|||
1b7af71d29
|
|||
c02f357d84 | |||
fffe4a8641 | |||
585c1608b8
|
|||
5a6b92bb5f
|
|||
54aeaacf86 | |||
3afd518399
|
|||
65dc2d81a3 | |||
22f6b3922e | |||
e5475bc099 | |||
301fe0e906 | |||
36fce6bf25
|
|||
e0ee932fff
|
|||
71576d7686
|
|||
2b4141fed3
|
|||
c5dd493cb0
|
|||
daede8799a
|
|||
f00f7601f4
|
|||
bd7e324a31
|
|||
9764db9789
|
|||
681a00b3ed
|
|||
79046d7bd0
|
|||
4b0f8e86b4
|
|||
bcddcfc311
|
|||
fe45ed8010
|
|||
0323920a0d
|
|||
fcc80b5843
|
|||
9e5756ceef
|
|||
e317d25977
|
|||
75ec2590f6
|
|||
80621bda22
|
|||
c5c6e37dfb | |||
b03ea9ded7 | |||
a341052c40 | |||
acea3c7d4d | |||
79f509da74 | |||
bff259008f | |||
36cfe78205 | |||
2c6aaf82f4 | |||
043ba99ea9 | |||
3c69f013fc | |||
650411bac4 | |||
b66d41a98b | |||
218ebc67d9 | |||
70288fc69f | |||
1bb5d16082 | |||
a37ae5eb70 | |||
63b6761044 | |||
b9a087c169 | |||
fd12f7ad2a | |||
41563454af | |||
6873052b6e | |||
7125374fa3 | |||
00d996d8dd | |||
b987828a25 | |||
d0c59ac659 | |||
6178e4fc22 | |||
3c42b43fe6 | |||
2b31e81f50 | |||
4ea7928fd5 | |||
50de3dad87 | |||
acce17b82c | |||
6c8f113861 | |||
a4989b4eb0 | |||
37ce571b9c | |||
6810f7432f | |||
e542ce0178 | |||
8865416331 | |||
1529874094 | |||
2fae44f387 | |||
4f421c84d5 | |||
b21c5d3b2c | |||
98d1215d7a | |||
7a10457f99 | |||
f31575b50c | |||
980e20818f | |||
e9aca78c9c | |||
a56f5decc7 | |||
02a21ec747 | |||
24258da42d | |||
37bb44066b | |||
1d7da26dcd | |||
999da37e11 | |||
7d46a4de96 | |||
870e8a06a2 | |||
8fa4456fa3 | |||
9b41a0f8b8 | |||
3aae5954f2 | |||
b3ab9cfb1a | |||
d38dac9c4d | |||
1c17252b93 | |||
22f1806692 | |||
3a905e646a | |||
36a94bebe9 | |||
5ba42e3361 | |||
11422a3b43 | |||
ec0b519fe3 | |||
74d984128b | |||
82adb0f1e1 | |||
6219d4302b | |||
5b2cec59db | |||
c437b11409 | |||
33daac87f1 | |||
3b3dee7e9a | |||
5fb64d4e73 | |||
30a91bdb85 | |||
600e46c457 | |||
2b4f0fde90 | |||
65be33cd81 | |||
41f43d1484 | |||
477e8e6459 | |||
92a1189762 | |||
b992260506 | |||
e86f1cd95c | |||
9717f6c239 | |||
8803eaa37d | |||
e7a09d0640 | |||
9093e8d01e | |||
e771320e6a | |||
7646d4e629 | |||
c901ff9408 | |||
cb5f00cbc0 | |||
845ee0f2cd | |||
35336208de | |||
d9648152ef | |||
27a33e55e7 | |||
b421b84b11 | |||
f1012b2fe2 | |||
cc633a7c85 | |||
40b13430cb | |||
dda4e9e771 | |||
1390b73218 | |||
5f7f8dd4e5 | |||
7e4dbd7e03 | |||
6dabc32905 | |||
feedd21413 | |||
851c363aba | |||
c72bff5b2e | |||
679bf1a5c1 |
58
.clang-format
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
# Based on Webkit style
|
||||||
|
BasedOnStyle: Webkit
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 120
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
Standard: Cpp11
|
||||||
|
# Pointers aligned to the left
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
PointerAlignment: Left
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: true
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterExternBlock: true
|
||||||
|
BeforeCatch: true
|
||||||
|
BeforeElse: true
|
||||||
|
SplitEmptyFunction: false
|
||||||
|
SplitEmptyRecord: false
|
||||||
|
SplitEmptyNamespace: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IncludeCategories:
|
||||||
|
# C++ standard headers (no .h)
|
||||||
|
- Regex: '<[[:alnum:]_-]+>'
|
||||||
|
Priority: 1
|
||||||
|
# Extenal libraries (with .h)
|
||||||
|
- Regex: '<[[:alnum:]_./-]+>'
|
||||||
|
Priority: 2
|
||||||
|
# Headers from same folder
|
||||||
|
- Regex: '"[[:alnum:]_.-]+"'
|
||||||
|
Priority: 3
|
||||||
|
# Headers from other folders
|
||||||
|
- Regex: '"[[:alnum:]_/.-]+"'
|
||||||
|
Priority: 4
|
||||||
|
IndentCaseLabels: false
|
||||||
|
NamespaceIndentation: All
|
||||||
|
SortIncludes: true
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
SpaceAfterTemplateKeyword: true
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
UseTab: Never
|
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
10
.gitignore
vendored
@ -26,3 +26,13 @@
|
|||||||
Network Trash Folder
|
Network Trash Folder
|
||||||
Temporary Items
|
Temporary Items
|
||||||
.apdisk
|
.apdisk
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
# Backup files produced by some editors
|
||||||
|
*~
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
677
README.md
@ -1,138 +1,665 @@
|
|||||||
# ESPUI
|
# ESPUI
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
|
ESPUI is a simple library to make a web-based user interface for your projects using
|
||||||
|
the **ESP8266** or the **ESP32** It uses web sockets and lets you create,
|
||||||
|
|
||||||
|
ol, and update elements on your GUI through multiple devices like phones
|
||||||
|
and tablets.
|
||||||
|
|
||||||
|
ESPUI uses simple Arduino-style syntax for creating a solid, functioning user
|
||||||
|
interface without too much boilerplate code.
|
||||||
|
|
||||||
|
So if you either don't know how or just don't want to waste time: this is your
|
||||||
|
simple solution user interface without the need of internet connectivity or any
|
||||||
|
additional servers.
|
||||||
|
|
||||||
|
The Library runs on any kind of **ESP8266** and **ESP32** (NodeMCU, AI Thinker, etc.).
|
||||||
|
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [How to Install](#how-to-install)
|
||||||
|
- [Getting started](#getting-started)
|
||||||
|
- [UI Elements](#documentation)
|
||||||
|
* [Button](#button)
|
||||||
|
* [Switch](#switch)
|
||||||
|
* [Buttonpad](#buttonpad)
|
||||||
|
* [Labels](#labels)
|
||||||
|
* [Slider](#slider)
|
||||||
|
* [Number Input](#number-input)
|
||||||
|
* [Text Input](#text-input)
|
||||||
|
* [Date, Time, Colour and Password Input](#date-time-colour-and-password-input)
|
||||||
|
* [Select control](#select-control)
|
||||||
|
* [Getting the Time](#getting-the-time)
|
||||||
|
* [Separators](#separators)
|
||||||
|
- [Initialisation of the UI](#initialisation-of-the-ui)
|
||||||
|
- [Tabs](#tabs)
|
||||||
|
- [Log output](#log-output)
|
||||||
|
- [Colours](#colours)
|
||||||
|
- [Advanced Features](#advanced-features)
|
||||||
|
* [Dynamic Visibility](#dynamic-visibility)
|
||||||
|
* [Inline Styles](#inline-styles)
|
||||||
|
* [Disabling Controls](#disabling-controls)
|
||||||
|
* [Grouped controls](#grouped-controls)
|
||||||
|
* [Wide controls](#wide-controls)
|
||||||
|
* [Graph (Experimental)](#graph--experimental-)
|
||||||
|
- [Notes for Development](#notes-for-development)
|
||||||
|
- [Contribute](#contribute)
|
||||||
|
|
||||||
|
|
||||||
ESPUI is a simple library to make a web user interface for your projects using the **ESP8266** or the **ESP32**
|
### Contributed features
|
||||||
It uses web sockets and lets you create, control, and update elements on your GUI through multiple devices like phones and tablets.
|
|
||||||
|
|
||||||
ESPUI uses simple Arduino-style syntax for creating a solid, functioning user interface without too much boilerplate code.
|
- Tabs by @eringerli
|
||||||
|
- Generic API by @eringerli
|
||||||
|
- Min Max on slider by @eringerli
|
||||||
|
- OptionList by @eringerli
|
||||||
|
- Public Access to ESPAsyncServer
|
||||||
|
- Inline CSS styles by @iangray001
|
||||||
|
- Separators by @iangray001
|
||||||
|
- Grouped and wide controls by @iangray001
|
||||||
|
- Transport layer rework by @iangray001
|
||||||
|
- Time control by @iangray001
|
||||||
|
- Vertical controls by @iangray001
|
||||||
|
- Time/date/password/color input types by @pcbbc
|
||||||
|
|
||||||
So if you either don't know how or just don't want to waste time: this is your simple solution user interface without the need of internet connectivity or any additional servers.
|
## Roadmap
|
||||||
|
|
||||||
I completely rewrote the EasyUI Library created by ayushsharma82 [Here](https://github.com/ayushsharma82/)
|
- Fully implement graphs
|
||||||
Now it uses ESPAsyncWebserver and is mainly to be used with the ESP32 Processor.
|
- Expand number input features (floats etc.)
|
||||||
|
- Support for enabling and disabling controls
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
This library is dependent on the following libraries to function properly.
|
|
||||||
|
This library is dependent on the following libraries.
|
||||||
|
|
||||||
- [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer)
|
- [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer)
|
||||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
|
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) (Last tested with
|
||||||
|
version 6.10.0)
|
||||||
**Plus for ESP8266**
|
|
||||||
- [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP)
|
|
||||||
|
|
||||||
**Additionally necessary for ESP32**
|
|
||||||
- [AsyncTCP](https://github.com/me-no-dev/AsyncTCP)
|
|
||||||
|
|
||||||
|
- (_For ESP8266_) [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP)
|
||||||
|
- (_For ESP32_) [AsyncTCP](https://github.com/me-no-dev/AsyncTCP)
|
||||||
|
- (_For ESP32_) [lorol/LittleFS_esp32](https://github.com/lorol/LITTLEFS)
|
||||||
|
|
||||||
## How to Install
|
## How to Install
|
||||||
|
|
||||||
Make sure all the dependencies are installed, then install like so:
|
Make sure all the dependencies are installed, then install like so:
|
||||||
|
|
||||||
#### Directly Through Arduino IDE
|
#### Using PlatformIO (_recommended_)
|
||||||
|
|
||||||
You can find this Library in the Arduino IDE library manager
|
Just include this library as a dependency in `lib_deps` like so:
|
||||||
Go to Sketch > Include Library > Library Manager > Search for "ESPUI" > Install
|
|
||||||
|
|
||||||
#### Manual Install
|
```
|
||||||
|
lib_deps =
|
||||||
|
ESPUI
|
||||||
|
ESP Async WebServer
|
||||||
|
ESPAsyncTCP # (or AsyncTCP on ESP32)
|
||||||
|
LittleFS_esp32 # (ESP32 only)
|
||||||
|
```
|
||||||
|
|
||||||
For Windows: Download the [Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the .zip in Documents>Arduino>Libraries>{Place "ESPUI" folder Here}
|
#### Using the Arduino IDE (_recommended_)
|
||||||
|
|
||||||
For Linux: Download the [Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the .zip in Sketchbook/Libraries/{Place "ESPUI" folder Here}
|
You can find this Library in the Arduino IDE library manager. Go to
|
||||||
|
`Sketch > Include Library > Library Manager` search for `ESPUI` and install.
|
||||||
|
|
||||||
For macOs: Download the [Repository](https://github.com/s00500/ESPUI/archive/master.zip) and extract the .zip in ~/Documents/Arduino/libraries/{Place "ESPUI" folder Here}
|
If you cannot use the Library Manager, you can download the [repository](https://github.com/s00500/ESPUI/archive/master.zip) and follow
|
||||||
|
the [instructions to manually install libraries](https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/how-to-install-a-library).
|
||||||
|
|
||||||
#### Manually through IDE
|
## Getting started
|
||||||
|
|
||||||
Download the [Repository](https://github.com/s00500/ESPUI/archive/master.zip), Go to Sketch>Include Library>Add .zip Library> Select the Downloaded .zip File.
|
ESPUI serves several files to the browser to build up its web interface. This
|
||||||
|
can be achieved in 2 ways: _PROGMEM_ or _LITTLEFS_
|
||||||
|
|
||||||
## Getting started (Filesystem upload)
|
_When `ESPUI.begin()` is called the default is serving files from Memory and
|
||||||
|
ESPUI should work out of the box!_
|
||||||
|
|
||||||
ESPUI **NEEDS** its files burnt on the SPIFFS filesystem on the ESP. **Without this ESPUI will NOT work at all**
|
**OPTIONAL:** But if this causes your program to _use too much memory_ you can
|
||||||
There are now two ways to do this: you can either use the upload tool or you use the library function `ESPUI.prepareFileSystem()`
|
burn the files into the LITTLEFS filesystem on the ESP. There are now two ways to
|
||||||
|
do this: you can either use the ESP file upload tool or you use the library
|
||||||
|
function `ESPUI.prepareFileSystem()`
|
||||||
|
|
||||||
#### Simple filesystem preparation (recomended)
|
#### Simple filesystem preparation (_recommended_)
|
||||||
|
|
||||||
Just open the example sketch **prepareFileSystem** and run it on the ESP, (give it 5 - 10 seconds),
|
Just open the example sketch **prepareFileSystem** and run it on the ESP, (give
|
||||||
The library will create all needed files.
|
it up to 30 seconds, you can see the status on the Serial Monitor), The library
|
||||||
Congratulations, you are done, from now on you just need to to this again when there is a library update, or when you want to use another chip :-)
|
will create all needed files. Congratulations, you are done, from now on you
|
||||||
Now you can upload your normal sketch, when you do not call the `ESPUI.prepareFileSystem()` function the compiler will strip out all the unnecessary that is already saved in the chip's filesystem and you have more programm memory to work with.
|
just need to do this again when there is a library update, or when you want to
|
||||||
|
use another chip :-) Now you can upload your normal sketch, when you do not call
|
||||||
|
the `ESPUI.prepareFileSystem()` function the compiler will strip out all the
|
||||||
#### Manual way (mainly for development)
|
unnecessary strings that are already saved in the chip's filesystem and you have
|
||||||
|
more program memory to work with.
|
||||||
To do this download and install me-no-devs wonderful [ESP32 sketch data uploader](https://github.com/me-no-dev/arduino-esp32fs-plugin) or for ESP8266 [ESP8266 sketch data uploader](https://github.com/esp8266/arduino-esp8266fs-plugin)
|
|
||||||
|
|
||||||
Then open the **gui** example sketch and select "Upload Sketch Data" from the Tools menu for your processor.
|
|
||||||
Now you are set to go and use any code you want to with this library
|
|
||||||
|
|
||||||
## User interface Elements
|
## User interface Elements
|
||||||
- Label (updateable)
|
|
||||||
- Button
|
|
||||||
- Switch (updateable)
|
|
||||||
- Control pad
|
|
||||||
- Control pad with center button
|
|
||||||
- Slider
|
|
||||||
|
|
||||||
Checkout the example for the usage
|
- Label
|
||||||
|
- Button
|
||||||
|
- Switch
|
||||||
|
- Control pad
|
||||||
|
- Slider
|
||||||
|
- Text Input
|
||||||
|
- Date, Time, Colour and Password Input
|
||||||
|
- Numberinput
|
||||||
|
- Option select
|
||||||
|
- Separator
|
||||||
|
- Time
|
||||||
|
- Graph (partial implementation)
|
||||||
|
|
||||||
## Roadmap :
|
|
||||||
|
|
||||||
- ~~Setup SPIFFS using values in program memory~~
|
|
||||||
- ~~ESP8266 support~~
|
|
||||||
- Document slider
|
|
||||||
- proper return value (as int and not as string) for slider
|
|
||||||
- Maybe a slider range setting, meanwhile please use map()
|
|
||||||
- Improve slider stability
|
|
||||||
- Improve general stability
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The heart of ESPUI is [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer).
|
The heart of ESPUI is
|
||||||
ESPUI's frontend is based on [Skeleton CSS](http://getskeleton.com/) and jQuery-like lightweight [zepto.js](https://zeptojs.com/) for Handling Click Events Etc. The communication between the *ESP32* and the client browser works using web sockets.
|
[ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer). ESPUI's
|
||||||
ESPUI does not need network access and can be used in standalone access point mode.
|
frontend is based on [Skeleton CSS](http://getskeleton.com/) and jQuery-like
|
||||||
All assets are loaded from the internal SPIFFS filesystem of the ESP32.
|
lightweight [zepto.js](https://zeptojs.com/) for handling events. The
|
||||||
|
communication between the ESP and the client browser works using web
|
||||||
|
sockets. ESPUI does not need network access and can be used in standalone access
|
||||||
|
point mode, all resources are loaded directly from the ESPs memory.
|
||||||
|
|
||||||
This section will explain in detail how the Library is to be used from the Arduino code side. As of now the Facilino blocks are not implemented.
|
This section will explain in detail how the Library is to be used from the
|
||||||
In the arduino setup() routine the interface can be customised by adding UI Elements. This is done by calling the corresponding library methods on the Library object ESPUI. Eg: ESPUI.button(“button”, &myCallback); creates a button in the interface that calls the “myCallback” function when changed. All buttons and items call their callback whenever there is a state change from them. This means the button will call the callback when it is pressed and also again when it is released. To separate different events an integer number with the event name is passed to the callback function that can be handled in a switch(){}case{} statement. Here is an overview of the currently implemented different elements of the UI library:
|
Arduino code side. In the arduino `setup()` routine the interface can be customised by adding UI Elements.
|
||||||
|
This is done by calling the corresponding library methods on the Library object
|
||||||
|
`ESPUI`. Eg: `ESPUI.button("button", &myCallback);` creates a button in the
|
||||||
|
interface that calls the `myCallback(Control *sender, int value)` function when changed. All buttons and
|
||||||
|
items call their callback whenever there is a state change from them. This means
|
||||||
|
the button will call the callback when it is pressed and also again when it is
|
||||||
|
released. To separate different events an integer number with the event name is
|
||||||
|
passed to the callback function that can be handled in a `switch(){}case{}`
|
||||||
|
statement.
|
||||||
|
|
||||||
|
|
||||||
#### Button
|
#### Button
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Buttons have a name and a callback value. They have one event for press and one for release.
|
Buttons have a name and a callback value. Their text can be changed at runtime using `ESPUI.updateButton()`.
|
||||||
|
|
||||||
|
Events:
|
||||||
|
- `B_DOWN` - Fired when button is pressed.
|
||||||
|
- `B_UP` - Fired when button is released.
|
||||||
|
|
||||||
#### Switch
|
#### Switch
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Switches sync their state on all connected devices. This means when you change their value they change visibly on all tablets or computers that currently display the interface. They also have two types of events: one for turning on and one for turning off.
|
Switches sync their state on all connected devices. This means when you change
|
||||||
|
their value (either by pressing them, or programmatically using `ESPUI.updateSwitcher()`) they change visibly
|
||||||
|
on all tablets or computers that currently display the interface.
|
||||||
|
|
||||||
|
Events:
|
||||||
|
- `S_ACTIVE` - Fired when turning on.
|
||||||
|
- `S_INACTIVE` - Fired when turning off.
|
||||||
|
|
||||||
#### Buttonpad
|
#### Buttonpad
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Button pads come in two flavours: with or without a center button. They are very useful for con-trolling all kinds of movements of vehicles or also of course our walking robots. They use a single callback per pad and have 8 or 10 different event types to differentiate the button actions.
|
Button pads come in two flavours: with or without a center button. They are
|
||||||
|
useful for controlling movements of vehicles/cameras etc. They use a single
|
||||||
|
callback per pad and have 8 or 10 different event types to differentiate the
|
||||||
|
button actions.
|
||||||
|
|
||||||
|
- `P_LEFT_DOWN`
|
||||||
|
- `P_LEFT_UP`
|
||||||
|
- `P_RIGHT_DOWN`
|
||||||
|
- `P_RIGHT_UP`
|
||||||
|
- `P_FOR_DOWN`
|
||||||
|
- `P_FOR_UP`
|
||||||
|
- `P_BACK_DOWN`
|
||||||
|
- `P_BACK_UP`
|
||||||
|
- `P_CENTER_DOWN`
|
||||||
|
- `P_CENTER_UP`
|
||||||
|
|
||||||
#### Labels
|
#### Labels
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
Labels are used to display textual information (i.e. states, values of sensors,
|
||||||
|
configuration parameters etc.). To send data from the code use `ESP.updateLabel()` .
|
||||||
|
Labels get a name on creation and a initial value.
|
||||||
|
|
||||||
|
Labels automatically wrap your text. If you want them to have multiple lines use
|
||||||
|
the normal `<br>` tag in the string you print to the label.
|
||||||
|
|
||||||
|
In fact, because HTML can be used in the label's value, you can make a label display
|
||||||
|
images by including an `<img>` tag.
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.label("An Image Label", ControlColor::Peterriver, "<img src='path/to/image'>");
|
||||||
|
```
|
||||||
|
|
||||||
|
This requires that the client has access to the image in question, either from the internet or a local web server.
|
||||||
|
|
||||||
Labels are a nice tool to get information from the robot to the user interface. This can be done to show states, values of sensors and configuration parameters. To send data from the code use ESP.print(labelId, “Text”); . Labels get a name on creation and a initial value. The name is not changeable once the UI initialised.
|
|
||||||
|
|
||||||
#### Slider
|
#### Slider
|
||||||
|
|
||||||
There is also an slider component now, needs to be documented though
|

|
||||||
|
|
||||||
#### Initialisation of the UI
|
Sliders can be used to select (or display) a numerical value. Sliders provide
|
||||||
|
realtime data and are touch compatible. Note that like all ESPUI functions, the callback does not return an `int`
|
||||||
|
but a `String` so should be converted with the `.toInt()` function. See the examples for more details. Sliders can
|
||||||
|
be updated from code using `ESP.updateSlider()`.
|
||||||
|
|
||||||
After all the elements are configured you can use ESPUI.begin(“Some Title”); to start the UI interface. Make sure you setup a working network connection or AccesPoint **before** (See example). The web interface can then be used from multiple devices at once and also shows an connection status in the top bar.
|
A slider usually only sends a new value when it is released to save network bandwidth.
|
||||||
The library is designed to be easy to use and can still be extended with a lot of more functionality.
|
This behaviour can be changed globally by setting `sliderContinuous` before `begin()`:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.sliderContinuous = true;
|
||||||
|
ESPUI.begin("ESPUI Control");
|
||||||
|
```
|
||||||
|
|
||||||
|
Events:
|
||||||
|
- `SL_VALUE` - Fired when a slider value changes.
|
||||||
|
|
||||||
|
#### Number Input
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The number input can be used to receive numbers from the user. You can
|
||||||
|
enter a value into it and when you are done with your change it is sent to the
|
||||||
|
ESP. A number box needs to have a min and a max value. To set it up just use:
|
||||||
|
|
||||||
|
`ESPUI.number("Numbertest", &numberCall, ControlColor::Alizarin, 5, 0, 10);`
|
||||||
|
|
||||||
|
Number inputs can be updated from code using `ESP.updateNumber()`.
|
||||||
|
|
||||||
|
Note that HTML number boxes will respect their min and max when the user
|
||||||
|
clicks the up and down arrows, but it is possible on most clients to simply type
|
||||||
|
any number in. As with all user input, numbers should be validated in callback code
|
||||||
|
because all client side checks can be skipped. If any value from the UI might
|
||||||
|
cause a problem, validate it.
|
||||||
|
|
||||||
|
Events:
|
||||||
|
- `N_VALUE` - Fired when a number value changes.
|
||||||
|
|
||||||
|
|
||||||
|
#### Text Input
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The text input works very similar like the number input but allows any string to be entered.
|
||||||
|
If you attach a Max control to the text input then a max length will be applied
|
||||||
|
to the control.
|
||||||
|
|
||||||
|
```
|
||||||
|
text = ESPUI.text("Label", callback, ControlColor::Dark, "Initial value");
|
||||||
|
ESPUI.addControl(ControlType::Max, "", "32", ControlColor::None, text);
|
||||||
|
```
|
||||||
|
|
||||||
|
Text inputs can be updated from code using `ESP.updateText()`.
|
||||||
|
|
||||||
|
However even with a set maximum length, user input should still be validated
|
||||||
|
because it is easy to bypass client-side checks. Never trust user input.
|
||||||
|
|
||||||
|
Events:
|
||||||
|
- `T_VALUE` - Fired when a text value changes.
|
||||||
|
|
||||||
|
|
||||||
|
#### Date, Time, Colour and Password Input
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
As an extension to the text input control, you can also specify the type attribute to be used for the HTML input element.
|
||||||
|
This allows you to easily create input controls for Date, Time, Colour and Passwords, or indeed any other
|
||||||
|
[HTML Input Types](https://www.w3schools.com/html/html_form_input_types.asp) supported by your browser.
|
||||||
|
|
||||||
|
```
|
||||||
|
text_date = ESPUI.text("Date", callback, ControlColor::Dark, "2022-05-24");
|
||||||
|
ESPUI.setInputType(text_date, "date");
|
||||||
|
|
||||||
|
text_time = ESPUI.text("Time", callback, ControlColor::Dark, "13:00");
|
||||||
|
ESPUI.setInputType(text_time, "time");
|
||||||
|
|
||||||
|
text_colour = ESPUI.text("Colour", callback, ControlColor::Dark, "#FF0000");
|
||||||
|
ESPUI.setInputType(text_colour, "color");
|
||||||
|
|
||||||
|
text_password = ESPUI.text("Password", callback, ControlColor::Dark, "tiddles123");
|
||||||
|
ESPUI.setInputType(text_password, "password");
|
||||||
|
```
|
||||||
|
|
||||||
|
*Important!* This function should be called _before_ `ESPUI.begin` or results will be unreliable.
|
||||||
|
|
||||||
|
Note that not all browsers support all input types, and that the control displayed to edit the input is browser dependent.
|
||||||
|
|
||||||
|
However even with a type set, user input should still be validated
|
||||||
|
because it is easy to bypass client-side checks. Never trust user input.
|
||||||
|
|
||||||
|
|
||||||
|
#### Select control
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
The Select control lets the user select from a predefined list of options. First create a select widget like so
|
||||||
|
|
||||||
|
```
|
||||||
|
uint16_t select1 = ESPUI.addControl( ControlType::Select, "Select Title", "Initial Value", ControlColor::Alizarin, tab1, &selectExample );
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add Options to it, specifying the Select as the parent:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.addControl( ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1);
|
||||||
|
ESPUI.addControl( ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1);
|
||||||
|
ESPUI.addControl( ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1);
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the **tabbedGui** example for a working demo. Selectors can be updated from code using `ESP.updateSelect()`.
|
||||||
|
|
||||||
|
Events:
|
||||||
|
- `S_VALUE` - Fired when a select value changes.
|
||||||
|
|
||||||
|
#### Getting the Time
|
||||||
|
|
||||||
|
ESPUI can create an invisible control that can be used to fetch the current time from the client
|
||||||
|
when they are connected to the UI. This could be used to intermittently provide an accurate time
|
||||||
|
source to your ESP. Remember that clients cannot be relied upon to be correct or truthful.
|
||||||
|
|
||||||
|
Create a Time control with the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
//Add the invisible "Time" control
|
||||||
|
auto timeId = ESPUI.addControl(Time, "", "", None, 0, timeCallback);
|
||||||
|
```
|
||||||
|
|
||||||
|
After creating the UI, sending an update to the Time control will cause it to fetch the current
|
||||||
|
time from the client and then fire its callback with the result.
|
||||||
|
|
||||||
|
```
|
||||||
|
//Request an update to the time
|
||||||
|
ESPUI.updateTime(timeId);
|
||||||
|
//Will trigger timeCallback
|
||||||
|
```
|
||||||
|
|
||||||
|
In `timeCallback` you can then print the control's value as normal:
|
||||||
|
|
||||||
|
```
|
||||||
|
void timeCallback(Control *sender, int type) {
|
||||||
|
if(type == TM_VALUE) {
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned string will be an [ISO string](https://www.w3schools.com/jsref/jsref_toisostring.asp) as returned by the Javascript `new Date().toISOString()`. The format is `YYYY-MM-DDTHH:mm:ss.sssZ` so for example: `2022-01-20T21:44:22.913Z`.
|
||||||
|
|
||||||
|
Events:
|
||||||
|
- `TM_VALUE` - Fires when the control is updated with `updateTime()`
|
||||||
|
|
||||||
|
#### Separators
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can use separators to break up the UI and better organise your controls. Adding a separator will force any following controls onto the subsequent line. Add separators as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.separator("Separator name");
|
||||||
|
//or
|
||||||
|
ESPUI.addControl(ControlType::Separator, "Separator name", "", ControlColor::None, maintab);
|
||||||
|
```
|
||||||
|
|
||||||
|
Separators fire no events.
|
||||||
|
|
||||||
|
### Initialisation of the UI
|
||||||
|
|
||||||
|
After all the elements are configured, call `ESPUI.begin("Some Title");`
|
||||||
|
to start the UI interface. (Or `ESPUI.beginLITTLEFS("Some Title");` respectively)
|
||||||
|
Make sure you setup a working network connection or AccessPoint **before** (see
|
||||||
|
the `gui.ino` example). The web interface can then be used from multiple devices at once and
|
||||||
|
also shows connection status in the top bar.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Tabs
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Tabs can be used to organize your controls into pages. Check the **tabbedGui** example to see
|
||||||
|
how this is done. Tabs can be created as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.addControl(ControlType::Tab, "Settings 1", "Settings 1");
|
||||||
|
```
|
||||||
|
|
||||||
|
Then all widgets for the tab need to be added to it by specifying the tab as the parent. Widgets not
|
||||||
|
added to a tab will be shown above the tab selctor.
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.addControl(ControlType::Text, "Text Title", "a Text Field", ControlColor::Alizarin, tab1, &textCall);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the basic functions to add controls like `ESPUI.button()` or `ESPUI.slider()` do not add to any tab,
|
||||||
|
so they are good for building small UIs. However if you need to use tabs then you will have to add all your
|
||||||
|
controls using the full `ESPUI.addControl()` function.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Log output
|
||||||
|
|
||||||
|
ESPUI has several different log levels. You can set them using the
|
||||||
|
`ESPUI.setVerbosity(Verbosity::VerboseJSON)` function.
|
||||||
|
|
||||||
|
Loglevels are:
|
||||||
|
|
||||||
|
- `Verbosity::Quiet` (default)
|
||||||
|
- `Verbosity::Verbose`
|
||||||
|
- `Verbosity::VerboseJSON`
|
||||||
|
|
||||||
|
VerboseJSON outputs the most debug information.
|
||||||
|
|
||||||
|
### Colours
|
||||||
|
|
||||||
|
A selection of basic colours are available to use:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If you want more control over the UI design, see the Inline Styles section below.
|
||||||
|
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
ESPUI includes a range of advanced features that can customise your UIs.
|
||||||
|
|
||||||
|
|
||||||
|
### Dynamic Visibility
|
||||||
|
|
||||||
|
Cotrols can be made visible or invisible at runtime with the `updateVisibility()` function.
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.updateVisibility(controlId, false);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that you cannot hide individual controls from a [control group](#grouped-controls), you have to hide the entire group.
|
||||||
|
|
||||||
|
|
||||||
|
### Inline Styles
|
||||||
|
|
||||||
|
You can add custom CSS styles to controls. This allows you to style the UI with custom colors, drop shadows,
|
||||||
|
or other CSS effects. Add styles with the following functions:
|
||||||
|
|
||||||
|
```
|
||||||
|
setPanelStyle(uint16_t id, String style);
|
||||||
|
setElementStyle(uint16_t id, String style)
|
||||||
|
```
|
||||||
|
|
||||||
|
A panel style is applied to the panel on which the UI element is placed, an element style is applied to the element itself.
|
||||||
|
Because CSS inline styles can only style one specific DOM element, for controls made up of multiple elements (like the pad)
|
||||||
|
this is limited. Element styles can be applied to all controls, but will only work correctly for the Button, Label, Slider,
|
||||||
|
Switcher, Number, Text, and Selector.
|
||||||
|
|
||||||
|
Dynamic update of styles is supported. When either of the above functions are called, the control is updated live. This could
|
||||||
|
be used to refect a warning state by changing the color of a button, or for similar effects.
|
||||||
|
|
||||||
|
For example, this code will set a control's panel to a random background color:
|
||||||
|
|
||||||
|
```
|
||||||
|
char stylecol[30];
|
||||||
|
sprintf(stylecol, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
|
||||||
|
ESPUI.setPanelStyle(switch1, stylecol);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can get quite creative with this.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The [completeExample](examples/completeExample/completeExample.cpp) example includes a range of things that you can do with inline styles.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### Disabling Controls
|
||||||
|
|
||||||
|
It is possible to dynamically enable and disable controls to, for example, provide feedback to the user that a particular feature is
|
||||||
|
temporarily unavailable. To do this use the following function call:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.setEnabled(controlId, enabled);
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting `enabled` to false will make the control noninteractive and it will visually change to illustrate this to the user. The control
|
||||||
|
will stop firing any events. Note that whilst the widget will change appearance, the panel of the control will remain whatever colour
|
||||||
|
it was set to. If you wish to also change the colour of the panel then you should use inline styles to show the noninteractive state. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.setEnabled(mainButton, false);
|
||||||
|
const String disabledstyle = "background-color: #bbb; border-bottom: #999 3px solid;";
|
||||||
|
ESPUI.setPanelStyle(mainButton, disabledstyle);
|
||||||
|
```
|
||||||
|
|
||||||
|
This CSS style sets the panel background and its border to grey. To put the control back to enabled use the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.setEnabled(mainButton, true);
|
||||||
|
ESPUI.setPanelStyle(mainButton, ";");
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we have to set the inline style to `";"` (i.e. an empty CSS rule) because if we just try to set it to `""` this will be
|
||||||
|
interpreted as "do not change the style".
|
||||||
|
|
||||||
|
Controls can also be set to disabled before the UI is started.
|
||||||
|
|
||||||
|
### Grouped controls
|
||||||
|
|
||||||
|
Normally, whenever a control is added to the UI, a new panel is generated with a title. However, you can instead
|
||||||
|
set the "parent" of a new control to be an existing control. This allows you to add multiple widgets into the same
|
||||||
|
panel. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
panel1 = ESPUI.addControl(ControlType::Button, "Button Set", "Button A", ControlColor::Turquoise, Control::noParent, btncallback);
|
||||||
|
ESPUI.addControl(ControlType::Button, "", "Button B", ControlColor::None, panel1, btncallback);
|
||||||
|
ESPUI.addControl(ControlType::Button, "", "Button C", ControlColor::None, panel1, btncallback);
|
||||||
|
```
|
||||||
|
|
||||||
|
The first call to `addControl` has no parent (or it could be set to a tab if you are using a tabbed UI), so therefore a new panel is added containing one button
|
||||||
|
with the value `Button A`. The two subsequent calls have their parent set to the first control we added, so instead of creating
|
||||||
|
a new panel, the result is the following:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The grouped controls operate entirely independently, and can be assigned different callbacks, or updated separately. The grouping
|
||||||
|
is purely visual.
|
||||||
|
|
||||||
|
Most controls can be grouped this way, but the result is not always visually pleasant. This works best with labels, sliders, switchers,
|
||||||
|
and buttons.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
For sliders and switchers, you can also set the controls to be displayed vertically.
|
||||||
|
|
||||||
|
```
|
||||||
|
auto vertswitcher = ESPUI.addControl(Switcher, "Vertical Switcher", "0", Dark, tab1);
|
||||||
|
ESPUI.setVertical(vertswitcher);
|
||||||
|
```
|
||||||
|
|
||||||
|
This must be done before `ESPUI.begin()` is called. Vertical layouts are currently only supported for sliders and switchers, and it
|
||||||
|
is a purely visual change. Behaviour is identical. Mixing horizontal and vertical controls can result in some unpredictable layouts.
|
||||||
|
|
||||||
|
When you add multiple buttons to a single panel, the buttons have a title so they can be differentiated. For sliders and switchers this is
|
||||||
|
not the case. Therefore you might want to add additional labels so that the controls can be distinguished. There is not yet automatic
|
||||||
|
support for doing this, so the approach is to add labels that have been styled using [inline styles](#inline-styles). By doing this
|
||||||
|
you can acheieve effects such as this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The code to do this is in the [completeExample](examples/completeExample/completeExample.cpp) example.
|
||||||
|
|
||||||
|
### Wide controls
|
||||||
|
|
||||||
|
Controls can be set to be displayed "wide" with the function:
|
||||||
|
|
||||||
|
```
|
||||||
|
ESPUI.setPanelWide(controlid, true);
|
||||||
|
```
|
||||||
|
|
||||||
|
*Important!* This function should be called _before_ `ESPUI.begin` or results will be unreliable.
|
||||||
|
|
||||||
|
Setting a control to wide tells ESPUI to lay out that control as if there was only a single column, even on wide displays.
|
||||||
|
This can be applied to every element to force a single column layout, or to individual elements to customise the display.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Note that this will have no effect on small screens.
|
||||||
|
|
||||||
|
|
||||||
|
### Graph (Experimental)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The graph widget can display graph points with timestamp at wich they arrive
|
||||||
|
|
||||||
|
Use `ESPUI.addGraphPoint(graphId, random(1, 50));` to add a new value at the current time, use `ESPUI.clearGraph(graphId)` to clear the entire graph.
|
||||||
|
Graph points are saved in the browser in **localstorage** to be persistant, clear local storageto remove the points or use clearGraph() from a bbutton callback to provide a clear button.
|
||||||
|
|
||||||
|
_There are many issues with the graph component currently and work is ongoing. Consider helping us out with development!_
|
||||||
|
|
||||||
|
# Notes for Development
|
||||||
|
|
||||||
|
If you want to work on the HTML/CSS/JS files, do make changes in the _data_
|
||||||
|
directory. When you need to transfer that code to the ESP, run
|
||||||
|
`tools/prepare_static_ui_sources.py -a` (this script needs **python3** with the
|
||||||
|
modules **htmlmin**, **jsmin** and **csscompressor**). This will generate a) minified files
|
||||||
|
next to the original files and b) the C header files in `src` that contain the minified and
|
||||||
|
gzipped HTML/CSS/JS data. Alternatively, you can specify the `--source` and `--target` arguments to the
|
||||||
|
`prepare_static_ui_sources.py` script (run the script without arguments for
|
||||||
|
help) if you want to use different locations.
|
||||||
|
|
||||||
|
If you don't have a python environment, you need to minify and gzip the
|
||||||
|
HTML/CSS/JS files manually. I wrote a little useful jsfiddle for this,
|
||||||
|
[see here](https://jsfiddle.net/s00500/yvLbhuuv/).
|
||||||
|
|
||||||
|
If you change something in HTML/CSS/JS and want to create a pull request, please
|
||||||
|
do include the minified versions and corresponding C header files in your
|
||||||
|
commits. (Do **NOT** commit all the minified versions for the non changed files)
|
||||||
|
|
||||||
|
# Experimental debugging environment using emulation on host
|
||||||
|
|
||||||
|
It is possible to run or debug this library on a unix-like computer (Linux,
|
||||||
|
macOS, WSL) without flashing on hardware, and with your favourite debugging
|
||||||
|
tools (gdb, valgrind, ...). This is accomplished through the
|
||||||
|
esp8266/Arduino "emulation on host" environment.
|
||||||
|
|
||||||
|
A fake AsyncWebserver library is needed because lwIP is not yet ported to
|
||||||
|
the emulation environment. Full instructions can be found in this project's
|
||||||
|
[readme page](https://github.com/d-a-v/emuAsync).
|
||||||
|
|
||||||
|
## Changelog for 2.1:
|
||||||
|
|
||||||
|
- Adds the ability to have inline styles for widgets and panels
|
||||||
|
- Adds LittleFS on the ESP32
|
||||||
|
- Cleans up examples
|
||||||
|
- Adds Button Animation
|
||||||
|
- Adds chunking for the widgets so you can add even more of them
|
||||||
|
- Fixes lots of bugs related to invisible UI elements and sliders
|
||||||
|
- Adds the ability to change port
|
||||||
|
|
||||||
|
## Changelog for 2.0:
|
||||||
|
|
||||||
|
- ArduinoJSON 6.10.0 Support
|
||||||
|
- Split pad into pad and padWithCenter
|
||||||
|
- Cleaned order of parameters on switch and pad
|
||||||
|
- Changes all numbers to actually be numbers (slider value, number value, min and max)
|
||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
Liked this Library? You can **support** me by sending me a :coffee: [Coffee](https://paypal.me/lukasbachschwell/3).
|
|
||||||
|
Liked this Library? You can **support** me by sending me a :coffee:
|
||||||
|
[Coffee](https://paypal.me/lukasbachschwell/5).
|
||||||
|
|
||||||
|
Otherwise I really welcome **Pull Requests**.
|
||||||
|
1
data/css/normalize.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
|
1200
data/css/style.css
Normal file
1
data/css/style.min.css
vendored
Normal file
35
data/index.htm
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Control</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="/css/normalize.css" />
|
||||||
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
|
|
||||||
|
<script src="/js/zepto.min.js"></script>
|
||||||
|
<script src="/js/slider.js"></script>
|
||||||
|
<script src="/js/graph.js"></script>
|
||||||
|
<script src="/js/controls.js"></script>
|
||||||
|
<script src="/js/tabbedcontent.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="javascript:start();">
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
<div id="mainHeader">Control</div>
|
||||||
|
<span id="conStatus" class="label">Offline</span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="container">
|
||||||
|
<div id="row" class="row u-full-width"></div>
|
||||||
|
<ul id="tabsnav" class="navigation navigation-tabs u-full-width"></ul>
|
||||||
|
<div id="tabscontent" class="tabscontent u-full-width"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
data/index.min.htm
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html>
|
893
data/js/controls.js
vendored
Normal file
@ -0,0 +1,893 @@
|
|||||||
|
const UI_INITIAL_GUI = 200;
|
||||||
|
const UI_RELOAD = 201;
|
||||||
|
const UPDATE_OFFSET = 100;
|
||||||
|
|
||||||
|
const UI_EXTEND_GUI = 210;
|
||||||
|
|
||||||
|
const UI_TITEL = 0;
|
||||||
|
|
||||||
|
const UI_PAD = 1;
|
||||||
|
const UPDATE_PAD = 101;
|
||||||
|
|
||||||
|
const UI_CPAD = 2;
|
||||||
|
const UPDATE_CPAD = 102;
|
||||||
|
|
||||||
|
const UI_BUTTON = 3;
|
||||||
|
const UPDATE_BUTTON = 103;
|
||||||
|
|
||||||
|
const UI_LABEL = 4;
|
||||||
|
const UPDATE_LABEL = 104;
|
||||||
|
|
||||||
|
const UI_SWITCHER = 5;
|
||||||
|
const UPDATE_SWITCHER = 105;
|
||||||
|
|
||||||
|
const UI_SLIDER = 6;
|
||||||
|
const UPDATE_SLIDER = 106;
|
||||||
|
|
||||||
|
const UI_NUMBER = 7;
|
||||||
|
const UPDATE_NUMBER = 107;
|
||||||
|
|
||||||
|
const UI_TEXT_INPUT = 8;
|
||||||
|
const UPDATE_TEXT_INPUT = 108;
|
||||||
|
|
||||||
|
const UI_GRAPH = 9;
|
||||||
|
const ADD_GRAPH_POINT = 10;
|
||||||
|
const CLEAR_GRAPH = 109;
|
||||||
|
|
||||||
|
const UI_TAB = 11;
|
||||||
|
const UPDATE_TAB = 111;
|
||||||
|
|
||||||
|
const UI_SELECT = 12;
|
||||||
|
const UPDATE_SELECT = 112;
|
||||||
|
|
||||||
|
const UI_OPTION = 13;
|
||||||
|
const UPDATE_OPTION = 113;
|
||||||
|
const UI_MIN = 14;
|
||||||
|
const UPDATE_MIN = 114;
|
||||||
|
const UI_MAX = 15;
|
||||||
|
const UPDATE_MAX = 115;
|
||||||
|
const UI_STEP = 16;
|
||||||
|
const UPDATE_STEP = 116;
|
||||||
|
|
||||||
|
const UI_GAUGE = 17;
|
||||||
|
const UPDATE_GAUGE = 117;
|
||||||
|
const UI_ACCEL = 18;
|
||||||
|
const UPDATE_ACCEL = 118;
|
||||||
|
|
||||||
|
const UI_SEPARATOR = 19;
|
||||||
|
const UPDATE_SEPARATOR = 119;
|
||||||
|
|
||||||
|
const UI_TIME = 20;
|
||||||
|
const UPDATE_TIME = 120;
|
||||||
|
|
||||||
|
const UP = 0;
|
||||||
|
const DOWN = 1;
|
||||||
|
const LEFT = 2;
|
||||||
|
const RIGHT = 3;
|
||||||
|
const CENTER = 4;
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const C_TURQUOISE = 0;
|
||||||
|
const C_EMERALD = 1;
|
||||||
|
const C_PETERRIVER = 2;
|
||||||
|
const C_WETASPHALT = 3;
|
||||||
|
const C_SUNFLOWER = 4;
|
||||||
|
const C_CARROT = 5;
|
||||||
|
const C_ALIZARIN = 6;
|
||||||
|
const C_DARK = 7;
|
||||||
|
const C_NONE = 255;
|
||||||
|
|
||||||
|
var graphData = new Array();
|
||||||
|
var hasAccel = false;
|
||||||
|
var sliderContinuous = false;
|
||||||
|
|
||||||
|
function colorClass(colorId) {
|
||||||
|
colorId = Number(colorId);
|
||||||
|
switch (colorId) {
|
||||||
|
case C_TURQUOISE:
|
||||||
|
return "turquoise";
|
||||||
|
|
||||||
|
case C_EMERALD:
|
||||||
|
return "emerald";
|
||||||
|
|
||||||
|
case C_PETERRIVER:
|
||||||
|
return "peterriver";
|
||||||
|
|
||||||
|
case C_WETASPHALT:
|
||||||
|
return "wetasphalt";
|
||||||
|
|
||||||
|
case C_SUNFLOWER:
|
||||||
|
return "sunflower";
|
||||||
|
|
||||||
|
case C_CARROT:
|
||||||
|
return "carrot";
|
||||||
|
|
||||||
|
case C_ALIZARIN:
|
||||||
|
return "alizarin";
|
||||||
|
|
||||||
|
case C_DARK:
|
||||||
|
case C_NONE:
|
||||||
|
return "dark";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var websock;
|
||||||
|
var websockConnected = false;
|
||||||
|
|
||||||
|
function requestOrientationPermission() {
|
||||||
|
/*
|
||||||
|
// Currently this fails, since it needs secure context on IOS safari
|
||||||
|
if (typeof DeviceMotionEvent.requestPermission === "function") {
|
||||||
|
DeviceOrientationEvent.requestPermission()
|
||||||
|
.then(response => {
|
||||||
|
if (response == "granted") {
|
||||||
|
window.addEventListener("deviceorientation", handleOrientation);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
} else {
|
||||||
|
// Non IOS 13
|
||||||
|
window.addEventListener("deviceorientation", handleOrientation);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
function handleOrientation(event) {
|
||||||
|
var x = event.beta; // In degree in the range [-180,180]
|
||||||
|
var y = event.gamma; // In degree in the range [-90,90]
|
||||||
|
|
||||||
|
var output = document.querySelector(".output");
|
||||||
|
output.innerHTML = "beta : " + x + "\n";
|
||||||
|
output.innerHTML += "gamma: " + y + "\n";
|
||||||
|
|
||||||
|
// Because we don't want to have the device upside down
|
||||||
|
// We constrain the x value to the range [-90,90]
|
||||||
|
if (x > 90) {
|
||||||
|
x = 90;
|
||||||
|
}
|
||||||
|
if (x < -90) {
|
||||||
|
x = -90;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To make computation easier we shift the range of
|
||||||
|
// x and y to [0,180]
|
||||||
|
x += 90;
|
||||||
|
y += 90;
|
||||||
|
|
||||||
|
// 10 is half the size of the ball
|
||||||
|
// It center the positioning point to the center of the ball
|
||||||
|
var ball = document.querySelector(".ball");
|
||||||
|
var garden = document.querySelector(".garden");
|
||||||
|
var maxX = garden.clientWidth - ball.clientWidth;
|
||||||
|
var maxY = garden.clientHeight - ball.clientHeight;
|
||||||
|
ball.style.top = (maxY * y) / 180 - 10 + "px";
|
||||||
|
ball.style.left = (maxX * x) / 180 - 10 + "px";
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function saveGraphData() {
|
||||||
|
localStorage.setItem("espuigraphs", JSON.stringify(graphData));
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreGraphData(id) {
|
||||||
|
var savedData = localStorage.getItem("espuigraphs", graphData);
|
||||||
|
if (savedData != null) {
|
||||||
|
savedData = JSON.parse(savedData);
|
||||||
|
return savedData[id];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function restart() {
|
||||||
|
$(document).add("*").off();
|
||||||
|
$("#row").html("");
|
||||||
|
websock.close();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function conStatusError() {
|
||||||
|
websockConnected = false;
|
||||||
|
$("#conStatus").removeClass("color-green");
|
||||||
|
$("#conStatus").addClass("color-red");
|
||||||
|
$("#conStatus").html("Error / No Connection ↻");
|
||||||
|
$("#conStatus").off();
|
||||||
|
$("#conStatus").on({
|
||||||
|
click: restart,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVisibilityChange() {
|
||||||
|
if (!websockConnected && !document.hidden) {
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
document.addEventListener("visibilitychange", handleVisibilityChange, false);
|
||||||
|
if (
|
||||||
|
window.location.port != "" ||
|
||||||
|
window.location.port != 80 ||
|
||||||
|
window.location.port != 443
|
||||||
|
) {
|
||||||
|
websock = new WebSocket(
|
||||||
|
"ws://" + window.location.hostname + ":" + window.location.port + "/ws"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
websock = new WebSocket("ws://" + window.location.hostname + "/ws");
|
||||||
|
}
|
||||||
|
websock.onopen = function (evt) {
|
||||||
|
console.log("websock open");
|
||||||
|
$("#conStatus").addClass("color-green");
|
||||||
|
$("#conStatus").text("Connected");
|
||||||
|
websockConnected = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
websock.onclose = function (evt) {
|
||||||
|
console.log("websock close");
|
||||||
|
conStatusError();
|
||||||
|
};
|
||||||
|
|
||||||
|
websock.onerror = function (evt) {
|
||||||
|
console.log(evt);
|
||||||
|
conStatusError();
|
||||||
|
};
|
||||||
|
|
||||||
|
var handleEvent = function (evt) {
|
||||||
|
console.log(evt);
|
||||||
|
var data = JSON.parse(evt.data);
|
||||||
|
var e = document.body;
|
||||||
|
var center = "";
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case UI_INITIAL_GUI:
|
||||||
|
// Clear current elements
|
||||||
|
$("#row").html("");
|
||||||
|
$("#tabsnav").html("");
|
||||||
|
$("#tabscontent").html("");
|
||||||
|
|
||||||
|
if (data.sliderContinuous) {
|
||||||
|
sliderContinuous = data.sliderContinuous;
|
||||||
|
}
|
||||||
|
data.controls.forEach(element => {
|
||||||
|
var fauxEvent = {
|
||||||
|
data: JSON.stringify(element),
|
||||||
|
};
|
||||||
|
handleEvent(fauxEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
//If there are more elements in the complete UI, then request them
|
||||||
|
//Note: we subtract 1 from data.controls.length because the controls always
|
||||||
|
//includes the title element
|
||||||
|
if(data.totalcontrols > (data.controls.length - 1)) {
|
||||||
|
websock.send("uiok:" + (data.controls.length - 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_EXTEND_GUI:
|
||||||
|
data.controls.forEach(element => {
|
||||||
|
var fauxEvent = {
|
||||||
|
data: JSON.stringify(element),
|
||||||
|
};
|
||||||
|
handleEvent(fauxEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Do we need to keep requesting more UI elements?
|
||||||
|
if(data.totalcontrols > data.startindex + (data.controls.length - 1)) {
|
||||||
|
websock.send("uiok:" + (data.startindex + (data.controls.length - 1)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_RELOAD:
|
||||||
|
window.location.reload();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_TITEL:
|
||||||
|
document.title = data.label;
|
||||||
|
$("#mainHeader").html(data.label);
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Most elements have the same behaviour when added.
|
||||||
|
*/
|
||||||
|
case UI_LABEL:
|
||||||
|
case UI_NUMBER:
|
||||||
|
case UI_TEXT_INPUT:
|
||||||
|
case UI_SELECT:
|
||||||
|
case UI_GAUGE:
|
||||||
|
case UI_SEPARATOR:
|
||||||
|
if (data.visible) addToHTML(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
These elements must call additional functions after being added to the DOM
|
||||||
|
*/
|
||||||
|
case UI_BUTTON:
|
||||||
|
if (data.visible) {
|
||||||
|
addToHTML(data);
|
||||||
|
$("#btn" + data.id).on({
|
||||||
|
touchstart: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
buttonclick(data.id, true);
|
||||||
|
},
|
||||||
|
touchend: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
buttonclick(data.id, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_SWITCHER:
|
||||||
|
if (data.visible) {
|
||||||
|
addToHTML(data);
|
||||||
|
switcher(data.id, data.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_CPAD:
|
||||||
|
case UI_PAD:
|
||||||
|
if (data.visible) {
|
||||||
|
addToHTML(data);
|
||||||
|
$("#pf" + data.id).on({
|
||||||
|
touchstart: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(UP, data.id, true);
|
||||||
|
},
|
||||||
|
touchend: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(UP, data.id, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
$("#pl" + data.id).on({
|
||||||
|
touchstart: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(LEFT, data.id, true);
|
||||||
|
},
|
||||||
|
touchend: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(LEFT, data.id, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
$("#pr" + data.id).on({
|
||||||
|
touchstart: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(RIGHT, data.id, true);
|
||||||
|
},
|
||||||
|
touchend: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(RIGHT, data.id, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
$("#pb" + data.id).on({
|
||||||
|
touchstart: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(DOWN, data.id, true);
|
||||||
|
},
|
||||||
|
touchend: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(DOWN, data.id, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
$("#pc" + data.id).on({
|
||||||
|
touchstart: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(CENTER, data.id, true);
|
||||||
|
},
|
||||||
|
touchend: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
padclick(CENTER, data.id, false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_SLIDER:
|
||||||
|
//https://codepen.io/seanstopnik/pen/CeLqA
|
||||||
|
if (data.visible) {
|
||||||
|
addToHTML(data);
|
||||||
|
rangeSlider(!sliderContinuous);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_TAB:
|
||||||
|
if (data.visible) {
|
||||||
|
$("#tabsnav").append(
|
||||||
|
"<li><a onmouseup='tabclick(" + data.id + ")' href='#tab" + data.id + "'>" + data.value + "</a></li>"
|
||||||
|
);
|
||||||
|
$("#tabscontent").append("<div id='tab" + data.id + "'></div>");
|
||||||
|
|
||||||
|
tabs = $(".tabscontent").tabbedContent({ loop: true }).data("api");
|
||||||
|
// switch to tab...
|
||||||
|
$("a")
|
||||||
|
.filter(function () {
|
||||||
|
return $(this).attr("href") === "#click-to-switch";
|
||||||
|
})
|
||||||
|
.on("click", function (e) {
|
||||||
|
var tab = prompt("Tab to switch to (number or id)?");
|
||||||
|
if (!tabs.switchTab(tab)) {
|
||||||
|
alert("That tab does not exist :\\");
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_OPTION:
|
||||||
|
if (data.parentControl) {
|
||||||
|
var parent = $("#select" + data.parentControl);
|
||||||
|
parent.append(
|
||||||
|
"<option id='option" +
|
||||||
|
data.id +
|
||||||
|
"' value='" +
|
||||||
|
data.value +
|
||||||
|
"' " +
|
||||||
|
data.selected +
|
||||||
|
">" +
|
||||||
|
data.label +
|
||||||
|
"</option>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_MIN:
|
||||||
|
if (data.parentControl) {
|
||||||
|
//Is it applied to a slider?
|
||||||
|
if($('#sl' + data.parentControl).length) {
|
||||||
|
$('#sl' + data.parentControl).attr("min", data.value);
|
||||||
|
} else if($('#num' + data.parentControl).length) {
|
||||||
|
//Or a number
|
||||||
|
$('#num' + data.parentControl).attr("min", data.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_MAX:
|
||||||
|
if (data.parentControl) {
|
||||||
|
//Is it applied to a slider?
|
||||||
|
if($('#sl' + data.parentControl).length) {
|
||||||
|
$('#sl' + data.parentControl).attr("max", data.value);
|
||||||
|
} else if($('#text' + data.parentControl).length) {
|
||||||
|
//Is it a text element
|
||||||
|
$('#text' + data.parentControl).attr("maxlength", data.value);
|
||||||
|
} else if($('#num' + data.parentControl).length) {
|
||||||
|
//Or a number
|
||||||
|
$('#num' + data.parentControl).attr("max", data.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_STEP:
|
||||||
|
if (data.parentControl) {
|
||||||
|
var parent = $("#id" + data.parentControl + " input");
|
||||||
|
if (parent.size()) {
|
||||||
|
parent.attr("step", data.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_GRAPH:
|
||||||
|
if (data.visible) {
|
||||||
|
addToHTML(data);
|
||||||
|
graphData[data.id] = restoreGraphData(data.id);
|
||||||
|
renderGraphSvg(graphData[data.id], "graph" + data.id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ADD_GRAPH_POINT:
|
||||||
|
var ts = Math.round(new Date().getTime() / 1000);
|
||||||
|
graphData[data.id].push({ x: ts, y: data.value });
|
||||||
|
saveGraphData();
|
||||||
|
renderGraphSvg(graphData[data.id], "graph" + data.id);
|
||||||
|
break;
|
||||||
|
case CLEAR_GRAPH:
|
||||||
|
graphData[data.id] = [];
|
||||||
|
saveGraphData();
|
||||||
|
renderGraphSvg(graphData[data.id], "graph" + data.id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_ACCEL:
|
||||||
|
if (hasAccel) break;
|
||||||
|
hasAccel = true;
|
||||||
|
if (data.visible) {
|
||||||
|
addToHTML(data);
|
||||||
|
requestOrientationPermission();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update messages change the value/style of a component without adding new HTML
|
||||||
|
*/
|
||||||
|
case UPDATE_LABEL:
|
||||||
|
$("#l" + data.id).html(data.value);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#l" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_SWITCHER:
|
||||||
|
switcher(data.id, data.value == "0" ? 0 : 1);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#sl" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_SLIDER:
|
||||||
|
$("#sl" + data.id).attr("value", data.value)
|
||||||
|
slider_move($("#id" + data.id), data.value, "100", false);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#sl" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_NUMBER:
|
||||||
|
$("#num" + data.id).val(data.value);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#num" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_TEXT_INPUT:
|
||||||
|
$("#text" + data.id).val(data.value);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#text" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
if(data.hasOwnProperty('inputType')) {
|
||||||
|
$("#text" + data.id).attr("type", data.inputType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_SELECT:
|
||||||
|
$("#select" + data.id).val(data.value);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#select" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_BUTTON:
|
||||||
|
$("#btn" + data.id).val(data.value);
|
||||||
|
$("#btn" + data.id).text(data.value);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#btn" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_PAD:
|
||||||
|
case UPDATE_CPAD:
|
||||||
|
break;
|
||||||
|
case UPDATE_GAUGE:
|
||||||
|
$("#gauge" + data.id).val(data.value);
|
||||||
|
if(data.hasOwnProperty('elementStyle')) {
|
||||||
|
$("#gauge" + data.id).attr("style", data.elementStyle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UPDATE_ACCEL:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_TIME:
|
||||||
|
var rv = new Date().toISOString();
|
||||||
|
websock.send("time:" + rv + ":" + data.id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error("Unknown type or event");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type >= UI_TITEL && data.type < UPDATE_OFFSET) {
|
||||||
|
//A UI element was just added to the DOM
|
||||||
|
processEnabled(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type >= UPDATE_OFFSET && data.type < UI_INITIAL_GUI) {
|
||||||
|
//An "update" message was just recieved and processed
|
||||||
|
var element = $("#id" + data.id);
|
||||||
|
|
||||||
|
if(data.hasOwnProperty('panelStyle')) {
|
||||||
|
$("#id" + data.id).attr("style", data.panelStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.hasOwnProperty('visible')) {
|
||||||
|
if(data['visible'])
|
||||||
|
$("#id" + data.id).show();
|
||||||
|
else
|
||||||
|
$("#id" + data.id).hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type == UPDATE_SLIDER) {
|
||||||
|
element.removeClass(
|
||||||
|
"slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin"
|
||||||
|
);
|
||||||
|
element.addClass("slider-" + colorClass(data.color));
|
||||||
|
} else {
|
||||||
|
element.removeClass(
|
||||||
|
"turquoise emerald peterriver wetasphalt sunflower carrot alizarin"
|
||||||
|
);
|
||||||
|
element.addClass(colorClass(data.color));
|
||||||
|
}
|
||||||
|
|
||||||
|
processEnabled(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".range-slider__range").each(function(){
|
||||||
|
$(this)[0].value = $(this).attr("value");
|
||||||
|
$(this).next().html($(this).attr("value"));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
websock.onmessage = handleEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sliderchange(number) {
|
||||||
|
var val = $("#sl" + number).val();
|
||||||
|
websock.send("slvalue:" + val + ":" + number);
|
||||||
|
|
||||||
|
$(".range-slider__range").each(function(){
|
||||||
|
$(this).attr("value", $(this)[0].value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberchange(number) {
|
||||||
|
var val = $("#num" + number).val();
|
||||||
|
websock.send("nvalue:" + val + ":" + number);
|
||||||
|
}
|
||||||
|
|
||||||
|
function textchange(number) {
|
||||||
|
var val = $("#text" + number).val();
|
||||||
|
websock.send("tvalue:" + val + ":" + number);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tabclick(number) {
|
||||||
|
var val = $("#tab" + number).val();
|
||||||
|
websock.send("tabvalue:" + val + ":" + number);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectchange(number) {
|
||||||
|
var val = $("#select" + number).val();
|
||||||
|
websock.send("svalue:" + val + ":" + number);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonclick(number, isdown) {
|
||||||
|
if (isdown) websock.send("bdown:" + number);
|
||||||
|
else websock.send("bup:" + number);
|
||||||
|
}
|
||||||
|
|
||||||
|
function padclick(type, number, isdown) {
|
||||||
|
if($("#id" + number + " nav").hasClass("disabled")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case CENTER:
|
||||||
|
if (isdown) websock.send("pcdown:" + number);
|
||||||
|
else websock.send("pcup:" + number);
|
||||||
|
break;
|
||||||
|
case UP:
|
||||||
|
if (isdown) websock.send("pfdown:" + number);
|
||||||
|
else websock.send("pfup:" + number);
|
||||||
|
break;
|
||||||
|
case DOWN:
|
||||||
|
if (isdown) websock.send("pbdown:" + number);
|
||||||
|
else websock.send("pbup:" + number);
|
||||||
|
break;
|
||||||
|
case LEFT:
|
||||||
|
if (isdown) websock.send("pldown:" + number);
|
||||||
|
else websock.send("plup:" + number);
|
||||||
|
break;
|
||||||
|
case RIGHT:
|
||||||
|
if (isdown) websock.send("prdown:" + number);
|
||||||
|
else websock.send("prup:" + number);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switcher(number, state) {
|
||||||
|
if (state == null) {
|
||||||
|
if (!$("#sl" + number).hasClass("checked")) {
|
||||||
|
websock.send("sactive:" + number);
|
||||||
|
$("#sl" + number).addClass("checked");
|
||||||
|
} else {
|
||||||
|
websock.send("sinactive:" + number);
|
||||||
|
$("#sl" + number).removeClass("checked");
|
||||||
|
}
|
||||||
|
} else if (state == 1) {
|
||||||
|
$("#sl" + number).addClass("checked");
|
||||||
|
$("#sl" + number).prop("checked", true);
|
||||||
|
} else if (state == 0) {
|
||||||
|
$("#sl" + number).removeClass("checked");
|
||||||
|
$("#sl" + number).prop("checked", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rangeSlider = function (isDiscrete) {
|
||||||
|
var range = $(".range-slider__range");
|
||||||
|
var slidercb = function() {
|
||||||
|
sliderchange($(this).attr("id").replace(/^\D+/g, ""));
|
||||||
|
};
|
||||||
|
|
||||||
|
range.on({input: function() {
|
||||||
|
$(this).next().html(this.value)}
|
||||||
|
});
|
||||||
|
|
||||||
|
range.each(function() {
|
||||||
|
$(this).next().html(this.value);
|
||||||
|
if($(this).attr("callbackSet") != "true") {
|
||||||
|
if (!isDiscrete) {
|
||||||
|
$(this).on({input: slidercb}); //input fires when dragging
|
||||||
|
} else {
|
||||||
|
$(this).on({change: slidercb}); //change fires only once released
|
||||||
|
}
|
||||||
|
$(this).attr("callbackSet", "true");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var addToHTML = function(data) {
|
||||||
|
panelStyle = data.hasOwnProperty('panelStyle') ? " style='" + data.panelStyle + "' " : "";
|
||||||
|
panelwide = data.hasOwnProperty('wide') ? "wide" : "";
|
||||||
|
|
||||||
|
if(!data.hasOwnProperty('parentControl') || $("#tab" + data.parentControl).length > 0) {
|
||||||
|
//We add the control with its own panel
|
||||||
|
var parent = data.hasOwnProperty('parentControl') ?
|
||||||
|
$("#tab" + data.parentControl) :
|
||||||
|
$("#row");
|
||||||
|
|
||||||
|
var html = "";
|
||||||
|
switch(data.type) {
|
||||||
|
case UI_LABEL:
|
||||||
|
case UI_BUTTON:
|
||||||
|
case UI_SWITCHER:
|
||||||
|
case UI_CPAD:
|
||||||
|
case UI_PAD:
|
||||||
|
case UI_SLIDER:
|
||||||
|
case UI_NUMBER:
|
||||||
|
case UI_TEXT_INPUT:
|
||||||
|
case UI_SELECT:
|
||||||
|
case UI_GRAPH:
|
||||||
|
case UI_GAUGE:
|
||||||
|
case UI_ACCEL:
|
||||||
|
html = "<div id='id" + data.id + "' " + panelStyle + " class='two columns " + panelwide + " card tcenter " +
|
||||||
|
colorClass(data.color) + "'><h5>" + data.label + "</h5><hr/>" +
|
||||||
|
elementHTML(data) +
|
||||||
|
"</div>";
|
||||||
|
break;
|
||||||
|
case UI_SEPARATOR:
|
||||||
|
html = "<div id='id" + data.id + "' " + panelStyle + " class='sectionbreak columns'>" +
|
||||||
|
"<h5>" + data.label + "</h5><hr/></div>";
|
||||||
|
break;
|
||||||
|
case UI_TIME:
|
||||||
|
//Invisible element
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.append(html);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//We are adding to an existing panel so we only need the HTML for the element
|
||||||
|
var parent = $("#id" + data.parentControl);
|
||||||
|
parent.append(elementHTML(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var elementHTML = function(data) {
|
||||||
|
var id = data.id
|
||||||
|
var elementStyle = data.hasOwnProperty('elementStyle') ? " style='" + data.elementStyle + "' " : "";
|
||||||
|
var inputType = data.hasOwnProperty('inputType') ? " type='" + data.inputType + "' " : "";
|
||||||
|
switch(data.type) {
|
||||||
|
case UI_LABEL:
|
||||||
|
return "<span id='l" + id + "' " + elementStyle +
|
||||||
|
" class='label label-wrap'>" + data.value + "</span>";
|
||||||
|
case UI_BUTTON:
|
||||||
|
return "<button id='btn" + id + "' " + elementStyle +
|
||||||
|
" onmousedown='buttonclick(" + id + ", true)'" +
|
||||||
|
" onmouseup='buttonclick(" + id + ", false)'>" +
|
||||||
|
data.value + "</button>";
|
||||||
|
case UI_SWITCHER:
|
||||||
|
return "<label id='sl" + id + "' " + elementStyle +
|
||||||
|
" class='switch " + (data.value == "1" ? "checked" : "") +
|
||||||
|
(data.hasOwnProperty('vertical') ? " vert-switcher " : "") +
|
||||||
|
"'>" +
|
||||||
|
"<div class='in'>" +
|
||||||
|
"<input type='checkbox' id='s" + id + "' onClick='switcher(" + id + ",null)' " +
|
||||||
|
(data.value == "1" ? "checked" : "") + "/></div></label>";
|
||||||
|
case UI_CPAD:
|
||||||
|
case UI_PAD:
|
||||||
|
return "<nav class='control'><ul>" +
|
||||||
|
"<li><a onmousedown='padclick(UP, " + id + ", true)' " +
|
||||||
|
"onmouseup='padclick(UP, " + id + ", false)' id='pf" + id + "'>▲</a></li>" +
|
||||||
|
"<li><a onmousedown='padclick(RIGHT, " + id + ", true)' " +
|
||||||
|
"onmouseup='padclick(RIGHT, " + id + ", false)' id='pr" + id + "'>▲</a></li>" +
|
||||||
|
"<li><a onmousedown='padclick(LEFT, " + id + ", true)' " +
|
||||||
|
"onmouseup='padclick(LEFT, " + id + ", false)' id='pl" + id + "'>▲</a></li>" +
|
||||||
|
"<li><a onmousedown='padclick(DOWN, " + id + ", true)' " +
|
||||||
|
"onmouseup='padclick(DOWN, " + id + ", false)' id='pb" + id + "'>▲</a></li>" +
|
||||||
|
"</ul>" +
|
||||||
|
(data.type == UI_CPAD
|
||||||
|
? "<a class='confirm' onmousedown='padclick(CENTER," + id + ", true)' " +
|
||||||
|
"onmouseup='padclick(CENTER, " + id + ", false)' id='pc" + id + "'>OK</a>"
|
||||||
|
: "") +
|
||||||
|
"</nav>";
|
||||||
|
case UI_SLIDER:
|
||||||
|
return "<div class='range-slider " +
|
||||||
|
(data.hasOwnProperty('vertical') ? " vert-slider " : "") +
|
||||||
|
"'>" +
|
||||||
|
"<input id='sl" + id + "' type='range' min='0' max='100' value='" + data.value + "' " +
|
||||||
|
elementStyle + " class='range-slider__range'><span class='range-slider__value'>" +
|
||||||
|
data.value + "</span></div>";
|
||||||
|
case UI_NUMBER:
|
||||||
|
return "<input style='color:black;' " + elementStyle + " id='num" + id +
|
||||||
|
"' type='number' value='" + data.value + "' onchange='numberchange(" + id + ")' />";
|
||||||
|
case UI_TEXT_INPUT:
|
||||||
|
return "<input " + inputType + "style='color:black;' " + elementStyle + " id='text" + id +
|
||||||
|
"' value='" + data.value + "' onchange='textchange(" + id + ")' />";
|
||||||
|
case UI_SELECT:
|
||||||
|
return "<select style='color:black;' " + elementStyle + " id='select" + id +
|
||||||
|
"' onchange='selectchange(" + id + ")' />";
|
||||||
|
case UI_GRAPH:
|
||||||
|
return "<figure id='graph" + id + "'><figcaption>" + data.label + "</figcaption></figure>";
|
||||||
|
case UI_GAUGE:
|
||||||
|
return "WILL BE A GAUGE <input style='color:black;' id='gauge" + id +
|
||||||
|
"' type='number' value='" + data.value + "' onchange='numberchange(" + id + ")' />";
|
||||||
|
case UI_ACCEL:
|
||||||
|
return "ACCEL // Not implemented fully!<div class='accelerometer' id='accel" + id +
|
||||||
|
"' ><div class='ball" + id + "'></div><pre class='accelerometeroutput" + id + "'></pre>";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var processEnabled = function(data) {
|
||||||
|
//Handle the enabling and disabling of controls
|
||||||
|
//Most controls can be disabled through the use of $("#<item>").prop("disabled", true) and CSS will style it accordingly
|
||||||
|
//The switcher and pads also require the addition of the "disabled" class
|
||||||
|
switch(data.type) {
|
||||||
|
case UI_SWITCHER:
|
||||||
|
case UPDATE_SWITCHER:
|
||||||
|
if(data.enabled) {
|
||||||
|
$("#sl" + data.id).removeClass('disabled');
|
||||||
|
$("#s" + data.id).prop("disabled", false);
|
||||||
|
} else {
|
||||||
|
$("#sl" + data.id).addClass('disabled');
|
||||||
|
$("#s" + data.id).prop("disabled", true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_SLIDER:
|
||||||
|
case UPDATE_SLIDER:
|
||||||
|
$("#sl" + data.id).prop("disabled", !data.enabled);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_NUMBER:
|
||||||
|
case UPDATE_NUMBER:
|
||||||
|
$("#num" + data.id).prop("disabled", !data.enabled);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_TEXT_INPUT:
|
||||||
|
case UPDATE_TEXT_INPUT:
|
||||||
|
$("#text" + data.id).prop("disabled", !data.enabled);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_SELECT:
|
||||||
|
case UPDATE_SELECT:
|
||||||
|
$("#select" + data.id).prop("disabled", !data.enabled);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_BUTTON:
|
||||||
|
case UPDATE_BUTTON:
|
||||||
|
$("#btn" + data.id).prop("disabled", !data.enabled);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UI_PAD:
|
||||||
|
case UI_CPAD:
|
||||||
|
case UPDATE_PAD:
|
||||||
|
case UPDATE_CPAD:
|
||||||
|
if(data.enabled) {
|
||||||
|
$("#id" + data.id + " nav").removeClass('disabled');
|
||||||
|
} else {
|
||||||
|
$("#id" + data.id + " nav").addClass('disabled');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
104
data/js/controls.min.js
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
const UI_INITIAL_GUI=200;const UI_RELOAD=201;const UPDATE_OFFSET=100;const UI_EXTEND_GUI=210;const UI_TITEL=0;const UI_PAD=1;const UPDATE_PAD=101;const UI_CPAD=2;const UPDATE_CPAD=102;const UI_BUTTON=3;const UPDATE_BUTTON=103;const UI_LABEL=4;const UPDATE_LABEL=104;const UI_SWITCHER=5;const UPDATE_SWITCHER=105;const UI_SLIDER=6;const UPDATE_SLIDER=106;const UI_NUMBER=7;const UPDATE_NUMBER=107;const UI_TEXT_INPUT=8;const UPDATE_TEXT_INPUT=108;const UI_GRAPH=9;const ADD_GRAPH_POINT=10;const CLEAR_GRAPH=109;const UI_TAB=11;const UPDATE_TAB=111;const UI_SELECT=12;const UPDATE_SELECT=112;const UI_OPTION=13;const UPDATE_OPTION=113;const UI_MIN=14;const UPDATE_MIN=114;const UI_MAX=15;const UPDATE_MAX=115;const UI_STEP=16;const UPDATE_STEP=116;const UI_GAUGE=17;const UPDATE_GAUGE=117;const UI_ACCEL=18;const UPDATE_ACCEL=118;const UI_SEPARATOR=19;const UPDATE_SEPARATOR=119;const UI_TIME=20;const UPDATE_TIME=120;const UP=0;const DOWN=1;const LEFT=2;const RIGHT=3;const CENTER=4;const C_TURQUOISE=0;const C_EMERALD=1;const C_PETERRIVER=2;const C_WETASPHALT=3;const C_SUNFLOWER=4;const C_CARROT=5;const C_ALIZARIN=6;const C_DARK=7;const C_NONE=255;var graphData=new Array();var hasAccel=false;var sliderContinuous=false;function colorClass(colorId){colorId=Number(colorId);switch(colorId){case C_TURQUOISE:return"turquoise";case C_EMERALD:return"emerald";case C_PETERRIVER:return"peterriver";case C_WETASPHALT:return"wetasphalt";case C_SUNFLOWER:return"sunflower";case C_CARROT:return"carrot";case C_ALIZARIN:return"alizarin";case C_DARK:case C_NONE:return"dark";default:return"";}}
|
||||||
|
var websock;var websockConnected=false;function requestOrientationPermission(){}
|
||||||
|
function saveGraphData(){localStorage.setItem("espuigraphs",JSON.stringify(graphData));}
|
||||||
|
function restoreGraphData(id){var savedData=localStorage.getItem("espuigraphs",graphData);if(savedData!=null){savedData=JSON.parse(savedData);return savedData[id];}
|
||||||
|
return[];}
|
||||||
|
function restart(){$(document).add("*").off();$("#row").html("");websock.close();start();}
|
||||||
|
function conStatusError(){websockConnected=false;$("#conStatus").removeClass("color-green");$("#conStatus").addClass("color-red");$("#conStatus").html("Error / No Connection ↻");$("#conStatus").off();$("#conStatus").on({click:restart,});}
|
||||||
|
function handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
|
||||||
|
function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);if(window.location.port!=""||window.location.port!=80||window.location.port!=443){websock=new WebSocket("ws://"+window.location.hostname+":"+window.location.port+"/ws");}else{websock=new WebSocket("ws://"+window.location.hostname+"/ws");}
|
||||||
|
websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log(evt);conStatusError();};var handleEvent=function(evt){console.log(evt);var data=JSON.parse(evt.data);var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:$("#row").html("");$("#tabsnav").html("");$("#tabscontent").html("");if(data.sliderContinuous){sliderContinuous=data.sliderContinuous;}
|
||||||
|
data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>(data.controls.length-1)){websock.send("uiok:"+(data.controls.length-1));}
|
||||||
|
break;case UI_EXTEND_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element),};handleEvent(fauxEvent);});if(data.totalcontrols>data.startindex+(data.controls.length-1)){websock.send("uiok:"+(data.startindex+(data.controls.length-1)));}
|
||||||
|
break;case UI_RELOAD:window.location.reload();break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GAUGE:case UI_SEPARATOR:if(data.visible)addToHTML(data);break;case UI_BUTTON:if(data.visible){addToHTML(data);$("#btn"+data.id).on({touchstart:function(e){e.preventDefault();buttonclick(data.id,true);},touchend:function(e){e.preventDefault();buttonclick(data.id,false);},});}
|
||||||
|
break;case UI_SWITCHER:if(data.visible){addToHTML(data);switcher(data.id,data.value);}
|
||||||
|
break;case UI_CPAD:case UI_PAD:if(data.visible){addToHTML(data);$("#pf"+data.id).on({touchstart:function(e){e.preventDefault();padclick(UP,data.id,true);},touchend:function(e){e.preventDefault();padclick(UP,data.id,false);},});$("#pl"+data.id).on({touchstart:function(e){e.preventDefault();padclick(LEFT,data.id,true);},touchend:function(e){e.preventDefault();padclick(LEFT,data.id,false);},});$("#pr"+data.id).on({touchstart:function(e){e.preventDefault();padclick(RIGHT,data.id,true);},touchend:function(e){e.preventDefault();padclick(RIGHT,data.id,false);},});$("#pb"+data.id).on({touchstart:function(e){e.preventDefault();padclick(DOWN,data.id,true);},touchend:function(e){e.preventDefault();padclick(DOWN,data.id,false);},});$("#pc"+data.id).on({touchstart:function(e){e.preventDefault();padclick(CENTER,data.id,true);},touchend:function(e){e.preventDefault();padclick(CENTER,data.id,false);},});}
|
||||||
|
break;case UI_SLIDER:if(data.visible){addToHTML(data);rangeSlider(!sliderContinuous);}
|
||||||
|
break;case UI_TAB:if(data.visible){$("#tabsnav").append("<li><a onmouseup='tabclick("+data.id+")' href='#tab"+data.id+"'>"+data.value+"</a></li>");$("#tabscontent").append("<div id='tab"+data.id+"'></div>");tabs=$(".tabscontent").tabbedContent({loop:true}).data("api");$("a").filter(function(){return $(this).attr("href")==="#click-to-switch";}).on("click",function(e){var tab=prompt("Tab to switch to (number or id)?");if(!tabs.switchTab(tab)){alert("That tab does not exist :\\");}
|
||||||
|
e.preventDefault();});}
|
||||||
|
break;case UI_OPTION:if(data.parentControl){var parent=$("#select"+data.parentControl);parent.append("<option id='option"+
|
||||||
|
data.id+
|
||||||
|
"' value='"+
|
||||||
|
data.value+
|
||||||
|
"' "+
|
||||||
|
data.selected+
|
||||||
|
">"+
|
||||||
|
data.label+
|
||||||
|
"</option>");}
|
||||||
|
break;case UI_MIN:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("min",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("min",data.value);}}
|
||||||
|
break;case UI_MAX:if(data.parentControl){if($('#sl'+data.parentControl).length){$('#sl'+data.parentControl).attr("max",data.value);}else if($('#text'+data.parentControl).length){$('#text'+data.parentControl).attr("maxlength",data.value);}else if($('#num'+data.parentControl).length){$('#num'+data.parentControl).attr("max",data.value);}}
|
||||||
|
break;case UI_STEP:if(data.parentControl){var parent=$("#id"+data.parentControl+" input");if(parent.size()){parent.attr("step",data.value);}}
|
||||||
|
break;case UI_GRAPH:if(data.visible){addToHTML(data);graphData[data.id]=restoreGraphData(data.id);renderGraphSvg(graphData[data.id],"graph"+data.id);}
|
||||||
|
break;case ADD_GRAPH_POINT:var ts=Math.round(new Date().getTime()/1000);graphData[data.id].push({x:ts,y:data.value});saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case CLEAR_GRAPH:graphData[data.id]=[];saveGraphData();renderGraphSvg(graphData[data.id],"graph"+data.id);break;case UI_ACCEL:if(hasAccel)break;hasAccel=true;if(data.visible){addToHTML(data);requestOrientationPermission();}
|
||||||
|
break;case UPDATE_LABEL:$("#l"+data.id).html(data.value);if(data.hasOwnProperty('elementStyle')){$("#l"+data.id).attr("style",data.elementStyle);}
|
||||||
|
break;case UPDATE_SWITCHER:switcher(data.id,data.value=="0"?0:1);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
|
||||||
|
break;case UPDATE_SLIDER:$("#sl"+data.id).attr("value",data.value)
|
||||||
|
slider_move($("#id"+data.id),data.value,"100",false);if(data.hasOwnProperty('elementStyle')){$("#sl"+data.id).attr("style",data.elementStyle);}
|
||||||
|
break;case UPDATE_NUMBER:$("#num"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#num"+data.id).attr("style",data.elementStyle);}
|
||||||
|
break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#text"+data.id).attr("style",data.elementStyle);}
|
||||||
|
if(data.hasOwnProperty('inputType')){$("#text"+data.id).attr("type",data.inputType);}
|
||||||
|
break;case UPDATE_SELECT:$("#select"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#select"+data.id).attr("style",data.elementStyle);}
|
||||||
|
break;case UPDATE_BUTTON:$("#btn"+data.id).val(data.value);$("#btn"+data.id).text(data.value);if(data.hasOwnProperty('elementStyle')){$("#btn"+data.id).attr("style",data.elementStyle);}
|
||||||
|
break;case UPDATE_PAD:case UPDATE_CPAD:break;case UPDATE_GAUGE:$("#gauge"+data.id).val(data.value);if(data.hasOwnProperty('elementStyle')){$("#gauge"+data.id).attr("style",data.elementStyle);}
|
||||||
|
break;case UPDATE_ACCEL:break;case UPDATE_TIME:var rv=new Date().toISOString();websock.send("time:"+rv+":"+data.id);break;default:console.error("Unknown type or event");break;}
|
||||||
|
if(data.type>=UI_TITEL&&data.type<UPDATE_OFFSET){processEnabled(data);}
|
||||||
|
if(data.type>=UPDATE_OFFSET&&data.type<UI_INITIAL_GUI){var element=$("#id"+data.id);if(data.hasOwnProperty('panelStyle')){$("#id"+data.id).attr("style",data.panelStyle);}
|
||||||
|
if(data.hasOwnProperty('visible')){if(data['visible'])
|
||||||
|
$("#id"+data.id).show();else
|
||||||
|
$("#id"+data.id).hide();}
|
||||||
|
if(data.type==UPDATE_SLIDER){element.removeClass("slider-turquoise slider-emerald slider-peterriver slider-wetasphalt slider-sunflower slider-carrot slider-alizarin");element.addClass("slider-"+colorClass(data.color));}else{element.removeClass("turquoise emerald peterriver wetasphalt sunflower carrot alizarin");element.addClass(colorClass(data.color));}
|
||||||
|
processEnabled(data);}
|
||||||
|
$(".range-slider__range").each(function(){$(this)[0].value=$(this).attr("value");$(this).next().html($(this).attr("value"));});};websock.onmessage=handleEvent;}
|
||||||
|
function sliderchange(number){var val=$("#sl"+number).val();websock.send("slvalue:"+val+":"+number);$(".range-slider__range").each(function(){$(this).attr("value",$(this)[0].value);});}
|
||||||
|
function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);}
|
||||||
|
function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);}
|
||||||
|
function tabclick(number){var val=$("#tab"+number).val();websock.send("tabvalue:"+val+":"+number);}
|
||||||
|
function selectchange(number){var val=$("#select"+number).val();websock.send("svalue:"+val+":"+number);}
|
||||||
|
function buttonclick(number,isdown){if(isdown)websock.send("bdown:"+number);else websock.send("bup:"+number);}
|
||||||
|
function padclick(type,number,isdown){if($("#id"+number+" nav").hasClass("disabled")){return;}
|
||||||
|
switch(type){case CENTER:if(isdown)websock.send("pcdown:"+number);else websock.send("pcup:"+number);break;case UP:if(isdown)websock.send("pfdown:"+number);else websock.send("pfup:"+number);break;case DOWN:if(isdown)websock.send("pbdown:"+number);else websock.send("pbup:"+number);break;case LEFT:if(isdown)websock.send("pldown:"+number);else websock.send("plup:"+number);break;case RIGHT:if(isdown)websock.send("prdown:"+number);else websock.send("prup:"+number);break;}}
|
||||||
|
function switcher(number,state){if(state==null){if(!$("#sl"+number).hasClass("checked")){websock.send("sactive:"+number);$("#sl"+number).addClass("checked");}else{websock.send("sinactive:"+number);$("#sl"+number).removeClass("checked");}}else if(state==1){$("#sl"+number).addClass("checked");$("#sl"+number).prop("checked",true);}else if(state==0){$("#sl"+number).removeClass("checked");$("#sl"+number).prop("checked",false);}}
|
||||||
|
var rangeSlider=function(isDiscrete){var range=$(".range-slider__range");var slidercb=function(){sliderchange($(this).attr("id").replace(/^\D+/g,""));};range.on({input:function(){$(this).next().html(this.value)}});range.each(function(){$(this).next().html(this.value);if($(this).attr("callbackSet")!="true"){if(!isDiscrete){$(this).on({input:slidercb});}else{$(this).on({change:slidercb});}
|
||||||
|
$(this).attr("callbackSet","true");}});};var addToHTML=function(data){panelStyle=data.hasOwnProperty('panelStyle')?" style='"+data.panelStyle+"' ":"";panelwide=data.hasOwnProperty('wide')?"wide":"";if(!data.hasOwnProperty('parentControl')||$("#tab"+data.parentControl).length>0){var parent=data.hasOwnProperty('parentControl')?$("#tab"+data.parentControl):$("#row");var html="";switch(data.type){case UI_LABEL:case UI_BUTTON:case UI_SWITCHER:case UI_CPAD:case UI_PAD:case UI_SLIDER:case UI_NUMBER:case UI_TEXT_INPUT:case UI_SELECT:case UI_GRAPH:case UI_GAUGE:case UI_ACCEL:html="<div id='id"+data.id+"' "+panelStyle+" class='two columns "+panelwide+" card tcenter "+
|
||||||
|
colorClass(data.color)+"'><h5>"+data.label+"</h5><hr/>"+
|
||||||
|
elementHTML(data)+
|
||||||
|
"</div>";break;case UI_SEPARATOR:html="<div id='id"+data.id+"' "+panelStyle+" class='sectionbreak columns'>"+
|
||||||
|
"<h5>"+data.label+"</h5><hr/></div>";break;case UI_TIME:break;}
|
||||||
|
parent.append(html);}else{var parent=$("#id"+data.parentControl);parent.append(elementHTML(data));}}
|
||||||
|
var elementHTML=function(data){var id=data.id
|
||||||
|
var elementStyle=data.hasOwnProperty('elementStyle')?" style='"+data.elementStyle+"' ":"";var inputType=data.hasOwnProperty('inputType')?" type='"+data.inputType+"' ":"";switch(data.type){case UI_LABEL:return"<span id='l"+id+"' "+elementStyle+
|
||||||
|
" class='label label-wrap'>"+data.value+"</span>";case UI_BUTTON:return"<button id='btn"+id+"' "+elementStyle+
|
||||||
|
" onmousedown='buttonclick("+id+", true)'"+
|
||||||
|
" onmouseup='buttonclick("+id+", false)'>"+
|
||||||
|
data.value+"</button>";case UI_SWITCHER:return"<label id='sl"+id+"' "+elementStyle+
|
||||||
|
" class='switch "+(data.value=="1"?"checked":"")+
|
||||||
|
(data.hasOwnProperty('vertical')?" vert-switcher ":"")+
|
||||||
|
"'>"+
|
||||||
|
"<div class='in'>"+
|
||||||
|
"<input type='checkbox' id='s"+id+"' onClick='switcher("+id+",null)' "+
|
||||||
|
(data.value=="1"?"checked":"")+"/></div></label>";case UI_CPAD:case UI_PAD:return"<nav class='control'><ul>"+
|
||||||
|
"<li><a onmousedown='padclick(UP, "+id+", true)' "+
|
||||||
|
"onmouseup='padclick(UP, "+id+", false)' id='pf"+id+"'>▲</a></li>"+
|
||||||
|
"<li><a onmousedown='padclick(RIGHT, "+id+", true)' "+
|
||||||
|
"onmouseup='padclick(RIGHT, "+id+", false)' id='pr"+id+"'>▲</a></li>"+
|
||||||
|
"<li><a onmousedown='padclick(LEFT, "+id+", true)' "+
|
||||||
|
"onmouseup='padclick(LEFT, "+id+", false)' id='pl"+id+"'>▲</a></li>"+
|
||||||
|
"<li><a onmousedown='padclick(DOWN, "+id+", true)' "+
|
||||||
|
"onmouseup='padclick(DOWN, "+id+", false)' id='pb"+id+"'>▲</a></li>"+
|
||||||
|
"</ul>"+
|
||||||
|
(data.type==UI_CPAD?"<a class='confirm' onmousedown='padclick(CENTER,"+id+", true)' "+
|
||||||
|
"onmouseup='padclick(CENTER, "+id+", false)' id='pc"+id+"'>OK</a>":"")+
|
||||||
|
"</nav>";case UI_SLIDER:return"<div class='range-slider "+
|
||||||
|
(data.hasOwnProperty('vertical')?" vert-slider ":"")+
|
||||||
|
"'>"+
|
||||||
|
"<input id='sl"+id+"' type='range' min='0' max='100' value='"+data.value+"' "+
|
||||||
|
elementStyle+" class='range-slider__range'><span class='range-slider__value'>"+
|
||||||
|
data.value+"</span></div>";case UI_NUMBER:return"<input style='color:black;' "+elementStyle+" id='num"+id+
|
||||||
|
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_TEXT_INPUT:return"<input "+inputType+"style='color:black;' "+elementStyle+" id='text"+id+
|
||||||
|
"' value='"+data.value+"' onchange='textchange("+id+")' />";case UI_SELECT:return"<select style='color:black;' "+elementStyle+" id='select"+id+
|
||||||
|
"' onchange='selectchange("+id+")' />";case UI_GRAPH:return"<figure id='graph"+id+"'><figcaption>"+data.label+"</figcaption></figure>";case UI_GAUGE:return"WILL BE A GAUGE <input style='color:black;' id='gauge"+id+
|
||||||
|
"' type='number' value='"+data.value+"' onchange='numberchange("+id+")' />";case UI_ACCEL:return"ACCEL // Not implemented fully!<div class='accelerometer' id='accel"+id+
|
||||||
|
"' ><div class='ball"+id+"'></div><pre class='accelerometeroutput"+id+"'></pre>";default:return"";}}
|
||||||
|
var processEnabled=function(data){switch(data.type){case UI_SWITCHER:case UPDATE_SWITCHER:if(data.enabled){$("#sl"+data.id).removeClass('disabled');$("#s"+data.id).prop("disabled",false);}else{$("#sl"+data.id).addClass('disabled');$("#s"+data.id).prop("disabled",true);}
|
||||||
|
break;case UI_SLIDER:case UPDATE_SLIDER:$("#sl"+data.id).prop("disabled",!data.enabled);break;case UI_NUMBER:case UPDATE_NUMBER:$("#num"+data.id).prop("disabled",!data.enabled);break;case UI_TEXT_INPUT:case UPDATE_TEXT_INPUT:$("#text"+data.id).prop("disabled",!data.enabled);break;case UI_SELECT:case UPDATE_SELECT:$("#select"+data.id).prop("disabled",!data.enabled);break;case UI_BUTTON:case UPDATE_BUTTON:$("#btn"+data.id).prop("disabled",!data.enabled);break;case UI_PAD:case UI_CPAD:case UPDATE_PAD:case UPDATE_CPAD:if(data.enabled){$("#id"+data.id+" nav").removeClass('disabled');}else{$("#id"+data.id+" nav").addClass('disabled');}
|
||||||
|
break;}}
|
297
data/js/graph.js
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
function lineGraph(parent, xAccessor, yAccessor) {
|
||||||
|
// Constant size definitions TODO: this could well be improved and calculated...
|
||||||
|
const width = 620;
|
||||||
|
const height = 420;
|
||||||
|
const gutter = 40;
|
||||||
|
const pixelsPerTick = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an object that contatins transform functions that:
|
||||||
|
* transforms numeric data into coordinate space, linearly
|
||||||
|
* transforms coordinates into numeric data, linearly
|
||||||
|
*/
|
||||||
|
function numericTransformer(dataMin, dataMax, pxMin, pxMax) {
|
||||||
|
var dataDiff = dataMax - dataMin,
|
||||||
|
pxDiff = pxMax - pxMin,
|
||||||
|
dataRatio = pxDiff / dataDiff,
|
||||||
|
coordRatio = dataDiff / pxDiff;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// transforms a data point to a coordinate point
|
||||||
|
toCoord: function(data) {
|
||||||
|
return (data - dataMin) * dataRatio + pxMin;
|
||||||
|
},
|
||||||
|
// transforms a coord point to a data point
|
||||||
|
toData: function(coord) {
|
||||||
|
return (coord - pxMin) * coordRatio + dataMin;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an axis.
|
||||||
|
* orientation = 'x' or 'y'
|
||||||
|
* transform = a function for transforming px into data for labeling/creating tick marks
|
||||||
|
*/
|
||||||
|
function axisRenderer(orientation, transform) {
|
||||||
|
var axisGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||||
|
var axisPath = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"path"
|
||||||
|
);
|
||||||
|
|
||||||
|
axisGroup.setAttribute("class", orientation + "-axis");
|
||||||
|
|
||||||
|
var xMin = gutter;
|
||||||
|
var xMax = width - gutter;
|
||||||
|
var yMin = height - gutter;
|
||||||
|
var yMax = gutter;
|
||||||
|
|
||||||
|
if (orientation === "x") {
|
||||||
|
axisPath.setAttribute(
|
||||||
|
"d",
|
||||||
|
"M " + xMin + " " + yMin + " L " + xMax + " " + yMin
|
||||||
|
);
|
||||||
|
|
||||||
|
// generate labels
|
||||||
|
for (var i = xMin; i <= xMax; i++) {
|
||||||
|
if ((i - xMin) % pixelsPerTick === 0 && i !== xMin) {
|
||||||
|
var text = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"text"
|
||||||
|
);
|
||||||
|
// primitive formatting
|
||||||
|
text.innerHTML = Math.floor(transform(i));
|
||||||
|
text.setAttribute("x", i);
|
||||||
|
text.setAttribute("y", yMin);
|
||||||
|
// offset the text by 1 em
|
||||||
|
text.setAttribute("dy", "1em");
|
||||||
|
axisGroup.appendChild(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
axisPath.setAttribute(
|
||||||
|
"d",
|
||||||
|
"M " + xMin + " " + yMin + " L " + xMin + " " + yMax
|
||||||
|
);
|
||||||
|
|
||||||
|
// generate labels
|
||||||
|
for (var i = yMax; i <= yMin; i++) {
|
||||||
|
if ((i - yMin) % pixelsPerTick === 0 && i !== yMin) {
|
||||||
|
var tickGroup = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"g"
|
||||||
|
);
|
||||||
|
var gridLine = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"path"
|
||||||
|
);
|
||||||
|
text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
||||||
|
// primitive formatting
|
||||||
|
text.innerHTML = Math.floor(transform(i));
|
||||||
|
text.setAttribute("x", xMin);
|
||||||
|
text.setAttribute("y", i);
|
||||||
|
// offset the text labels to align with grid line and keeping it to the left of the y-axis
|
||||||
|
text.setAttribute("dx", "-.5em");
|
||||||
|
text.setAttribute("dy", ".3em");
|
||||||
|
|
||||||
|
gridLine.setAttribute(
|
||||||
|
"d",
|
||||||
|
"M " + xMin + " " + i + " L " + xMax + " " + i
|
||||||
|
);
|
||||||
|
|
||||||
|
tickGroup.appendChild(gridLine);
|
||||||
|
tickGroup.appendChild(text);
|
||||||
|
axisGroup.appendChild(tickGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
axisGroup.appendChild(axisPath);
|
||||||
|
parent.appendChild(axisGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a line
|
||||||
|
*/
|
||||||
|
function lineRenderer(xAccessor, yAccessor, xTransform, yTransform) {
|
||||||
|
var line = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||||
|
|
||||||
|
xAccessor.reset();
|
||||||
|
yAccessor.reset();
|
||||||
|
if (!xAccessor.hasNext() || !yAccessor.hasNext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathString =
|
||||||
|
"M " + xTransform(xAccessor.next()) + " " + yTransform(yAccessor.next());
|
||||||
|
while (xAccessor.hasNext() && yAccessor.hasNext()) {
|
||||||
|
pathString +=
|
||||||
|
" L " +
|
||||||
|
xTransform(xAccessor.next()) +
|
||||||
|
" " +
|
||||||
|
yTransform(yAccessor.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
line.setAttribute("class", "series");
|
||||||
|
line.setAttribute("d", pathString);
|
||||||
|
|
||||||
|
parent.appendChild(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders data point circles + text labels
|
||||||
|
*/
|
||||||
|
function pointRenderer(xAccessor, yAccessor, xTransform, yTransform) {
|
||||||
|
var pointGroup = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"g"
|
||||||
|
);
|
||||||
|
|
||||||
|
pointGroup.setAttribute("class", "data-points");
|
||||||
|
|
||||||
|
xAccessor.reset();
|
||||||
|
yAccessor.reset();
|
||||||
|
if (!xAccessor.hasNext() || !yAccessor.hasNext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (xAccessor.hasNext() && yAccessor.hasNext()) {
|
||||||
|
var xDataValue = xAccessor.next();
|
||||||
|
var x = xTransform(xDataValue);
|
||||||
|
var yDataValue = yAccessor.next();
|
||||||
|
var y = yTransform(yDataValue);
|
||||||
|
|
||||||
|
var circle = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"circle"
|
||||||
|
);
|
||||||
|
circle.setAttribute("cx", x);
|
||||||
|
circle.setAttribute("cy", y);
|
||||||
|
circle.setAttribute("r", "4");
|
||||||
|
|
||||||
|
var text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
||||||
|
// primitive formatting
|
||||||
|
text.innerHTML = Math.floor(xDataValue) + " / " + Math.floor(yDataValue);
|
||||||
|
text.setAttribute("x", x);
|
||||||
|
text.setAttribute("y", y);
|
||||||
|
|
||||||
|
text.setAttribute("dx", "1em");
|
||||||
|
text.setAttribute("dy", "-.7em");
|
||||||
|
|
||||||
|
pointGroup.appendChild(circle);
|
||||||
|
pointGroup.appendChild(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.appendChild(pointGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform the rendering
|
||||||
|
xTransform = numericTransformer(
|
||||||
|
xAccessor.min(),
|
||||||
|
xAccessor.max(),
|
||||||
|
0 + gutter,
|
||||||
|
width - gutter
|
||||||
|
);
|
||||||
|
// NOTE: for y... have to reverse coordinate space
|
||||||
|
yTransform = numericTransformer(
|
||||||
|
yAccessor.min(),
|
||||||
|
yAccessor.max(),
|
||||||
|
height - gutter,
|
||||||
|
0 + gutter
|
||||||
|
);
|
||||||
|
|
||||||
|
axisRenderer("x", xTransform.toData);
|
||||||
|
axisRenderer("y", yTransform.toData);
|
||||||
|
|
||||||
|
lineRenderer(xAccessor, yAccessor, xTransform.toCoord, yTransform.toCoord);
|
||||||
|
pointRenderer(xAccessor, yAccessor, xTransform.toCoord, yTransform.toCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final render function
|
||||||
|
function renderGraphSvg(dataArray, renderId) {
|
||||||
|
var figure = document.getElementById(renderId);
|
||||||
|
while (figure.hasChildNodes()) {
|
||||||
|
figure.removeChild(figure.lastChild);
|
||||||
|
}
|
||||||
|
//console.log(dataArray);
|
||||||
|
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||||
|
svg.setAttribute("viewBox", "0 0 640 440");
|
||||||
|
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
||||||
|
|
||||||
|
lineGraph(
|
||||||
|
svg,
|
||||||
|
// time accessor
|
||||||
|
(function(data, min, max) {
|
||||||
|
var i = 0;
|
||||||
|
return {
|
||||||
|
hasNext: function() {
|
||||||
|
return i < data.length;
|
||||||
|
},
|
||||||
|
next: function() {
|
||||||
|
return data[i++].x;
|
||||||
|
},
|
||||||
|
reset: function() {
|
||||||
|
i = 0;
|
||||||
|
},
|
||||||
|
min: function() {
|
||||||
|
return min;
|
||||||
|
},
|
||||||
|
max: function() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(
|
||||||
|
dataArray,
|
||||||
|
Math.min.apply(
|
||||||
|
Math,
|
||||||
|
dataArray.map(function(o) {
|
||||||
|
return o.x;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
Math.max.apply(
|
||||||
|
Math,
|
||||||
|
dataArray.map(function(o) {
|
||||||
|
return o.x;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// value accessor
|
||||||
|
(function(data, min, max) {
|
||||||
|
var i = 0;
|
||||||
|
return {
|
||||||
|
hasNext: function() {
|
||||||
|
return i < data.length;
|
||||||
|
},
|
||||||
|
next: function() {
|
||||||
|
return data[i++].y;
|
||||||
|
},
|
||||||
|
reset: function() {
|
||||||
|
i = 0;
|
||||||
|
},
|
||||||
|
min: function() {
|
||||||
|
return min;
|
||||||
|
},
|
||||||
|
max: function() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(
|
||||||
|
dataArray,
|
||||||
|
Math.min.apply(
|
||||||
|
Math,
|
||||||
|
dataArray.map(function(o) {
|
||||||
|
return o.y;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
Math.max.apply(
|
||||||
|
Math,
|
||||||
|
dataArray.map(function(o) {
|
||||||
|
return o.y;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
figure.appendChild(svg);
|
||||||
|
}
|
15
data/js/graph.min.js
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
function lineGraph(parent,xAccessor,yAccessor){const width=620;const height=420;const gutter=40;const pixelsPerTick=30;function numericTransformer(dataMin,dataMax,pxMin,pxMax){var dataDiff=dataMax-dataMin,pxDiff=pxMax-pxMin,dataRatio=pxDiff/dataDiff,coordRatio=dataDiff/pxDiff;return{toCoord:function(data){return(data-dataMin)*dataRatio+pxMin;},toData:function(coord){return(coord-pxMin)*coordRatio+dataMin;}};}
|
||||||
|
function axisRenderer(orientation,transform){var axisGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var axisPath=document.createElementNS("http://www.w3.org/2000/svg","path");axisGroup.setAttribute("class",orientation+"-axis");var xMin=gutter;var xMax=width-gutter;var yMin=height-gutter;var yMax=gutter;if(orientation==="x"){axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMax+" "+yMin);for(var i=xMin;i<=xMax;i++){if((i-xMin)%pixelsPerTick===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",i);text.setAttribute("y",yMin);text.setAttribute("dy","1em");axisGroup.appendChild(text);}}}else{axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMin+" "+yMax);for(var i=yMax;i<=yMin;i++){if((i-yMin)%pixelsPerTick===0&&i!==yMin){var tickGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var gridLine=document.createElementNS("http://www.w3.org/2000/svg","path");text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",xMin);text.setAttribute("y",i);text.setAttribute("dx","-.5em");text.setAttribute("dy",".3em");gridLine.setAttribute("d","M "+xMin+" "+i+" L "+xMax+" "+i);tickGroup.appendChild(gridLine);tickGroup.appendChild(text);axisGroup.appendChild(tickGroup);}}}
|
||||||
|
axisGroup.appendChild(axisPath);parent.appendChild(axisGroup);}
|
||||||
|
function lineRenderer(xAccessor,yAccessor,xTransform,yTransform){var line=document.createElementNS("http://www.w3.org/2000/svg","path");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
|
||||||
|
var pathString="M "+xTransform(xAccessor.next())+" "+yTransform(yAccessor.next());while(xAccessor.hasNext()&&yAccessor.hasNext()){pathString+=" L "+
|
||||||
|
xTransform(xAccessor.next())+
|
||||||
|
" "+
|
||||||
|
yTransform(yAccessor.next());}
|
||||||
|
line.setAttribute("class","series");line.setAttribute("d",pathString);parent.appendChild(line);}
|
||||||
|
function pointRenderer(xAccessor,yAccessor,xTransform,yTransform){var pointGroup=document.createElementNS("http://www.w3.org/2000/svg","g");pointGroup.setAttribute("class","data-points");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
|
||||||
|
while(xAccessor.hasNext()&&yAccessor.hasNext()){var xDataValue=xAccessor.next();var x=xTransform(xDataValue);var yDataValue=yAccessor.next();var y=yTransform(yDataValue);var circle=document.createElementNS("http://www.w3.org/2000/svg","circle");circle.setAttribute("cx",x);circle.setAttribute("cy",y);circle.setAttribute("r","4");var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(xDataValue)+" / "+Math.floor(yDataValue);text.setAttribute("x",x);text.setAttribute("y",y);text.setAttribute("dx","1em");text.setAttribute("dy","-.7em");pointGroup.appendChild(circle);pointGroup.appendChild(text);}
|
||||||
|
parent.appendChild(pointGroup);}
|
||||||
|
xTransform=numericTransformer(xAccessor.min(),xAccessor.max(),0+gutter,width-gutter);yTransform=numericTransformer(yAccessor.min(),yAccessor.max(),height-gutter,0+gutter);axisRenderer("x",xTransform.toData);axisRenderer("y",yTransform.toData);lineRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);pointRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);}
|
||||||
|
function renderGraphSvg(dataArray,renderId){var figure=document.getElementById(renderId);while(figure.hasChildNodes()){figure.removeChild(figure.lastChild);}
|
||||||
|
var svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.setAttribute("viewBox","0 0 640 440");svg.setAttribute("preserveAspectRatio","xMidYMid meet");lineGraph(svg,(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].x;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.x;})),Math.max.apply(Math,dataArray.map(function(o){return o.x;}))),(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].y;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.y;})),Math.max.apply(Math,dataArray.map(function(o){return o.y;}))));figure.appendChild(svg);}
|
123
data/js/slider.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/* -----------------------------------------------------
|
||||||
|
Material Design Sliders
|
||||||
|
CodePen URL: https://codepen.io/rkchauhan/pen/xVGGpR
|
||||||
|
By: Ravikumar Chauhan
|
||||||
|
-------------------------------------------------------- */
|
||||||
|
function rkmd_rangeSlider(selector) {
|
||||||
|
var self, slider_width, slider_offset, curnt, sliderDiscrete, range, slider;
|
||||||
|
self = $(selector);
|
||||||
|
slider_width = self.width();
|
||||||
|
slider_offset = self.offset().left;
|
||||||
|
sliderDiscrete = self;
|
||||||
|
|
||||||
|
sliderDiscrete.each(function (i, v) {
|
||||||
|
curnt = $(this);
|
||||||
|
curnt.append(sliderDiscrete_tmplt());
|
||||||
|
range = curnt.find('input[type="range"]');
|
||||||
|
slider = curnt.find(".slider");
|
||||||
|
slider_fill = slider.find(".slider-fill");
|
||||||
|
slider_handle = slider.find(".slider-handle");
|
||||||
|
slider_label = slider.find(".slider-label");
|
||||||
|
|
||||||
|
var range_val = parseInt(range.val());
|
||||||
|
slider_fill.css("width", range_val + "%");
|
||||||
|
slider_handle.css("left", range_val + "%");
|
||||||
|
slider_label.find("span").text(range_val);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.on("mousedown touchstart", ".slider-handle", function (e) {
|
||||||
|
if (e.button === 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var parents = $(this).parents(".rkmd-slider");
|
||||||
|
var slider_width = parents.width();
|
||||||
|
var slider_offset = parents.offset().left;
|
||||||
|
var check_range = parents.find('input[type="range"]').is(":disabled");
|
||||||
|
if (check_range === true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$(this).addClass("is-active");
|
||||||
|
var moveFu = function (e) {
|
||||||
|
var pageX = e.pageX || e.changedTouches[0].pageX;
|
||||||
|
var slider_new_width = pageX - slider_offset;
|
||||||
|
if (slider_new_width <= slider_width && !(slider_new_width < "0")) {
|
||||||
|
slider_move(parents, slider_new_width, slider_width, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var upFu = function (e) {
|
||||||
|
$(this).off(handlers);
|
||||||
|
parents.find(".is-active").removeClass("is-active");
|
||||||
|
};
|
||||||
|
|
||||||
|
var handlers = {
|
||||||
|
mousemove: moveFu,
|
||||||
|
touchmove: moveFu,
|
||||||
|
mouseup: upFu,
|
||||||
|
touchend: upFu,
|
||||||
|
};
|
||||||
|
$(document).on(handlers);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.on("mousedown touchstart", ".slider", function (e) {
|
||||||
|
if (e.button === 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parents = $(this).parents(".rkmd-slider");
|
||||||
|
var slider_width = parents.width();
|
||||||
|
var slider_offset = parents.offset().left;
|
||||||
|
var check_range = parents.find('input[type="range"]').is(":disabled");
|
||||||
|
|
||||||
|
if (check_range === true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var slider_new_width = e.pageX - slider_offset;
|
||||||
|
if (slider_new_width <= slider_width && !(slider_new_width < "0")) {
|
||||||
|
slider_move(parents, slider_new_width, slider_width, true);
|
||||||
|
}
|
||||||
|
var upFu = function (e) {
|
||||||
|
$(this).off(handlers);
|
||||||
|
};
|
||||||
|
|
||||||
|
var handlers = {
|
||||||
|
mouseup: upFu,
|
||||||
|
touchend: upFu,
|
||||||
|
};
|
||||||
|
$(document).on(handlers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sliderDiscrete_tmplt() {
|
||||||
|
var tmplt =
|
||||||
|
'<div class="slider">' +
|
||||||
|
'<div class="slider-fill"></div>' +
|
||||||
|
'<div class="slider-handle"><div class="slider-label"><span>0</span></div></div>' +
|
||||||
|
"</div>";
|
||||||
|
|
||||||
|
return tmplt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function slider_move(parents, newW, sliderW, send) {
|
||||||
|
var slider_new_val = parseInt(Math.round((newW / sliderW) * 100));
|
||||||
|
|
||||||
|
var slider_fill = parents.find(".slider-fill");
|
||||||
|
var slider_handle = parents.find(".slider-handle");
|
||||||
|
var range = parents.find('input[type="range"]');
|
||||||
|
range.next().html(newW); // update value
|
||||||
|
|
||||||
|
slider_fill.css("width", slider_new_val + "%");
|
||||||
|
slider_handle.css({
|
||||||
|
left: slider_new_val + "%",
|
||||||
|
transition: "none",
|
||||||
|
"-webkit-transition": "none",
|
||||||
|
"-moz-transition": "none",
|
||||||
|
});
|
||||||
|
|
||||||
|
range.val(slider_new_val);
|
||||||
|
if (parents.find(".slider-handle span").text() != slider_new_val) {
|
||||||
|
parents.find(".slider-handle span").text(slider_new_val);
|
||||||
|
var number = parents.attr("id").substring(2);
|
||||||
|
if (send) websock.send("slvalue:" + slider_new_val + ":" + number);
|
||||||
|
}
|
||||||
|
}
|
11
data/js/slider.min.js
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find(".slider");slider_fill=slider.find(".slider-fill");slider_handle=slider.find(".slider-handle");slider_label=slider.find(".slider-label");var range_val=parseInt(range.val());slider_fill.css("width",range_val+"%");slider_handle.css("left",range_val+"%");slider_label.find("span").text(range_val);});self.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2){return false;}
|
||||||
|
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
|
||||||
|
$(this).addClass("is-active");var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find(".is-active").removeClass("is-active");};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu,};$(document).on(handlers);});self.on("mousedown touchstart",".slider",function(e){if(e.button===2){return false;}
|
||||||
|
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
|
||||||
|
var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}
|
||||||
|
var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu,};$(document).on(handlers);});}
|
||||||
|
function sliderDiscrete_tmplt(){var tmplt='<div class="slider">'+
|
||||||
|
'<div class="slider-fill"></div>'+
|
||||||
|
'<div class="slider-handle"><div class="slider-label"><span>0</span></div></div>'+
|
||||||
|
"</div>";return tmplt;}
|
||||||
|
function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round((newW/sliderW)*100));var slider_fill=parents.find(".slider-fill");var slider_handle=parents.find(".slider-handle");var range=parents.find('input[type="range"]');range.next().html(newW);slider_fill.css("width",slider_new_val+"%");slider_handle.css({left:slider_new_val+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none",});range.val(slider_new_val);if(parents.find(".slider-handle span").text()!=slider_new_val){parents.find(".slider-handle span").text(slider_new_val);var number=parents.attr("id").substring(2);if(send)websock.send("slvalue:"+slider_new_val+":"+number);}}
|
351
data/js/tabbedcontent.js
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
/**
|
||||||
|
* Tabs plugin for jQuery created by Òscar Casajuana < elboletaire at underave dot net >
|
||||||
|
*
|
||||||
|
* @copyright Copyright 2013-2016 Òscar Casajuana
|
||||||
|
* @license MIT
|
||||||
|
* @author Òscar Casajuana Alonso <elboletaire at underave dot net>
|
||||||
|
*/
|
||||||
|
;(function($, document, window, undefined) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Tabbedcontent = function(tabcontent, options) {
|
||||||
|
var defaults = {
|
||||||
|
links : tabcontent.prev().find('a').length ? tabcontent.prev().find('a') : '.tabs a', // the tabs itself. By default it selects the links contained in the previous wrapper or the links inside ".tabs a" if there's no previous item
|
||||||
|
errorSelector : '.error-message', // false to disable
|
||||||
|
speed : false, // speed of the show effect. Set to null or false to disable
|
||||||
|
onSwitch : false, // onSwitch callback
|
||||||
|
onInit : false, // onInit callback
|
||||||
|
currentClass : 'active', // current selected tab class (is set to the <a> element)
|
||||||
|
tabErrorClass : 'has-errors', // a class to be added to the tab where errorSelector is detected
|
||||||
|
history : true, // set to false to disable HTML5 history
|
||||||
|
historyOnInit : true, // allows to deactivate the history for the intial autmatically tab switch on load
|
||||||
|
loop : false // if set to true will loop between tabs when using the next() and prev() api methods
|
||||||
|
},
|
||||||
|
firstTime = false,
|
||||||
|
children = tabcontent.children(),
|
||||||
|
history = window.history,
|
||||||
|
loc = document.location,
|
||||||
|
current = null
|
||||||
|
;
|
||||||
|
|
||||||
|
options = $.extend(defaults, options);
|
||||||
|
|
||||||
|
if (!(options.links instanceof $)) {
|
||||||
|
options.links = $(options.links);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the specified tab id exists.
|
||||||
|
*
|
||||||
|
* @param string tab Tab #id
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function tabExists(tab) {
|
||||||
|
return Boolean(children.filter(tab).length);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if the current tab is the
|
||||||
|
* first one in the tabs set.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isFirst() {
|
||||||
|
return current === 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if the passed number is an integer.
|
||||||
|
*
|
||||||
|
* @param mixed num The value to be checked.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isInt(num) {
|
||||||
|
return num % 1 === 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if the current tab is the
|
||||||
|
* last one in the tabs set.
|
||||||
|
*
|
||||||
|
* @return {Boolean} [description]
|
||||||
|
*/
|
||||||
|
function isLast() {
|
||||||
|
return current === children.length - 1;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Filters a tab based on current links href.
|
||||||
|
*
|
||||||
|
* Method for compatibility with Zepto.js
|
||||||
|
*
|
||||||
|
* @param string tab Tab #href
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function filterTab(tab) {
|
||||||
|
return $(this).attr('href').match(new RegExp(tab + '$'));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns an object containing two jQuery instances:
|
||||||
|
* one for the tab content and the other for its link.
|
||||||
|
*
|
||||||
|
* @param mixed tab A tab id, #id or index.
|
||||||
|
* @return object With thi
|
||||||
|
*/
|
||||||
|
function getTab(tab) {
|
||||||
|
if (tab instanceof $) {
|
||||||
|
return {
|
||||||
|
tab : tab,
|
||||||
|
link : options.links.eq(tab.index())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isInt(tab)) {
|
||||||
|
return {
|
||||||
|
tab : children.eq(tab),
|
||||||
|
link : options.links.eq(tab)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (children.filter(tab).length) {
|
||||||
|
return {
|
||||||
|
tab : children.filter(tab),
|
||||||
|
link : options.links.filter(function() {
|
||||||
|
return filterTab.apply(this, [tab]);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// assume it's an id without #
|
||||||
|
return {
|
||||||
|
tab : children.filter('#' + tab),
|
||||||
|
link : options.links.filter(function() {
|
||||||
|
return filterTab.apply(this, ['#' + tab]);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the index of the current tab.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
function getCurrent() {
|
||||||
|
return options.links.parent().filter('.' + options.currentClass).index();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Go to the next tab in the tabs set.
|
||||||
|
*
|
||||||
|
* @param bool loop If defined will overwrite options.loop
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function next(loop) {
|
||||||
|
++current;
|
||||||
|
|
||||||
|
if (loop === undefined) loop = options.loop;
|
||||||
|
|
||||||
|
if (current < children.length) {
|
||||||
|
return switchTab(current, true);
|
||||||
|
} else if (loop && current >= children.length) {
|
||||||
|
return switchTab(0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Go to the previous tab in the tabs set.
|
||||||
|
*
|
||||||
|
* @param bool loop If defined will overwrite options.loop
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function prev(loop) {
|
||||||
|
--current;
|
||||||
|
|
||||||
|
if (loop === undefined) loop = options.loop;
|
||||||
|
|
||||||
|
if (current >= 0) {
|
||||||
|
return switchTab(current, true);
|
||||||
|
} else if (loop && current < 0) {
|
||||||
|
return switchTab(children.length - 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* onSwitch callback for switchTab.
|
||||||
|
*
|
||||||
|
* @param string tab The tab #id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function onSwitch(tab) {
|
||||||
|
if (options.history && options.historyOnInit && firstTime && history !== undefined && ('pushState' in history)) {
|
||||||
|
firstTime = false;
|
||||||
|
window.setTimeout(function() {
|
||||||
|
history.replaceState(null, '', tab);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
current = getCurrent();
|
||||||
|
if (options.onSwitch && typeof options.onSwitch === 'function') {
|
||||||
|
options.onSwitch(tab, api());
|
||||||
|
}
|
||||||
|
tabcontent.trigger('tabcontent.switch', [tab, api()]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Switch to specified tab.
|
||||||
|
*
|
||||||
|
* @param mixed tab The tab to switch to.
|
||||||
|
* @param bool api Set to true to force history writing.
|
||||||
|
* @return bool Returns false if tab does not exist; true otherwise.
|
||||||
|
*/
|
||||||
|
function switchTab(tab, api) {
|
||||||
|
if (!tab.toString().match(/^#/)) {
|
||||||
|
tab = '#' + getTab(tab).tab.attr('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tabExists(tab)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle active class
|
||||||
|
options.links.attr('aria-selected','false').parent().removeClass(options.currentClass);
|
||||||
|
options.links.filter(function() {
|
||||||
|
return filterTab.apply(this, [tab]);
|
||||||
|
}).attr('aria-selected','true').parent().addClass(options.currentClass);
|
||||||
|
// Hide tabs
|
||||||
|
children.hide();
|
||||||
|
|
||||||
|
// We need to force the change of the hash if we're using the API
|
||||||
|
if (options.history && api) {
|
||||||
|
if (history !== undefined && ('pushState' in history)) {
|
||||||
|
history.pushState(null, '', tab);
|
||||||
|
} else {
|
||||||
|
// force hash change to add it to the history
|
||||||
|
window.location.hash = tab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show tabs
|
||||||
|
children.attr('aria-hidden','true').filter(tab).show(options.speed, function() {
|
||||||
|
if (options.speed) {
|
||||||
|
onSwitch(tab);
|
||||||
|
}
|
||||||
|
}).attr('aria-hidden','false');
|
||||||
|
if (!options.speed) {
|
||||||
|
onSwitch(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Api method to switch tabs.
|
||||||
|
*
|
||||||
|
* @param mixed tab Tab to switch to.
|
||||||
|
* @return bool Returns false if tab does not exist; true otherwise.
|
||||||
|
*/
|
||||||
|
function apiSwitch(tab) {
|
||||||
|
return switchTab(tab, true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Method used to switch tabs using the
|
||||||
|
* browser query hash.
|
||||||
|
*
|
||||||
|
* @param object e Event.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function hashSwitch(e) {
|
||||||
|
switchTab(loc.hash);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Initialization method.
|
||||||
|
*
|
||||||
|
* The tab checking preference is:
|
||||||
|
* - document.location.hash
|
||||||
|
* - options.errorSelector
|
||||||
|
* - first tab in the set of tabs
|
||||||
|
*
|
||||||
|
* The onInit method is called at the
|
||||||
|
* end of this method.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function init() {
|
||||||
|
// Switch to tab using location.hash
|
||||||
|
if (tabExists(loc.hash)) {
|
||||||
|
// Switch to current hash tab
|
||||||
|
switchTab(loc.hash);
|
||||||
|
}
|
||||||
|
// If there's a tab link with the options.currentClass set,
|
||||||
|
// switch to that tab.
|
||||||
|
else if (options.links.parent().filter('.' + options.currentClass).length) {
|
||||||
|
switchTab(options.links.parent().filter('.' + options.currentClass).index());
|
||||||
|
}
|
||||||
|
// Switch to tab containing class options.errorSelector
|
||||||
|
else if (options.errorSelector && children.find(options.errorSelector).length) {
|
||||||
|
// Search for errors and show first tab containing one
|
||||||
|
children.each(function() {
|
||||||
|
if ($(this).find(options.errorSelector).length) {
|
||||||
|
switchTab("#" + $(this).attr("id"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Open first tab
|
||||||
|
else {
|
||||||
|
switchTab("#" + children.filter(":first-child").attr("id"));
|
||||||
|
}
|
||||||
|
// Add a class to every tab containing errors
|
||||||
|
if (options.errorSelector) {
|
||||||
|
children.find(options.errorSelector).each(function() {
|
||||||
|
var tab = getTab($(this).parent());
|
||||||
|
tab.link.parent().addClass(options.tabErrorClass);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binding
|
||||||
|
if ('onhashchange' in window) {
|
||||||
|
$(window).bind('hashchange', hashSwitch);
|
||||||
|
} else { // old browsers
|
||||||
|
var current_href = loc.href;
|
||||||
|
window.setInterval(function() {
|
||||||
|
if (current_href !== loc.href) {
|
||||||
|
hashSwitch.call(window.event);
|
||||||
|
current_href = loc.href;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
// Bind click event on links, to ensure we don't rewrite the URI in
|
||||||
|
// case history is disabled
|
||||||
|
$(options.links).on('click', function(e) {
|
||||||
|
switchTab($(this).attr('href').replace(/^[^#]+/, ''), options.history);
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// onInit callback
|
||||||
|
if (options.onInit && typeof options.onInit === 'function') {
|
||||||
|
options.onInit(api());
|
||||||
|
}
|
||||||
|
tabcontent.trigger('tabcontent.init', [api()]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the methods exposed in the api.
|
||||||
|
*
|
||||||
|
* @return object Containing each api method.
|
||||||
|
*/
|
||||||
|
function api() {
|
||||||
|
return {
|
||||||
|
'switch' : apiSwitch,
|
||||||
|
'switchTab' : apiSwitch, // for old browsers
|
||||||
|
'getCurrent' : getCurrent,
|
||||||
|
'getTab' : getTab,
|
||||||
|
'next' : next,
|
||||||
|
'prev' : prev,
|
||||||
|
'isFirst' : isFirst,
|
||||||
|
'isLast' : isLast
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return api();
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tabbedContent = function(options) {
|
||||||
|
return this.each(function() {
|
||||||
|
var tabs = new Tabbedcontent($(this), options);
|
||||||
|
$(this).data('api', tabs);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
})(window.jQuery || window.Zepto || window.$, document, window);
|
35
data/js/tabbedcontent.min.js
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
;(function($,document,window,undefined){"use strict";var Tabbedcontent=function(tabcontent,options){var defaults={links:tabcontent.prev().find('a').length?tabcontent.prev().find('a'):'.tabs a',errorSelector:'.error-message',speed:false,onSwitch:false,onInit:false,currentClass:'active',tabErrorClass:'has-errors',history:true,historyOnInit:true,loop:false},firstTime=false,children=tabcontent.children(),history=window.history,loc=document.location,current=null;options=$.extend(defaults,options);if(!(options.links instanceof $)){options.links=$(options.links);}
|
||||||
|
function tabExists(tab){return Boolean(children.filter(tab).length);}
|
||||||
|
function isFirst(){return current===0;}
|
||||||
|
function isInt(num){return num%1===0;}
|
||||||
|
function isLast(){return current===children.length-1;}
|
||||||
|
function filterTab(tab){return $(this).attr('href').match(new RegExp(tab+'$'));}
|
||||||
|
function getTab(tab){if(tab instanceof $){return{tab:tab,link:options.links.eq(tab.index())};}
|
||||||
|
if(isInt(tab)){return{tab:children.eq(tab),link:options.links.eq(tab)};}
|
||||||
|
if(children.filter(tab).length){return{tab:children.filter(tab),link:options.links.filter(function(){return filterTab.apply(this,[tab]);})};}
|
||||||
|
return{tab:children.filter('#'+tab),link:options.links.filter(function(){return filterTab.apply(this,['#'+tab]);})};}
|
||||||
|
function getCurrent(){return options.links.parent().filter('.'+options.currentClass).index();}
|
||||||
|
function next(loop){++current;if(loop===undefined)loop=options.loop;if(current<children.length){return switchTab(current,true);}else if(loop&¤t>=children.length){return switchTab(0,true);}
|
||||||
|
return false;}
|
||||||
|
function prev(loop){--current;if(loop===undefined)loop=options.loop;if(current>=0){return switchTab(current,true);}else if(loop&¤t<0){return switchTab(children.length-1,true);}
|
||||||
|
return false;}
|
||||||
|
function onSwitch(tab){if(options.history&&options.historyOnInit&&firstTime&&history!==undefined&&('pushState'in history)){firstTime=false;window.setTimeout(function(){history.replaceState(null,'',tab);},100);}
|
||||||
|
current=getCurrent();if(options.onSwitch&&typeof options.onSwitch==='function'){options.onSwitch(tab,api());}
|
||||||
|
tabcontent.trigger('tabcontent.switch',[tab,api()]);}
|
||||||
|
function switchTab(tab,api){if(!tab.toString().match(/^#/)){tab='#'+getTab(tab).tab.attr('id');}
|
||||||
|
if(!tabExists(tab)){return false;}
|
||||||
|
options.links.attr('aria-selected','false').parent().removeClass(options.currentClass);options.links.filter(function(){return filterTab.apply(this,[tab]);}).attr('aria-selected','true').parent().addClass(options.currentClass);children.hide();if(options.history&&api){if(history!==undefined&&('pushState'in history)){history.pushState(null,'',tab);}else{window.location.hash=tab;}}
|
||||||
|
children.attr('aria-hidden','true').filter(tab).show(options.speed,function(){if(options.speed){onSwitch(tab);}}).attr('aria-hidden','false');if(!options.speed){onSwitch(tab);}
|
||||||
|
return true;}
|
||||||
|
function apiSwitch(tab){return switchTab(tab,true);}
|
||||||
|
function hashSwitch(e){switchTab(loc.hash);}
|
||||||
|
function init(){if(tabExists(loc.hash)){switchTab(loc.hash);}
|
||||||
|
else if(options.links.parent().filter('.'+options.currentClass).length){switchTab(options.links.parent().filter('.'+options.currentClass).index());}
|
||||||
|
else if(options.errorSelector&&children.find(options.errorSelector).length){children.each(function(){if($(this).find(options.errorSelector).length){switchTab("#"+$(this).attr("id"));return false;}});}
|
||||||
|
else{switchTab("#"+children.filter(":first-child").attr("id"));}
|
||||||
|
if(options.errorSelector){children.find(options.errorSelector).each(function(){var tab=getTab($(this).parent());tab.link.parent().addClass(options.tabErrorClass);});}
|
||||||
|
if('onhashchange'in window){$(window).bind('hashchange',hashSwitch);}else{var current_href=loc.href;window.setInterval(function(){if(current_href!==loc.href){hashSwitch.call(window.event);current_href=loc.href;}},100);}
|
||||||
|
$(options.links).on('click',function(e){switchTab($(this).attr('href').replace(/^[^#]+/,''),options.history);e.preventDefault();});if(options.onInit&&typeof options.onInit==='function'){options.onInit(api());}
|
||||||
|
tabcontent.trigger('tabcontent.init',[api()]);}
|
||||||
|
function api(){return{'switch':apiSwitch,'switchTab':apiSwitch,'getCurrent':getCurrent,'getTab':getTab,'next':next,'prev':prev,'isFirst':isFirst,'isLast':isLast};}
|
||||||
|
init();return api();};$.fn.tabbedContent=function(options){return this.each(function(){var tabs=new Tabbedcontent($(this),options);$(this).data('api',tabs);});};})(window.jQuery||window.Zepto||window.$,document,window);
|
BIN
docs/Memory ESP32.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/Memory ESP8266.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
docs/SPIFFS ESP32.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
docs/SPIFFS ESP8266.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/inlinestyles.gif
Normal file
After Width: | Height: | Size: 326 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
BIN
docs/ui_colours.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 47 KiB |
BIN
docs/ui_graph.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
docs/ui_groupedbuttons.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
docs/ui_groupedbuttons2.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
docs/ui_groupedbuttons3.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
docs/ui_inlinestyles2.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
docs/ui_inputtypes.png
Normal file
After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 21 KiB |
BIN
docs/ui_number.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
docs/ui_select1.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
docs/ui_select2.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
docs/ui_separators.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
docs/ui_slider.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 11 KiB |
BIN
docs/ui_tabs.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
docs/ui_text.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
docs/ui_widecontrols.png
Normal file
After Width: | Height: | Size: 22 KiB |
523
examples/completeExample/completeExample.cpp
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
/**
|
||||||
|
* @file completeExample.cpp
|
||||||
|
* @author Ian Gray @iangray1000
|
||||||
|
*
|
||||||
|
* This is an example GUI to show off all of the features of ESPUI.
|
||||||
|
* This can be built using the Arduino IDE, or PlatformIO.
|
||||||
|
*
|
||||||
|
* ---------------------------------------------------------------------------------------
|
||||||
|
* If you just want to see examples of the ESPUI code, jump down to the setUpUI() function
|
||||||
|
* ---------------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* When this program boots, it will load an SSID and password from the EEPROM.
|
||||||
|
* The SSID is a null-terminated C string stored at EEPROM addresses 0-31
|
||||||
|
* The password is a null-terminated C string stored at EEPROM addresses 32-95.
|
||||||
|
* If these credentials do not work for some reason, the ESP will create an Access
|
||||||
|
* Point wifi with the SSID HOSTNAME (defined below). You can then connect and use
|
||||||
|
* the controls on the "Wifi Credentials" tab to store credentials into the EEPROM.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
#include <ESPUI.h>
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#else
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//Settings
|
||||||
|
#define SLOW_BOOT 0
|
||||||
|
#define HOSTNAME "ESPUITest"
|
||||||
|
#define FORCE_USE_HOTSPOT 0
|
||||||
|
|
||||||
|
|
||||||
|
//Function Prototypes
|
||||||
|
void connectWifi();
|
||||||
|
void setUpUI();
|
||||||
|
void enterWifiDetailsCallback(Control *sender, int type);
|
||||||
|
void textCallback(Control *sender, int type);
|
||||||
|
void generalCallback(Control *sender, int type);
|
||||||
|
void scrambleCallback(Control *sender, int type);
|
||||||
|
void styleCallback(Control *sender, int type);
|
||||||
|
void updateCallback(Control *sender, int type);
|
||||||
|
void getTimeCallback(Control *sender, int type);
|
||||||
|
void graphAddCallback(Control *sender, int type);
|
||||||
|
void graphClearCallback(Control *sender, int type);
|
||||||
|
void randomString(char *buf, int len);
|
||||||
|
|
||||||
|
//UI handles
|
||||||
|
uint16_t wifi_ssid_text, wifi_pass_text;
|
||||||
|
uint16_t mainLabel, mainSwitcher, mainSlider, mainText, mainNumber, mainScrambleButton, mainTime;
|
||||||
|
uint16_t styleButton, styleLabel, styleSwitcher, styleSlider, styleButton2, styleLabel2, styleSlider2;
|
||||||
|
uint16_t graph;
|
||||||
|
volatile bool updates = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This is the main function which builds our GUI
|
||||||
|
void setUpUI() {
|
||||||
|
//Turn off verbose debugging
|
||||||
|
ESPUI.setVerbosity(Verbosity::Quiet);
|
||||||
|
|
||||||
|
//Make sliders continually report their position as they are being dragged.
|
||||||
|
ESPUI.sliderContinuous = true;
|
||||||
|
|
||||||
|
//This GUI is going to be a tabbed GUI, so we are adding most controls using ESPUI.addControl
|
||||||
|
//which allows us to set a parent control. If we didn't need tabs we could use the simpler add
|
||||||
|
//functions like:
|
||||||
|
// ESPUI.button()
|
||||||
|
// ESPUI.label()
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tab: Basic Controls
|
||||||
|
* This tab contains all the basic ESPUI controls, and shows how to read and update them at runtime.
|
||||||
|
*-----------------------------------------------------------------------------------------------------------*/
|
||||||
|
auto maintab = ESPUI.addControl(Tab, "", "Basic controls");
|
||||||
|
|
||||||
|
ESPUI.addControl(Separator, "General controls", "", None, maintab);
|
||||||
|
ESPUI.addControl(Button, "Button", "Button 1", Alizarin, maintab, generalCallback);
|
||||||
|
mainLabel = ESPUI.addControl(Label, "Label", "Label text", Emerald, maintab, generalCallback);
|
||||||
|
mainSwitcher = ESPUI.addControl(Switcher, "Switcher", "", Sunflower, maintab, generalCallback);
|
||||||
|
|
||||||
|
//Sliders default to being 0 to 100, but if you want different limits you can add a Min and Max control
|
||||||
|
mainSlider = ESPUI.addControl(Slider, "Slider", "200", Turquoise, maintab, generalCallback);
|
||||||
|
ESPUI.addControl(Min, "", "10", None, mainSlider);
|
||||||
|
ESPUI.addControl(Max, "", "400", None, mainSlider);
|
||||||
|
|
||||||
|
//These are the values for the selector's options. (Note that they *must* be declared static
|
||||||
|
//so that the storage is allocated in global memory and not just on the stack of this function.)
|
||||||
|
static String optionValues[] {"Value 1", "Value 2", "Value 3", "Value 4", "Value 5"};
|
||||||
|
auto mainselector = ESPUI.addControl(Select, "Selector", "Selector", Wetasphalt, maintab, generalCallback);
|
||||||
|
for(auto const& v : optionValues) {
|
||||||
|
ESPUI.addControl(Option, v.c_str(), v, None, mainselector);
|
||||||
|
}
|
||||||
|
|
||||||
|
mainText = ESPUI.addControl(Text, "Text Input", "Initial value", Alizarin, maintab, generalCallback);
|
||||||
|
|
||||||
|
//Number inputs also accept Min and Max components, but you should still validate the values.
|
||||||
|
mainNumber = ESPUI.addControl(Number, "Number Input", "42", Emerald, maintab, generalCallback);
|
||||||
|
ESPUI.addControl(Min, "", "10", None, mainNumber);
|
||||||
|
ESPUI.addControl(Max, "", "50", None, mainNumber);
|
||||||
|
|
||||||
|
ESPUI.addControl(Separator, "Updates", "", None, maintab);
|
||||||
|
|
||||||
|
//This button will update all the updatable controls on this tab to random values
|
||||||
|
mainScrambleButton = ESPUI.addControl(Button, "Scramble Values", "Scramble Values", Carrot, maintab, scrambleCallback);
|
||||||
|
ESPUI.addControl(Switcher, "Constant updates", "0", Carrot, maintab, updateCallback);
|
||||||
|
mainTime = ESPUI.addControl(Time, "", "", None, 0, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Get Time", "Get Time", Carrot, maintab, getTimeCallback);
|
||||||
|
|
||||||
|
ESPUI.addControl(Separator, "Control Pads", "", None, maintab);
|
||||||
|
ESPUI.addControl(Pad, "Normal", "", Peterriver, maintab, generalCallback);
|
||||||
|
ESPUI.addControl(PadWithCenter, "With center", "", Peterriver, maintab, generalCallback);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tab: Colours
|
||||||
|
* This tab shows all the basic colours
|
||||||
|
*-----------------------------------------------------------------------------------------------------------*/
|
||||||
|
auto colourtab = ESPUI.addControl(Tab, "", "Colours");
|
||||||
|
ESPUI.addControl(Button, "Alizarin", "Alizarin", Alizarin, colourtab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Turquoise", "Turquoise", Turquoise, colourtab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Emerald", "Emerald", Emerald, colourtab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Peterriver", "Peterriver", Peterriver, colourtab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Wetasphalt", "Wetasphalt", Wetasphalt, colourtab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Sunflower", "Sunflower", Sunflower, colourtab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Carrot", "Carrot", Carrot, colourtab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Dark", "Dark", Dark, colourtab, generalCallback);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tab: Styled controls
|
||||||
|
* This tab shows off how inline CSS styles can be applied to elements and panels in order
|
||||||
|
* to customise the look of the UI.
|
||||||
|
*-----------------------------------------------------------------------------------------------------------*/
|
||||||
|
auto styletab = ESPUI.addControl(Tab, "", "Styled controls");
|
||||||
|
styleButton = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback);
|
||||||
|
styleLabel = ESPUI.addControl(Label, "Styled Label", "This is a label", Alizarin, styletab, generalCallback);
|
||||||
|
styleSwitcher = ESPUI.addControl(Switcher, "Styled Switcher", "1", Alizarin, styletab, generalCallback);
|
||||||
|
styleSlider = ESPUI.addControl(Slider, "Styled Slider", "0", Alizarin, styletab, generalCallback);
|
||||||
|
|
||||||
|
//This button will randomise the colours of the above controls to show updating of inline styles
|
||||||
|
ESPUI.addControl(Button, "Randomise Colours", "Randomise Colours", Sunflower, styletab, styleCallback);
|
||||||
|
|
||||||
|
ESPUI.addControl(Separator, "Other styling examples", "", None, styletab);
|
||||||
|
styleButton2 = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback);
|
||||||
|
ESPUI.setPanelStyle(styleButton2, "background: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%, rgba(252,176,69,1) 100%); border-bottom: #555;");
|
||||||
|
ESPUI.setElementStyle(styleButton2, "border-radius: 2em; border: 3px solid black; width: 30%; background-color: #8df;");
|
||||||
|
|
||||||
|
styleSlider2 = ESPUI.addControl(Slider, "Styled Slider", "0", Dark, styletab, generalCallback);
|
||||||
|
ESPUI.setElementStyle(styleSlider2, "background: linear-gradient(to right, red, orange, yellow, green, blue);");
|
||||||
|
|
||||||
|
styleLabel2 = ESPUI.addControl(Label, "Styled Label", "This is a label", Dark, styletab, generalCallback);
|
||||||
|
ESPUI.setElementStyle(styleLabel2, "text-shadow: 3px 3px #74b1ff, 6px 6px #c64ad7; font-size: 60px; font-variant-caps: small-caps; background-color: unset; color: #c4f0bb; -webkit-text-stroke: 1px black;");
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tab: Grouped controls
|
||||||
|
* This tab shows how multiple control can be grouped into the same panel through the use of the
|
||||||
|
* parentControl value. This also shows how to add labels to grouped controls, and how to use vertical controls.
|
||||||
|
*-----------------------------------------------------------------------------------------------------------*/
|
||||||
|
auto grouptab = ESPUI.addControl(Tab, "", "Grouped controls");
|
||||||
|
|
||||||
|
//The parent of this button is a tab, so it will create a new panel with one control.
|
||||||
|
auto groupbutton = ESPUI.addControl(Button, "Button Group", "Button A", Dark, grouptab, generalCallback);
|
||||||
|
//However the parent of this button is another control, so therefore no new panel is
|
||||||
|
//created and the button is added to the existing panel.
|
||||||
|
ESPUI.addControl(Button, "", "Button B", Alizarin, groupbutton, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "", "Button C", Alizarin, groupbutton, generalCallback);
|
||||||
|
|
||||||
|
|
||||||
|
//Sliders can be grouped as well
|
||||||
|
//To label each slider in the group, we are going add additional labels and give them custom CSS styles
|
||||||
|
//We need this CSS style rule, which will remove the label's background and ensure that it takes up the entire width of the panel
|
||||||
|
String clearLabelStyle = "background-color: unset; width: 100%;";
|
||||||
|
//First we add the main slider to create a panel
|
||||||
|
auto groupsliders = ESPUI.addControl(Slider, "Slider Group", "10", Dark, grouptab, generalCallback);
|
||||||
|
//Then we add a label and set its style to the clearLabelStyle. Here we've just given it the name "A"
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupsliders), clearLabelStyle);
|
||||||
|
//We can now continue to add additional sliders and labels
|
||||||
|
ESPUI.addControl(Slider, "", "20", None, groupsliders, generalCallback);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupsliders), clearLabelStyle);
|
||||||
|
ESPUI.addControl(Slider, "", "30", None, groupsliders, generalCallback);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupsliders), clearLabelStyle);
|
||||||
|
|
||||||
|
//We can also usefully group switchers.
|
||||||
|
auto groupswitcher = ESPUI.addControl(Switcher, "Switcher Group", "0", Dark, grouptab, generalCallback);
|
||||||
|
ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback);
|
||||||
|
ESPUI.addControl(Switcher, "", "0", Sunflower, groupswitcher, generalCallback);
|
||||||
|
ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback);
|
||||||
|
//To label these switchers we need to first go onto a "new line" below the line of switchers
|
||||||
|
//To do this we add an empty label set to be clear and full width (with our clearLabelStyle)
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, groupswitcher), clearLabelStyle);
|
||||||
|
//We will now need another label style. This one sets its width to the same as a switcher (and turns off the background)
|
||||||
|
String switcherLabelStyle = "width: 60px; margin-left: .3rem; margin-right: .3rem; background-color: unset;";
|
||||||
|
//We can now just add the styled labels.
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupswitcher), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupswitcher), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupswitcher), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, groupswitcher), switcherLabelStyle);
|
||||||
|
|
||||||
|
//You can mix and match different control types, but the results might sometimes
|
||||||
|
//need additional styling to lay out nicely.
|
||||||
|
auto grouplabel = ESPUI.addControl(Label, "Mixed Group", "Main label", Dark, grouptab);
|
||||||
|
auto grouplabel2 = ESPUI.addControl(Label, "", "Secondary label", Emerald, grouplabel);
|
||||||
|
ESPUI.addControl(Button, "", "Button D", Alizarin, grouplabel, generalCallback);
|
||||||
|
ESPUI.addControl(Switcher, "", "1", Sunflower, grouplabel, generalCallback);
|
||||||
|
ESPUI.setElementStyle(grouplabel2, "font-size: x-large; font-family: serif;");
|
||||||
|
|
||||||
|
//Some controls can even support vertical orientation, currently Switchers and Sliders
|
||||||
|
ESPUI.addControl(Separator, "Vertical controls", "", None, grouptab);
|
||||||
|
auto vertgroupswitcher = ESPUI.addControl(Switcher, "Vertical Switcher Group", "0", Dark, grouptab, generalCallback);
|
||||||
|
ESPUI.setVertical(vertgroupswitcher);
|
||||||
|
//On the following lines we wrap the value returned from addControl and send it straight to setVertical
|
||||||
|
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
|
||||||
|
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
|
||||||
|
ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback));
|
||||||
|
//The mechanism for labelling vertical switchers is the same as we used above for horizontal ones
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupswitcher), clearLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupswitcher), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupswitcher), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupswitcher), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupswitcher), switcherLabelStyle);
|
||||||
|
|
||||||
|
auto vertgroupslider = ESPUI.addControl(Slider, "Vertical Slider Group", "15", Dark, grouptab, generalCallback);
|
||||||
|
ESPUI.setVertical(vertgroupslider);
|
||||||
|
ESPUI.setVertical(ESPUI.addControl(Slider, "", "25", None, vertgroupslider, generalCallback));
|
||||||
|
ESPUI.setVertical(ESPUI.addControl(Slider, "", "35", None, vertgroupslider, generalCallback));
|
||||||
|
ESPUI.setVertical(ESPUI.addControl(Slider, "", "45", None, vertgroupslider, generalCallback));
|
||||||
|
//The mechanism for labelling vertical sliders is the same as we used above for switchers
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupslider), clearLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupslider), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupslider), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupslider), switcherLabelStyle);
|
||||||
|
ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupslider), switcherLabelStyle);
|
||||||
|
|
||||||
|
//Note that combining vertical and horizontal sliders is going to result in very messy layout!
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tab: Example UI
|
||||||
|
* An example UI for the documentation
|
||||||
|
*-----------------------------------------------------------------------------------------------------------*/
|
||||||
|
auto exampletab = ESPUI.addControl(Tab, "Example", "Example");
|
||||||
|
ESPUI.addControl(Separator, "Control and Status", "", None, exampletab);
|
||||||
|
ESPUI.addControl(Switcher, "Power", "1", Alizarin, exampletab, generalCallback);
|
||||||
|
ESPUI.addControl(Label, "Status", "System status: OK", Wetasphalt, exampletab, generalCallback);
|
||||||
|
|
||||||
|
ESPUI.addControl(Separator, "Settings", "", None, exampletab);
|
||||||
|
ESPUI.addControl(PadWithCenter, "Attitude Control", "", Dark, exampletab, generalCallback);
|
||||||
|
auto examplegroup1 = ESPUI.addControl(Button, "Activate Features", "Feature A", Carrot, exampletab, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Activate Features", "Feature B", Carrot, examplegroup1, generalCallback);
|
||||||
|
ESPUI.addControl(Button, "Activate Features", "Feature C", Carrot, examplegroup1, generalCallback);
|
||||||
|
ESPUI.addControl(Slider, "Value control", "45", Peterriver, exampletab, generalCallback);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tab: WiFi Credentials
|
||||||
|
* You use this tab to enter the SSID and password of a wifi network to autoconnect to.
|
||||||
|
*-----------------------------------------------------------------------------------------------------------*/
|
||||||
|
auto wifitab = ESPUI.addControl(Tab, "", "WiFi Credentials");
|
||||||
|
wifi_ssid_text = ESPUI.addControl(Text, "SSID", "", Alizarin, wifitab, textCallback);
|
||||||
|
//Note that adding a "Max" control to a text control sets the max length
|
||||||
|
ESPUI.addControl(Max, "", "32", None, wifi_ssid_text);
|
||||||
|
wifi_pass_text = ESPUI.addControl(Text, "Password", "", Alizarin, wifitab, textCallback);
|
||||||
|
ESPUI.addControl(Max, "", "64", None, wifi_pass_text);
|
||||||
|
ESPUI.addControl(Button, "Save", "Save", Peterriver, wifitab, enterWifiDetailsCallback);
|
||||||
|
|
||||||
|
|
||||||
|
//Finally, start up the UI.
|
||||||
|
//This should only be called once we are connected to WiFi.
|
||||||
|
ESPUI.begin(HOSTNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This callback generates and applies inline styles to a bunch of controls to change their colour.
|
||||||
|
//The styles created are of the form:
|
||||||
|
// "border-bottom: #999 3px solid; background-color: #aabbcc;"
|
||||||
|
// "background-color: #aabbcc;"
|
||||||
|
void styleCallback(Control *sender, int type) {
|
||||||
|
//Declare space for style strings. These have to be static so that they are always available
|
||||||
|
//to the websocket layer. If we'd not made them static they'd be allocated on the heap and
|
||||||
|
//will be unavailable when we leave this function.
|
||||||
|
static char stylecol1[60], stylecol2[30];
|
||||||
|
if(type == B_UP) {
|
||||||
|
//Generate two random HTML hex colour codes, and print them into CSS style rules
|
||||||
|
sprintf(stylecol1, "border-bottom: #999 3px solid; background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
|
||||||
|
sprintf(stylecol2, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF));
|
||||||
|
|
||||||
|
//Apply those styles to various elements to show how controls react to styling
|
||||||
|
ESPUI.setPanelStyle(styleButton, stylecol1);
|
||||||
|
ESPUI.setElementStyle(styleButton, stylecol2);
|
||||||
|
ESPUI.setPanelStyle(styleLabel, stylecol1);
|
||||||
|
ESPUI.setElementStyle(styleLabel, stylecol2);
|
||||||
|
ESPUI.setPanelStyle(styleSwitcher, stylecol1);
|
||||||
|
ESPUI.setElementStyle(styleSwitcher, stylecol2);
|
||||||
|
ESPUI.setPanelStyle(styleSlider, stylecol1);
|
||||||
|
ESPUI.setElementStyle(styleSlider, stylecol2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//This callback updates the "values" of a bunch of controls
|
||||||
|
void scrambleCallback(Control *sender, int type) {
|
||||||
|
static char rndString1[10];
|
||||||
|
static char rndString2[20];
|
||||||
|
static bool scText = false;
|
||||||
|
|
||||||
|
if(type == B_UP) { //Button callbacks generate events for both UP and DOWN.
|
||||||
|
//Generate some random text
|
||||||
|
randomString(rndString1, 10);
|
||||||
|
randomString(rndString2, 20);
|
||||||
|
|
||||||
|
//Set the various controls to random value to show how controls can be updated at runtime
|
||||||
|
ESPUI.updateLabel(mainLabel, String(rndString1));
|
||||||
|
ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true);
|
||||||
|
ESPUI.updateSlider(mainSlider, random(10, 400));
|
||||||
|
ESPUI.updateText(mainText, String(rndString2));
|
||||||
|
ESPUI.updateNumber(mainNumber, random(100000));
|
||||||
|
ESPUI.updateButton(mainScrambleButton, scText ? "Scrambled!" : "Scrambled.");
|
||||||
|
scText = !scText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateCallback(Control *sender, int type) {
|
||||||
|
updates = (sender->value.toInt() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getTimeCallback(Control *sender, int type) {
|
||||||
|
if(type == B_UP) {
|
||||||
|
ESPUI.updateTime(mainTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphAddCallback(Control *sender, int type) {
|
||||||
|
if(type == B_UP) {
|
||||||
|
ESPUI.addGraphPoint(graph, random(1, 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphClearCallback(Control *sender, int type) {
|
||||||
|
if(type == B_UP) {
|
||||||
|
ESPUI.clearGraph(graph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Most elements in this test UI are assigned this generic callback which prints some
|
||||||
|
//basic information. Event types are defined in ESPUI.h
|
||||||
|
void generalCallback(Control *sender, int type) {
|
||||||
|
Serial.print("CB: id(");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(") Type(");
|
||||||
|
Serial.print(type);
|
||||||
|
Serial.print(") '");
|
||||||
|
Serial.print(sender->label);
|
||||||
|
Serial.print("' = ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
randomSeed(0);
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
if(SLOW_BOOT) delay(5000); //Delay booting to give time to connect a serial monitor
|
||||||
|
connectWifi();
|
||||||
|
#if defined(ESP32)
|
||||||
|
WiFi.setSleep(false); //For the ESP32: turn off sleeping to increase UI responsivness (at the cost of power use)
|
||||||
|
#endif
|
||||||
|
setUpUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
static long unsigned lastTime = 0;
|
||||||
|
|
||||||
|
//Send periodic updates if switcher is turned on
|
||||||
|
if(updates && millis() > lastTime + 500) {
|
||||||
|
static uint16_t sliderVal = 10;
|
||||||
|
|
||||||
|
//Flick this switcher on and off
|
||||||
|
ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true);
|
||||||
|
sliderVal += 10;
|
||||||
|
if(sliderVal > 400) sliderVal = 10;
|
||||||
|
|
||||||
|
//Sliders, numbers, and labels can all be updated at will
|
||||||
|
ESPUI.updateSlider(mainSlider, sliderVal);
|
||||||
|
ESPUI.updateNumber(mainNumber, random(100000));
|
||||||
|
ESPUI.updateLabel(mainLabel, String(sliderVal));
|
||||||
|
lastTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Simple debug UART interface
|
||||||
|
if(Serial.available()) {
|
||||||
|
switch(Serial.read()) {
|
||||||
|
case 'w': //Print IP details
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
break;
|
||||||
|
case 'W': //Reconnect wifi
|
||||||
|
connectWifi();
|
||||||
|
break;
|
||||||
|
case 'C': //Force a crash (for testing exception decoder)
|
||||||
|
#if !defined(ESP32)
|
||||||
|
((void (*)())0xf00fdead)();
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
Serial.print('#');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(ESP32)
|
||||||
|
//We don't need to call this explicitly on ESP32 but we do on 8266
|
||||||
|
MDNS.update();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Utilities
|
||||||
|
//
|
||||||
|
//If you are here just to see examples of how to use ESPUI, you can ignore the following functions
|
||||||
|
//------------------------------------------------------------------------------------------------
|
||||||
|
void readStringFromEEPROM(String& buf, int baseaddress, int size) {
|
||||||
|
buf.reserve(size);
|
||||||
|
for (int i = baseaddress; i < baseaddress+size; i++) {
|
||||||
|
char c = EEPROM.read(i);
|
||||||
|
buf += c;
|
||||||
|
if(!c) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectWifi() {
|
||||||
|
int connect_timeout;
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
WiFi.setHostname(HOSTNAME);
|
||||||
|
#else
|
||||||
|
WiFi.hostname(HOSTNAME);
|
||||||
|
#endif
|
||||||
|
Serial.println("Begin wifi...");
|
||||||
|
|
||||||
|
//Load credentials from EEPROM
|
||||||
|
if(!(FORCE_USE_HOTSPOT)) {
|
||||||
|
yield();
|
||||||
|
EEPROM.begin(100);
|
||||||
|
String stored_ssid, stored_pass;
|
||||||
|
readStringFromEEPROM(stored_ssid, 0, 32);
|
||||||
|
readStringFromEEPROM(stored_pass, 32, 96);
|
||||||
|
EEPROM.end();
|
||||||
|
|
||||||
|
//Try to connect with stored credentials, fire up an access point if they don't work.
|
||||||
|
#if defined(ESP32)
|
||||||
|
WiFi.begin(stored_ssid.c_str(), stored_pass.c_str());
|
||||||
|
#else
|
||||||
|
WiFi.begin(stored_ssid, stored_pass);
|
||||||
|
#endif
|
||||||
|
connect_timeout = 28; //7 seconds
|
||||||
|
while (WiFi.status() != WL_CONNECTED && connect_timeout > 0) {
|
||||||
|
delay(250);
|
||||||
|
Serial.print(".");
|
||||||
|
connect_timeout--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
Serial.println("Wifi started");
|
||||||
|
|
||||||
|
if (!MDNS.begin(HOSTNAME)) {
|
||||||
|
Serial.println("Error setting up MDNS responder!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Serial.println("\nCreating access point...");
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
|
||||||
|
WiFi.softAP(HOSTNAME);
|
||||||
|
|
||||||
|
connect_timeout = 20;
|
||||||
|
do {
|
||||||
|
delay(250);
|
||||||
|
Serial.print(",");
|
||||||
|
connect_timeout--;
|
||||||
|
} while(connect_timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void enterWifiDetailsCallback(Control *sender, int type) {
|
||||||
|
if(type == B_UP) {
|
||||||
|
Serial.println("Saving credentials to EPROM...");
|
||||||
|
Serial.println(ESPUI.getControl(wifi_ssid_text)->value);
|
||||||
|
Serial.println(ESPUI.getControl(wifi_pass_text)->value);
|
||||||
|
unsigned int i;
|
||||||
|
EEPROM.begin(100);
|
||||||
|
for(i = 0; i < ESPUI.getControl(wifi_ssid_text)->value.length(); i++) {
|
||||||
|
EEPROM.write(i, ESPUI.getControl(wifi_ssid_text)->value.charAt(i));
|
||||||
|
if(i==30) break; //Even though we provided a max length, user input should never be trusted
|
||||||
|
}
|
||||||
|
EEPROM.write(i, '\0');
|
||||||
|
|
||||||
|
for(i = 0; i < ESPUI.getControl(wifi_pass_text)->value.length(); i++) {
|
||||||
|
EEPROM.write(i + 32, ESPUI.getControl(wifi_pass_text)->value.charAt(i));
|
||||||
|
if(i==94) break; //Even though we provided a max length, user input should never be trusted
|
||||||
|
}
|
||||||
|
EEPROM.write(i + 32, '\0');
|
||||||
|
EEPROM.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void textCallback(Control *sender, int type) {
|
||||||
|
//This callback is needed to handle the changed values, even though it doesn't do anything itself.
|
||||||
|
}
|
||||||
|
|
||||||
|
void randomString(char *buf, int len) {
|
||||||
|
for(auto i = 0; i < len-1; i++)
|
||||||
|
buf[i] = random(0, 26) + 'A';
|
||||||
|
buf[len-1] = '\0';
|
||||||
|
}
|
299
examples/gui-generic-api/gui-generic-api.ino
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
#include <DNSServer.h>
|
||||||
|
#include <ESPUI.h>
|
||||||
|
|
||||||
|
const byte DNS_PORT = 53;
|
||||||
|
IPAddress apIP(192, 168, 4, 1);
|
||||||
|
DNSServer dnsServer;
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#else
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = "ESPUI";
|
||||||
|
const char* password = "espui";
|
||||||
|
const char* hostname = "espui";
|
||||||
|
|
||||||
|
uint16_t status;
|
||||||
|
uint16_t button1;
|
||||||
|
uint16_t millisLabelId;
|
||||||
|
uint16_t switchOne;
|
||||||
|
|
||||||
|
void numberCall(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void textCall(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.print("Text: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void slider(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.print("Slider: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonCallback(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Button DOWN");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Button UP");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonExample(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Status: Start");
|
||||||
|
ESPUI.updateControlValue(status, "Start");
|
||||||
|
|
||||||
|
ESPUI.getControl(button1)->color = ControlColor::Carrot;
|
||||||
|
ESPUI.updateControl(button1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Status: Stop");
|
||||||
|
ESPUI.updateControlValue(status, "Stop");
|
||||||
|
|
||||||
|
ESPUI.getControl(button1)->color = ControlColor::Peterriver;
|
||||||
|
ESPUI.updateControl(button1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void padExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case P_LEFT_DOWN:
|
||||||
|
Serial.print("left down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_LEFT_UP:
|
||||||
|
Serial.print("left up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_DOWN:
|
||||||
|
Serial.print("right down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_UP:
|
||||||
|
Serial.print("right up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_DOWN:
|
||||||
|
Serial.print("for down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_UP:
|
||||||
|
Serial.print("for up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_DOWN:
|
||||||
|
Serial.print("back down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_UP:
|
||||||
|
Serial.print("back up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_DOWN:
|
||||||
|
Serial.print("center down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_UP:
|
||||||
|
Serial.print("center up");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void switchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
Serial.print("Select: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void otherSwitchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void)
|
||||||
|
{
|
||||||
|
ESPUI.setVerbosity(Verbosity::VerboseJSON);
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
WiFi.setHostname(hostname);
|
||||||
|
#else
|
||||||
|
WiFi.hostname(hostname);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// try to connect to existing network
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.print("\n\nTry to connect to existing network");
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t timeout = 10;
|
||||||
|
|
||||||
|
// Wait for connection, 5s timeout
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout && WiFi.status() != WL_CONNECTED);
|
||||||
|
|
||||||
|
// not connected -> create hotspot
|
||||||
|
if (WiFi.status() != WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.print("\n\nCreating hotspot");
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
delay(100);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
#if defined(ESP32)
|
||||||
|
uint32_t chipid = 0;
|
||||||
|
for (int i = 0; i < 17; i = i + 8)
|
||||||
|
{
|
||||||
|
chipid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
uint32_t chipid = ESP.getChipId();
|
||||||
|
#endif char ap_ssid[25];
|
||||||
|
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
|
||||||
|
WiFi.softAP(ap_ssid);
|
||||||
|
|
||||||
|
timeout = 5;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.start(DNS_PORT, "*", apIP);
|
||||||
|
|
||||||
|
Serial.println("\n\nWiFi parameters:");
|
||||||
|
Serial.print("Mode: ");
|
||||||
|
Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
||||||
|
|
||||||
|
status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
|
||||||
|
|
||||||
|
uint16_t select1 = ESPUI.addControl(
|
||||||
|
ControlType::Select, "Select:", "", ControlColor::Alizarin, Control::noParent, &selectExample);
|
||||||
|
|
||||||
|
ESPUI.addControl(ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1);
|
||||||
|
ESPUI.addControl(ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1);
|
||||||
|
ESPUI.addControl(ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1);
|
||||||
|
|
||||||
|
ESPUI.addControl(
|
||||||
|
ControlType::Text, "Text Test:", "a Text Field", ControlColor::Alizarin, Control::noParent, &textCall);
|
||||||
|
|
||||||
|
millisLabelId = ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, Control::noParent);
|
||||||
|
button1 = ESPUI.addControl(
|
||||||
|
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, Control::noParent, &buttonCallback);
|
||||||
|
ESPUI.addControl(
|
||||||
|
ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, Control::noParent, &buttonExample);
|
||||||
|
ESPUI.addControl(
|
||||||
|
ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, Control::noParent, &padExample);
|
||||||
|
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, Control::noParent, &padExample);
|
||||||
|
switchOne = ESPUI.addControl(
|
||||||
|
ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, Control::noParent, &switchExample);
|
||||||
|
ESPUI.addControl(
|
||||||
|
ControlType::Switcher, "Switch two", "", ControlColor::None, Control::noParent, &otherSwitchExample);
|
||||||
|
ESPUI.addControl(ControlType::Slider, "Slider one", "30", ControlColor::Alizarin, Control::noParent, &slider);
|
||||||
|
ESPUI.addControl(ControlType::Slider, "Slider two", "100", ControlColor::Alizarin, Control::noParent, &slider);
|
||||||
|
ESPUI.addControl(ControlType::Number, "Number:", "50", ControlColor::Alizarin, Control::noParent, &numberCall);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* .begin loads and serves all files from PROGMEM directly.
|
||||||
|
* If you want to serve the files from LITTLEFS use ESPUI.beginLITTLEFS
|
||||||
|
* (.prepareFileSystem has to be run in an empty sketch before)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
|
||||||
|
// ESPUI.sliderContinuous = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
|
||||||
|
* SECURE way of limiting access.
|
||||||
|
* Anyone who is able to sniff traffic will be able to intercept your password
|
||||||
|
* since it is transmitted in cleartext. Just add a string as username and
|
||||||
|
* password, for example begin("ESPUI Control", "username", "password")
|
||||||
|
*/
|
||||||
|
|
||||||
|
ESPUI.begin("ESPUI Control");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void)
|
||||||
|
{
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
|
||||||
|
static long oldTime = 0;
|
||||||
|
static bool testSwitchState = false;
|
||||||
|
|
||||||
|
if (millis() - oldTime > 5000)
|
||||||
|
{
|
||||||
|
ESPUI.updateControlValue(millisLabelId, String(millis()));
|
||||||
|
testSwitchState = !testSwitchState;
|
||||||
|
ESPUI.updateControlValue(switchOne, testSwitchState ? "1" : "0");
|
||||||
|
|
||||||
|
oldTime = millis();
|
||||||
|
}
|
||||||
|
}
|
1
examples/gui/data/css/normalize.min.css
vendored
@ -1 +0,0 @@
|
|||||||
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
|
|
@ -1,765 +0,0 @@
|
|||||||
.container {
|
|
||||||
position: relative;
|
|
||||||
width: 79%;
|
|
||||||
margin: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column, .columns {
|
|
||||||
width: 100%;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin-top: 2%;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: 0 4px 4px rgba(204, 197, 185, 0.5);
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
min-width: 150px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-slider {
|
|
||||||
padding-bottom: 10px
|
|
||||||
}
|
|
||||||
|
|
||||||
.turquoise {
|
|
||||||
background: #1abc9c;
|
|
||||||
border-bottom: #16a085 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emerald {
|
|
||||||
background: #2ecc71;
|
|
||||||
border-bottom: #27ae60 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.peterriver {
|
|
||||||
background: #3498db;
|
|
||||||
border-bottom: #2980b9 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wetasphalt {
|
|
||||||
background: #34495e;
|
|
||||||
border-bottom: #2c3e50 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sunflower {
|
|
||||||
background: #f1c40f;
|
|
||||||
border-bottom: #E6BB0F 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carrot {
|
|
||||||
background: #e67e22;
|
|
||||||
border-bottom: #d35400 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alizarin {
|
|
||||||
background: #e74c3c;
|
|
||||||
border-bottom: #c0392b 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
box-sizing: border-box;
|
|
||||||
white-space: nowrap;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
padding: 0.12em 0.4em 0.14em;
|
|
||||||
text-align: center;
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
display: inline-block;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: baseline;
|
|
||||||
position: relative;
|
|
||||||
top: -0.15em;
|
|
||||||
background-color: #999999;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.color-blue {
|
|
||||||
background-color: #6f9ad1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.color-red {
|
|
||||||
background-color: #d37c7c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.color-green {
|
|
||||||
background-color: #9bc268;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.color-orange {
|
|
||||||
background-color: #dea154;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.color-yellow {
|
|
||||||
background-color: #e9d641;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.color-purple {
|
|
||||||
background-color: #9f83d1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For devices larger than 400px */
|
|
||||||
|
|
||||||
@media (min-width: 400px) {
|
|
||||||
.container {
|
|
||||||
width: 84%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For devices larger than 550px */
|
|
||||||
|
|
||||||
@media (min-width: 630px) {
|
|
||||||
.container {
|
|
||||||
width: 98%;
|
|
||||||
}
|
|
||||||
.column, .columns {
|
|
||||||
margin-right: 2%;
|
|
||||||
}
|
|
||||||
.column:first-child, .columns:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
.one.column, .one.columns {
|
|
||||||
width: 4.66666666667%;
|
|
||||||
}
|
|
||||||
.two.columns {
|
|
||||||
width: 13.3333333333%;
|
|
||||||
}
|
|
||||||
.three.columns {
|
|
||||||
width: 22%;
|
|
||||||
}
|
|
||||||
.four.columns {
|
|
||||||
width: 30.6666666667%;
|
|
||||||
}
|
|
||||||
.five.columns {
|
|
||||||
width: 39.3333333333%;
|
|
||||||
}
|
|
||||||
.six.columns {
|
|
||||||
width: 48%;
|
|
||||||
}
|
|
||||||
.seven.columns {
|
|
||||||
width: 56.6666666667%;
|
|
||||||
}
|
|
||||||
.eight.columns {
|
|
||||||
width: 65.3333333333%;
|
|
||||||
}
|
|
||||||
.nine.columns {
|
|
||||||
width: 74.0%;
|
|
||||||
}
|
|
||||||
.ten.columns {
|
|
||||||
width: 82.6666666667%;
|
|
||||||
}
|
|
||||||
.eleven.columns {
|
|
||||||
width: 91.3333333333%;
|
|
||||||
}
|
|
||||||
.twelve.columns {
|
|
||||||
width: 100%;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
.one-third.column {
|
|
||||||
width: 30.6666666667%;
|
|
||||||
}
|
|
||||||
.two-thirds.column {
|
|
||||||
width: 65.3333333333%;
|
|
||||||
}
|
|
||||||
.one-half.column {
|
|
||||||
width: 48%;
|
|
||||||
}
|
|
||||||
/* Offsets */
|
|
||||||
.offset-by-one.column, .offset-by-one.columns {
|
|
||||||
margin-left: 8.66666666667%;
|
|
||||||
}
|
|
||||||
.offset-by-two.column, .offset-by-two.columns {
|
|
||||||
margin-left: 17.3333333333%;
|
|
||||||
}
|
|
||||||
.offset-by-three.column, .offset-by-three.columns {
|
|
||||||
margin-left: 26%;
|
|
||||||
}
|
|
||||||
.offset-by-four.column, .offset-by-four.columns {
|
|
||||||
margin-left: 34.6666666667%;
|
|
||||||
}
|
|
||||||
.offset-by-five.column, .offset-by-five.columns {
|
|
||||||
margin-left: 43.3333333333%;
|
|
||||||
}
|
|
||||||
.offset-by-six.column, .offset-by-six.columns {
|
|
||||||
margin-left: 52%;
|
|
||||||
}
|
|
||||||
.offset-by-seven.column, .offset-by-seven.columns {
|
|
||||||
margin-left: 60.6666666667%;
|
|
||||||
}
|
|
||||||
.offset-by-eight.column, .offset-by-eight.columns {
|
|
||||||
margin-left: 69.3333333333%;
|
|
||||||
}
|
|
||||||
.offset-by-nine.column, .offset-by-nine.columns {
|
|
||||||
margin-left: 78.0%;
|
|
||||||
}
|
|
||||||
.offset-by-ten.column, .offset-by-ten.columns {
|
|
||||||
margin-left: 86.6666666667%;
|
|
||||||
}
|
|
||||||
.offset-by-eleven.column, .offset-by-eleven.columns {
|
|
||||||
margin-left: 95.3333333333%;
|
|
||||||
}
|
|
||||||
.offset-by-one-third.column, .offset-by-one-third.columns {
|
|
||||||
margin-left: 34.6666666667%;
|
|
||||||
}
|
|
||||||
.offset-by-two-thirds.column, .offset-by-two-thirds.columns {
|
|
||||||
margin-left: 69.3333333333%;
|
|
||||||
}
|
|
||||||
.offset-by-one-half.column, .offset-by-one-half.columns {
|
|
||||||
margin-left: 52%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Base Styles
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 62.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.5em;
|
|
||||||
line-height: 1.0;
|
|
||||||
font-weight: 400;
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
color: #222;
|
|
||||||
background-color: #ecf0f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Typography
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 4.0rem;
|
|
||||||
line-height: 1.2;
|
|
||||||
letter-spacing: -.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 3.6rem;
|
|
||||||
line-height: 1.25;
|
|
||||||
letter-spacing: -.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 3.0rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
letter-spacing: -.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 2.4rem;
|
|
||||||
line-height: 1.35;
|
|
||||||
letter-spacing: -.08rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 1.8rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
letter-spacing: -.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
letter-spacing: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Larger than phablet */
|
|
||||||
|
|
||||||
@media (min-width: 630px) {
|
|
||||||
h1 {
|
|
||||||
font-size: 5.0rem;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 4.2rem;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 3.6rem;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-size: 3.0rem;
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
font-size: 2.0rem;
|
|
||||||
}
|
|
||||||
h6 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Links
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #1EAEDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #0FA0CE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
button {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Head Part
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
#mainHeader {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#conStatus {
|
|
||||||
position: inherit;
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Spacing
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
button, .button {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Utilities
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
.u-full-width {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.u-max-full-width {
|
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.u-pull-right {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.u-pull-left {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tcenter {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Misc
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 1.2rem;
|
|
||||||
border-width: 0;
|
|
||||||
border-top: 1px solid #E1E1E1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clearing
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
/* Self Clearing Goodness */
|
|
||||||
|
|
||||||
.container:after, .row:after, .u-cf {
|
|
||||||
content: "";
|
|
||||||
display: table;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ButtonPad
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
.control {
|
|
||||||
background-color: #ddd;
|
|
||||||
background-image: linear-gradient(hsla(0, 0%, 0%, .1), hsla(0, 0%, 100%, .1));
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: inset 0 1px 1px 1px hsla(0, 0%, 100%, .5), 0 0 1px 1px hsla(0, 0%, 100%, .75), 0 0 1px 2px hsla(0, 0%, 100%, .25), 0 0 1px 3px hsla(0, 0%, 100%, .25), 0 0 1px 4px hsla(0, 0%, 100%, .25), 0 0 1px 6px hsla(0, 0%, 0%, .75);
|
|
||||||
height: 9em;
|
|
||||||
margin: 3em auto;
|
|
||||||
position: relative;
|
|
||||||
width: 9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control ul {
|
|
||||||
height: 100%;
|
|
||||||
padding: 0;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.control li {
|
|
||||||
border-radius: 100% 0 0 0;
|
|
||||||
box-shadow: inset -1px -1px 1px hsla(0, 0%, 100%, .5), 0 0 1px hsla(0, 0%, 0%, .75);
|
|
||||||
display: inline-block;
|
|
||||||
height: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control ul li:nth-child(2) {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.control ul li:nth-child(3) {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.control ul li:nth-child(4) {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.control ul a {
|
|
||||||
height: 200%;
|
|
||||||
position: relative;
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
width: 200%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control a:hover, .control a:focus {
|
|
||||||
background-color: hsla(0, 0%, 100%, .25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.control a {
|
|
||||||
border-radius: 50%;
|
|
||||||
color: #333;
|
|
||||||
display: block;
|
|
||||||
font: bold 1em/3 sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: 0 1px 1px hsla(0, 0%, 100%, .4);
|
|
||||||
transition: .15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control .confirm {
|
|
||||||
background-color: #ddd;
|
|
||||||
background-image: linear-gradient(hsla(0, 0%, 0%, .15), hsla(0, 0%, 100%, .25));
|
|
||||||
box-shadow: inset 0 1px 1px 1px hsla(0, 0%, 100%, .5), 0 0 1px 1px hsla(0, 0%, 100%, .25), 0 0 1px 2px hsla(0, 0%, 100%, .25), 0 0 1px 3px hsla(0, 0%, 100%, .25), 0 0 1px 4px hsla(0, 0%, 100%, .25), 0 0 1px 6px hsla(0, 0%, 0%, .85);
|
|
||||||
left: 50%;
|
|
||||||
line-height: 3;
|
|
||||||
margin: -1.5em;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control .confirm:hover, .control .confirm:focus {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Switch
|
|
||||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
||||||
|
|
||||||
.switch {
|
|
||||||
display: inline-block !important;
|
|
||||||
background-color: #bebebe;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
font-size: 14px;
|
|
||||||
height: 26px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
position: relative;
|
|
||||||
width: 60px;
|
|
||||||
-webkit-transition: background-color 0.2s ease-in-out;
|
|
||||||
-moz-transition: background-color 0.2s ease-in-out;
|
|
||||||
-o-transition: background-color 0.2s ease-in-out;
|
|
||||||
-ms-transition: background-color 0.2s ease-in-out;
|
|
||||||
transition: background-color 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch.checked {
|
|
||||||
background-color: #76d21d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input[type="checkbox"] {
|
|
||||||
cursor: pointer;
|
|
||||||
height: 10px;
|
|
||||||
left: 12px;
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
-webkit-transition: left 0.05s ease-in-out;
|
|
||||||
-moz-transition: left 0.05s ease-in-out;
|
|
||||||
-o-transition: left 0.05s ease-in-out;
|
|
||||||
-ms-transition: left 0.05s ease-in-out;
|
|
||||||
transition: left 0.05s ease-in-out;
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch.checked input[type="checkbox"] {
|
|
||||||
left: 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input:before {
|
|
||||||
background: #fff;
|
|
||||||
background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%);
|
|
||||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #f0f0f0));
|
|
||||||
background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%);
|
|
||||||
background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%);
|
|
||||||
background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%);
|
|
||||||
background: linear-gradient(to bottom, #fff 0%, #f0f0f0 100%);
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
|
|
||||||
content: '';
|
|
||||||
height: 18px;
|
|
||||||
position: absolute;
|
|
||||||
top: -5px;
|
|
||||||
left: -9px;
|
|
||||||
width: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input:after {
|
|
||||||
background: #f0f0f0;
|
|
||||||
background: -moz-linear-gradient(top, #f0f0f0 0%, #fff 100%);
|
|
||||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f0f0f0), color-stop(100%, #fff));
|
|
||||||
background: -webkit-linear-gradient(top, #f0f0f0 0%, #fff 100%);
|
|
||||||
background: -o-linear-gradient(top, #f0f0f0 0%, #fff 100%);
|
|
||||||
background: -ms-linear-gradient(top, #f0f0f0 0%, #fff 100%);
|
|
||||||
background: linear-gradient(to bottom, #f0f0f0 0%, #fff 100%);
|
|
||||||
border-radius: 10px;
|
|
||||||
content: '';
|
|
||||||
height: 12px;
|
|
||||||
margin: -1px 0 0 -1px;
|
|
||||||
position: absolute;
|
|
||||||
width: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch .icon-ok, .switch .icon-remove {
|
|
||||||
line-height: 28px;
|
|
||||||
text-shadow: 0 -2px 0 rgba(0, 0, 0, 0.2);
|
|
||||||
margin: 0 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch .icon-ok {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch .icon-remove {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
Material Design Range Slider - by Ravikumar Chauhan
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
.rkmd-slider {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: 'Roboto', sans-serif;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"] {
|
|
||||||
overflow: hidden;
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"] + .slider {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
background-color: #bebebe;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"] + .slider .slider-fill {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
width: 0%;
|
|
||||||
height: 100%;
|
|
||||||
user-select: none;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"] + .slider .slider-handle {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: -5.5px;
|
|
||||||
left: 0%;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
margin-left: -8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all .2s ease;
|
|
||||||
user-select: none;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider {
|
|
||||||
background-color: #b0b0b0 !important;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-fill,
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-handle {
|
|
||||||
cursor: default !important;
|
|
||||||
background-color: #b0b0b0 !important;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-fill .slider-label,
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-handle .slider-label {
|
|
||||||
display: none;
|
|
||||||
background-color: #b0b0b0 !important;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-fill.is-active,
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-handle.is-active {
|
|
||||||
top: -5.5px;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
margin-left: -8px;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-fill.is-active .slider-label,
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-handle.is-active .slider-label {
|
|
||||||
display: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
.rkmd-slider input[type="range"]:disabled + .slider .slider-handle:active {
|
|
||||||
box-shadow: none !important;
|
|
||||||
transform: scale(1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
|
||||||
Discrete Range Slider - by Ravikumar Chauhan
|
|
||||||
------------------------------------------------------------------------- */
|
|
||||||
.rkmd-slider.slider-discrete .slider .slider-handle {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete .slider .slider-handle .slider-label {
|
|
||||||
position: absolute;
|
|
||||||
top: -17.5px;
|
|
||||||
left: -2px;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
-webkit-transform-origin: 50% 100%;
|
|
||||||
transform-origin: 50% 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
-webkit-transform: scale(0.5) rotate(-45deg);
|
|
||||||
transform: scale(0.5) rotate(-45deg);
|
|
||||||
-webkit-transition: all .2s ease;
|
|
||||||
transition: all .2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete .slider .slider-handle .slider-label span {
|
|
||||||
position: absolute;
|
|
||||||
top: 7px;
|
|
||||||
left: 0px;
|
|
||||||
width: 100%;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
-webkit-transform: rotate(45deg);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete .slider .slider-handle.is-active {
|
|
||||||
top: 0px;
|
|
||||||
margin-left: -2px;
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
}
|
|
||||||
.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label {
|
|
||||||
top: -15px;
|
|
||||||
left: -2px;
|
|
||||||
border-radius: 15px 15px 15px 0;
|
|
||||||
-webkit-transform: rotate(-45deg) translate(23px, -25px);
|
|
||||||
transform: rotate(-45deg) translate(23px, -25px);
|
|
||||||
}
|
|
||||||
.rkmd-slider.slider-discrete .slider .slider-handle.is-active .slider-label span {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete.slider-turquoise .slider-label {
|
|
||||||
background-color: #16a085;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete.slider-emerald .slider-label {
|
|
||||||
background-color: #27ae60;
|
|
||||||
}
|
|
||||||
|
|
||||||
.peterriver {
|
|
||||||
background: #3498db;
|
|
||||||
border-bottom: #2980b9 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete.slider-peterriver .slider-label {
|
|
||||||
background-color: #2980b9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wetasphalt {
|
|
||||||
background: #34495e;
|
|
||||||
border-bottom: #2c3e50 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete.slider-wetasphalt .slider-label {
|
|
||||||
background-color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sunflower {
|
|
||||||
background: #f1c40f;
|
|
||||||
border-bottom: #E6BB0F 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete.slider-sunflower .slider-label {
|
|
||||||
background-color: #E6BB0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carrot {
|
|
||||||
background: #e67e22;
|
|
||||||
border-bottom: #d35400 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete.slider-carrot .slider-label {
|
|
||||||
background-color: #d35400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alizarin {
|
|
||||||
background: #e74c3c;
|
|
||||||
border-bottom: #c0392b 3px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rkmd-slider.slider-discrete.slider-alizarin .slider-label {
|
|
||||||
background-color: #c0392b;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
.rkmd-slider.slider-light input[type="range"] + .slider {
|
|
||||||
background-color: #5c5c5c;
|
|
||||||
}
|
|
||||||
.rkmd-slider.slider-light input[type="range"]:disabled + .slider {
|
|
||||||
background-color: #5c5c5c !important;
|
|
||||||
}
|
|
||||||
.rkmd-slider.slider-light input[type="range"]:disabled + .slider .slider-fill,
|
|
||||||
.rkmd-slider.slider-light input[type="range"]:disabled + .slider .slider-handle {
|
|
||||||
background-color: #5c5c5c !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
1
examples/gui/data/css/style.min.css
vendored
@ -1,28 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Control</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC"
|
|
||||||
/>
|
|
||||||
<link rel="stylesheet" href="/css/normalize.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
|
|
||||||
<script src="/js/zepto.min.js"></script>
|
|
||||||
<script src="/js/slider.js"></script>
|
|
||||||
<script src="/js/controls.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body onload="javascript:start();">
|
|
||||||
<div>
|
|
||||||
<h4><div id="mainHeader">Control</div> <span id="conStatus" class="label">Offline</span></h4></div>
|
|
||||||
<hr />
|
|
||||||
<div class="container">
|
|
||||||
<div id="row" class="row u-full-width">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
293
examples/gui/data/js/controls.js
vendored
@ -1,293 +0,0 @@
|
|||||||
const UI_TITEL = 0;
|
|
||||||
const UI_LABEL = 1;
|
|
||||||
const UI_BUTTON = 2;
|
|
||||||
const UI_SWITCHER = 3;
|
|
||||||
const UI_PAD = 4;
|
|
||||||
const UI_CPAD = 5;
|
|
||||||
const UPDATE_LABEL = 6;
|
|
||||||
const UPDATE_SWITCHER = 7;
|
|
||||||
const UI_SLIDER = 8;
|
|
||||||
const UPDATE_SLIDER = 9;
|
|
||||||
|
|
||||||
const FOR = 0;
|
|
||||||
const BACK = 1;
|
|
||||||
const LEFT = 2;
|
|
||||||
const RIGHT = 3;
|
|
||||||
const CENTER = 4;
|
|
||||||
|
|
||||||
// Colors
|
|
||||||
const C_TURQUOISE = 0;
|
|
||||||
const C_EMERALD = 1;
|
|
||||||
const C_PETERRIVER = 2;
|
|
||||||
const C_WETASPHALT = 3;
|
|
||||||
const C_SUNFLOWER = 4;
|
|
||||||
const C_CARROT = 5;
|
|
||||||
const C_ALIZARIN = 6;
|
|
||||||
const C_NONE = 7;
|
|
||||||
|
|
||||||
function colorClass(colorId) {
|
|
||||||
colorId = Number(colorId);
|
|
||||||
switch (colorId) {
|
|
||||||
case C_TURQUOISE:
|
|
||||||
return "turquoise";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case C_EMERALD:
|
|
||||||
return "emerald";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case C_PETERRIVER:
|
|
||||||
return "peterriver";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case C_WETASPHALT:
|
|
||||||
return "wetasphalt";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case C_SUNFLOWER:
|
|
||||||
return "sunflower";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case C_CARROT:
|
|
||||||
return "carrot"
|
|
||||||
break;
|
|
||||||
|
|
||||||
case C_ALIZARIN:
|
|
||||||
return "alizarin"
|
|
||||||
break;
|
|
||||||
|
|
||||||
case C_NONE:
|
|
||||||
return ""
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var websock;
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
websock = new WebSocket('ws://' + window.location.hostname + '/ws');
|
|
||||||
websock.onopen = function(evt) {
|
|
||||||
console.log('websock open');
|
|
||||||
$("#conStatus").addClass("color-green");
|
|
||||||
$("#conStatus").text("Connected");
|
|
||||||
};
|
|
||||||
websock.onclose = function(evt) {
|
|
||||||
console.log('websock close');
|
|
||||||
$("#conStatus").removeClass("color-green");
|
|
||||||
$("#conStatus").addClass("color-red");
|
|
||||||
$("#conStatus").text("Error / No Connection");
|
|
||||||
};
|
|
||||||
websock.onerror = function(evt) {
|
|
||||||
console.log(evt);
|
|
||||||
$("#conStatus").removeClass("color-green");
|
|
||||||
$("#conStatus").addClass("color-red");
|
|
||||||
$("#conStatus").text("Error / No Connection");
|
|
||||||
};
|
|
||||||
websock.onmessage = function(evt) {
|
|
||||||
console.log(evt);
|
|
||||||
var data = JSON.parse(evt.data);
|
|
||||||
var e = document.body;
|
|
||||||
var center = "";
|
|
||||||
switch (data.type) {
|
|
||||||
case UI_TITEL:
|
|
||||||
document.title = data.label;
|
|
||||||
$('#mainHeader').html(data.label);
|
|
||||||
break;
|
|
||||||
case UI_LABEL:
|
|
||||||
$('#row').append("<div class='two columns card tcenter " + colorClass(data.color) + "'><h5 id='" + data.id + "'>" + data.label + "</h5><hr /><span id='l" + data.id + "' class='label'>" + data.value + "</span></div>");
|
|
||||||
break;
|
|
||||||
case UI_BUTTON:
|
|
||||||
$('#row').append("<div class='one columns card tcenter " + colorClass(data.color) + "'><h5>" + data.label + "</h5><hr/><button onmousedown='buttonclick(" + data.id + ", true)' onmouseup='buttonclick(" + data.id + ", false)' id='" + data.id + "'>" + data.value + "</button></div>");
|
|
||||||
$('#' + data.id).on({
|
|
||||||
'touchstart': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
buttonclick(data.id, true)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#' + data.id).on({
|
|
||||||
'touchend': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
buttonclick(data.id, false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case UI_SWITCHER:
|
|
||||||
var label = "<label id='sl" + data.id + "' class='switch checked'>";
|
|
||||||
var input = "<input type='checkbox' id='s" + data.id + "' onClick='switcher(" + data.id + ",null)' checked>";
|
|
||||||
if (data.value == "0") {
|
|
||||||
label = "<label id='sl" + data.id + "' class='switch'>";
|
|
||||||
input = "<input type='checkbox' id='s" + data.id + "' onClick='switcher(" + data.id + ",null)' >";
|
|
||||||
}
|
|
||||||
$('#row').append(
|
|
||||||
"<div id='" + data.id + "' class='one columns card tcenter " + colorClass(data.color) + "'><h5>" + data.label + "</h5><hr/>" +
|
|
||||||
label + "<i class='icon-ok'></i>" +
|
|
||||||
"<i class='icon-remove'></i>" + input +
|
|
||||||
"</label>" +
|
|
||||||
"</div>");
|
|
||||||
break;
|
|
||||||
case UI_CPAD:
|
|
||||||
center = "<a class='confirm' onmousedown='padclick(CENTER, " + data.id + ", true)' onmouseup='padclick(CENTER, " + data.id + ", false)' href='#' id='pc" + data.id + "'>OK</a>";
|
|
||||||
//NO BREAK
|
|
||||||
case UI_PAD:
|
|
||||||
$('#row').append(
|
|
||||||
"<div class='two columns card tcenter " + colorClass(data.color) + "'><h5>" + data.label + "</h5><hr/>" +
|
|
||||||
"<nav class='control'>" +
|
|
||||||
"<ul>" +
|
|
||||||
"<li><a onmousedown='padclick(FOR, " + data.id + ", true)' onmouseup='padclick(FOR, " + data.id + ", false)' href='#' id='pf" + data.id + "'>▲</a></li>" +
|
|
||||||
"<li><a onmousedown='padclick(RIGHT, " + data.id + ", true)' onmouseup='padclick(RIGHT, " + data.id + ", false)' href='#' id='pr" + data.id + "'>▲</a></li>" +
|
|
||||||
"<li><a onmousedown='padclick(LEFT, " + data.id + ", true)' onmouseup='padclick(LEFT, " + data.id + ", false)' href='#' id='pl" + data.id + "'>▲</a></li>" +
|
|
||||||
"<li><a onmousedown='padclick(BACK, " + data.id + ", true)' onmouseup='padclick(BACK, " + data.id + ", false)' href='#' id='pb" + data.id + "'>▲</a></li>" +
|
|
||||||
"</ul>" +
|
|
||||||
center +
|
|
||||||
"</nav>" +
|
|
||||||
"</div>");
|
|
||||||
|
|
||||||
$('#pf' + data.id).on({
|
|
||||||
'touchstart': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(FOR, data.id, true)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pf' + data.id).on({
|
|
||||||
'touchend': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(FOR, data.id, false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pl' + data.id).on({
|
|
||||||
'touchstart': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(LEFT, data.id, true)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pl' + data.id).on({
|
|
||||||
'touchend': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(LEFT, data.id, false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pr' + data.id).on({
|
|
||||||
'touchstart': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(RIGHT, data.id, true)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pr' + data.id).on({
|
|
||||||
'touchend': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(RIGHT, data.id, false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pb' + data.id).on({
|
|
||||||
'touchstart': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(BACK, data.id, true)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pb' + data.id).on({
|
|
||||||
'touchend': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(BACK, data.id, false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pc' + data.id).on({
|
|
||||||
'touchstart': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(CENTER, data.id, true)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#pc' + data.id).on({
|
|
||||||
'touchend': function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
padclick(CENTER, data.id, false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
case UPDATE_LABEL:
|
|
||||||
$('#l' + data.id).html(data.value);
|
|
||||||
break;
|
|
||||||
case UPDATE_SWITCHER:
|
|
||||||
if (data.value == "0")
|
|
||||||
switcher(data.id, 0);
|
|
||||||
else
|
|
||||||
switcher(data.id, 1);
|
|
||||||
break;
|
|
||||||
case UI_SLIDER:
|
|
||||||
$('#row').append(
|
|
||||||
"<div class='two columns card tcenter card-slider " + colorClass(data.color) + "'>" +
|
|
||||||
"<h5 id='" + data.id + "'>" + data.label + "</h5><hr />" +
|
|
||||||
"<div id='sl" + data.id + "' class='rkmd-slider slider-discrete slider-" + colorClass(data.color) + "'>" +
|
|
||||||
"<input type='range' min='0' max='100' value='" + data.value + "'>" +
|
|
||||||
"</div>" +
|
|
||||||
"</div>"
|
|
||||||
);
|
|
||||||
$('#row').append(
|
|
||||||
"<script>" +
|
|
||||||
"rkmd_rangeSlider('#sl" + data.id + "');" +
|
|
||||||
"</script>"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UPDATE_SLIDER:
|
|
||||||
slider_move($('#sl'+data.id), data.value ,'100', false);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('Unknown type or event');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function buttonclick(number, isdown) {
|
|
||||||
if (isdown) websock.send("bdown:" + number);
|
|
||||||
else websock.send("bup:" + number);
|
|
||||||
}
|
|
||||||
|
|
||||||
function padclick(type, number, isdown) {
|
|
||||||
switch (type) {
|
|
||||||
case CENTER:
|
|
||||||
if (isdown) websock.send("pcdown:" + number);
|
|
||||||
else websock.send("pcup:" + number);
|
|
||||||
break;
|
|
||||||
case FOR:
|
|
||||||
if (isdown) websock.send("pfdown:" + number);
|
|
||||||
else websock.send("pfup:" + number);
|
|
||||||
break;
|
|
||||||
case BACK:
|
|
||||||
if (isdown) websock.send("pbdown:" + number);
|
|
||||||
else websock.send("pbup:" + number);
|
|
||||||
break;
|
|
||||||
case LEFT:
|
|
||||||
if (isdown) websock.send("pldown:" + number);
|
|
||||||
else websock.send("plup:" + number);
|
|
||||||
break;
|
|
||||||
case RIGHT:
|
|
||||||
if (isdown) websock.send("prdown:" + number);
|
|
||||||
else websock.send("prup:" + number);
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function switcher(number, state) {
|
|
||||||
if (state == null) {
|
|
||||||
if ($('#s' + number).is(':checked')) {
|
|
||||||
websock.send("sactive:" + number);
|
|
||||||
$('#sl' + number).addClass('checked');
|
|
||||||
} else {
|
|
||||||
websock.send("sinactive:" + number);
|
|
||||||
$('#sl' + number).removeClass('checked');
|
|
||||||
}
|
|
||||||
} else if (state == 1) {
|
|
||||||
$('#sl' + number).addClass('checked');
|
|
||||||
$('#sl' + number).prop("checked", true);
|
|
||||||
} else if (state == 0) {
|
|
||||||
$('#sl' + number).removeClass('checked');
|
|
||||||
$('#sl' + number).prop("checked", false);
|
|
||||||
}
|
|
||||||
}
|
|
1
examples/gui/data/js/controls.min.js
vendored
@ -1,125 +0,0 @@
|
|||||||
/* -----------------------------------------------------
|
|
||||||
Material Design Sliders
|
|
||||||
CodePen URL: https://codepen.io/rkchauhan/pen/xVGGpR
|
|
||||||
By: Ravikumar Chauhan
|
|
||||||
-------------------------------------------------------- */
|
|
||||||
function rkmd_rangeSlider(selector) {
|
|
||||||
var self, slider_width, slider_offset, curnt, sliderDiscrete, range, slider;
|
|
||||||
self = $(selector);
|
|
||||||
slider_width = self.width();
|
|
||||||
slider_offset = self.offset().left;
|
|
||||||
sliderDiscrete = self;
|
|
||||||
|
|
||||||
sliderDiscrete.each(function(i, v) {
|
|
||||||
curnt = $(this);
|
|
||||||
curnt.append(sliderDiscrete_tmplt());
|
|
||||||
range = curnt.find('input[type="range"]');
|
|
||||||
slider = curnt.find('.slider');
|
|
||||||
slider_fill = slider.find('.slider-fill');
|
|
||||||
slider_handle = slider.find('.slider-handle');
|
|
||||||
slider_label = slider.find('.slider-label');
|
|
||||||
|
|
||||||
var range_val = parseInt(range.val());
|
|
||||||
slider_fill.css('width', range_val + '%');
|
|
||||||
slider_handle.css('left', range_val + '%');
|
|
||||||
slider_label.find('span').text(range_val);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.on('mousedown touchstart', '.slider-handle', function(e) {
|
|
||||||
if (e.button === 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var parents = $(this).parents('.rkmd-slider');
|
|
||||||
var slider_width = parents.width();
|
|
||||||
var slider_offset = parents.offset().left;
|
|
||||||
var check_range = parents.find('input[type="range"]').is(':disabled');
|
|
||||||
if (check_range === true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$(this).addClass('is-active');
|
|
||||||
var moveFu =
|
|
||||||
function(e) {
|
|
||||||
var slider_new_width = e.pageX - slider_offset;
|
|
||||||
|
|
||||||
if (slider_new_width <= slider_width && !(slider_new_width < '0')) {
|
|
||||||
slider_move(parents, slider_new_width, slider_width, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var upFu =
|
|
||||||
function(e) {
|
|
||||||
$(this).off(handlers);
|
|
||||||
parents.find('.is-active').removeClass('is-active');
|
|
||||||
};
|
|
||||||
|
|
||||||
var handlers = {
|
|
||||||
mousemove: moveFu,
|
|
||||||
touchmove: moveFu,
|
|
||||||
mouseup: upFu,
|
|
||||||
touchend: upFu
|
|
||||||
};
|
|
||||||
$(document).on(handlers);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.on('mousedown touchstart', '.slider', function(e) {
|
|
||||||
if (e.button === 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parents = $(this).parents('.rkmd-slider');
|
|
||||||
var slider_width = parents.width();
|
|
||||||
var slider_offset = parents.offset().left;
|
|
||||||
var check_range = parents.find('input[type="range"]').is(':disabled');
|
|
||||||
|
|
||||||
if (check_range === true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var slider_new_width = e.pageX - slider_offset;
|
|
||||||
if (slider_new_width <= slider_width && !(slider_new_width < '0')) {
|
|
||||||
slider_move(parents, slider_new_width, slider_width, true);
|
|
||||||
}
|
|
||||||
var upFu =
|
|
||||||
function(e) {
|
|
||||||
$(this).off(handlers);
|
|
||||||
};
|
|
||||||
|
|
||||||
var handlers = {
|
|
||||||
mouseup: upFu,
|
|
||||||
touchend: upFu
|
|
||||||
};
|
|
||||||
$(document).on(handlers);
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function sliderDiscrete_tmplt() {
|
|
||||||
var tmplt = '<div class="slider">' +
|
|
||||||
'<div class="slider-fill"></div>' +
|
|
||||||
'<div class="slider-handle"><div class="slider-label"><span>0</span></div></div>' +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
return tmplt;
|
|
||||||
}
|
|
||||||
|
|
||||||
function slider_move(parents, newW, sliderW, send) {
|
|
||||||
var slider_new_val = parseInt(Math.round(newW / sliderW * 100));
|
|
||||||
|
|
||||||
var slider_fill = parents.find('.slider-fill');
|
|
||||||
var slider_handle = parents.find('.slider-handle');
|
|
||||||
var range = parents.find('input[type="range"]');
|
|
||||||
|
|
||||||
slider_fill.css('width', slider_new_val + '%');
|
|
||||||
slider_handle.css({
|
|
||||||
'left': slider_new_val + '%',
|
|
||||||
'transition': 'none',
|
|
||||||
'-webkit-transition': 'none',
|
|
||||||
'-moz-transition': 'none'
|
|
||||||
});
|
|
||||||
|
|
||||||
range.val(slider_new_val);
|
|
||||||
if (parents.find('.slider-handle span').text() != slider_new_val) {
|
|
||||||
parents.find('.slider-handle span').text(slider_new_val);
|
|
||||||
var number = parents.attr('id').substring(2);
|
|
||||||
if (send) websock.send('slvalue:' + slider_new_val + ':' + number);
|
|
||||||
}
|
|
||||||
}
|
|
1
examples/gui/data/js/slider.min.js
vendored
@ -1 +0,0 @@
|
|||||||
function rkmd_rangeSlider(b){var f,e,c,a,g,d,h;f=$(b);e=f.width();c=f.offset().left;g=f;g.each(function(k,j){a=$(this);a.append(sliderDiscrete_tmplt());d=a.find('input[type="range"]');h=a.find(".slider");slider_fill=h.find(".slider-fill");slider_handle=h.find(".slider-handle");slider_label=h.find(".slider-label");var l=parseInt(d.val());slider_fill.css("width",l+"%");slider_handle.css("left",l+"%");slider_label.find("span").text(l)});f.on("mousedown touchstart",".slider-handle",function(o){if(o.button===2){return false}var m=$(this).parents(".rkmd-slider");var l=m.width();var i=m.offset().left;var k=m.find('input[type="range"]').is(":disabled");if(k===true){return false}$(this).addClass("is-active");var p=function(r){var q=r.pageX-i;if(q<=l&&!(q<"0")){slider_move(m,q,l,true)}};var n=function(q){$(this).off(j);m.find(".is-active").removeClass("is-active")};var j={mousemove:p,touchmove:p,mouseup:n,touchend:n};$(document).on(j)});f.on("mousedown touchstart",".slider",function(p){if(p.button===2){return false}var m=$(this).parents(".rkmd-slider");var l=m.width();var i=m.offset().left;var k=m.find('input[type="range"]').is(":disabled");if(k===true){return false}var o=p.pageX-i;if(o<=l&&!(o<"0")){slider_move(m,o,l,true)}var n=function(q){$(this).off(j)};var j={mouseup:n,touchend:n};$(document).on(j)})}function sliderDiscrete_tmplt(){var a='<div class="slider"><div class="slider-fill"></div><div class="slider-handle"><div class="slider-label"><span>0</span></div></div></div>';return a}function slider_move(g,a,h,e){var i=parseInt(Math.round(a/h*100));var b=g.find(".slider-fill");var c=g.find(".slider-handle");var f=g.find('input[type="range"]');b.css("width",i+"%");c.css({left:i+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none"});f.val(i);if(g.find(".slider-handle span").text()!=i){g.find(".slider-handle span").text(i);var d=g.attr("id").substring(2);if(e){websock.send("slvalue:"+i+":"+d)}}};
|
|
@ -1,157 +1,280 @@
|
|||||||
|
#include <DNSServer.h>
|
||||||
#include <ESPUI.h>
|
#include <ESPUI.h>
|
||||||
|
|
||||||
|
const byte DNS_PORT = 53;
|
||||||
|
IPAddress apIP(192, 168, 4, 1);
|
||||||
|
DNSServer dnsServer;
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#else
|
#else
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *ssid = "ESPUI";
|
const char* ssid = "ESPUI";
|
||||||
const char *password = "";
|
const char* password = "espui";
|
||||||
|
|
||||||
long oldTime = 0;
|
const char* hostname = "espui";
|
||||||
bool switchi = false;
|
|
||||||
|
|
||||||
void setup(void) {
|
int statusLabelId;
|
||||||
Serial.begin(115200);
|
int graphId;
|
||||||
WiFi.mode(WIFI_AP);
|
int millisLabelId;
|
||||||
|
int testSwitchId;
|
||||||
#if defined(ESP32)
|
|
||||||
WiFi.setHostname(ssid);
|
|
||||||
#else
|
|
||||||
WiFi.hostname(ssid);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
WiFi.softAP(ssid);
|
void numberCall(Control* sender, int type)
|
||||||
// WiFi.softAP(ssid, password);
|
{
|
||||||
Serial.println("");
|
Serial.println(sender->value);
|
||||||
Serial.print("IP address: ");
|
}
|
||||||
Serial.println(WiFi.softAPIP());
|
|
||||||
|
|
||||||
// change the beginning to this if you want to join an existing network
|
void textCall(Control* sender, int type)
|
||||||
/*
|
{
|
||||||
Serial.begin(115200);
|
Serial.print("Text: ID: ");
|
||||||
WiFi.begin(ssid, password);
|
Serial.print(sender->id);
|
||||||
Serial.println("");
|
Serial.print(", Value: ");
|
||||||
// Wait for connection
|
Serial.println(sender->value);
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
}
|
||||||
delay(500);
|
|
||||||
Serial.print(".");
|
void slider(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.print("Slider: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
// Like all Control Values in ESPUI slider values are Strings. To use them as int simply do this:
|
||||||
|
int sliderValueWithOffset = sender->value.toInt() + 100;
|
||||||
|
Serial.print("SliderValue with offset");
|
||||||
|
Serial.println(sliderValueWithOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonCallback(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Button DOWN");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Button UP");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Serial.println("");
|
}
|
||||||
|
|
||||||
|
void buttonExample(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Status: Start");
|
||||||
|
ESPUI.print(statusLabelId, "Start");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Status: Stop");
|
||||||
|
ESPUI.print(statusLabelId, "Stop");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void padExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case P_LEFT_DOWN:
|
||||||
|
Serial.print("left down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_LEFT_UP:
|
||||||
|
Serial.print("left up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_DOWN:
|
||||||
|
Serial.print("right down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_UP:
|
||||||
|
Serial.print("right up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_DOWN:
|
||||||
|
Serial.print("for down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_UP:
|
||||||
|
Serial.print("for up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_DOWN:
|
||||||
|
Serial.print("back down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_UP:
|
||||||
|
Serial.print("back up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_DOWN:
|
||||||
|
Serial.print("center down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_UP:
|
||||||
|
Serial.print("center up");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void switchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void otherSwitchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void)
|
||||||
|
{
|
||||||
|
ESPUI.setVerbosity(Verbosity::VerboseJSON);
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
WiFi.setHostname(hostname);
|
||||||
|
#else
|
||||||
|
WiFi.hostname(hostname);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// try to connect to existing network
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.print("\n\nTry to connect to existing network");
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t timeout = 10;
|
||||||
|
|
||||||
|
// Wait for connection, 5s timeout
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout && WiFi.status() != WL_CONNECTED);
|
||||||
|
|
||||||
|
// not connected -> create hotspot
|
||||||
|
if (WiFi.status() != WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.print("\n\nCreating hotspot");
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
delay(100);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
#if defined(ESP32)
|
||||||
|
uint32_t chipid = 0;
|
||||||
|
for (int i = 0; i < 17; i = i + 8)
|
||||||
|
{
|
||||||
|
chipid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
uint32_t chipid = ESP.getChipId();
|
||||||
|
#endif
|
||||||
|
char ap_ssid[25];
|
||||||
|
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
|
||||||
|
WiFi.softAP(ap_ssid);
|
||||||
|
|
||||||
|
timeout = 5;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.start(DNS_PORT, "*", apIP);
|
||||||
|
|
||||||
|
Serial.println("\n\nWiFi parameters:");
|
||||||
|
Serial.print("Mode: ");
|
||||||
|
Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
|
||||||
Serial.print("IP address: ");
|
Serial.print("IP address: ");
|
||||||
Serial.println(WiFi.localIP());
|
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
||||||
*/
|
|
||||||
|
|
||||||
ESPUI.label("Status:", COLOR_TURQUOISE, "Stop");
|
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
||||||
ESPUI.label("Millis:", COLOR_EMERALD, "0");
|
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
||||||
ESPUI.button("Push Button", &buttonCallback, COLOR_PETERRIVER);
|
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
|
||||||
ESPUI.button("Other Button", &buttonExample, COLOR_WETASPHALT, "Press");
|
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press");
|
||||||
ESPUI.pad("Pad with center", true, &padExample, COLOR_SUNFLOWER);
|
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
|
||||||
ESPUI.pad("Pad without center", false, &padExample, COLOR_CARROT);
|
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
|
||||||
ESPUI.switcher("Switch one", false, &switchExample, COLOR_ALIZARIN);
|
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
||||||
ESPUI.switcher("Switch two", true, &otherSwitchExample, COLOR_NONE);
|
ESPUI.switcher("Switch two", &otherSwitchExample, ControlColor::None, true);
|
||||||
ESPUI.slider("Slider one", &slider, COLOR_ALIZARIN, "30");
|
ESPUI.slider("Slider one", &slider, ControlColor::Alizarin, 30);
|
||||||
ESPUI.slider("Slider two", &slider, COLOR_NONE, "100");
|
ESPUI.slider("Slider two", &slider, ControlColor::None, 100);
|
||||||
|
ESPUI.text("Text Test:", &textCall, ControlColor::Alizarin, "a Text Field");
|
||||||
|
ESPUI.number("Numbertest", &numberCall, ControlColor::Alizarin, 5, 0, 10);
|
||||||
|
|
||||||
ESPUI.begin("ESP32 Control");
|
graphId = ESPUI.graph("Graph Test", ControlColor::Wetasphalt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* .begin loads and serves all files from PROGMEM directly.
|
||||||
|
* If you want to serve the files from LITTLEFS use ESPUI.beginLITTLEFS
|
||||||
|
* (.prepareFileSystem has to be run in an empty sketch before)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
|
||||||
|
// ESPUI.sliderContinuous = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
|
||||||
|
* SECURE way of limiting access.
|
||||||
|
* Anyone who is able to sniff traffic will be able to intercept your password
|
||||||
|
* since it is transmitted in cleartext. Just add a string as username and
|
||||||
|
* password, for example begin("ESPUI Control", "username", "password")
|
||||||
|
*/
|
||||||
|
|
||||||
|
ESPUI.begin("ESPUI Control");
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(void) {
|
void loop(void)
|
||||||
if (millis() - oldTime > 5000) {
|
{
|
||||||
ESPUI.print("Millis:", String(millis()));
|
dnsServer.processNextRequest();
|
||||||
switchi = !switchi;
|
|
||||||
ESPUI.updateSwitcher("Switch one", switchi);
|
|
||||||
oldTime = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void slider(Control sender, int type) {
|
static long oldTime = 0;
|
||||||
Serial.println(sender.value);
|
static bool testSwitchState = false;
|
||||||
}
|
|
||||||
|
|
||||||
void buttonCallback(Control sender, int type) {
|
if (millis() - oldTime > 5000)
|
||||||
switch (type) {
|
{
|
||||||
case B_DOWN:
|
ESPUI.print(millisLabelId, String(millis()));
|
||||||
Serial.println("Button DOWN");
|
|
||||||
break;
|
|
||||||
case B_UP:
|
|
||||||
Serial.println("Button UP");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void buttonExample(Control sender, int type) {
|
ESPUI.addGraphPoint(graphId, random(1, 50));
|
||||||
switch (type) {
|
|
||||||
case B_DOWN:
|
|
||||||
Serial.println("Status: Start");
|
|
||||||
ESPUI.print(0, "Status: Start");
|
|
||||||
break;
|
|
||||||
case B_UP:
|
|
||||||
Serial.println("Status: Stop");
|
|
||||||
ESPUI.print(0, "Status: Stop");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void padExample(Control sender, int value) {
|
|
||||||
switch (value) {
|
|
||||||
case P_LEFT_DOWN:
|
|
||||||
Serial.print("left down");
|
|
||||||
break;
|
|
||||||
case P_LEFT_UP:
|
|
||||||
Serial.print("left up");
|
|
||||||
break;
|
|
||||||
case P_RIGHT_DOWN:
|
|
||||||
Serial.print("right down");
|
|
||||||
break;
|
|
||||||
case P_RIGHT_UP:
|
|
||||||
Serial.print("right up");
|
|
||||||
break;
|
|
||||||
case P_FOR_DOWN:
|
|
||||||
Serial.print("for down");
|
|
||||||
break;
|
|
||||||
case P_FOR_UP:
|
|
||||||
Serial.print("for up");
|
|
||||||
break;
|
|
||||||
case P_BACK_DOWN:
|
|
||||||
Serial.print("back down");
|
|
||||||
break;
|
|
||||||
case P_BACK_UP:
|
|
||||||
Serial.print("back up");
|
|
||||||
break;
|
|
||||||
case P_CENTER_DOWN:
|
|
||||||
Serial.print("center down");
|
|
||||||
break;
|
|
||||||
case P_CENTER_UP:
|
|
||||||
Serial.print("center up");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Serial.print(" ");
|
|
||||||
Serial.println(sender.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void switchExample(Control sender, int value) {
|
testSwitchState = !testSwitchState;
|
||||||
switch (value) {
|
ESPUI.updateSwitcher(testSwitchId, testSwitchState);
|
||||||
case S_ACTIVE:
|
|
||||||
Serial.print("Active:");
|
|
||||||
break;
|
|
||||||
case S_INACTIVE:
|
|
||||||
Serial.print("Inactive");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Serial.print(" ");
|
|
||||||
Serial.println(sender.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void otherSwitchExample(Control sender, int value) {
|
oldTime = millis();
|
||||||
switch (value) {
|
}
|
||||||
case S_ACTIVE:
|
|
||||||
Serial.print("Active:");
|
|
||||||
break;
|
|
||||||
case S_INACTIVE:
|
|
||||||
Serial.print("Inactive");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Serial.print(" ");
|
|
||||||
Serial.println(sender.id);
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#include <ESPUI.h>
|
#include <ESPUI.h>
|
||||||
|
|
||||||
void setup(void) {
|
void setup(void)
|
||||||
|
{
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
ESPUI.prepareFileSystem();
|
ESPUI.prepareFileSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
297
examples/tabbedGui/tabbedGui.ino
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
#include <DNSServer.h>
|
||||||
|
#include <ESPUI.h>
|
||||||
|
|
||||||
|
const byte DNS_PORT = 53;
|
||||||
|
IPAddress apIP(192, 168, 4, 1);
|
||||||
|
DNSServer dnsServer;
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#else
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = "ESPUI";
|
||||||
|
const char* password = "espui";
|
||||||
|
const char* hostname = "espui";
|
||||||
|
|
||||||
|
uint16_t button1;
|
||||||
|
uint16_t switchOne;
|
||||||
|
uint16_t status;
|
||||||
|
|
||||||
|
void numberCall(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void textCall(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.print("Text: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void slider(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.print("Slider: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonCallback(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Button DOWN");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Button UP");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonExample(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Status: Start");
|
||||||
|
ESPUI.updateControlValue(status, "Start");
|
||||||
|
|
||||||
|
ESPUI.getControl(button1)->color = ControlColor::Carrot;
|
||||||
|
ESPUI.updateControl(button1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Status: Stop");
|
||||||
|
ESPUI.updateControlValue(status, "Stop");
|
||||||
|
|
||||||
|
ESPUI.getControl(button1)->color = ControlColor::Peterriver;
|
||||||
|
ESPUI.updateControl(button1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void padExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case P_LEFT_DOWN:
|
||||||
|
Serial.print("left down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_LEFT_UP:
|
||||||
|
Serial.print("left up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_DOWN:
|
||||||
|
Serial.print("right down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_UP:
|
||||||
|
Serial.print("right up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_DOWN:
|
||||||
|
Serial.print("for down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_UP:
|
||||||
|
Serial.print("for up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_DOWN:
|
||||||
|
Serial.print("back down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_UP:
|
||||||
|
Serial.print("back up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_DOWN:
|
||||||
|
Serial.print("center down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_UP:
|
||||||
|
Serial.print("center up");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void switchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
Serial.print("Select: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void otherSwitchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void)
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
WiFi.setHostname(hostname);
|
||||||
|
#else
|
||||||
|
WiFi.hostname(hostname);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// try to connect to existing network
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.print("\n\nTry to connect to existing network");
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t timeout = 10;
|
||||||
|
|
||||||
|
// Wait for connection, 5s timeout
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout && WiFi.status() != WL_CONNECTED);
|
||||||
|
|
||||||
|
// not connected -> create hotspot
|
||||||
|
if (WiFi.status() != WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.print("\n\nCreating hotspot");
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
delay(100);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
#if defined(ESP32)
|
||||||
|
uint32_t chipid = 0;
|
||||||
|
for (int i = 0; i < 17; i = i + 8)
|
||||||
|
{
|
||||||
|
chipid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
uint32_t chipid = ESP.getChipId();
|
||||||
|
#endif
|
||||||
|
char ap_ssid[25];
|
||||||
|
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
|
||||||
|
WiFi.softAP(ap_ssid);
|
||||||
|
|
||||||
|
timeout = 5;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.start(DNS_PORT, "*", apIP);
|
||||||
|
|
||||||
|
Serial.println("\n\nWiFi parameters:");
|
||||||
|
Serial.print("Mode: ");
|
||||||
|
Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
||||||
|
|
||||||
|
uint16_t tab1 = ESPUI.addControl(ControlType::Tab, "Settings 1", "Settings 1");
|
||||||
|
uint16_t tab2 = ESPUI.addControl(ControlType::Tab, "Settings 2", "Settings 2");
|
||||||
|
uint16_t tab3 = ESPUI.addControl(ControlType::Tab, "Settings 3", "Settings 3");
|
||||||
|
|
||||||
|
// shown above all tabs
|
||||||
|
status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise);
|
||||||
|
|
||||||
|
uint16_t select1
|
||||||
|
= ESPUI.addControl(ControlType::Select, "Select:", "", ControlColor::Alizarin, tab1, &selectExample);
|
||||||
|
ESPUI.addControl(ControlType::Option, "Option1", "Opt1", ControlColor::Alizarin, select1);
|
||||||
|
ESPUI.addControl(ControlType::Option, "Option2", "Opt2", ControlColor::Alizarin, select1);
|
||||||
|
ESPUI.addControl(ControlType::Option, "Option3", "Opt3", ControlColor::Alizarin, select1);
|
||||||
|
|
||||||
|
ESPUI.addControl(ControlType::Text, "Text Test:", "a Text Field", ControlColor::Alizarin, tab1, &textCall);
|
||||||
|
|
||||||
|
// tabbed controls
|
||||||
|
ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, tab1);
|
||||||
|
button1 = ESPUI.addControl(
|
||||||
|
ControlType::Button, "Push Button", "Press", ControlColor::Peterriver, tab1, &buttonCallback);
|
||||||
|
ESPUI.addControl(ControlType::Button, "Other Button", "Press", ControlColor::Wetasphalt, tab1, &buttonExample);
|
||||||
|
ESPUI.addControl(ControlType::PadWithCenter, "Pad with center", "", ControlColor::Sunflower, tab2, &padExample);
|
||||||
|
ESPUI.addControl(ControlType::Pad, "Pad without center", "", ControlColor::Carrot, tab3, &padExample);
|
||||||
|
switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, tab3, &switchExample);
|
||||||
|
ESPUI.addControl(ControlType::Switcher, "Switch two", "", ControlColor::None, tab3, &otherSwitchExample);
|
||||||
|
ESPUI.addControl(ControlType::Slider, "Slider one", "30", ControlColor::Alizarin, tab1, &slider);
|
||||||
|
ESPUI.addControl(ControlType::Slider, "Slider two", "100", ControlColor::Alizarin, tab3, &slider);
|
||||||
|
ESPUI.addControl(ControlType::Number, "Number:", "50", ControlColor::Alizarin, tab3, &numberCall);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* .begin loads and serves all files from PROGMEM directly.
|
||||||
|
* If you want to serve the files from LITTLEFS use ESPUI.beginLITTLEFS
|
||||||
|
* (.prepareFileSystem has to be run in an empty sketch before)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
|
||||||
|
// ESPUI.sliderContinuous = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
|
||||||
|
* SECURE way of limiting access.
|
||||||
|
* Anyone who is able to sniff traffic will be able to intercept your password
|
||||||
|
* since it is transmitted in cleartext. Just add a string as username and
|
||||||
|
* password, for example begin("ESPUI Control", "username", "password")
|
||||||
|
*/
|
||||||
|
|
||||||
|
ESPUI.begin("ESPUI Control");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void)
|
||||||
|
{
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
|
||||||
|
static long oldTime = 0;
|
||||||
|
static bool switchi = false;
|
||||||
|
|
||||||
|
if (millis() - oldTime > 5000)
|
||||||
|
{
|
||||||
|
switchi = !switchi;
|
||||||
|
ESPUI.updateControlValue(switchOne, switchi ? "1" : "0");
|
||||||
|
|
||||||
|
oldTime = millis();
|
||||||
|
}
|
||||||
|
}
|
35
keywords.txt
@ -15,13 +15,16 @@ ESPUI KEYWORD1
|
|||||||
|
|
||||||
label KEYWORD2
|
label KEYWORD2
|
||||||
button KEYWORD2
|
button KEYWORD2
|
||||||
switcher KEYWORD2
|
switcher KEYWORD2
|
||||||
pad KEYWORD2
|
pad KEYWORD2
|
||||||
slider KEYWORD2
|
slider KEYWORD2
|
||||||
|
|
||||||
begin KEYWORD2
|
begin KEYWORD2
|
||||||
|
beginSPIFFS KEYWORD2
|
||||||
|
beginLITTLEFS KEYWORD2
|
||||||
print KEYWORD2
|
print KEYWORD2
|
||||||
updateSwitcher KEYWORD2
|
updateSwitcher KEYWORD2
|
||||||
|
updateSlider KEYWORD2
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Instances (KEYWORD2)
|
# Instances (KEYWORD2)
|
||||||
@ -31,18 +34,18 @@ updateSwitcher KEYWORD2
|
|||||||
# Constants (LITERAL1)
|
# Constants (LITERAL1)
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
B_DOWN LITERAL1
|
B_DOWN LITERAL1
|
||||||
B_UP LITERAL1
|
B_UP LITERAL1
|
||||||
P_LEFT_DOWN LITERAL1
|
P_LEFT_DOWN LITERAL1
|
||||||
P_LEFT_UP LITERAL1
|
P_LEFT_UP LITERAL1
|
||||||
P_RIGHT_DOWN LITERAL1
|
P_RIGHT_DOWN LITERAL1
|
||||||
P_RIGHT_UP LITERAL1
|
P_RIGHT_UP LITERAL1
|
||||||
P_FOR_DOWN LITERAL1
|
P_FOR_DOWN LITERAL1
|
||||||
P_FOR_UP LITERAL1
|
P_FOR_UP LITERAL1
|
||||||
P_BACK_DOWN LITERAL1
|
P_BACK_DOWN LITERAL1
|
||||||
P_BACK_UP LITERAL1
|
P_BACK_UP LITERAL1
|
||||||
P_CENTER_DOWN LITERAL1
|
P_CENTER_DOWN LITERAL1
|
||||||
P_CENTER_UP LITERAL1
|
P_CENTER_UP LITERAL1
|
||||||
S_ACTIVE LITERAL1
|
S_ACTIVE LITERAL1
|
||||||
S_INACTIVE LITERAL1
|
S_INACTIVE LITERAL1
|
||||||
SL_VALUE LITERAL1
|
SL_VALUE LITERAL1
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
{
|
{
|
||||||
"langs":
|
"langs": {
|
||||||
{
|
"en-GB": {
|
||||||
"en-GB":
|
"keys": {
|
||||||
{
|
|
||||||
"keys":
|
|
||||||
{
|
|
||||||
"LANG_SUBCATERGORY_ESPUI": "User Interface",
|
"LANG_SUBCATERGORY_ESPUI": "User Interface",
|
||||||
"LANG_ESPUI_ESPUI_TITLE": "Title",
|
"LANG_ESPUI_ESPUI_TITLE": "Title",
|
||||||
"LANG_ESPUI_ESPUI_HOTSPOT": "Enable Wifi Hotspot Code",
|
"LANG_ESPUI_ESPUI_HOTSPOT": "Enable Wifi Hotspot Code",
|
||||||
@ -22,6 +19,26 @@
|
|||||||
"LANG_ESPUI_PAD_PAD": "UI Pad",
|
"LANG_ESPUI_PAD_PAD": "UI Pad",
|
||||||
"LANG_ESPUI_PAD_CENTER": "Center button?"
|
"LANG_ESPUI_PAD_CENTER": "Center button?"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"zh-CN": {
|
||||||
|
"keys": {
|
||||||
|
"LANG_SUBCATERGORY_ESPUI": "用户接口",
|
||||||
|
"LANG_ESPUI_ESPUI_TITLE": "标题",
|
||||||
|
"LANG_ESPUI_ESPUI_HOTSPOT": "启用无线热点代码",
|
||||||
|
"LANG_ESPUI_ESPUI_TOOLTIP": "在ESP32/ESP8266创建一网络接口",
|
||||||
|
"LANG_ESPUI_BUTTON_BUTTON": "UI 按钮",
|
||||||
|
"LANG_ESPUI_NAME": "名称",
|
||||||
|
"LANG_ESPUI_COLOR": "颜色",
|
||||||
|
"LANG_ESPUI_TEXT": "文本",
|
||||||
|
"LANG_ESPUI_BUTTON_TOOLTIP": "一个网页接口的按钮",
|
||||||
|
"LANG_ESPUI_LABEL_LABEL": "UI 标签",
|
||||||
|
"LANG_ESPUI_LABEL": "标签",
|
||||||
|
"LANG_ESPUI_LABEL_TOOLTIP": "一个网页接口的标签,你可通过修改代码去更新它",
|
||||||
|
"LANG_ESPUI_STATE": "状态",
|
||||||
|
"LANG_ESPUI_SWITCH_SWITCH": "UI 开关",
|
||||||
|
"LANG_ESPUI_PAD_PAD": "UI 面板",
|
||||||
|
"LANG_ESPUI_PAD_CENTER": "中间按钮?"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
library.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "ESPUI",
|
||||||
|
"keywords": "espressif web interface iot control simple easy ui userinterface",
|
||||||
|
"description": "ESP32 and ESP8266 Web Interface Library",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/s00500/ESPUI.git"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Lukas Bachschwell",
|
||||||
|
"email": "lukas@lbsfilm.at",
|
||||||
|
"url": "https://lbsfilm.at",
|
||||||
|
"maintainer": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"name": "ESP Async WebServer",
|
||||||
|
"authors": "Hristo Gochkov",
|
||||||
|
"frameworks": "arduino"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ArduinoJson",
|
||||||
|
"authors": "Benoit Blanchon",
|
||||||
|
"frameworks": "arduino"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LittleFS_esp32",
|
||||||
|
"authors": "lorol",
|
||||||
|
"frameworks": "arduino"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": "2.1.1",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": "*"
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
name=ESPUI
|
name=ESPUI
|
||||||
version=1.4.2
|
version=2.1.1
|
||||||
author=Lukas Bachschwell
|
author=Lukas Bachschwell
|
||||||
maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
|
maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
|
||||||
sentence=ESP32 and ESP8266 Web Interface Library
|
sentence=ESP32 and ESP8266 Web Interface Library
|
||||||
@ -7,3 +7,4 @@ paragraph=A simple library that implements a web graphical user interface for ES
|
|||||||
category=Communication
|
category=Communication
|
||||||
url=https://github.com/s00500/ESPUI
|
url=https://github.com/s00500/ESPUI
|
||||||
architectures=*
|
architectures=*
|
||||||
|
depends=ArduinoJson
|
||||||
|
1
pio_examples/gui/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.pio
|
37
pio_examples/gui/platformio.ini
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
src_dir = ./src
|
||||||
|
data_dir = ../../data
|
||||||
|
|
||||||
|
[env]
|
||||||
|
lib_extra_dirs = ../../
|
||||||
|
board_build.filesystem = littlefs
|
||||||
|
; Additional scripts: Usage: see https://github.com/s00500/ESPUI/issues/144#issuecomment-1005135077
|
||||||
|
;extra_scripts =
|
||||||
|
; LittleFSBuilder.py
|
||||||
|
|
||||||
|
[env:esp8266]
|
||||||
|
platform = espressif8266
|
||||||
|
framework = arduino
|
||||||
|
board = nodemcuv2
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson @ ^6.18.5
|
||||||
|
me-no-dev/ESP Async WebServer @ ^1.2.3
|
||||||
|
|
||||||
|
[env:esp32]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32dev
|
||||||
|
lib_deps =
|
||||||
|
lorol/LittleFS_esp32@^1.0.6
|
||||||
|
bblanchon/ArduinoJson @ ^6.18.5
|
||||||
|
me-no-dev/ESP Async WebServer @ ^1.2.3
|
283
pio_examples/gui/src/gui.ino
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
#include <DNSServer.h>
|
||||||
|
#include <ESPUI.h>
|
||||||
|
|
||||||
|
const byte DNS_PORT = 53;
|
||||||
|
IPAddress apIP(192, 168, 4, 1);
|
||||||
|
DNSServer dnsServer;
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#else
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = "ESPUI";
|
||||||
|
const char* password = "espui";
|
||||||
|
|
||||||
|
const char* hostname = "espui";
|
||||||
|
|
||||||
|
int statusLabelId;
|
||||||
|
int graphId;
|
||||||
|
int millisLabelId;
|
||||||
|
int testSwitchId;
|
||||||
|
|
||||||
|
void numberCall(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void textCall(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.print("Text: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void slider(Control* sender, int type)
|
||||||
|
{
|
||||||
|
Serial.print("Slider: ID: ");
|
||||||
|
Serial.print(sender->id);
|
||||||
|
Serial.print(", Value: ");
|
||||||
|
Serial.println(sender->value);
|
||||||
|
// Like all Control Values in ESPUI slider values are Strings. To use them as int simply do this:
|
||||||
|
int sliderValueWithOffset = sender->value.toInt() + 100;
|
||||||
|
Serial.print("SliderValue with offset");
|
||||||
|
Serial.println(sliderValueWithOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonCallback(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Button DOWN");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Button UP");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void buttonExample(Control* sender, int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case B_DOWN:
|
||||||
|
Serial.println("Status: Start");
|
||||||
|
ESPUI.print(statusLabelId, "Start");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_UP:
|
||||||
|
Serial.println("Status: Stop");
|
||||||
|
ESPUI.print(statusLabelId, "Stop");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void padExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case P_LEFT_DOWN:
|
||||||
|
Serial.print("left down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_LEFT_UP:
|
||||||
|
Serial.print("left up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_DOWN:
|
||||||
|
Serial.print("right down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_RIGHT_UP:
|
||||||
|
Serial.print("right up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_DOWN:
|
||||||
|
Serial.print("for down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_FOR_UP:
|
||||||
|
Serial.print("for up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_DOWN:
|
||||||
|
Serial.print("back down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_BACK_UP:
|
||||||
|
Serial.print("back up");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_DOWN:
|
||||||
|
Serial.print("center down");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case P_CENTER_UP:
|
||||||
|
Serial.print("center up");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void switchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void otherSwitchExample(Control* sender, int value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case S_ACTIVE:
|
||||||
|
Serial.print("Active:");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_INACTIVE:
|
||||||
|
Serial.print("Inactive");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(sender->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void)
|
||||||
|
{
|
||||||
|
ESPUI.setVerbosity(Verbosity::VerboseJSON);
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
WiFi.setHostname(hostname);
|
||||||
|
#else
|
||||||
|
WiFi.hostname(hostname);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// try to connect to existing network
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.print("\n\nTry to connect to existing network");
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t timeout = 10;
|
||||||
|
|
||||||
|
// Wait for connection, 5s timeout
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout && WiFi.status() != WL_CONNECTED);
|
||||||
|
|
||||||
|
// not connected -> create hotspot
|
||||||
|
if (WiFi.status() != WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.print("\n\nCreating hotspot");
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
delay(100);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
#if defined(ESP32)
|
||||||
|
uint32_t chipid = 0;
|
||||||
|
for (int i = 0; i < 17; i = i + 8)
|
||||||
|
{
|
||||||
|
chipid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
uint32_t chipid = ESP.getChipId();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char ap_ssid[25];
|
||||||
|
snprintf(ap_ssid, 26, "ESPUI-%08X", chipid);
|
||||||
|
WiFi.softAP(ap_ssid);
|
||||||
|
|
||||||
|
timeout = 5;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
timeout--;
|
||||||
|
} while (timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.start(DNS_PORT, "*", apIP);
|
||||||
|
|
||||||
|
Serial.println("\n\nWiFi parameters:");
|
||||||
|
Serial.print("Mode: ");
|
||||||
|
Serial.println(WiFi.getMode() == WIFI_AP ? "Station" : "Client");
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.getMode() == WIFI_AP ? WiFi.softAPIP() : WiFi.localIP());
|
||||||
|
|
||||||
|
statusLabelId = ESPUI.label("Status:", ControlColor::Turquoise, "Stop");
|
||||||
|
millisLabelId = ESPUI.label("Millis:", ControlColor::Emerald, "0");
|
||||||
|
ESPUI.button("Push Button", &buttonCallback, ControlColor::Peterriver, "Press");
|
||||||
|
ESPUI.button("Other Button", &buttonExample, ControlColor::Wetasphalt, "Press");
|
||||||
|
ESPUI.padWithCenter("Pad with center", &padExample, ControlColor::Sunflower);
|
||||||
|
ESPUI.pad("Pad without center", &padExample, ControlColor::Carrot);
|
||||||
|
testSwitchId = ESPUI.switcher("Switch one", &switchExample, ControlColor::Alizarin, false);
|
||||||
|
ESPUI.switcher("Switch two", &otherSwitchExample, ControlColor::None, true);
|
||||||
|
ESPUI.slider("Slider one", &slider, ControlColor::Alizarin, 30, 0, 30);
|
||||||
|
ESPUI.slider("Slider two", &slider, ControlColor::None, 100);
|
||||||
|
ESPUI.text("Text Test:", &textCall, ControlColor::Alizarin, "a Text Field");
|
||||||
|
ESPUI.number("Numbertest", &numberCall, ControlColor::Alizarin, 5, 0, 10);
|
||||||
|
|
||||||
|
graphId = ESPUI.graph("Graph Test", ControlColor::Wetasphalt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* .begin loads and serves all files from PROGMEM directly.
|
||||||
|
* If you want to serve the files from LITTLEFS use ESPUI.beginLITTLEFS
|
||||||
|
* (.prepareFileSystem has to be run in an empty sketch before)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Enable this option if you want sliders to be continuous (update during move) and not discrete (update on stop)
|
||||||
|
// ESPUI.sliderContinuous = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
|
||||||
|
* SECURE way of limiting access.
|
||||||
|
* Anyone who is able to sniff traffic will be able to intercept your password
|
||||||
|
* since it is transmitted in cleartext. Just add a string as username and
|
||||||
|
* password, for example begin("ESPUI Control", "username", "password")
|
||||||
|
*/
|
||||||
|
ESPUI.sliderContinuous = true;
|
||||||
|
ESPUI.begin("ESPUI Control");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void)
|
||||||
|
{
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
|
||||||
|
static long oldTime = 0;
|
||||||
|
static bool testSwitchState = false;
|
||||||
|
delay(10);
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (millis() - oldTime > 5000)
|
||||||
|
{
|
||||||
|
ESPUI.print(millisLabelId, String(millis()));
|
||||||
|
|
||||||
|
ESPUI.addGraphPoint(graphId, random(1, 50));
|
||||||
|
|
||||||
|
testSwitchState = !testSwitchState;
|
||||||
|
ESPUI.updateSwitcher(testSwitchId, testSwitchState);
|
||||||
|
|
||||||
|
oldTime = millis();
|
||||||
|
}
|
||||||
|
}
|
1739
src/ESPUI.cpp
344
src/ESPUI.h
@ -1,55 +1,188 @@
|
|||||||
#ifndef ESPUI_h
|
#ifndef ESPUI_h
|
||||||
#define ESPUI_h
|
#define ESPUI_h
|
||||||
|
|
||||||
#define debug true
|
#define DEBUG_ESPUI true
|
||||||
|
#define WS_AUTHENTICATION false
|
||||||
|
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
#include "FS.h"
|
|
||||||
#include "stdlib_noniso.h"
|
#include "stdlib_noniso.h"
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
|
|
||||||
#include "SPIFFS.h"
|
|
||||||
#include "WiFi.h"
|
|
||||||
#include <AsyncTCP.h>
|
#include <AsyncTCP.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <LITTLEFS.h>
|
||||||
|
|
||||||
|
#include "WiFi.h"
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include <ArduinoOTA.h>
|
|
||||||
#include <FS.h>
|
|
||||||
#include <Hash.h>
|
|
||||||
#include <ESPAsyncTCP.h>
|
#include <ESPAsyncTCP.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <SPIFFSEditor.h>
|
#include <Hash.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
#define FILE_WRITE "w"
|
#define FILE_WRITE "w"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct Control {
|
// Message Types (and control types)
|
||||||
unsigned int type;
|
|
||||||
unsigned int id; // just mirroring the id here for practical reasons
|
|
||||||
const char *label;
|
|
||||||
void (*callback)(Control, int);
|
|
||||||
String value;
|
|
||||||
unsigned int color;
|
|
||||||
} Control;
|
|
||||||
|
|
||||||
// Types
|
enum ControlType : uint8_t
|
||||||
#define UI_TITEL 0
|
{
|
||||||
#define UI_LABEL 1
|
// fixed controls
|
||||||
#define UI_BUTTON 2
|
Title = 0,
|
||||||
#define UI_SWITCHER 3
|
|
||||||
#define UI_PAD 4
|
// updatable controls
|
||||||
#define UI_CPAD 5
|
Pad,
|
||||||
#define UPDATE_LABEL 6
|
PadWithCenter,
|
||||||
#define UPDATE_SWITCHER 7
|
Button,
|
||||||
#define UI_SLIDER 8
|
Label,
|
||||||
#define UPDATE_SLIDER 9
|
Switcher,
|
||||||
|
Slider,
|
||||||
|
Number,
|
||||||
|
Text,
|
||||||
|
Graph,
|
||||||
|
GraphPoint,
|
||||||
|
Tab,
|
||||||
|
Select,
|
||||||
|
Option,
|
||||||
|
Min,
|
||||||
|
Max,
|
||||||
|
Step,
|
||||||
|
Gauge,
|
||||||
|
Accel,
|
||||||
|
Separator,
|
||||||
|
Time,
|
||||||
|
|
||||||
|
UpdateOffset = 100,
|
||||||
|
UpdatePad = 101,
|
||||||
|
UpdatePadWithCenter,
|
||||||
|
ButtonButton,
|
||||||
|
UpdateLabel,
|
||||||
|
UpdateSwitcher,
|
||||||
|
UpdateSlider,
|
||||||
|
UpdateNumber,
|
||||||
|
UpdateText,
|
||||||
|
ClearGraph,
|
||||||
|
UpdateTab,
|
||||||
|
UpdateSelection,
|
||||||
|
UpdateOption,
|
||||||
|
UpdateMin,
|
||||||
|
UpdateMax,
|
||||||
|
UpdateStep,
|
||||||
|
UpdateGauge,
|
||||||
|
UpdateAccel,
|
||||||
|
UpdateSeparator,
|
||||||
|
UpdateTime,
|
||||||
|
|
||||||
|
InitialGui = 200,
|
||||||
|
Reload = 201,
|
||||||
|
ExtendGUI = 210
|
||||||
|
};
|
||||||
|
|
||||||
|
#define UI_INITIAL_GUI ControlType::InitialGui
|
||||||
|
#define UI_RELOAD ControlType::Reload
|
||||||
|
#define UI_EXTEND_GUI ControlType::ExtendGUI
|
||||||
|
|
||||||
|
#define UI_TITLE ControlType::Title
|
||||||
|
#define UI_LABEL ControlType::Label
|
||||||
|
#define UI_BUTTON ControlType::Button
|
||||||
|
#define UI_SWITCHER ControlType::Switcher
|
||||||
|
#define UI_PAD ControlType::Pad
|
||||||
|
#define UI_CPAD ControlType::Cpad
|
||||||
|
#define UI_SLIDER ControlType::Slider
|
||||||
|
#define UI_NUMBER ControlType::Number
|
||||||
|
#define UI_TEXT_INPUT ControlType::Text
|
||||||
|
#define UI_GRAPH ControlType::Graph
|
||||||
|
#define UI_ADD_GRAPH_POINT ControlType::GraphPoint
|
||||||
|
|
||||||
|
#define UPDATE_LABEL ControlType::UpdateLabel
|
||||||
|
#define UPDATE_SWITCHER ControlType::UpdateSwitcher
|
||||||
|
#define UPDATE_SLIDER ControlType::UpdateSlider
|
||||||
|
#define UPDATE_NUMBER ControlType::UpdateNumber
|
||||||
|
#define UPDATE_TEXT_INPUT ControlType::UpdateText
|
||||||
|
#define CLEAR_GRAPH ControlType::ClearGraph
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
enum ControlColor : uint8_t
|
||||||
|
{
|
||||||
|
Turquoise,
|
||||||
|
Emerald,
|
||||||
|
Peterriver,
|
||||||
|
Wetasphalt,
|
||||||
|
Sunflower,
|
||||||
|
Carrot,
|
||||||
|
Alizarin,
|
||||||
|
Dark,
|
||||||
|
None = 0xFF
|
||||||
|
};
|
||||||
|
#define COLOR_TURQUOISE ControlColor::Turquoise
|
||||||
|
#define COLOR_EMERALD ControlColor::Emerald
|
||||||
|
#define COLOR_PETERRIVER ControlColor::Peterriver
|
||||||
|
#define COLOR_WETASPHALT ControlColor::Wetasphalt
|
||||||
|
#define COLOR_SUNFLOWER ControlColor::Sunflower
|
||||||
|
#define COLOR_CARROT ControlColor::Carrot
|
||||||
|
#define COLOR_ALIZARIN ControlColor::Alizarin
|
||||||
|
#define COLOR_DARK ControlColor::Dark
|
||||||
|
#define COLOR_NONE ControlColor::None
|
||||||
|
|
||||||
|
class Control
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ControlType type;
|
||||||
|
uint16_t id; // just mirroring the id here for practical reasons
|
||||||
|
const char* label;
|
||||||
|
void (*callback)(Control*, int);
|
||||||
|
String value;
|
||||||
|
ControlColor color;
|
||||||
|
bool visible;
|
||||||
|
bool wide;
|
||||||
|
bool vertical;
|
||||||
|
bool enabled;
|
||||||
|
uint16_t parentControl;
|
||||||
|
String panelStyle;
|
||||||
|
String elementStyle;
|
||||||
|
String inputType;
|
||||||
|
Control* next;
|
||||||
|
|
||||||
|
static constexpr uint16_t noParent = 0xffff;
|
||||||
|
|
||||||
|
Control(ControlType type, const char* label, void (*callback)(Control*, int), const String& value,
|
||||||
|
ControlColor color, bool visible = true, uint16_t parentControl = Control::noParent)
|
||||||
|
: type(type),
|
||||||
|
label(label),
|
||||||
|
callback(callback),
|
||||||
|
value(value),
|
||||||
|
color(color),
|
||||||
|
visible(visible),
|
||||||
|
wide(false),
|
||||||
|
vertical(false),
|
||||||
|
enabled(true),
|
||||||
|
parentControl(parentControl),
|
||||||
|
next(nullptr)
|
||||||
|
{
|
||||||
|
id = idCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Control(const Control& control)
|
||||||
|
: type(control.type),
|
||||||
|
id(control.id),
|
||||||
|
label(control.label),
|
||||||
|
callback(control.callback),
|
||||||
|
value(control.value),
|
||||||
|
color(control.color),
|
||||||
|
visible(control.visible),
|
||||||
|
parentControl(control.parentControl),
|
||||||
|
next(control.next)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static uint16_t idCounter;
|
||||||
|
};
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
#define B_DOWN -1
|
#define B_DOWN -1
|
||||||
@ -70,60 +203,131 @@ typedef struct Control {
|
|||||||
#define S_INACTIVE 7
|
#define S_INACTIVE 7
|
||||||
|
|
||||||
#define SL_VALUE 8
|
#define SL_VALUE 8
|
||||||
|
#define N_VALUE 9
|
||||||
|
#define T_VALUE 10
|
||||||
|
#define S_VALUE 11
|
||||||
|
#define TM_VALUE 12
|
||||||
|
|
||||||
|
enum Verbosity : uint8_t
|
||||||
|
{
|
||||||
|
Quiet = 0,
|
||||||
|
Verbose,
|
||||||
|
VerboseJSON
|
||||||
|
};
|
||||||
|
|
||||||
// Colors
|
class ESPUIClass
|
||||||
#define COLOR_TURQUOISE 0
|
{
|
||||||
#define COLOR_EMERALD 1
|
|
||||||
#define COLOR_PETERRIVER 2
|
|
||||||
#define COLOR_WETASPHALT 3
|
|
||||||
#define COLOR_SUNFLOWER 4
|
|
||||||
#define COLOR_CARROT 5
|
|
||||||
#define COLOR_ALIZARIN 6
|
|
||||||
#define COLOR_NONE 6
|
|
||||||
|
|
||||||
|
|
||||||
class ESPUIClass {
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void begin(const char *_title); // Setup servers and page
|
ESPUIClass()
|
||||||
|
{
|
||||||
|
verbosity = Verbosity::Quiet;
|
||||||
|
jsonUpdateDocumentSize = 2000;
|
||||||
|
jsonInitialDocumentSize = 8000;
|
||||||
|
sliderContinuous = false;
|
||||||
|
}
|
||||||
|
unsigned int jsonUpdateDocumentSize;
|
||||||
|
unsigned int jsonInitialDocumentSize;
|
||||||
|
bool sliderContinuous;
|
||||||
|
|
||||||
void prepareFileSystem(); // Initially preps the filesystem and loads a lot of stuff into SPIFFS
|
void setVerbosity(Verbosity verbosity);
|
||||||
|
void begin(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
||||||
|
uint16_t port = 80); // Setup server and page in Memorymode
|
||||||
|
void beginSPIFFS(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
||||||
|
uint16_t port = 80); // Setup server and page in LITTLEFS mode (DEPRECATED, use beginLITTLEFS)
|
||||||
|
void beginLITTLEFS(const char* _title, const char* username = nullptr, const char* password = nullptr,
|
||||||
|
uint16_t port = 80); // Setup server and page in LITTLEFS mode
|
||||||
|
|
||||||
// Creating Elements
|
void prepareFileSystem(); // Initially preps the filesystem and loads a lot of
|
||||||
void label(const char *label, int color, String value = ""); // Create Label
|
// stuff into LITTLEFS
|
||||||
void button(const char *label, void (*callBack)(Control, int), int color,
|
void list(); // Lists LITTLEFS directory
|
||||||
String value = ""); // Create Event Button
|
|
||||||
void switcher(const char *label, bool startState,
|
|
||||||
void (*callBack)(Control, int),
|
|
||||||
int color); // Create Toggle Button
|
|
||||||
void pad(const char *label, bool centerButton, void (*callBack)(Control, int),
|
|
||||||
int color); // Create Pad Control
|
|
||||||
void slider(const char *label, void (*callBack)(Control, int), int color, String value); // Create Slider Control
|
|
||||||
|
|
||||||
// Update Elements
|
uint16_t addControl(ControlType type, const char* label, const String& value = String(""),
|
||||||
void print(int id, String value);
|
ControlColor color = ControlColor::Turquoise, uint16_t parentControl = Control::noParent,
|
||||||
void print(String label, String value);
|
void (*callback)(Control*, int) = nullptr);
|
||||||
|
bool removeControl(uint16_t id, bool force_reload_ui = false);
|
||||||
|
|
||||||
void updateSwitcher(int id, bool nValue, int clientId = -1);
|
// create Elements
|
||||||
void updateSwitcher(String label, bool nValue, int clientId = -1);
|
uint16_t button(const char* label, void (*callback)(Control*, int), ControlColor color,
|
||||||
|
const String& value = ""); // Create Event Button
|
||||||
|
uint16_t switcher(const char* label, void (*callback)(Control*, int), ControlColor color,
|
||||||
|
bool startState = false); // Create Toggle Button
|
||||||
|
uint16_t pad(const char* label, void (*callback)(Control*, int),
|
||||||
|
ControlColor color); // Create Pad Control
|
||||||
|
uint16_t padWithCenter(const char* label, void (*callback)(Control*, int),
|
||||||
|
ControlColor color); // Create Pad Control with Centerbutton
|
||||||
|
|
||||||
void updateSlider(int id, int nValue, int clientId = -1);
|
uint16_t slider(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0,
|
||||||
void updateSlider(String label, int nValue, int clientId = -1);
|
int max = 100); // Create Slider Control
|
||||||
|
uint16_t number(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0,
|
||||||
|
int max = 100); // Create a Number Input Control
|
||||||
|
uint16_t text(const char* label, void (*callback)(Control*, int), ControlColor color,
|
||||||
|
const String& value = ""); // Create a Text Input Control
|
||||||
|
|
||||||
void textThem(String text, int clientId);
|
// Output only
|
||||||
|
uint16_t label(const char* label, ControlColor color,
|
||||||
|
const String& value = ""); // Create Label
|
||||||
|
uint16_t graph(const char* label, ControlColor color); // Create Graph display
|
||||||
|
uint16_t gauge(const char* label, ControlColor color, int value, int min = 0,
|
||||||
|
int max = 100); // Create Gauge display
|
||||||
|
uint16_t separator(const char* label); //Create separator
|
||||||
|
|
||||||
// Variables ---
|
// Input only
|
||||||
const char *ui_title = "ESPUI"; // Store UI Title and Header Name
|
uint16_t accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color);
|
||||||
int cIndex = 0; // Control index
|
|
||||||
Control *controls[25];
|
// Update Elements
|
||||||
void jsonDom(AsyncWebSocketClient *client);
|
|
||||||
int getIdByLabel(String label);
|
Control* getControl(uint16_t id);
|
||||||
bool labelExists(String label);
|
|
||||||
|
// Update Elements
|
||||||
|
void updateControlValue(uint16_t id, const String& value, int clientId = -1);
|
||||||
|
void updateControlValue(Control* control, const String& value, int clientId = -1);
|
||||||
|
|
||||||
|
void updateControl(uint16_t id, int clientId = -1);
|
||||||
|
void updateControl(Control* control, int clientId = -1);
|
||||||
|
|
||||||
|
void print(uint16_t id, const String& value);
|
||||||
|
void updateLabel(uint16_t id, const String& value);
|
||||||
|
void updateButton(uint16_t id, const String& value);
|
||||||
|
void updateSwitcher(uint16_t id, bool nValue, int clientId = -1);
|
||||||
|
void updateSlider(uint16_t id, int nValue, int clientId = -1);
|
||||||
|
void updateNumber(uint16_t id, int nValue, int clientId = -1);
|
||||||
|
void updateText(uint16_t id, const String& nValue, int clientId = -1);
|
||||||
|
void updateSelect(uint16_t id, const String& nValue, int clientId = -1);
|
||||||
|
void updateGauge(uint16_t id, int number, int clientId);
|
||||||
|
void updateTime(uint16_t id, int clientId = -1);
|
||||||
|
|
||||||
|
void clearGraph(uint16_t id, int clientId = -1);
|
||||||
|
void addGraphPoint(uint16_t id, int nValue, int clientId = -1);
|
||||||
|
|
||||||
|
void setPanelStyle(uint16_t id, String style, int clientId = -1);
|
||||||
|
void setElementStyle(uint16_t id, String style, int clientId = -1);
|
||||||
|
void setInputType(uint16_t id, String type, int clientId = -1);
|
||||||
|
|
||||||
|
void setPanelWide(uint16_t id, bool wide);
|
||||||
|
void setVertical(uint16_t id, bool vert = true);
|
||||||
|
void setEnabled(uint16_t id, bool enabled = true, int clientId = -1);
|
||||||
|
|
||||||
|
void updateVisibility(uint16_t id, bool visibility, int clientId = -1);
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
const char* ui_title = "ESPUI"; // Store UI Title and Header Name
|
||||||
|
Control* controls = nullptr;
|
||||||
|
void jsonReload();
|
||||||
|
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr);
|
||||||
|
|
||||||
|
Verbosity verbosity;
|
||||||
|
|
||||||
|
AsyncWebServer* server;
|
||||||
|
AsyncWebSocket* ws;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AsyncWebServer *server;
|
const char* basicAuthUsername = nullptr;
|
||||||
AsyncWebSocket *ws;
|
const char* basicAuthPassword = nullptr;
|
||||||
|
bool basicAuth = true;
|
||||||
|
|
||||||
|
uint16_t controlCount = 0;
|
||||||
|
|
||||||
|
void prepareJSONChunk(AsyncWebSocketClient* client, uint16_t startindex, JsonArray* items);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ESPUIClass ESPUI;
|
extern ESPUIClass ESPUI;
|
||||||
|
108
src/dataControlsJS.h
Normal file
19
src/dataGraphJS.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const char JS_GRAPH[] PROGMEM = R"=====(
|
||||||
|
function lineGraph(parent,xAccessor,yAccessor){const width=620;const height=420;const gutter=40;const pixelsPerTick=30;function numericTransformer(dataMin,dataMax,pxMin,pxMax){var dataDiff=dataMax-dataMin,pxDiff=pxMax-pxMin,dataRatio=pxDiff/dataDiff,coordRatio=dataDiff/pxDiff;return{toCoord:function(data){return(data-dataMin)*dataRatio+pxMin;},toData:function(coord){return(coord-pxMin)*coordRatio+dataMin;}};}
|
||||||
|
function axisRenderer(orientation,transform){var axisGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var axisPath=document.createElementNS("http://www.w3.org/2000/svg","path");axisGroup.setAttribute("class",orientation+"-axis");var xMin=gutter;var xMax=width-gutter;var yMin=height-gutter;var yMax=gutter;if(orientation==="x"){axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMax+" "+yMin);for(var i=xMin;i<=xMax;i++){if((i-xMin)%pixelsPerTick===0&&i!==xMin){var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",i);text.setAttribute("y",yMin);text.setAttribute("dy","1em");axisGroup.appendChild(text);}}}else{axisPath.setAttribute("d","M "+xMin+" "+yMin+" L "+xMin+" "+yMax);for(var i=yMax;i<=yMin;i++){if((i-yMin)%pixelsPerTick===0&&i!==yMin){var tickGroup=document.createElementNS("http://www.w3.org/2000/svg","g");var gridLine=document.createElementNS("http://www.w3.org/2000/svg","path");text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(transform(i));text.setAttribute("x",xMin);text.setAttribute("y",i);text.setAttribute("dx","-.5em");text.setAttribute("dy",".3em");gridLine.setAttribute("d","M "+xMin+" "+i+" L "+xMax+" "+i);tickGroup.appendChild(gridLine);tickGroup.appendChild(text);axisGroup.appendChild(tickGroup);}}}
|
||||||
|
axisGroup.appendChild(axisPath);parent.appendChild(axisGroup);}
|
||||||
|
function lineRenderer(xAccessor,yAccessor,xTransform,yTransform){var line=document.createElementNS("http://www.w3.org/2000/svg","path");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
|
||||||
|
var pathString="M "+xTransform(xAccessor.next())+" "+yTransform(yAccessor.next());while(xAccessor.hasNext()&&yAccessor.hasNext()){pathString+=" L "+
|
||||||
|
xTransform(xAccessor.next())+
|
||||||
|
" "+
|
||||||
|
yTransform(yAccessor.next());}
|
||||||
|
line.setAttribute("class","series");line.setAttribute("d",pathString);parent.appendChild(line);}
|
||||||
|
function pointRenderer(xAccessor,yAccessor,xTransform,yTransform){var pointGroup=document.createElementNS("http://www.w3.org/2000/svg","g");pointGroup.setAttribute("class","data-points");xAccessor.reset();yAccessor.reset();if(!xAccessor.hasNext()||!yAccessor.hasNext()){return;}
|
||||||
|
while(xAccessor.hasNext()&&yAccessor.hasNext()){var xDataValue=xAccessor.next();var x=xTransform(xDataValue);var yDataValue=yAccessor.next();var y=yTransform(yDataValue);var circle=document.createElementNS("http://www.w3.org/2000/svg","circle");circle.setAttribute("cx",x);circle.setAttribute("cy",y);circle.setAttribute("r","4");var text=document.createElementNS("http://www.w3.org/2000/svg","text");text.innerHTML=Math.floor(xDataValue)+" / "+Math.floor(yDataValue);text.setAttribute("x",x);text.setAttribute("y",y);text.setAttribute("dx","1em");text.setAttribute("dy","-.7em");pointGroup.appendChild(circle);pointGroup.appendChild(text);}
|
||||||
|
parent.appendChild(pointGroup);}
|
||||||
|
xTransform=numericTransformer(xAccessor.min(),xAccessor.max(),0+gutter,width-gutter);yTransform=numericTransformer(yAccessor.min(),yAccessor.max(),height-gutter,0+gutter);axisRenderer("x",xTransform.toData);axisRenderer("y",yTransform.toData);lineRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);pointRenderer(xAccessor,yAccessor,xTransform.toCoord,yTransform.toCoord);}
|
||||||
|
function renderGraphSvg(dataArray,renderId){var figure=document.getElementById(renderId);while(figure.hasChildNodes()){figure.removeChild(figure.lastChild);}
|
||||||
|
var svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.setAttribute("viewBox","0 0 640 440");svg.setAttribute("preserveAspectRatio","xMidYMid meet");lineGraph(svg,(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].x;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.x;})),Math.max.apply(Math,dataArray.map(function(o){return o.x;}))),(function(data,min,max){var i=0;return{hasNext:function(){return i<data.length;},next:function(){return data[i++].y;},reset:function(){i=0;},min:function(){return min;},max:function(){return max;}};})(dataArray,Math.min.apply(Math,dataArray.map(function(o){return o.y;})),Math.max.apply(Math,dataArray.map(function(o){return o.y;}))));figure.appendChild(svg);}
|
||||||
|
)=====";
|
||||||
|
|
||||||
|
const uint8_t JS_GRAPH_GZIP[1245] PROGMEM = { 31,139,8,0,143,181,138,98,2,255,205,87,95,111,219,54,16,127,247,167,112,4,44,16,107,89,86,27,175,3,170,240,33,109,135,174,64,18,20,77,48,96,24,246,192,73,180,76,76,150,4,138,182,69,184,254,238,59,146,162,36,219,82,134,58,45,186,135,56,226,253,231,241,119,119,228,98,157,69,130,229,217,56,101,25,253,192,73,177,116,11,194,105,38,188,234,38,138,104,89,230,220,147,246,11,237,162,60,43,197,120,203,98,177,196,175,95,5,161,89,47,41,75,150,2,207,27,66,178,22,130,114,60,183,235,130,85,52,45,63,81,254,200,162,127,240,85,16,46,172,219,108,189,162,156,69,143,156,100,229,34,231,176,112,99,34,200,29,203,60,253,159,84,94,81,169,21,252,146,10,237,54,132,143,21,227,61,91,44,112,45,49,181,26,69,165,201,90,116,106,212,20,235,51,1,87,216,48,103,86,217,139,242,156,199,134,101,105,51,35,19,114,42,214,60,219,137,252,157,146,121,99,131,213,145,161,157,225,234,133,245,140,94,52,126,38,218,111,184,247,68,254,30,104,173,178,246,215,104,235,149,137,17,189,104,67,153,212,6,195,253,62,220,143,154,44,145,138,149,159,105,22,83,14,249,201,57,131,243,81,210,153,39,108,222,76,102,148,220,7,158,175,11,28,231,17,100,54,19,126,196,41,17,244,215,148,170,213,253,131,235,44,133,40,222,204,102,219,237,214,223,94,249,57,79,102,175,130,32,152,149,155,196,241,156,196,65,161,53,244,137,192,33,159,105,167,0,93,48,213,196,227,151,84,220,8,193,217,223,107,65,93,39,74,73,89,58,94,103,39,19,103,170,132,107,247,42,45,216,128,168,94,147,10,107,212,77,59,84,169,164,12,246,14,201,32,92,175,217,162,155,46,140,177,83,57,104,103,119,119,20,85,12,129,223,141,157,137,242,62,113,224,67,154,143,91,77,35,85,67,67,33,164,220,85,190,24,214,167,205,174,177,18,8,217,100,130,118,224,211,101,83,125,178,63,29,2,31,227,224,242,146,93,96,173,100,78,76,208,74,156,155,100,165,11,9,83,255,124,150,101,148,255,246,120,119,139,239,212,198,22,41,64,202,109,208,225,50,84,203,29,238,184,114,60,214,203,144,142,103,54,218,195,139,129,233,188,164,171,131,243,37,69,1,248,124,183,100,105,236,42,29,4,8,222,195,214,233,121,201,110,104,80,243,157,100,75,157,228,107,44,117,210,219,100,203,167,146,45,219,100,3,253,155,148,71,194,89,124,11,13,243,153,229,241,195,15,191,26,58,99,57,4,140,24,180,156,169,255,179,62,254,33,112,248,87,154,109,179,244,95,39,207,142,107,76,185,182,71,117,128,44,107,113,136,111,144,55,128,74,171,161,161,57,234,23,178,96,69,161,153,130,39,76,107,161,237,205,106,112,54,189,185,103,104,122,85,51,220,60,249,120,216,175,211,231,99,168,241,232,115,10,121,118,81,40,79,40,80,36,23,173,220,146,148,247,144,39,23,125,249,114,33,79,169,118,68,193,30,85,136,202,205,3,156,93,150,96,115,106,205,22,218,205,250,153,209,52,53,219,10,200,99,129,112,11,137,164,110,79,44,151,151,189,161,180,222,39,216,128,100,244,100,0,35,21,193,232,201,16,246,163,244,20,147,245,64,114,74,184,140,80,53,133,210,94,220,182,225,244,2,36,213,208,236,96,163,200,89,38,206,5,135,86,126,118,187,106,173,12,108,89,223,99,180,84,249,157,225,244,181,135,175,39,191,186,68,253,78,210,53,197,199,167,109,110,6,184,139,135,70,216,48,101,171,44,251,148,37,238,34,229,72,57,98,60,74,207,174,78,163,13,9,53,31,199,185,87,189,119,136,167,102,239,0,143,131,229,121,61,131,190,235,240,232,100,18,138,122,6,69,213,97,118,51,53,48,87,6,47,21,195,67,229,229,147,35,101,234,255,162,249,29,56,119,43,207,100,107,144,93,223,72,70,61,53,219,106,40,129,22,76,184,231,101,210,34,112,197,50,23,121,157,53,169,96,29,76,204,181,211,235,94,85,161,130,158,52,42,143,140,202,35,163,7,23,220,198,133,153,114,77,107,209,73,111,172,250,230,237,113,44,163,242,127,42,243,21,243,203,175,95,68,135,102,52,169,78,253,183,48,212,233,159,92,155,211,207,210,135,77,162,159,92,55,156,19,233,25,198,199,216,52,137,5,75,214,188,83,170,9,21,117,37,188,149,31,99,183,17,174,199,143,17,87,157,70,67,224,62,143,105,169,250,77,77,231,116,149,111,168,65,71,77,130,86,41,52,1,213,83,17,202,232,220,218,83,191,40,132,223,35,152,111,24,221,190,205,85,37,4,227,96,252,122,30,140,231,243,160,87,178,80,141,152,111,232,77,89,208,72,232,119,35,104,193,117,42,254,3,254,198,43,74,69,61,193,204,131,30,44,120,238,193,35,214,3,168,121,43,251,158,102,56,176,79,222,186,251,182,175,86,219,190,199,236,90,41,250,41,205,18,177,132,231,109,214,47,166,132,254,132,155,249,95,126,5,66,122,98,116,165,148,171,189,242,222,163,186,210,207,102,136,170,143,7,247,126,245,36,70,29,12,232,142,4,74,170,156,83,233,170,165,215,112,161,120,138,118,207,121,99,40,87,113,33,84,43,147,234,28,101,244,3,178,41,255,167,217,148,207,201,166,86,134,43,89,93,101,221,182,12,152,133,90,251,23,198,24,146,161,158,18,0,0 };
|
5
src/dataIndexHTML.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const char HTML_INDEX[] PROGMEM = R"=====(
|
||||||
|
<!DOCTYPE html><html> <head><meta charset=utf-8><title>Control</title><meta name=viewport content="width=device-width, initial-scale=1"><link rel="shortcut icon" href=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC><link rel=stylesheet href=/css/normalize.css><link rel=stylesheet href=/css/style.css><script src=/js/zepto.min.js></script><script src=/js/slider.js></script><script src=/js/graph.js></script><script src=/js/controls.js></script><script src=/js/tabbedcontent.js></script></head> <body onload=javascript:start();> <div> <h4> <div id=mainHeader>Control</div> <span id=conStatus class=label>Offline</span> </h4> </div> <hr> <div class=container> <div id=row class="row u-full-width"></div> <ul id=tabsnav class="navigation navigation-tabs u-full-width"></ul> <div id=tabscontent class="tabscontent u-full-width"></div> </div> </body> </html>
|
||||||
|
)=====";
|
||||||
|
|
||||||
|
const uint8_t HTML_INDEX_GZIP[916] PROGMEM = { 31,139,8,0,143,181,138,98,2,255,133,148,235,115,162,58,20,192,255,21,174,159,238,157,221,22,95,181,237,174,56,19,20,108,85,68,64,240,241,45,64,42,193,240,40,9,162,254,245,155,128,157,238,157,189,211,235,12,201,201,57,191,243,200,17,206,240,175,137,57,94,239,86,154,20,177,132,140,134,245,42,13,35,4,195,209,48,65,12,74,65,4,11,138,152,82,178,183,187,167,209,144,97,70,208,104,156,165,172,200,200,80,110,142,13,153,194,4,41,39,140,170,60,43,152,20,112,4,165,76,105,85,56,100,145,18,162,19,14,208,93,125,248,46,225,20,51,12,201,29,13,32,65,74,167,53,26,18,156,30,165,2,17,165,69,35,238,30,148,76,194,60,68,75,138,10,244,166,132,144,193,31,56,129,7,36,231,233,225,167,15,41,26,244,191,99,79,53,237,170,61,159,30,50,192,127,75,199,141,52,247,192,37,85,28,129,53,6,134,216,179,133,245,188,22,130,58,13,213,181,171,1,176,152,174,198,242,57,82,45,174,28,171,177,163,207,150,220,58,152,113,223,195,43,87,174,68,188,49,24,240,53,20,158,102,46,162,14,18,190,232,189,113,80,18,237,73,196,91,233,170,225,105,110,205,118,188,141,221,237,188,91,93,130,237,158,254,46,30,167,231,149,118,207,227,114,179,155,221,78,226,116,59,165,83,219,248,195,229,89,119,58,183,184,31,231,57,163,139,188,170,220,155,150,143,34,190,150,218,206,154,24,64,205,64,239,184,82,171,235,19,126,152,95,113,118,62,244,151,94,224,246,74,109,246,82,223,116,51,91,216,109,11,96,224,134,78,173,32,149,163,147,171,232,73,167,2,96,18,6,170,133,177,143,106,91,216,118,220,142,174,126,211,116,45,10,52,227,219,75,190,171,68,35,212,157,227,218,170,167,198,235,104,39,207,38,20,28,198,154,195,250,36,223,202,167,231,120,97,237,103,131,119,253,153,245,142,17,184,196,157,243,214,157,251,86,95,245,178,106,82,189,155,187,131,118,62,30,55,125,188,27,144,21,212,219,3,251,221,120,218,189,50,179,180,125,3,94,7,231,216,167,37,157,128,141,28,22,57,81,15,253,199,120,53,120,30,4,91,237,201,95,129,14,114,39,104,222,21,213,205,182,182,190,121,177,143,187,173,77,204,100,121,217,111,244,246,222,2,23,99,162,245,22,107,208,89,172,181,190,55,121,189,154,49,104,155,177,123,158,95,193,133,203,213,89,123,120,70,149,248,43,188,182,237,69,237,253,148,251,173,115,230,119,237,124,159,30,129,17,131,243,242,210,174,150,78,251,108,234,214,197,184,102,213,114,146,117,12,135,86,70,156,85,198,194,93,159,131,64,148,176,15,53,111,23,234,203,211,62,181,123,187,237,140,128,151,176,23,94,30,114,63,97,215,93,87,175,246,206,195,41,72,144,255,24,87,176,110,169,70,244,245,209,41,173,100,60,254,237,77,166,236,66,16,141,16,98,205,75,44,7,148,202,105,86,36,144,224,43,186,231,167,255,131,107,101,3,210,160,192,57,147,104,17,40,114,76,229,43,202,89,118,159,224,244,62,230,70,185,177,254,65,81,130,67,84,124,137,28,10,152,71,95,18,65,243,173,211,47,33,6,125,31,133,183,111,254,223,164,92,15,19,105,232,103,225,69,202,82,146,193,80,137,225,9,54,246,31,148,193,130,253,253,207,79,78,132,248,36,102,79,191,17,37,28,42,9,196,233,11,119,71,197,231,200,105,40,154,195,84,16,60,165,195,32,43,169,20,16,72,169,66,160,143,200,200,124,123,227,157,69,188,8,142,113,90,174,131,222,92,163,226,150,160,241,16,69,243,52,168,248,76,91,100,213,205,216,18,98,121,247,86,18,210,204,46,62,170,110,97,74,34,80,126,113,154,194,143,88,45,46,226,3,100,56,75,165,79,241,78,64,127,68,41,201,103,66,1,220,154,247,17,233,119,213,127,23,240,177,137,198,214,119,172,103,247,47,103,53,186,226,210,5,0,0 };
|
5
src/dataNormalizeCSS.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const char CSS_NORMALIZE[] PROGMEM = R"=====(
|
||||||
|
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
|
||||||
|
)=====";
|
||||||
|
|
||||||
|
const uint8_t CSS_NORMALIZE_GZIP[861] PROGMEM = { 31,139,8,0,143,181,138,98,2,255,149,84,237,142,155,58,16,125,149,104,171,74,183,146,137,216,237,199,94,25,221,39,137,242,99,176,7,112,227,47,217,38,155,20,241,238,119,12,132,36,219,108,165,254,2,6,123,230,204,57,103,166,75,70,15,141,179,169,104,192,40,125,230,17,108,44,34,6,213,84,133,137,69,194,83,42,162,250,133,5,200,159,125,76,252,185,44,63,87,197,27,214,7,149,30,255,29,107,39,207,131,129,208,42,203,203,17,66,82,66,35,131,168,36,50,137,9,148,142,172,81,173,0,159,148,179,249,181,15,200,26,231,18,6,214,33,200,252,104,131,235,61,51,160,44,51,104,123,102,225,200,34,138,233,70,236,13,165,63,15,82,69,175,225,204,107,237,196,97,132,94,42,199,4,216,35,68,230,131,107,3,198,200,142,84,213,173,39,149,213,202,98,49,93,168,142,152,161,129,46,64,171,214,242,26,34,230,191,115,34,110,93,250,103,39,136,153,224,116,220,127,89,83,88,103,177,234,80,181,93,162,238,118,157,146,18,237,158,37,52,244,59,225,221,185,17,134,26,196,33,247,98,101,33,156,118,129,167,64,12,123,8,104,211,8,28,168,163,35,145,195,59,71,112,6,215,167,12,33,211,86,215,97,151,84,210,184,31,106,23,136,147,162,118,41,57,195,159,253,105,35,233,21,229,88,179,72,240,108,59,43,248,54,131,170,157,150,163,108,236,28,140,233,172,145,171,68,61,138,177,123,94,130,36,25,127,65,83,45,42,109,127,188,162,217,148,35,125,30,110,16,243,79,77,83,86,51,236,79,101,89,142,209,128,214,55,41,254,37,181,99,79,40,122,127,19,125,253,254,185,154,104,190,176,84,121,23,85,86,142,7,36,142,168,225,15,185,207,153,146,243,188,216,126,71,147,115,15,75,215,197,246,37,71,148,105,23,58,136,163,120,108,39,153,120,32,239,124,25,50,131,141,118,111,124,214,100,156,141,117,113,226,51,117,248,173,244,167,177,11,67,97,220,47,162,243,148,241,42,219,242,44,51,233,145,67,213,7,225,85,113,79,41,215,74,208,39,55,10,71,198,62,212,146,76,135,44,130,241,119,3,101,156,117,164,183,64,182,190,85,87,174,8,213,88,247,212,161,101,202,250,62,49,231,211,108,125,34,132,236,206,242,136,145,89,96,152,101,80,182,163,217,76,83,134,245,99,157,181,57,211,21,222,81,69,85,107,188,84,152,83,14,211,212,78,54,108,92,48,179,81,151,19,29,173,131,205,4,100,151,206,30,255,123,154,227,79,123,118,27,164,193,194,244,46,70,82,25,69,193,225,178,27,192,123,4,42,34,144,207,73,42,209,135,72,45,120,167,136,214,176,148,220,209,188,0,97,148,251,219,226,107,112,88,46,73,108,160,215,105,185,196,249,164,96,227,68,31,11,101,45,45,140,233,222,239,241,213,44,149,7,41,179,168,229,56,29,29,110,29,106,137,7,208,227,109,63,162,67,113,32,225,223,183,14,180,27,158,242,72,174,46,89,167,243,244,190,198,114,199,246,166,198,240,180,39,116,11,55,19,180,34,122,101,139,91,241,63,60,79,107,225,254,252,176,0,159,252,119,39,3,113,46,186,199,50,100,221,27,133,90,86,127,242,255,229,226,95,141,199,67,12,87,252,115,164,16,25,134,126,212,242,135,87,36,10,23,32,239,142,71,29,77,214,157,90,34,67,94,164,206,251,49,58,173,228,38,42,77,147,176,142,199,230,197,95,37,218,126,165,117,178,217,254,120,153,30,175,121,183,104,108,209,202,71,142,89,135,240,126,240,47,179,250,251,250,77,217,189,151,189,77,147,171,193,71,228,151,151,106,249,145,151,193,82,64,178,212,13,215,130,255,3,4,241,118,208,151,7,0,0 };
|
15
src/dataSliderJS.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const char JS_SLIDER[] PROGMEM = R"=====(
|
||||||
|
function rkmd_rangeSlider(selector){var self,slider_width,slider_offset,curnt,sliderDiscrete,range,slider;self=$(selector);slider_width=self.width();slider_offset=self.offset().left;sliderDiscrete=self;sliderDiscrete.each(function(i,v){curnt=$(this);curnt.append(sliderDiscrete_tmplt());range=curnt.find('input[type="range"]');slider=curnt.find(".slider");slider_fill=slider.find(".slider-fill");slider_handle=slider.find(".slider-handle");slider_label=slider.find(".slider-label");var range_val=parseInt(range.val());slider_fill.css("width",range_val+"%");slider_handle.css("left",range_val+"%");slider_label.find("span").text(range_val);});self.on("mousedown touchstart",".slider-handle",function(e){if(e.button===2){return false;}
|
||||||
|
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
|
||||||
|
$(this).addClass("is-active");var moveFu=function(e){var pageX=e.pageX||e.changedTouches[0].pageX;var slider_new_width=pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}};var upFu=function(e){$(this).off(handlers);parents.find(".is-active").removeClass("is-active");};var handlers={mousemove:moveFu,touchmove:moveFu,mouseup:upFu,touchend:upFu,};$(document).on(handlers);});self.on("mousedown touchstart",".slider",function(e){if(e.button===2){return false;}
|
||||||
|
var parents=$(this).parents(".rkmd-slider");var slider_width=parents.width();var slider_offset=parents.offset().left;var check_range=parents.find('input[type="range"]').is(":disabled");if(check_range===true){return false;}
|
||||||
|
var slider_new_width=e.pageX-slider_offset;if(slider_new_width<=slider_width&&!(slider_new_width<"0")){slider_move(parents,slider_new_width,slider_width,true);}
|
||||||
|
var upFu=function(e){$(this).off(handlers);};var handlers={mouseup:upFu,touchend:upFu,};$(document).on(handlers);});}
|
||||||
|
function sliderDiscrete_tmplt(){var tmplt='<div class="slider">'+
|
||||||
|
'<div class="slider-fill"></div>'+
|
||||||
|
'<div class="slider-handle"><div class="slider-label"><span>0</span></div></div>'+
|
||||||
|
"</div>";return tmplt;}
|
||||||
|
function slider_move(parents,newW,sliderW,send){var slider_new_val=parseInt(Math.round((newW/sliderW)*100));var slider_fill=parents.find(".slider-fill");var slider_handle=parents.find(".slider-handle");var range=parents.find('input[type="range"]');range.next().html(newW);slider_fill.css("width",slider_new_val+"%");slider_handle.css({left:slider_new_val+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none",});range.val(slider_new_val);if(parents.find(".slider-handle span").text()!=slider_new_val){parents.find(".slider-handle span").text(slider_new_val);var number=parents.attr("id").substring(2);if(send)websock.send("slvalue:"+slider_new_val+":"+number);}}
|
||||||
|
)=====";
|
||||||
|
|
||||||
|
const uint8_t JS_SLIDER_GZIP[881] PROGMEM = { 31,139,8,0,143,181,138,98,2,255,237,86,77,143,218,48,16,189,243,43,88,107,187,196,93,240,210,61,18,204,165,85,165,30,122,106,165,86,90,173,144,73,156,141,69,112,162,216,129,182,44,255,189,227,143,132,36,192,106,219,83,15,61,37,246,60,143,223,204,60,123,156,84,50,210,34,151,195,114,189,137,151,37,147,79,252,75,38,98,94,6,138,103,60,210,121,137,247,91,86,14,97,148,140,149,181,44,119,34,214,105,61,200,147,68,113,61,142,170,82,106,63,247,65,168,168,228,154,143,173,59,63,25,26,15,244,250,232,54,108,123,163,198,74,236,111,208,88,156,107,103,114,255,1,38,25,79,116,216,221,199,34,122,115,132,179,40,13,18,31,93,32,198,91,188,183,28,129,130,78,133,194,161,29,17,86,20,92,198,65,119,241,82,111,138,12,54,195,161,141,128,58,104,34,0,56,18,178,168,244,131,254,89,112,138,172,21,61,142,106,198,109,32,34,110,14,53,225,36,34,203,168,251,239,66,38,198,114,196,165,76,198,25,63,143,116,182,35,54,99,43,126,193,169,53,1,210,84,207,18,93,110,89,70,11,86,42,254,73,234,192,78,17,152,50,97,182,24,146,72,169,0,217,74,160,113,179,238,22,189,233,19,116,64,83,142,75,56,203,192,179,82,5,147,8,19,205,127,248,173,13,26,135,7,28,186,242,202,0,109,242,74,241,56,223,201,161,206,171,40,85,154,149,224,186,31,250,184,169,41,199,123,145,4,156,172,42,173,115,73,41,189,199,123,168,30,148,96,152,176,76,241,240,48,48,177,67,196,92,106,85,215,157,248,49,36,202,72,126,210,84,201,170,188,45,73,15,108,84,217,2,120,101,214,136,174,56,13,46,74,121,180,118,199,169,65,189,160,31,34,128,207,44,22,138,173,50,30,3,25,8,172,237,130,82,93,86,252,36,188,58,36,22,199,239,51,102,202,33,212,132,65,118,182,220,7,180,201,183,252,99,69,219,57,115,57,121,226,223,41,39,246,251,252,12,181,76,205,62,241,87,147,120,174,30,166,143,206,212,142,89,242,93,147,24,48,77,58,169,48,132,251,184,57,109,167,243,230,230,234,20,129,166,8,227,189,159,54,92,3,159,172,113,31,218,189,123,108,54,194,195,193,242,171,138,94,132,117,90,128,90,224,84,83,194,121,239,148,1,145,86,166,72,201,205,222,103,82,232,54,168,125,208,189,149,168,193,206,92,98,199,86,169,237,9,139,168,138,153,33,229,172,112,189,184,209,33,188,14,226,60,170,54,64,3,27,197,31,185,189,254,24,252,215,127,47,188,19,117,122,85,255,11,250,28,252,129,60,207,74,237,111,132,116,24,212,155,13,207,119,53,123,3,216,127,58,154,199,98,59,140,140,240,41,242,74,88,140,110,7,103,230,93,147,90,204,239,192,114,9,226,175,232,197,25,147,235,70,139,185,233,3,139,233,252,206,126,157,179,198,37,114,127,40,244,101,182,28,79,227,233,22,2,42,240,205,39,31,190,144,35,255,94,57,214,167,211,246,62,51,157,146,50,175,64,138,129,89,122,231,151,226,183,239,166,83,220,17,185,237,215,189,91,163,219,176,91,96,223,180,207,195,155,174,221,244,226,215,28,10,247,248,32,210,180,76,76,82,189,201,44,227,203,221,186,27,243,165,150,189,55,135,116,118,138,5,209,50,169,132,73,244,12,201,92,66,171,69,147,29,95,173,133,158,28,77,232,104,219,228,191,206,25,14,53,113,243,182,232,110,99,207,245,75,41,26,182,159,9,248,138,246,150,239,95,189,182,191,175,73,188,172,54,43,120,163,213,62,152,214,37,220,246,112,219,16,85,173,148,46,133,124,10,238,45,69,43,35,8,93,229,209,154,152,1,188,95,50,240,83,241,25,186,237,103,14,166,156,103,211,144,126,3,128,124,107,46,79,11,0,0 };
|
5
src/dataStyleCSS.h
Normal file
39
src/dataTabbedcontentJS.h
Normal file
5
src/dataZeptoJS.h
Normal file
@ -1,27 +0,0 @@
|
|||||||
const char HTML_INDEX[] PROGMEM = R"=====(
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Control</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAPFBMVEUAAACA1VWR21qQ2liR3FqR3FqS3VuR3VqR3VuR3VqO21mS21uS3FqS3FqS21uJ2GKQ21qR3FuR3FoAAAB/3Gu7AAAAEnRSTlMABoA3kPBwz8i5Kzioxg4NVcU3uEJHAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+EFEhcEM+HpYwQAAABYSURBVBjThY/JDsAgCESt4lpX/v9jLQZJ6qF9t3khAyj1xXUKbQ4BVowDwqOYgExkkW4iY6lPaF06RqM8YItOuRbMaz6xjbsusDAW/drplBg47jP696cXE8bPA1eUDeK2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE4VDIzOjA0OjUxKzAyOjAwxE59ewAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xOFQyMzowNDo1MSswMjowMLUTxccAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC"
|
|
||||||
/>
|
|
||||||
<link rel="stylesheet" href="/css/normalize.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
|
|
||||||
<script src="/js/zepto.min.js"></script>
|
|
||||||
<script src="/js/slider.js"></script>
|
|
||||||
<script src="/js/controls.js"></script>
|
|
||||||
</head>
|
|
||||||
<body onload="javascript:start();">
|
|
||||||
<div>
|
|
||||||
<h4><div id="mainHeader">Control</div> <span id="conStatus" class="label">Offline</span></h4></div>
|
|
||||||
<hr />
|
|
||||||
<div class="container">
|
|
||||||
<div id="row" class="row u-full-width">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)=====";
|
|
@ -1,3 +0,0 @@
|
|||||||
const char CSS_NORMALIZE[] PROGMEM = R"=====(
|
|
||||||
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
|
|
||||||
)=====";
|
|
@ -1,3 +0,0 @@
|
|||||||
const char JS_SLIDER[] PROGMEM = R"=====(
|
|
||||||
function rkmd_rangeSlider(b){var f,e,c,a,g,d,h;f=$(b);e=f.width();c=f.offset().left;g=f;g.each(function(k,j){a=$(this);a.append(sliderDiscrete_tmplt());d=a.find('input[type="range"]');h=a.find(".slider");slider_fill=h.find(".slider-fill");slider_handle=h.find(".slider-handle");slider_label=h.find(".slider-label");var l=parseInt(d.val());slider_fill.css("width",l+"%");slider_handle.css("left",l+"%");slider_label.find("span").text(l)});f.on("mousedown touchstart",".slider-handle",function(o){if(o.button===2){return false}var m=$(this).parents(".rkmd-slider");var l=m.width();var i=m.offset().left;var k=m.find('input[type="range"]').is(":disabled");if(k===true){return false}$(this).addClass("is-active");var p=function(r){var q=r.pageX-i;if(q<=l&&!(q<"0")){slider_move(m,q,l,true)}};var n=function(q){$(this).off(j);m.find(".is-active").removeClass("is-active")};var j={mousemove:p,touchmove:p,mouseup:n,touchend:n};$(document).on(j)});f.on("mousedown touchstart",".slider",function(p){if(p.button===2){return false}var m=$(this).parents(".rkmd-slider");var l=m.width();var i=m.offset().left;var k=m.find('input[type="range"]').is(":disabled");if(k===true){return false}var o=p.pageX-i;if(o<=l&&!(o<"0")){slider_move(m,o,l,true)}var n=function(q){$(this).off(j)};var j={mouseup:n,touchend:n};$(document).on(j)})}function sliderDiscrete_tmplt(){var a='<div class="slider"><div class="slider-fill"></div><div class="slider-handle"><div class="slider-label"><span>0</span></div></div></div>';return a}function slider_move(g,a,h,e){var i=parseInt(Math.round(a/h*100));var b=g.find(".slider-fill");var c=g.find(".slider-handle");var f=g.find('input[type="range"]');b.css("width",i+"%");c.css({left:i+"%",transition:"none","-webkit-transition":"none","-moz-transition":"none"});f.val(i);if(g.find(".slider-handle span").text()!=i){g.find(".slider-handle span").text(i);var d=g.attr("id").substring(2);if(e){websock.send("slvalue:"+i+":"+d)}}};
|
|
||||||
)=====";
|
|
134
tools/prepare_static_ui_sources.py
Executable file
@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from jsmin import jsmin as jsminify
|
||||||
|
from htmlmin import minify as htmlminify
|
||||||
|
from csscompressor import compress as cssminify
|
||||||
|
import gzip
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
TARGET_TEMPLATE = '''const char {constant}[] PROGMEM = R"=====(
|
||||||
|
{minidata}
|
||||||
|
)=====";
|
||||||
|
|
||||||
|
const uint8_t {constant}_GZIP[{gziplen}] PROGMEM = {{ {gzipdata} }};
|
||||||
|
'''
|
||||||
|
|
||||||
|
def parse_arguments(args=None):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Prepares ESPUI header files by minifying and gzipping HTML, JS and CSS source files.")
|
||||||
|
parser.add_argument("--auto", "--all", "-a", dest="auto", action="store_true",
|
||||||
|
help="Automatically find all source files in data/ and write C header files to src/")
|
||||||
|
parser.add_argument("--source", "--sources", "-s", dest="sources", default=None,
|
||||||
|
help="Sources directory containing CSS or JS files OR one specific file to minify")
|
||||||
|
parser.add_argument("--target", "-t", dest="target", default=None,
|
||||||
|
help="Target directory containing C header files OR one C header file")
|
||||||
|
parser.add_argument("--nostoremini", "-m", action="store_false", dest="storemini",
|
||||||
|
help="Do not store intermediate minified files next to the originals (i.e. only write to the C header files)")
|
||||||
|
args = parser.parse_args(args)
|
||||||
|
if not args.auto and (not args.sources or not args.target):
|
||||||
|
print("ERROR: You need to specify either --auto or both --source and --target\n")
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
return args
|
||||||
|
|
||||||
|
def get_context(infile, outfile):
|
||||||
|
infile = os.path.realpath(infile)
|
||||||
|
dir, name, type = (os.path.basename(os.path.dirname(infile)), os.path.basename(infile).split(os.path.extsep)[0], os.path.basename(infile).split(os.path.extsep)[-1] )
|
||||||
|
type = type.strip(".").lower()
|
||||||
|
if dir.lower() == type:
|
||||||
|
dir = os.path.basename(os.path.dirname(os.path.dirname(infile)))
|
||||||
|
if type == "htm":
|
||||||
|
type = 'html'
|
||||||
|
name = os.path.basename(name)
|
||||||
|
indir = os.path.dirname(infile)
|
||||||
|
if os.path.isdir(outfile):
|
||||||
|
outdir = os.path.realpath(outfile)
|
||||||
|
outfilename = "%s%s%s.h" % (dir, name.capitalize(), type.upper())
|
||||||
|
outfile = os.path.realpath(os.path.sep.join([outdir, outfilename]))
|
||||||
|
else:
|
||||||
|
outfile = os.path.realpath(outfile)
|
||||||
|
outdir = os.path.dirname(outfile)
|
||||||
|
outfilename = os.path.basename(outfile)
|
||||||
|
minifile = re.sub('\.([^.]+)$', '.min.\\1', infile) if not ".min." in infile else infile
|
||||||
|
constant = '%s_%s' % (type.upper(), name.upper())
|
||||||
|
return locals()
|
||||||
|
|
||||||
|
def perform_gzip(c):
|
||||||
|
compressed = gzip.compress(bytes(c['minidata'], 'utf-8'))
|
||||||
|
c['gzipdata'] = ','.join([ str(b) for b in compressed ])
|
||||||
|
c['gziplen'] = len(compressed)
|
||||||
|
print(" GZIP data length: %s" % c['gziplen'])
|
||||||
|
return c
|
||||||
|
|
||||||
|
def perform_minify(c):
|
||||||
|
with open(c['infile']) as infile:
|
||||||
|
minifier = {
|
||||||
|
'css': cssminify,
|
||||||
|
'js': jsminify,
|
||||||
|
'html': htmlminify
|
||||||
|
}.get(c['type']) or htmlminify
|
||||||
|
print(" Using %s minifier" % c['type'])
|
||||||
|
c['minidata'] = minifier(infile.read())
|
||||||
|
return perform_gzip(c)
|
||||||
|
|
||||||
|
def process_file(infile, outdir, storemini=True):
|
||||||
|
print("Processing file %s" % infile)
|
||||||
|
c = get_context(infile, outdir)
|
||||||
|
c = perform_minify(c)
|
||||||
|
if storemini:
|
||||||
|
if c['infile'] == c['minifile']:
|
||||||
|
print(" Original file is already minified, refusing to overwrite it")
|
||||||
|
else:
|
||||||
|
print(" Writing minified file %s" % c['minifile'])
|
||||||
|
with open(c['minifile'], 'w+') as minifile:
|
||||||
|
minifile.write(c['minidata'])
|
||||||
|
with open(c['outfile'], 'w+') as outfile:
|
||||||
|
print(" Using C constant names %s and %s_GZIP" % (c['constant'], c['constant']))
|
||||||
|
print(" Writing C header file %s" % c['outfile'])
|
||||||
|
outfile.write(TARGET_TEMPLATE.format(**c))
|
||||||
|
|
||||||
|
def filenamefilter(pattern, strings):
|
||||||
|
return filter(re.compile(pattern).search, strings)
|
||||||
|
|
||||||
|
def process_dir(sourcedir, outdir, recursive=True, storemini=True):
|
||||||
|
pattern = r'/*\.(css|js|htm|html)$'
|
||||||
|
files = glob(sourcedir + "/**/*", recursive=True)+glob(sourcedir + "/*") if recursive else glob(sourcedir + "/*")
|
||||||
|
files = filenamefilter(pattern, files)
|
||||||
|
for f in set(files):
|
||||||
|
if not '.min.' in f:
|
||||||
|
process_file(f, outdir, storemini)
|
||||||
|
elif not os.path.isfile(f.replace(".min.", ".")):
|
||||||
|
process_file(f, outdir, storemini)
|
||||||
|
|
||||||
|
def check_args(args):
|
||||||
|
abort = 0
|
||||||
|
if not os.path.exists(args.sources):
|
||||||
|
print("ERROR: Source %s does not exist" % args.sources)
|
||||||
|
abort += 2
|
||||||
|
if not os.path.isdir(os.path.dirname(args.target)):
|
||||||
|
print("ERROR: Parent directory of target %s does not exist" % args.target)
|
||||||
|
abort += 4
|
||||||
|
if os.path.isdir(args.sources) and not os.path.isdir(args.target):
|
||||||
|
print("ERROR: Source %s is a directory, target %s is not" % (args.sources, args.target))
|
||||||
|
abort += 8
|
||||||
|
if abort > 0:
|
||||||
|
print("Aborting.")
|
||||||
|
sys.exit(abort)
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
args.sources = os.path.realpath(args.sources or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "data")))
|
||||||
|
args.target = os.path.realpath(args.target or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "src")))
|
||||||
|
check_args(args)
|
||||||
|
if os.path.isfile(args.sources):
|
||||||
|
print("Source %s is a file, will process one file only." % args.sources)
|
||||||
|
process_file(args.sources, args.target, storemini = args.storemini)
|
||||||
|
elif os.path.isdir(args.sources):
|
||||||
|
print("Source %s is a directory, searching for files recursively..." % args.sources)
|
||||||
|
process_dir(args.sources, args.target, recursive = True, storemini = args.storemini)
|
||||||
|
|
||||||
|
if __name__ == "__main__" and "get_ipython" not in dir():
|
||||||
|
main(parse_arguments())
|